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 using the improved memory mapping.
import sys
sys.path.insert(0, '../../')
from myirl.emulation.myhdl import *
from myirl.test.test_array import r1w1, SigArray
This implementation uses bypass logic when a write is occuring during a read from the same address.
@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()
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
Note myIRL inference does not yet detect built-in transparency, (redundant) FFs will be inferred.
from myirl.targets import pyosys
DATA_WIDTH = 32
Elaborate memory unit and emit CXXRTL code:
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
d = convert(DATA_WIDTH, 7)
CALLED MEMORY INSTANCE 32 Creating process 'r1w1/mem_rw_transparent' with sensitivity (clk'rising,) Adding module with name `r1w1` DEBUG: SKIP ARRAYTYPE `ram` : <class 'myirl.lists.SigArray'> DEBUG SLICE READ MEMORY to dout AWIDTH:7 DWIDTH:32 [Component 'meminit/meminit'] blackbox not returning instances DEBUG ADD ROM 7:32 PARAM PRIORITY --> 48 PARAM WORDS --> 128 PARAM ABITS --> 7 PARAM WIDTH --> 32 PARAM MEMID --> $mem_ram_sig [Component 'memrd/memrd'] blackbox not returning instances PARAM CLK_ENABLE --> False PARAM CLK_POLARITY --> True PARAM TRANSPARENT --> False PARAM ABITS --> 7 PARAM WIDTH --> 32 PARAM MEMID --> $mem_ram_sig DEBUG SLICE WRITE MEMORY to ram_sig AWIDTH:7 DWIDTH:32 [Component 'memwr/memwr'] blackbox not returning instances PARAM CLK_ENABLE --> True PARAM CLK_POLARITY --> True PARAM PRIORITY --> 48 PARAM ABITS --> 7 PARAM WIDTH --> 32 PARAM MEMID --> $mem_ram_sig DEBUG TOP LEVEL WRITE MEM ram_sig FINALIZE implementation `r1w1` of `r1w1` -- Running command `tee -q hierarchy -top \r1w1' -- -- Running command `tee -q hierarchy -check' -- -- Running command `stat' -- 3. Printing statistics. === r1w1 === Number of wires: 17 Number of wire bits: 338 Number of public wires: 7 Number of public wire bits: 112 Number of memories: 1 Number of memory bits: 4096 Number of processes: 0 Number of cells: 7 $and 1 $dff 1 $eq 1 $meminit 1 $memrd 1 $memwr 1 $mux 1 -- Running command `tee -q write_rtlil mem8.il' -- -- Running command `tee -q debug memory -nomap; debug opt' -- 6. Executing OPT pass (performing simple optimizations). 6.1. Executing OPT_EXPR pass (perform const folding). Optimizing module r1w1. 6.2. Executing OPT_MERGE pass (detect identical cells). Finding identical cells in module `\r1w1'. Removed a total of 0 cells. 6.3. Executing OPT_MUXTREE pass (detect dead branches in mux trees). Running muxtree optimizer on module \r1w1.. Creating internal representation of mux trees. No muxes found in this module. Removed 0 multiplexer ports. 6.4. Executing OPT_REDUCE pass (consolidate $*mux and $reduce_* inputs). Optimizing cells in module \r1w1. Performed a total of 0 changes. 6.5. Executing OPT_MERGE pass (detect identical cells). Finding identical cells in module `\r1w1'. Removed a total of 0 cells. 6.6. Executing OPT_DFF pass (perform DFF optimizations). 6.7. Executing OPT_CLEAN pass (remove unused cells and wires). Finding unused cells or wires in module \r1w1.. 6.8. Executing OPT_EXPR pass (perform const folding). Optimizing module r1w1. 6.9. Finished OPT passes. (There is nothing left to do.)
Note: Post map simulation may require external memory models of blackbox Vendor Primitives.
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_ecp5(d)
d.run("stat", capture = None)
d.run("write_rtlil mapped.il")
d.display_rtl(selection = '*', fmt = 'dot')
-- Running command `ls' -- 1 modules: r1w1 -- Running command `tee -q read_verilog -lib -specify /usr/share/yosys/ecp5/cells_sim.v /usr/share/yosys/ecp5/cells_bb.v' -- -- Running command `tee -q memory_libmap -lib /usr/share/yosys/ecp5/brams.txt' -- -- Running command `tee -q techmap -map /usr/share/yosys/ecp5/brams_map.v' -- -- Running command `tee -q memory_libmap -lib /usr/share/yosys/ecp5/lutrams.txt' -- -- Running command `tee -q techmap -map /usr/share/yosys/ecp5/lutrams_map.v' -- -- Running command `tee -q opt_clean' -- -- Running command `stat' -- 14. Printing statistics. === r1w1 === Number of wires: 12 Number of wire bits: 183 Number of public wires: 6 Number of public wire bits: 80 Number of memories: 0 Number of memory bits: 0 Number of processes: 0 Number of cells: 6 $and 1 $dff 1 $dffe 1 $eq 1 $mux 1 DP16KD 1 -- Running command `tee -q write_rtlil mapped.il' -- -- Running command `show -format dot -prefix memtest32 *' -- 16. Generating Graphviz representation of design. Writing dot description to `memtest32.dot'. Dumping module r1w1 to page 1.
from yosys import display
display.display_dot("memtest%d" % DATA_WIDTH)
d.write_verilog("test1")
-- Running command `ls; check' -- 17. Executing CHECK pass (checking for obvious problems). Found and reported 0 problems. -- Running command `hierarchy -check' -- 18. Executing HIERARCHY pass (managing design hierarchy). -- Running command `write_verilog test1_mapped.v' -- 19. Executing Verilog backend. 19.1. Executing BMUXMAP pass. 19.2. Executing DEMUXMAP pass.
Define a few custom targets:
class CustomTarget(pyosys.RTLIL):
def __init__(self, *args):
super().__init__(*args)
print("Selecting %s" % self.name)
self.debug = True
def finalize(self, top):
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)
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).
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()
r1w1.ctx.components
['r1w1_s1_s1_s7_s7_s32_s32_s128_0_32_7_1']
t = tb_memtest(data_w = 10)
Module r1w1: Existing instance r1w1, rename to r1w1_1
CALLED MEMORY INSTANCE 10
Creating process 'r1w1/mem_rw_transparent' with sensitivity (clk'rising,)
DEBUG: Skip non-simulation type <class 'myirl.emulation.myhdl.wrapped_wrapper'>
DEBUG: Skip non-simulation type <class 'type'>
DEBUG: Skip non-simulation type <class 'myirl.lists.SigArray'>
DEBUG: Skip non-simulation type <class 'function'>
DEBUG: Skip non-simulation type <class 'type'>
DEBUG: Skip non-simulation type <class 'function'>
t.debug()
t.run(100)
# t.design.run("write_rtlil memtest.il")
Selecting GateMate DEBUG DUMMY GET s_72ae Adding module with name `r1w1_1` DEBUG: SKIP ARRAYTYPE `ram` : <class 'myirl.lists.SigArray'> DEBUG SLICE READ MEMORY to dout AWIDTH:7 DWIDTH:10 DEBUG ADD ROM 7:10 PARAM PRIORITY --> 48 PARAM WORDS --> 128 PARAM ABITS --> 7 PARAM WIDTH --> 10 PARAM MEMID --> $mem_ram_sig PARAM CLK_ENABLE --> False PARAM CLK_POLARITY --> True PARAM TRANSPARENT --> False PARAM ABITS --> 7 PARAM WIDTH --> 10 PARAM MEMID --> $mem_ram_sig DEBUG SLICE WRITE MEMORY to ram_sig AWIDTH:7 DWIDTH:10 PARAM CLK_ENABLE --> True PARAM CLK_POLARITY --> True PARAM PRIORITY --> 48 PARAM ABITS --> 7 PARAM WIDTH --> 10 PARAM MEMID --> $mem_ram_sig DEBUG TOP LEVEL WRITE MEM ram_sig FINALIZE Dumping module `\r1w1'. -- Running command `tee -q hierarchy -top r1w1_1' -- -- Running command `tee -q read_verilog -lib -specify /usr/share/yosys/gatemate/cells_sim.v /usr/share/yosys/gatemate/cells_bb.v' -- -- Running command `tee -q memory_libmap -lib /usr/share/yosys/gatemate/brams.txt' -- -- Running command `tee -q techmap -map /usr/share/yosys/gatemate/brams_map.v' -- -- Running command `tee -q opt' -- -- Running command `stat' -- 26. Printing statistics. === r1w1_1 === Number of wires: 10 Number of wire bits: 58 Number of public wires: 6 Number of public wire bits: 36 Number of memories: 1 Number of memory bits: 1280 Number of processes: 0 Number of cells: 7 $and 1 $dff 1 $eq 1 $meminit 1 $memrd 1 $memwr 1 $mux 1 -- Running command `tee -q debug memory -nomap' -- -- Running command `write_rtlil top.il' -- 28. Executing RTLIL backend. Output filename: top.il -- Running command `show -format dot -prefix r1w1_1 *' -- 29. Generating Graphviz representation of design. Writing dot description to `r1w1_1.dot'. Dumping module r1w1_1 to page 1. DEBUG DUMMY GET s_72ae Tolerate exception: Module with name `r1w1_1` already existing Compiling /tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516.pyx because it changed. [1/1] Cythonizing /tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516.pyx running build_ext building 'r1w1_1_1516' extension creating build/temp.linux-x86_64-3.9/tmp/myirl_r1w1_td9ph_lc x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DCOSIM_NAMESPACE=r1w1_1_1516 -I../../myirl/../ -I/tmp/myirl_r1w1_td9ph_lc/ -I/usr/share/yosys/include -I/usr/include/python3.9 -c /tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516.cpp -o build/temp.linux-x86_64-3.9/tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516.o x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -DCOSIM_NAMESPACE=r1w1_1_1516 -I../../myirl/../ -I/tmp/myirl_r1w1_td9ph_lc/ -I/usr/share/yosys/include -I/usr/include/python3.9 -c /tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516_rtl.cpp -o build/temp.linux-x86_64-3.9/tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516_rtl.o x86_64-linux-gnu-g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fwrapv -O2 -Wl,-z,relro -g -fwrapv -O2 -g -ffile-prefix-map=/build/python3.9-RNBry6/python3.9-3.9.2=. -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.9/tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516.o build/temp.linux-x86_64-3.9/tmp/myirl_r1w1_td9ph_lc/r1w1_1_1516_rtl.o -o build/lib.linux-x86_64-3.9/r1w1_1_1516.cpython-39-x86_64-linux-gnu.so copying build/lib.linux-x86_64-3.9/r1w1_1_1516.cpython-39-x86_64-linux-gnu.so -> Open for writing: tb_memtest.vcd CXXRTL context: SKIP INTERFACE ITEM `ram` CXXRTL context: SKIP INTERFACE ITEM `MODE` CXXRTL context: SKIP INTERFACE ITEM `DWIDTH` CXXRTL context: SKIP INTERFACE ITEM `AWIDTH` CXXRTL context: SKIP INTERFACE ITEM `TRANSPARENCY` WRITE VERIFY 0 WRITE VERIFY 64 SIM DONE DEBUG STOP PROCESS stim
Attention: The pyosys API may crash the notebook without warning (terminate the kernel) when the yosys-abc utility is not present. May be the case in the Binder setup.
# ! [ `which yosys-abc` ] || sudo apt-get install yosys-ghdl
@block
def r2w1_gated(
clk : ClkSignal,
re : (Signal, bool),
we : Signal,
addr_r0: Signal,
addr_r1: Signal,
addr_w: Signal,
din: Signal,
dout0: Signal.Output,
dout1: Signal.Output,
ram_data : list,
MODE = False, # Conditional compilation flag w/o type annotation
DWIDTH=16, AWIDTH=10
) -> IRL:
ram = SigArray(ram_data, name='ram_sig', init=True)
@always(clk.posedge)
def mem_r2w():
dout1.next = ram[addr_r1]
if re:
dout0.next = ram[addr_r0]
if we:
ram[addr_w].next = din
return instances()
@simulation.sim.testbench(CXXRTL, time_unit = 'ns', target_class = ECP5Target)
def tb_memtest2(MODE = 0, STYLE = 1, data_w=16, addr_w=6, mem = r2w1_gated):
c = simulation.ClkSignal(name = 'clk')
c.init = True
rden = simulation.Signal(bool(), name = 're')
wren = simulation.Signal(bool(), name = 'we')
ra, rb, wa = [ simulation.Signal(intbv()[addr_w:]) for _ in range(3) ]
a, q0, q1 = [ simulation.Signal(intbv()[data_w:]) for n in ['a', 'q0', 'q1'] ]
ram_data = [ intbv(v)[data_w:] for v in range(2 ** addr_w)]
inst = mem(clk=c,
re=rden,
we=wren,
addr_r0=ra,
addr_r1=rb,
addr_w=wa, din=a,
dout0=q0, dout1=q1,
MODE=False, ram_data = ram_data,
AWIDTH=addr_w, DWIDTH=data_w)
@simulation.always(simulation.delay(2))
def clkgen():
c.next = ~c
@simulation.sequence
def stim():
rb.next = 0xfa
ra.next = 0xfa
wa.next = 0x20
wren.next = False
yield simulation.delay(5)
yield c.negedge
assert int(q1) == 0xfa
assert int(q0) != 0xfa
rden.next = True
yield c.negedge
ra.next = 0xaa
rden.next = False
wren.next = True
a.next = 0x44
yield c.negedge
wren.next = False
yield c.negedge
assert int(q0) == 0xfa
yield c.negedge
rb.next = 0x20
print(int(q0), int(q1))
yield c.negedge
print(int(q0), int(q1))
assert int(q1) == 0x44
print("SIM DONE")
return simulation.instances()
dw = 16
MEM = r2w1_gated
tb = tb_memtest2(data_w=dw, mem = MEM, addr_w = 9)
tb._force_compile = True
# tb.debug()
# tb.run(200)
Creating process 'r2w1_gated/mem_r2w' with sensitivity (clk'rising,)
DEBUG: Skip non-simulation type <class 'myirl.emulation.myhdl.wrapped_wrapper'>
DEBUG: Skip non-simulation type <class 'list'>
Selecting LatticeECP5
DEBUG DUMMY GET s_bb42
Adding module with name `r2w1_gated`
DEBUG: SKIP NON-SIGNAL ARGUMENT `ram_data` : <class 'list'>
DEBUG SLICE READ MEMORY to dout1 AWIDTH:9 DWIDTH:16
tb.design.run("write_rtlil r2w1.il", capture = None)