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()
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Writing 'tb' to file /tmp/myirl_tb_w7i57vwf/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()
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context 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_tb_14zgpfqs/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
-- File generated from source: -- /tmp/ipykernel_30190/2187491613.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 tb is end entity tb; architecture myIRL of tb is -- Local type declarations -- Signal declarations signal clk : std_ulogic := '0'; signal p : std_ulogic; signal mark : std_ulogic; begin clkgen: clk <= not clk after 5.000000 ns; seq: process begin p <= '0'; wait for 10 ns; mark <= '1'; wait until rising_edge(clk); mark <= '0'; wait until rising_edge(clk); wait until rising_edge(clk); wait until rising_edge(clk); mark <= '1'; wait until rising_edge(clk); wait until rising_edge(clk); wait until rising_edge(clk); mark <= '0'; wait until rising_edge(clk); wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); p <= not p; wait until rising_edge(clk); wait until rising_edge(clk); wait until rising_edge(clk); mark <= '1'; wait until rising_edge(clk); wait until rising_edge(clk); mark <= '0'; wait until rising_edge(clk); wait until rising_edge(clk); wait for 40 ns; p <= '0'; mark <= '1'; wait until rising_edge(clk); wait for 50 ns; std.env.stop; wait; end process; end architecture myIRL;