Some scenarios might be duplicates of myhdl_changes.ipynb
As hardware generators in general are considered functions that are decorated using a
@always
, @always_comb
, @always_seq
as opposed to simulation constructs not generating hardware in particular: @instance
a = c ^ b
@block
context can be digital logic, signal, or reference and may be resolved into
hardwareWith this in mind, you'll have to:
*
PEP570 construct)for ...
) inside generators, see Loop issues.sig.next[i]
into sig[i].next
, see Member assignment@block
s only allow dedicated inputs or outputs, no inout signals (except TristateSignals)@block
wherever possibleThe following block is a simple example to start from. Modifications of this base will exhibit a few pitfalls during migration. First, we import from emulation
:
from myirl.emulation.myhdl import *
Then we define an auxiliary for cheap unit testing using VHDL-93 dialect and GHDL for reference testing:
from myirl.test.common_test import run_ghdl
def test(uut, param = (), debug = False):
inst = uut(*param)
vhdl93 = targets.vhdl.VHDL93()
f = inst.elab(vhdl93, elab_all=True)
run_ghdl(f, inst, debug = debug, std = '93', vcdfile = inst.name + '.vcd')
return f
@block
def unit():
a = Signal(intbv(0xaa)[8:])
a.init = True
q = Signal(bool())
@instance
def stim():
q.next = False
if a[0] == True and a[1] == False and a[7] == False: # True boolean evaluation
q.next = True
yield delay(1)
assert q == False
return instances()
test(unit)
Instead of creating an auxiliary signal, we can create references to signal combinations inside the @block
context.
However, this may create redundant code when resolving to a HDL, as the reference is a true Python IRL object.
@block
def unit():
a = Signal(intbv(0xaa)[8:])
a.init = True
p, q = [ Signal(bool()) for _ in range(2) ]
z = (a[7] == True) & (a[6] == False) & (a[0] == True) # Reference to binary combination of boolean expressions
zb = a[7] & ~a[6] & a[0] # True binary combination
zs = Signal(bool())
# New wiring 'generator' construct:
wires = [
zs.set(zb)
]
@instance
def stim():
q.next = False
p.next = True
yield delay(1)
if a[7] == True and a[6] == False and a[0] == True: # True boolean evaluation
q.next = True
yield delay(1)
if z: # Evaluate reference
q.next = True
yield delay(1)
if zb: # Evaluate binary op reference
q.next = True
if zs == True: # Check signal
q.next = True
yield delay(1)
assert q == False
a.next = 0xa1
yield delay(1)
p.next = z
yield delay(1)
assert p == True
return instances()
f = test(unit, debug = True)
!cat -n {f[0]}
Under scrutiny. For now, avoid complicated variable scenarios. Variable usage in hardware generation will be unsupported for non-VHDL targets, however it is safe to use them for simulation constructs.
Variable
type assigned to False
yields a boolean type in the resulting HDL, whereas
a Signal(bool())
type assigned to a Python bool results in a std_logic
output.Note: In general, do not try generating hardware with variables. Use signals where possible, or use references to logic combinations.
Make sure to put variables that are supposed to be generic parameters past the *
and ensure they are given either a default (when desired in the HDL output) or a type hint:
@block
def unit1(a : Signal, b: Signal.Output, * , PARAM : bool = False):
@always_comb
def worker():
if a == 5:
b.next = 0
elif PARAM:
b.next = 1
return instances()
@block
def tb(unit):
a, b = (Signal(intbv()[5:]) for _ in range(2))
uut = unit(a, b, PARAM = True)
return instances()
f = test(tb, (unit1, ), debug = False)
!grep -A 5 generic {f[0]}
When PARAM
is not separated by a *
, it will not be inferred to HDL but resolved. This is used for conditional compilation.
@block
def unit2(a : Signal, b: Signal.Output, PARAM : bool = False):
@always_comb
def worker():
if a == 5:
b.next = 0
elif PARAM:
b.next = 1
return instances()
f = test(tb, (unit2, ), debug = False)
Note that PARAM
is not converted into a generic
, and occurences in the HDL code are resolved statically.
!cat {f[0]}
MyHDL allows loops inside hardware descriptions and creates generate statements in the resulting HDL.
Bool = Signal.Type(bool)
@block
def unit_loop0(b : Bool, N : int = 5):
a = [ Signal(bool()) for _ in range(N) ]
@always_comb
def worker():
a[0].next = b
for i in range(1, 5):
a[i].next = ~a[i-1]
return instances()
This is no longer supported, except in simulation constructs implemented inside @instance
functions.
Migration strategy:
@block
level, use procedural instancing@process
or @genprocess
constructs in the IRLReplace by:
@block
def unit_loop1(b : Bool):
a = [ Signal(bool()) for _ in range(5) ]
wires = [ a[0].set(b) ]
wires += [ a[i].set(a[i-1]) for i in range(1, 5)]
return instances()
or within a library, using IRL:
import myirl
@myirl.block
def unit_loop2(b: Bool, N = 5):
a = [ Signal(bool()) for _ in range(N) ]
@myirl.genprocess()
def worker():
yield [ a[0].set(b) ]
yield [ a[i].set(~a[i-1]) for i in range(1, N) ]
return instances()
Examine VHDL output:
def test_loop(unit):
b = Bool()
uut = unit(b, 8)
f = uut.elab(targets.VHDL)
return f
f = test_loop(unit_loop2)
! grep -A 30 architecture {f[0]}
Obviously, loop statements unroll explicitely. Thus, this is not intended for iterating through a large array. See also Loops.
Recommended approach for porting legacy class constructs containing signals:
@container()
for a bidirectional class (creates container structures)@container(mode=LEGACY_CLASS)
for legacy class behaviour (resolves each member of the signal)@bulkwrapper(target_list)
for unidirectional internal types supported by specific targets onlyFor bidirectional classes, you need to define input, output and auxiliary ports (always inputs).
This allows to still use your legacy class constructs from older myHDL code.
The derived classes can be used for type specification in the interface.
Further details: