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 Simulator
from myirl.test import ghdl, icarus
def test(uut, param = (), debug = False):
inst = uut(*param)
vhdl93 = targets.vhdl.VHDL93()
s = Simulator(vhdl93)
s.run(inst, 20, debug = True)
f = inst.elab(targets.VHDL, elab_all = True)
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
print("DONE")
raise StopSimulation
return instances()
test(unit)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Writing 'unit' to file /tmp/unit.vhdl Creating library file /tmp/module_defs.vhdl ==== COSIM stdout ==== DONE /tmp/unit.vhdl:36:9:@1ns:(assertion failure): Stop Simulation /tmp/unit:error: assertion failed in process .unit(myhdl_emulation).stim /tmp/unit:error: simulation failed Writing 'unit' to file /tmp/myirl_unit_9jtgp2n0/unit.vhdl Creating library file /tmp/myirl_module_defs_or225shu/module_defs.vhdl
['/tmp/myirl_unit_9jtgp2n0/unit.vhdl', '/tmp/myirl_module_defs_or225shu/module_defs.vhdl']
Note GHDL using the VHDL-93 standard will throw a 'failure' upon StopSimulation
, causing GHDL to exit with a return code other than 0. This is handled better with VHDL-08
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)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Writing 'unit' to file /tmp/unit.vhdl Creating library file /tmp/module_defs.vhdl Writing 'unit' to file /tmp/myirl_unit_j1ptaa4n/unit.vhdl Creating library file /tmp/myirl_module_defs_y9qpo7a6/module_defs.vhdl
!cat -n {f[0]}
1 -- File generated from source: 2 -- /tmp/ipykernel_90982/3287474469.py 3 -- (c) 2016-2022 section5.ch 4 -- Modifications may be lost, edit the source file instead. 5 6 library IEEE; 7 use IEEE.std_logic_1164.all; 8 use IEEE.numeric_std.all; 9 10 library work; 11 12 use work.txt_util.all; 13 use work.myirl_conversion.all; 14 15 entity unit is 16 end entity unit; 17 18 architecture myhdl_emulation of unit is 19 -- Local type declarations 20 -- Signal declarations 21 signal q : std_ulogic; 22 signal p : std_ulogic; 23 signal a : unsigned(7 downto 0) := x"aa"; 24 signal zs : std_ulogic; 25 begin 26 27 stim: 28 process 29 begin 30 q <= '0'; 31 p <= '1'; 32 wait for 1 ns; 33 if (((a(7) = '1') and (a(6) = '0')) and (a(0) = '1')) then 34 q <= '1'; 35 end if; 36 wait for 1 ns; 37 if (((a(7) = '1') and (a(6) = '0')) and (a(0) = '1')) then 38 q <= '1'; 39 end if; 40 wait for 1 ns; 41 if (((a(7) and not a(6)) and a(0))) = '1' then 42 q <= '1'; 43 end if; 44 if (zs = '1') then 45 q <= '1'; 46 end if; 47 wait for 1 ns; 48 assert (q = '0') 49 report "Failed in /tmp/ipykernel_90982/3287474469.py:unit():41" severity failure; 50 a <= x"a1"; 51 wait for 1 ns; 52 p <= from_bool((((a(7) = '1') and (a(6) = '0')) and (a(0) = '1'))); 53 wait for 1 ns; 54 assert (p = '1') 55 report "Failed in /tmp/ipykernel_90982/3287474469.py:unit():48" severity failure; 56 wait; 57 end process; 58 zs <= ((a(7) and not a(6)) and a(0)); 59 end architecture myhdl_emulation; 60
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)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Writing 'unit1' to file /tmp/unit1.vhdl Writing 'tb' to file /tmp/tb.vhdl Creating library file /tmp/module_defs.vhdl ==== COSIM stdout ==== ../../src/ieee/v93/numeric_std-body.vhdl:1613:7:@0ms:(assertion warning): NUMERIC_STD."=": metavalue detected, returning FALSE Writing 'unit1' to file /tmp/myirl_tb_tdcl5fj1/unit1.vhdl Writing 'tb' to file /tmp/myirl_tb_tdcl5fj1/tb.vhdl Creating library file /tmp/myirl_module_defs_u61egm2f/module_defs.vhdl
!grep -A 5 generic {f[0]}
generic ( PARAM: boolean := FALSE ); port ( a : in unsigned(4 downto 0); b : out unsigned(4 downto 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)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context
Module tb: Existing instance tb, rename to tb_1
Writing 'unit2' to file /tmp/unit2.vhdl
Writing 'tb_1' to file /tmp/tb_1.vhdl
Creating library file /tmp/module_defs.vhdl
==== COSIM stdout ====
../../src/ieee/v93/numeric_std-body.vhdl:1613:7:@0ms:(assertion warning): NUMERIC_STD."=": metavalue detected, returning FALSE
Writing 'unit2' to file /tmp/myirl_tb_z0asncyn/unit2.vhdl
Writing 'tb_1' to file /tmp/myirl_tb_z0asncyn/tb_1.vhdl
Creating library file /tmp/myirl_module_defs__5t2ll0j/module_defs.vhdl
Note that PARAM
is not converted into a generic
, and occurences in the HDL code are resolved statically.
!cat {f[0]}
-- File generated from source: -- /tmp/ipykernel_90982/3063955800.py -- (c) 2016-2022 section5.ch -- Modifications may be lost, edit the source file instead. library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library work; use work.txt_util.all; use work.myirl_conversion.all; entity unit2 is port ( a : in unsigned(4 downto 0); b : out unsigned(4 downto 0) ); end entity unit2; architecture myhdl_emulation of unit2 is -- Local type declarations -- Signal declarations begin worker: process(a) begin if (a = "00101") then b <= "00000"; elsif TRUE then b <= "00001"; end if; end process; end architecture myhdl_emulation;
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)
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Writing 'unit_loop2' to file /tmp/myirl_unit_loop2_sair1n13/unit_loop2.vhdl
! grep -A 30 architecture {f[0]}
architecture myIRL of unit_loop2 is -- Local type declarations -- Signal declarations signal a0 : std_ulogic; signal a1 : std_ulogic; signal a2 : std_ulogic; signal a3 : std_ulogic; signal a4 : std_ulogic; signal a5 : std_ulogic; signal a6 : std_ulogic; signal a7 : std_ulogic; begin worker: process(b, a0, a1, a2, a3, a4, a5, a6) begin a0 <= b; a1 <= not a0; a2 <= not a1; a3 <= not a2; a4 <= not a3; a5 <= not a4; a6 <= not a5; a7 <= not a6; end process; end architecture myIRL;
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: