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__rhdj3y3/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_83l01cbr/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_7487/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
/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")
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
0
!cat /tmp/tb_priorities.v
// File generated from source: // <not available (cython)> // (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 tb_priorities (); // Local type declarations // Signal declarations reg a_clk; initial a_clk = 1'b0; reg b_clk; initial b_clk = 1'b0; wire pa; wire pb; wire a_is_first; wire coincidence_reg; wire co_en; wire coincidence; wire [3:0] gca; wire fill_en; reg /* std_ulogic */ r; wire [3:0] gcb; wire valid; reg stat_raw; reg stat_rbw; reg stat_wt; reg stat_wto; reg stat_coincidence; reg stat_prio; reg stat_coll_wra_rdb; reg fill; reg read; reg a_ce; reg a_we; reg a_re; reg a_sel; reg [9:0] a_addr_user; reg [9:0] a_addr_auto; reg a_areset; reg [15:0] a_wdata; reg b_ce; reg b_we; reg b_re; reg b_sel; reg [9:0] b_addr_user; reg [9:0] b_addr_auto; reg b_areset; reg [15:0] b_wdata; wire [15:0] a_rdata; wire [15:0] b_rdata; reg [9:0] a_addr; reg s_ffda; reg [9:0] b_addr; reg s_155e; wire qen; always begin : CLKGEN_A #3.020000 a_clk <= ~a_clk; end always begin : CLKGEN_B #3.610000 b_clk <= ~b_clk; end // Instance coincidence_prio coincidence_prio coincidence_prio_0 ( .clka(a_clk), .clkb(b_clk), .pulse_a(pa), .pulse_b(pb), .a_b4_b(a_is_first) ); // Instance trigger_pulse trigger_pulse trigger_pulse_0 ( .clk(a_clk), .en(co_en), .coincidence(coincidence), .coincidence_reg(coincidence_reg) ); // Instance gray_counter gray_counter gray_counter_0 ( .clk(a_clk), .enable(fill_en), .reset(r), .gray_count(gca) ); // Instance gray_counter gray_counter gray_counter_1 ( .clk(b_clk), .enable(valid), .reset(r), .gray_count(gcb) ); initial begin : MAIN $dumpfile("tb_priorities.vcd"); $dumpvars(0, tb_priorities); stat_raw <= 1'b0; stat_rbw <= 1'b0; stat_wt <= 1'b0; stat_wto <= 1'b0; stat_coincidence <= 1'b0; stat_prio <= 1'b0; stat_coll_wra_rdb <= 1'b0; r = 1'b1; fill <= 1'b0; read <= 1'b0; #30.000000; r = 1'b0; a_ce <= 1'b0; a_we <= 1'b0; a_re <= 1'b0; a_sel <= 1'b0; a_addr_user <= 10'b0000000000; a_addr_auto <= 10'b0000000000; a_areset <= 1'b0; a_wdata <= 16'h0000; b_ce <= 1'b0; b_we <= 1'b0; b_re <= 1'b0; b_sel <= 1'b0; b_addr_user <= 10'b0000000000; b_addr_auto <= 10'b0000000000; b_areset <= 1'b0; b_wdata <= 16'h0000; #100.000000; a_ce <= 1'b1; b_ce <= 1'b1; @ (negedge a_clk); a_we <= 1'b1; a_addr_user <= 10'b0000000000; a_wdata <= 16'h640f; @ (negedge a_clk); a_addr_user <= 10'b0000000001; a_wdata <= 16'hab82; @ (negedge a_clk); a_addr_user <= 10'b0000000010; a_wdata <= 16'ha957; @ (negedge a_clk); a_we <= 1'b0; a_re <= 1'b1; read <= 1'b1; a_addr_user <= 10'b0000000000; @ (negedge a_clk); $write("%s ", "is: "); $write("0x%h ", a_rdata); $write("%s ", " wants: "); $write("%s ", "0x640f"); $write("\n"); `assert((a_rdata == 16'h640f), "Failed memory read") a_addr_user <= 10'b0000000001; @ (negedge a_clk); $write("%s ", "is: "); $write("0x%h ", a_rdata); $write("%s ", " wants: "); $write("%s ", "0xab82"); $write("\n"); `assert((a_rdata == 16'hab82), "Failed memory read") a_addr_user <= 10'b0000000010; @ (negedge a_clk); $write("%s ", "is: "); $write("0x%h ", a_rdata); $write("%s ", " wants: "); $write("%s ", "0xa957"); $write("\n"); `assert((a_rdata == 16'ha957), "Failed memory read") @ (negedge a_clk); #7.000000; while ((stat_prio == 1'b0)) begin $write("%s ", "RUN WRITE SEQUENCE..."); $write("\n"); while ((stat_coincidence == 1'b0)) begin b_addr_user <= 10'b0000100000; @ (negedge a_clk); a_we <= 1'b1; a_addr_user <= 10'b0000000000; a_wdata <= 16'h640f; @ (negedge a_clk); a_addr_user <= 10'b0000000001; a_wdata <= 16'hab82; @ (negedge a_clk); a_addr_user <= 10'b0000000010; a_wdata <= 16'ha957; @ (negedge a_clk); a_we <= 1'b0; #12.080000; @ (negedge a_clk); b_addr_user <= 10'b0000000000; a_we <= 1'b1; a_addr_user <= 10'b0000000000; a_wdata <= 16'h31cd; @ (negedge a_clk); a_we <= 1'b0; if ((coincidence_reg == 1'b1)) begin stat_coincidence <= 1'b1; stat_prio <= a_is_first; if ((b_rdata === 16'bxxxxxxxxxxxxxxxx)) begin stat_coll_wra_rdb <= 1'b1; end else if ((b_rdata == 16'h31cd)) begin stat_coll_wra_rdb <= 1'b0; $write("%s ", "OTHER PORT READ "); $write("0x%h ", b_rdata); $write("\n"); stat_wto <= 1'b1; end else begin $write("%s ", "OTHER PORT READ "); $write("0x%h ", b_rdata); $write("\n"); end end @ (posedge a_clk); if ((a_rdata == 16'h31cd)) begin stat_wt <= 1'b1; end else if ((a_rdata == 16'h640f)) begin @ (posedge a_clk); if ((a_rdata == 16'h31cd)) begin stat_rbw <= 1'b1; end else begin `assert(1'b0, "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():576") end end else if ((a_rdata == 16'ha957)) begin @ (posedge a_clk); if ((a_rdata == 16'h31cd)) begin stat_raw <= 1'b1; end else begin $write("%s ", "p0.rdata:"); $write("0x%h ", a_rdata); $write("%s ", "WRITETHROUGH"); $write("\n"); end end else begin `assert(1'b0, "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():585") end @ (posedge a_clk); if ((stat_raw == 1'b1)) begin `assert(1'b0, "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():590") $write("%s ", "WMODE: READ AFTER WRITE"); $write("\n"); end if ((stat_wt == 1'b1)) begin `assert(1'b1, "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():594") end if ((stat_rbw == 1'b1)) begin `assert(1'b0, "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():597") end if ((stat_wto == 1'b1)) begin $write("%s ", "WMODE: WRITETHROUGH TRANSPARENCY TO OTHER PORT"); $write("\n"); `assert(1'b1, "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():600") end `assert((((stat_raw | stat_wt) | stat_rbw) == 1'b1), "Failed in /home/testing/src/myhdl2/library/ram/ramtest.py:tdp_tester():605") end $write("%s ", "====== REPORT ======"); $write("\n"); $write("%s ", "Coincidence occured. GOOD."); $write("\n"); #5.000000; if ((stat_coll_wra_rdb == 1'b1)) begin if ((stat_prio == 1'b0)) begin $write("%s ", "Coincidence detected, but PRIO flag not set."); $write("\n"); $write("%s ", "Check `clkpulse` DELTA settings."); $write("\n"); end else begin $write("%s ", "Coincidence detected. GOOD."); $write("\n"); end end else if ((stat_wto == 1'b1)) begin $write("%s ", "Coincidence: WT transparency to other port."); $write("\n"); if (1'b1) begin $fatal(0, "Cross transparency check (strict) FAILED"); end else begin $write("%s ", "This is unreal, but tolerated."); $write("\n"); end end else if ((stat_prio == 1'b0)) begin $write("%s ", "Coincidence: Read before write"); $write("\n"); end else begin $write("%s ", "Coincidence not handled. BAD."); $write("\n"); end stat_coincidence <= 1'b0; #10.000000; end fill <= 1'b1; b_areset <= 1'b1; @ (posedge b_clk); b_areset <= 1'b0; b_sel <= 1'b1; #20.000000; @ (negedge a_clk); a_we <= 1'b1; a_addr_user <= 10'b0000100000; a_wdata <= 16'h640f; @ (negedge a_clk); a_addr_user <= 10'b0000100001; a_wdata <= 16'hab82; @ (negedge a_clk); a_addr_user <= 10'b0000100010; a_wdata <= 16'ha957; @ (negedge a_clk); a_we <= 1'b0; #100.000000; #50.000000; $display("Stop Simulation"); $finish; end // Instance tdpram_wrapper tdpram_wrapper tdpram_wrapper_0 ( .a_sel(a_sel), .a_addr_auto(a_addr_auto), .a_addr_user(a_addr_user), .a_areset(a_areset), .a_clk(a_clk), .a_addr(a_addr), .a_wdata(a_wdata), .a_rdata(a_rdata), .a_ce(a_ce), .a_re(a_re), .a_we(a_we), .b_sel(b_sel), .b_addr_auto(b_addr_auto), .b_addr_user(b_addr_user), .b_areset(b_areset), .b_clk(b_clk), .b_addr(b_addr), .b_wdata(b_wdata), .b_rdata(b_rdata), .b_ce(b_ce), .b_re(b_re), .b_we(b_we), .rst(r) ); always @ (a_sel or a_addr_auto or a_addr_user) begin : MUXER_A if ((a_sel == 1'b1)) begin a_addr <= a_addr_auto; end else begin a_addr <= a_addr_user; end end always @ (negedge a_clk ) begin : INC_A s_ffda <= valid; if ((a_areset == 1'b1)) begin a_addr_auto <= 10'b0000000000; end else if ((s_ffda == 1'b1)) begin a_addr_auto <= (a_addr_auto + 10'b0000000001) & 10'b1111111111 /* resize 11 -> 10 */ ; end end always @ (b_sel or b_addr_auto or b_addr_user) begin : MUXER_B if ((b_sel == 1'b1)) begin b_addr <= b_addr_auto; end else begin b_addr <= b_addr_user; end end always @ (negedge b_clk ) begin : INC_B s_155e <= valid; if ((b_areset == 1'b1)) begin b_addr_auto <= 10'b0000100000; end else if ((s_155e == 1'b1)) begin b_addr_auto <= (b_addr_auto + 10'b0000000001) & 10'b1111111111 /* resize 11 -> 10 */ ; end end assign coincidence = (pa & pb); assign qen = (read & valid); assign co_en = (a_we & ((a_addr == b_addr))); assign fill_en = (a_we & fill); assign valid = ((gca !== gcb)); endmodule // tb_priorities