#!/usr/bin/env python # coding: utf-8 # # Advanced Generators # # For complicated testbench setups, you might want to reuse certain sequences from a macro. # # The PEP380 `yield from` construct makes this look nicer: # In[1]: 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 # In[2]: @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] # In[3]: 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 # In[4]: t = test() # ### Waveform # In[5]: import wavedraw; import nbwavedrom TB = t.name; waveform = wavedraw.vcd2wave("/tmp/tb1.vcd", TB + '.clk', None) nbwavedrom.draw(waveform) # ## Bidirectional generator function # # 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)`: # In[6]: 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`: # In[7]: def wrapper(routine): print("CALL WRAPPER") yield from routine # In[8]: @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] # In[9]: 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() # In[10]: 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. # In[11]: get_ipython().system('cat {tb.ctx.path_prefix}/tb.vhdl') # In[ ]: