Logic generators

Unlike translation via AST from Python to a target, logic generators are lists of functions returning a generator item. These generators can either evaluate to a current state or value or emit a target HDL.

As an example how a MyHDL snippet translates:

In [1]:
import sys
sys.path.insert(0, "../..")
In [2]:
from myirl.emulation.myhdl import *

@block
def unit(clk, en, a, q):
    @always(clk.posedge)
    def worker():
        if en:
            q.next = a
            
    return instances()
In [3]:
print(unit.unparse())
==============================
Unparsing unit unit
==============================


@block
def unit(clk, en, a, q):

    @always_(clk.posedge)
    def worker():
        (yield [unit.ctx.If(en).Then(q.set(a))])
    return instances()

@generator : co-routine style coding

In many cases you might want to interject statements (without using kludgy if __debug__ constructs) that don't end up in the resulting HDL. For instance, your HDL may not support a specific iteration as part of its language. MyIRL is more strict in separating synthesizeable elements from debug statements, for instance, inserting a print command in a @process is not valid. The @generator style allows by separating in situ-execution from HDL output via yield, but is still limited to simple combinatorial logic for now.

The following example generates an inverse wiring order by unrolling a loop:

In [4]:
from myirl.kernel import sensitivity
@block
def unit_x():
    s = Signal(intbv()[8:])
    
    z = [ Signal(bool(), name = "z%d" % i) for i in range(8) ]
    
    @sensitivity.generator
    def wireup():
        for i in range(8):
            j = 7 - i
            print("DEBUG: Assign z[%d] = s[%d]" % (i, j))
            yield [ z[i].set(s[j]) ]
            
    return instances()
../../myirl/emulation/myhdl2irl.py:531: UserWarning: Not translating decorator `generator`
  warnings.warn("Not translating decorator `%s`" % n)
In [5]:
def convert():
    inst = unit_x()
    f = inst.elab(targets.VHDL)
    return f

f = convert()
DEBUG: Assign z[0] = s[7]
DEBUG: Assign z[1] = s[6]
DEBUG: Assign z[2] = s[5]
DEBUG: Assign z[3] = s[4]
DEBUG: Assign z[4] = s[3]
DEBUG: Assign z[5] = s[2]
DEBUG: Assign z[6] = s[1]
DEBUG: Assign z[7] = s[0]
 Writing 'unit_x' to file /tmp/myirl_top_unit_x_rd53c0m3/unit_x.vhdl 
In [6]:
! grep -A 20 architecture {f[0]}
architecture MyIRL of unit_x is
    -- Local type declarations
    -- Signal declarations
    signal z0 : std_ulogic;
    signal z1 : std_ulogic;
    signal z2 : std_ulogic;
    signal z3 : std_ulogic;
    signal z4 : std_ulogic;
    signal z5 : std_ulogic;
    signal z6 : std_ulogic;
    signal z7 : std_ulogic;
    signal s : unsigned(7 downto 0);
begin
    z0 <= s(7);
    z1 <= s(6);
    z2 <= s(5);
    z3 <= s(4);
    z4 <= s(3);
    z5 <= s(2);
    z6 <= s(1);
    z7 <= s(0);
end architecture MyIRL;

Likewise, the @simulator.generator supports this style for sequential, simulation specific commands.

In [7]:
from myirl import simulation
from myirl.test import common_test

@block
def testbench():
    s = Signal(intbv()[8:])
    rst = ResetSignal(0, 1, isasync = True)
    clk = ClkSignal(name = 'clk')
    clk.init = True
    a, b = [ Signal(intbv()[5:]) for _ in range(2) ]
    c = Signal(intbv()[6:])
    
    
    @always(delay(1))
    def clkgen():
        clk.next = ~clk
        
    @simulation.generator
    def stimulus():
        
        # These are the operations to be tested. We create references
        # for evaluation below:
        init = a.set(0xf), b.set(0x8),
        add_operation = c.set(a + b + 2)
        
        # Evaluate the operations:
        init[0].evaluate(), init[1].evaluate()
        v = add_operation.evaluate()

        # Generate HDL:
        yield [
            rst.set(True),
            simulation.wait('10 ns'),
            rst.set(False),
            *init,
            simulation.wait('1 ns'),
            add_operation, simulation.wait(clk.posedge),
            simulation.assert_(c == v, "Test failed"),
            simulation.print_("Test ok")
        ]

        # Unroll a loop:
        for i in range(4):
            reassign = a.set(i)
            reassign.evaluate()
            add_operation = c.set(a + b - 1)

            v = add_operation.evaluate()
            
            yield [
                reassign, simulation.wait(clk.posedge),
                add_operation,
                simulation.wait("1 ns"), simulation.print_(a, b, c, hex(v)),
                simulation.assert_(c == v, "Test failed")          
            ]
            
        for i in range(6):
            yield [ simulation.wait(clk.posedge ) ]
        yield [ simulation.raise_(simulation.StopSimulation)]
    
    return instances()
    
def convert():
    tb = testbench()
    files = tb.elab(targets.VHDL)
    common_test.run_ghdl(files, tb, vcdfile = "testbench.vcd", debug = True)
    
convert()
Creating process 'testbench/clkgen' with sensitivity ([ DeltaT 1 ns ],)
Creating sequential 'testbench/stimulus' 
 Writing 'testbench' to file /tmp/myirl_top_testbench_muboj0qa/testbench.vhdl 
Warning: Implicit truncation of ADD(ADD(a, b), C:2) result
Warning: Implicit truncation of SUB(ADD(a, b), C:1) result
Warning: Implicit truncation of SUB(ADD(a, b), C:1) result
Warning: Implicit truncation of SUB(ADD(a, b), C:1) result
Warning: Implicit truncation of SUB(ADD(a, b), C:1) result
==== COSIM stdout ====

==== COSIM stderr ====

==== COSIM stdout ====
analyze /home/testing/src/myhdl2/myirl/targets/../test/vhdl/txt_util.vhdl
analyze /home/testing/src/myhdl2/myirl/targets/libmyirl.vhdl
analyze /tmp/myirl_top_testbench_muboj0qa/testbench.vhdl
elaborate testbench

==== COSIM stderr ====

==== COSIM stdout ====
Test ok
0x00 0x08 0x07 0x7
0x01 0x08 0x08 0x8
0x02 0x08 0x09 0x9
0x03 0x08 0x0A 0xa
/tmp/myirl_top_testbench_muboj0qa/testbench.vhdl:80:9:@33ns:(assertion failure): Stop Simulation
/tmp/testbench:error: assertion failed
in process .testbench(myirl).stimulus
/tmp/testbench:error: simulation failed

==== COSIM stderr ====

../../myirl/kernel/components.py:1150: TranslationWarning: @component `testbench`: DEBUG UNUSED 's'
  base.warn("@component `%s`: DEBUG UNUSED '%s'" % (self.obj.func.__name__, n), category = base.TranslationWarning)

Waveform trace

In [8]:
import wavedraw
import nbwavedrom

TB = "testbench"

waveform = wavedraw.vcd2wave("testbench.vcd", TB + '.clk', None)
    
nbwavedrom.draw(waveform)