For complicated testbench setups, you might want to reuse certain sequences from a macro.
The PEP380 yield from
construct makes this look nicer:
from myirl import *
import myirl.simulation as sim
from myirl import targets
def toggle(p, m):
yield [m.set(False)]
for i in range(4):
yield [ p.set(~p), sim.wait('20 ns') ]
def data(clk, d, p, m):
a = 1
yield [m.set(True)]
for i in range(8):
yield [ sim.wait(clk.posedge), p.set((d & a) != 0) ]
a <<= 1
@block
def tb():
p = Signal(bool(), name='p')
marker = Signal(bool(), name = 'mark')
d = Signal(intbv()[8:], name = 'data')
clk = ClkSignal(name='clk')
@bulk_delay(5)
def clkgen():
yield [
clk.set(~clk)
]
@sim.generator
def seq():
yield [
p.set(False), sim.wait('5 ns'), marker.set(False), sim.wait(clk.posedge),
marker.set(True), sim.wait(clk.posedge)
]
yield from toggle(p, marker)
yield from data(clk, 0xa5, p, marker)
yield from toggle(p, marker)
yield [ sim.raise_(sim.StopSimulation)]
return [clkgen, seq]
from myirl.test.common_test import run_ghdl
def test():
t = tb()
f = t.elab(targets.VHDL)
run_ghdl(f, tb, vcdfile="/tmp/tb1.vcd", debug = False)
return t
t = test()
Creating sequential 'tb/seq' Writing 'tb' to file /tmp/myirl_top_tb_sjugsmpj/tb.vhdl
import wavedraw; import nbwavedrom
TB = t.name;
waveform = wavedraw.vcd2wave("/tmp/tb1.vcd", TB + '.clk', None)
nbwavedrom.draw(waveform)
The yield from
mechanisms can be taken further to generate testbenches from existing hardware descriptions.
Here, a simple 'software-style' generator FSM is constructed that reacts to a value sent to it via (yield)
:
from collections import namedtuple
t_state = namedtuple('state', ['A', 'B', 'C', 'END'])
enum = t_state(*range(4))
def fsm(p, m):
print("STARTED")
state = enum.A
while True:
if state == enum.A:
print("State A")
b = (yield)
if b == 1:
state = enum.B
yield [m.set(True)]
elif b == 0:
pass
else:
raise ValueError("Value can not be", b)
elif state == enum.B:
print("State B")
b = (yield)
if b == 1:
state = enum.C
yield [m.set(False)]
elif b == 0:
pass
else:
raise ValueError("Value can not be", b)
elif state == enum.C:
print("State C")
b = (yield)
if b == 1:
for i in range(8):
# TOGGLING (see below)
yield [ p.set(~p) ]
state = enum.A
elif b == 0:
state = enum.END
yield [ sim.wait('40 ns'), p.set(False), m.set(True) ]
else:
raise ValueError("Value can not be", b)
elif state == enum.END:
print("STATE END")
break
else:
raise ValueError("Bad state")
The wrapper only translates by yield from
:
def wrapper(routine):
print("CALL WRAPPER")
yield from routine
@block
def tb():
p = Signal(bool(), name='p')
marker = Signal(bool(), name = 'mark')
d = Signal(intbv()[8:], name = 'data')
clk = ClkSignal(name='clk')
@bulk_delay(5)
def clkgen():
yield [
clk.set(~clk)
]
@sim.generator
def seq():
yield [
p.set(False), sim.wait('10 ns'), marker.set(True), sim.wait(clk.posedge),
marker.set(False), sim.wait(clk.posedge)
]
# Construct wrapper for FSM:
w = wrapper(fsm(p, marker))
# Prime it:
w.send(None)
X = None # Don't care, allowed only during the 'TOGGLING' sequence
for b in [0, 0, 1, 0, 0, 1, 1, 1, X, X, X, X, X, X, X, X, 0, 1, 1, 1, 0, 0, 0]:
try:
it = w.send(b)
# print("#", it)
if it is not None:
yield it
yield [sim.wait(clk.posedge)]
except StopIteration:
break
yield [ sim.wait('50 ns'), sim.raise_(sim.StopSimulation)]
return [clkgen, seq]
from myirl.test.common_test import run_ghdl
def test():
t = tb()
f = t.elab(targets.VHDL)
run_ghdl(f, tb, vcdfile="/tmp/tb2.vcd", debug = False)
return t
test()
Creating sequential 'tb/seq' CALL WRAPPER STARTED State A State A State A State B State B State C State A State A State B State C STATE END Writing 'tb' to file /tmp/myirl_top_tb_fy_1x4gz/tb.vhdl
[Instance tb I/F: [// ID: tb_0 to tb]]
TB = tb.name;
waveform = wavedraw.vcd2wave("/tmp/tb2.vcd", TB + '.clk', None)
nbwavedrom.draw(waveform)
This may appear extra complicated, but helps a lot with verification of known models against a hardware implementation. Also, it allows to loop in other co-simulation objects/pipes for step-wise verification.
# !cat {tb.ctx.path_prefix}/tb.vhdl