#!/usr/bin/env python # coding: utf-8 # # Yosys inference of memories # # This example demonstrates direct inference of a dual port (simplex) memory description into a FPGA hard memory block (here: Lattice ECP5 DP16KD primitive). # It requires a recent (>= v0.12) release of yosys. Proper TDP inference may only be covered with a patched version. # # **Not applicable to binder demo** # In[2]: from myirl.emulation.myhdl import * from myirl.test.test_array import r1w1, SigArray # ## Simple read and write port memory # # This implementation uses bypass logic when a write is occuring during a read from the same address. # In[3]: @block def r1w1( clk : ClkSignal, we : Signal, addr_r: Signal, addr_w: Signal, din: Signal, dout: Signal.Output, ram : SigArray, MODE = False, # Conditional compilation flag w/o type annotation DWIDTH=16, AWIDTH=10, TRANSPARENCY = False ) -> IRL: print("CALLED MEMORY INSTANCE", DWIDTH) if TRANSPARENCY: @always(clk.posedge) def mem_rw_transparent(): if we and addr_w == addr_r: dout.next = din #Forward else: dout.next = ram[addr_r][DWIDTH:] if we: ram[addr_w].next = din else: @always(clk.posedge) def mem_rw(): dout.next = ram[addr_r][DWIDTH:] if we: ram[addr_w].next = din return instances() # In[4]: def memtest(MODE = 0, STYLE = 1, data_w=16, addr_w=6, mem = r1w1, TRANSPARENT = True): c = ClkSignal(name = 'clk') c.init = True wren = Signal(bool(), name = 'we') ra, wa = [ Signal(intbv()[addr_w:]) for n in ['addr_write', 'addr_read'] ] a, q = [ Signal(intbv()[data_w:]) for n in ['a', 'q'] ] ram_data = SigArray([ intbv(v)[data_w:] for v in range(2 ** addr_w)], name='ram_sig', init=True) inst = mem(clk=c, we=wren, addr_r=ra, addr_w=wa, din=a, dout=q, MODE=False, ram = ram_data, AWIDTH=addr_w, DWIDTH=data_w, TRANSPARENCY = TRANSPARENT) return inst # In[ ]: # ## Mapping to hardware # # **Note** myIRL inference does *not yet* detect built-in transparency, (redundant) FFs will be inferred. # In[5]: from myirl.targets import pyosys # In[6]: DATA_WIDTH = 32 # Elaborate memory unit and emit CXXRTL code: # In[7]: def convert(dw, aw = 7): tgt = pyosys.RTLIL("memtest%d" % dw) MEM = r1w1 tb = memtest(data_w=dw, mem = MEM, addr_w = aw, TRANSPARENT = True) d = tb.elab(tgt, elab_all = True) d = d[0] d.run("hierarchy -check") d.run("stat", capture = None) d.run("write_rtlil mem8.il") d.run("debug memory -nomap; debug opt") return d # In[8]: d = convert(DATA_WIDTH, 7) # ## Testing hardware generation # # Note: Post map simulation may require external memory models of blackbox Vendor Primitives. # In[9]: TECHMAP = '/usr/share/yosys' def synth_ecp5(d, libmap = True): # Read blackbox cells for awareness of DP16KD: d.run("read_verilog -lib -specify %s/ecp5/cells_sim.v %s/ecp5/cells_bb.v" % (TECHMAP, TECHMAP)) if libmap: # New libmap procedure d.run("memory_libmap -lib %s/ecp5/brams.txt" % TECHMAP) else: # First try DP16KD mapping: d.run("memory_bram -rules %s/ecp5/brams.txt" % TECHMAP) d.run("techmap -map %s/ecp5/brams_map.v" % TECHMAP) # Remaining (addr_w <= 8 bit) to LUT RAM: if libmap: d.run("memory_libmap -lib %s/ecp5/lutrams.txt" % TECHMAP) else: d.run("memory_bram -rules %s/ecp5/lutrams.txt" % TECHMAP) d.run("techmap -map %s/ecp5/lutrams_map.v" % TECHMAP) d.run("opt_clean") def synth_gatemate(d, libmap = True): d.run("read_verilog -lib -specify %s/gatemate/cells_sim.v %s/gatemate/cells_bb.v" % (TECHMAP, TECHMAP)) if libmap: # New libmap procedure d.run("memory_libmap -lib %s/gatemate/brams.txt" % TECHMAP) else: d.run("memory_bram -rules %s/gatemate/brams.txt" % TECHMAP) d.run("techmap -map %s/gatemate/brams_map.v" % TECHMAP) d.run("opt") d.run("ls", capture = None) synth_gatemate(d) d.run("stat", capture = None) d.run("write_rtlil mapped.il") d.display_rtl(selection = '*', fmt = 'dot') # In[10]: from yosys import display display.display_dot("memtest%d" % DATA_WIDTH) # In[11]: d.write_verilog("test1") # Define a few custom targets: # In[12]: class CustomTarget(pyosys.RTLIL): def __init__(self, *args): super().__init__(*args) print("Selecting %s" % self.name) self.debug = True def finalize(self, top, objs = None): print("FINALIZE") tname = top.name design = self._design design.run("hierarchy -top %s" % tname) self.synth(design) design.run('stat', capture = None) # self.write_cxxrtl(top) return [ design ] class GateMateTarget(CustomTarget): name = "GateMate" def synth(self, design): return synth_gatemate(design) class ECP5Target(CustomTarget): name = "LatticeECP5" def synth(self, design): design.run("read_verilog ../library/tech/lattice/ecp5u/DP16KD.v") return synth_ecp5(design) # Then run post-map simulation on one of them. # # **Note**: For the ECP5 target, you need a *synthesizeable* variant of the `DP16KD.v` entity, or co-simulate using iverilog (currently not integrated into the pyrite simulator API). Let's try the GateMateTarget for now (which includes primitive whitebox models). # In[13]: import simulation from yosys.simulator import CXXRTL @simulation.sim.testbench(CXXRTL, time_unit = 'ns', target_class = GateMateTarget) def tb_memtest(MODE = 0, STYLE = 1, data_w=16, addr_w=7, mem = r1w1): ClkSignal = simulation.ClkSignal Signal = simulation.Signal c = ClkSignal(name = 'clk') c.init = True M = (1 << (data_w - 1)) wren = Signal(bool(), name = 'we') ra, wa = [ Signal(intbv()[addr_w:]) for n in ['addr_write', 'addr_read'] ] a, q = [ Signal(intbv()[data_w:]) for n in ['a', 'q'] ] ram_sig = SigArray([intbv(M | v)[data_w:] for v in range(2 ** addr_w)], name='ram_sig', init=True) # print(ram_sig[0].size()) inst = mem(clk=c, we=wren, addr_r=ra, addr_w=wa, din=a, dout=q, MODE=False, ram = ram_sig, AWIDTH=addr_w, DWIDTH=data_w, TRANSPARENCY = True) @simulation.always(simulation.delay(2)) def clkgen(): c.next = ~c def write(addr, data): print("WRITE", addr, data) yield c.negedge wa.next = addr a.next = data wren.next = True yield c.negedge wren.next = False def write_verify(addr, data, value): print("WRITE VERIFY", addr) yield c.negedge wa.next = addr a.next = data wren.next = True yield c.negedge # print("DEBUG Q", bin(int(q))) assert int(q) == value wren.next = False @simulation.sequence def stim(): ra.next = 0x20 wa.next = 0x00 # Write and verify that we're not bypassing: # i.e. the value is expected to be the initial one yield from write_verify(0x00, 0xaa, M | 0x20) ra.next = 0x00 yield c.negedge # Now make sure we're getting the written value assert int(q) == 0xaa # ra == wa, verify transparency bypass: ra.next = 0x40 yield from write_verify(0x40, 0x55, 0x55) yield c.negedge ra.next = 0x00 yield c.negedge assert int(q) == 0xaa ra.next = 0x40 yield c.negedge assert int(q) == 0x55 ra.next = 0x1a yield c.negedge assert int(q) == M | 0x1a # Initial print("SIM DONE") return simulation.instances() # In[14]: r1w1.ctx.components # In[15]: t = tb_memtest(data_w = 10) # In[16]: t.debug() t.run(100) # t.design.run("write_rtlil memtest.il") # # Duplex read, single write # # This test is now part of the cyrite library TDPRAM