Describing memory and getting it synthesized correctly is cumbersome.
Therefore, this will only demonstrate a simple approach to generate memory.
Using Signal arrays, a simple r1w1
simplex dual port memory can be inferred to HDL.
This modification enables shared variable output for VHDL to support true dual port behaviour.
The general strategy with the MyIRL synthesis is to use a blackbox element (rely on the library)
from myhdl import intbv
from myirl import *
import random
We can not use a SigArray
for the RAM, because a true dual port RAM would provoke unresolved multiple drivers.
Therefore we hack the myirl.lists.SigArray
class by deriving and choosing a different element in return of the __getitem__
member which uses a shared variable.
Note Not working in VHDL-2008
from myirl import lists
from myirl.kernel import extensions, utils
from myirl import targets
class ArrayElem(lists.SigIndexed):
decl_type_vhdl = "shared variable"
def set(self, val):
w = self.size()
return CellAssign(self.seq, self.index, val, w)
class CellAssign: # extensions.ElemAssign
def __init__(self, parent, portion, val, width):
self._vref = parent
self.portion = portion
self.value = val
self.width = width
def emit(self, context):
tgt = context.target
n, v = self._vref.identifier, self.value
p = self.portion
sz = self.width
if tgt.lang == 'VHDL':
context.output("%s(to_integer(%s)) := %s;\n" % (n, p, base.convert(v, tgt, sz)))
elif tgt.lang == 'Verilog':
context.output("%s[%s] <= %s;\n" % (n, p, base.convert(v, tgt, sz)))
else:
raise TypeError("Unsupported target %s" % type(tgt))
def get_sources(self, srcs):
if isinstance(self.value, base.Sig):
self.value.get_sources(srcs)
def get_drivers(self, drvs):
pass
class RamBuffer(lists.SigArray):
def __getitem__(self, item):
if isinstance(item, (Sig, int)):
# We can not just return self.val[item], as the iterator
# has not initialized yet.
return ArrayElem(self, item)
else:
raise TypeError("Multi item slicing of iterator not supported")
Then define a RAM port (legacy class constructs will suffice) and the actual RAM implementation:
class RamPort:
_inputs = ['we', 'wa', 'ra', 'wd']
_outputs = ['rd']
_other = ['clk']
def __init__(self, AWIDTH, DWIDTH):
self.clk = ClkSignal()
self.we = Signal(bool())
self.ra, self.wa = [ Signal(intbv()[AWIDTH:]) for i in range(2) ]
self.rd, self.wd = [ Signal(intbv()[DWIDTH:]) for i in range(2) ]
@block
def tdp_ram(pa, pb, INITDATA):
inst = []
def gen_logic(p, i):
"Generate port mechanics inline"
rd = p.rd.clone("rd%d" % i)
@genprocess(p.clk, EDGE=p.clk.POS)
def proc():
yield [
proc.If(p.we == True).Then(
buf[p.wa].set(p.wd)
),
rd.set(buf[p.ra])
]
proc.rename("proc%d" % i)
wirings = [
p.rd.wireup(rd)
]
return proc, wirings
buf = RamBuffer(INITDATA)
for i, p in enumerate([pa, pb]):
inst += (gen_logic(p, i))
return instances()
Verify we can evaluate the content:
a = RamBuffer([intbv(i + 0x80)[8:] for i in range(200) ])
b = Signal(intbv(10)[5:])
a[b].evaluate()
intbv(138)
a[0].wire
intbv(128)
from myirl import targets
from myirl.test.common_test import run_ghdl, run_icarus
random.seed(0)
def test_vhdl():
RAM_CONTENT = [ intbv(random.randint(0, 2 ** 9))[8:] for i in range(2 ** 9) ]
pa, pb = [ RamPort(AWIDTH=9, DWIDTH=8) for i in range(2) ]
inst = tdp_ram(pa, pb, RAM_CONTENT)
f = inst.elab(targets.VHDL)
run_ghdl(f, inst, std = "93", debug = True) # Note we run with std '93'
return f
f = test_vhdl()
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context Adding INITDATA to port dict (fallback for type <class 'list'>) Writing 'tdp_ram' to file /tmp/myirl_tdp_ram_mtavs6c_/tdp_ram.vhdl
! grep -A 20 MyIRL {f[0]}
The same design translated to Verilog, run through the icarus simulator.
def test_verilog():
RAM_CONTENT = [ intbv(random.randint(0, 2 ** 9))[8:] for i in range(2 ** 9) ]
pa, pb = [ RamPort(AWIDTH=9, DWIDTH=8) for i in range(2) ]
inst = tdp_ram(pa, pb, RAM_CONTENT)
f = inst.elab(targets.Verilog)
run_icarus(f, inst, debug = True)
return f
f = test_verilog()
FALLBACK: UNHANDLED ROOT CLASS <class 'ipykernel.zmqshell.ZMQInteractiveShell'>, create new context
Adding INITDATA to port dict (fallback for type <class 'list'>)
Module tdp_ram: Existing instance tdp_ram, rename to tdp_ram_1
Writing 'tdp_ram_1' to file /tmp/myirl_tdp_ram_346w7dtl/tdp_ram_1.v
DEBUG SKIP INST [pa_rd <= s_8a50] <class 'list'>
DEBUG SKIP INST [pb_rd <= s_9a6a] <class 'list'>
!cat {f[0]}
// File generated from source: // /tmp/ipykernel_32224/3504810182.py // (c) 2016-2022 section5.ch // Modifications may be lost, edit the source file instead. `timescale 1 ns / 1 ps `include "aux.v" // Architecture myIRL module tdp_ram_1 ( input wire pa_clk, input wire pa_we, input wire [8:0] pa_ra, input wire [8:0] pa_wa, output wire [7:0] pa_rd, input wire [7:0] pa_wd, input wire pb_clk, input wire pb_we, input wire [8:0] pb_ra, input wire [8:0] pb_wa, output wire [7:0] pb_rd, input wire [7:0] pb_wd ); // Local type declarations // Signal declarations reg [7:0] s_8a50; // Dummy array definition v_8c25 reg [7:0] v_8c25[511:0]; reg [7:0] s_9a6a; always @ (posedge pa_clk ) begin : PROC0 if ((pa_we == 1'b1)) begin v_8c25[pa_wa] <= pa_wd; end s_8a50 <= v_8c25[pa_ra]; end assign pa_rd = s_8a50; always @ (posedge pb_clk ) begin : PROC1 if ((pb_we == 1'b1)) begin v_8c25[pb_wa] <= pb_wd; end s_9a6a <= v_8c25[pb_ra]; end assign pb_rd = s_9a6a; endmodule // tdp_ram_1
TDPRAM verification can be done on various levels:
The fine detail in the above HDL models: depending on the RAM instance (as VHDL shared variable or a Verilog signal), as well as the specific assignment mode (blocking or non-blocking) in the Verilog inference do have a crucial impact on the read/write priorities of such RAMs.
Most TDP RAM models allow the following priority modes.
For concurrent accesses from two ports, even more complexity is introduced. For example, a WRITE action on one port and a READ on the other from the same address can also be subject to the priority configuration, moreover, different clock domains are getting into the play.
Several combinations of input and output port widths are supported by TDPRAM architectures in general. If both data widths are in an integer ratio, only address translation and mapping is relevant for either bitwise slicing or concatenation. If not, alignment rules may apply.
As these properties are very technology dependent, it turns out to be complex to generalize test routines.
This is addressed by a generic tdp_tester
base class, containing a few common test routines. By derivation, customized TDP RAM testers are created that run a customized test for a particular architecture.
Note This may require a particular installation of the cyrite tdpram library
from cyrite.library.ram.tdpram import TDPRamGenerator
from myirl.test.ghdl import GHDL93
from myirl.test.icarus import ICARUS
class my_tester(TDPRamGenerator):
def __init__(self, SIMULATOR = ICARUS, delta = 0.2,
name = "tb_priorities"):
SIZE = 320*64
super().__init__(name, SIMULATOR, size = SIZE)
# Enable strict transparency check
self.STRICT_XTRANSPARENCY_CHECK = True
# Use generic wrapper as Unit Under Test:
self.uut = self.tdpram_wrapper
self.MODE_RAW = 'NORMAL'
self.MODE_WT = 'WRITETHROUGH'
self.undefined = Undefined
The tb_priorities
test uses two particularely detuned clocks for each RAM port and a small DELTA window for coincidence checks. It verifies that the RAM model reports an Undefined
signal value when simultaneous writes/reads occur in WRITETHROUGH
(Transparency) mode.
d = my_tester()
tb = d.tb_priorities(WMODE = "WRITETHROUGH")
tb.run(2000, debug = True, wavetrace = "test_ghdl.vcd")
Declare obj 'tb_priorities' in context '(my_tester 'tb_priorities')' DSIZE: use default 16 ASIZE: use default 10 Adding WMODE to port dict (fallback for type <class 'str'>) CLKA_PERIOD: use default 3.02 CLKB_PERIOD: use default 3.61 OUT_REG: use default False Declare obj 'trigger_pulse' in context '(my_tester 'tb_priorities')' Adding DELTA to port dict (fallback for type <class 'float'>) Insert unit trigger_pulse__my_tester_s1_s1_s1_s1_0.200000 Declare obj 'coincidence_prio' in context '(my_tester 'tb_priorities')' DELTA: use default 0.2 Declare obj 'clkpulse' in context '(my_tester 'tb_priorities')' Adding DELTA to port dict (fallback for type <class 'float'>) Insert unit clkpulse__my_tester_s1_s1_0.200000 Adding DELTA to port dict (fallback for type <class 'float'>) DEBUG: Cached unit clkpulse__my_tester_s1_s1_0.200000 Insert unit coincidence_prio__my_tester_s1_s1_s1_s1_s1_0.200000 DEBUG: INSTANCING uut with wmode = WRITETHROUGH Declare obj 'tdpram_wrapper' in context '(my_tester 'tb_priorities')' Adding wmode to port dict (fallback for type <class 'str'>) Declare obj 'tdpram' in context '(my_tester 'tb_priorities')' INITDATA: use default None Insert unit tdpram__my_tester__RAMPort__RAMPort__NoneType_20480_1 DELTA: use default 0.2 DEBUG: Cached unit trigger_pulse__my_tester_s1_s1_s1_s1_0.200000 DELTA: use default 0.2 DEBUG: Cached unit trigger_pulse__my_tester_s1_s1_s1_s1_0.200000 DELTA: use default 0.2 DEBUG: Cached unit coincidence_prio__my_tester_s1_s1_s1_s1_s1_0.200000 Insert unit tdpram_wrapper__my_tester__SimRamPort__SimRamPort_s1__str_0 Insert unit gray_counter_s1_s1_s1_s4 DEBUG: Cached unit gray_counter_s1_s1_s1_s4 Insert unit tb_priorities__my_tester_16_10__str_3.020000_3.610000_0 Writing 'gray_counter' to file /tmp/gray_counter.v Writing 'tdpram' to file /tmp/tdpram.v Writing 'tdpram_wrapper' to file /tmp/tdpram_wrapper.v Writing 'clkpulse' to file /tmp/clkpulse.v Writing 'coincidence_prio' to file /tmp/coincidence_prio.v Writing 'trigger_pulse' to file /tmp/trigger_pulse.v Renaming reserved identifier: reg -> reg_8557 Writing 'tb_priorities' to file /tmp/tb_priorities.v Warning: Implicit truncation of ADD(a_addr_auto, C:1) result Warning: Implicit truncation of ADD(b_addr_auto, C:1) result ==== COSIM stdout ==== VCD info: dumpfile tb_priorities.vcd opened for output. is: 0x640f wants: 0x640f is: 0xab82 wants: 0xab82 is: 0xa957 wants: 0xa957 RUN WRITE SEQUENCE... ====== REPORT ====== Coincidence occured. GOOD. Coincidence detected. GOOD. Stop Simulation
/home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/test/icarus.py:33: UserWarning: Ignoring wavetrace argument for Verilog simulator warnings.warn("Ignoring wavetrace argument for Verilog simulator")
0