#!/usr/bin/env python3 """Replace SimpleDualPortRAM_generic (HDL Coder) with blk_mem_gen SDP instances. Semantics of the original (must be preserved exactly): - single clock, `enb` gates BOTH write and read registration - read latency 1 cycle; write/read same address -> read returns OLD data (read-first) => blk_mem_gen: Simple Dual Port, common clock, Port-A write mode READ_FIRST, ENA/ENB pins used, NO output register. The wrapper keeps the module name/ports/params; a generate-if selects the per-(AddrWidth,DataWidth) IP; unlisted combos fall back to the original behavioral body (so simulation still works, and synthesis of an unlisted combo is loudly visible in the utilization report instead of silently wrong). Run AFTER rom_to_ip.py (needs rtl_ip/ copies), BEFORE namespace_ip.py. """ import argparse, json, os, re WRAP = '''// regenerated by ram_to_ip.py -- blk_mem_gen SDP wrappers (read-first, 1-cycle, common clk) `timescale 1 ns / 1 ns module {mod} (clk, enb, wr_din, wr_addr, wr_en, rd_addr, dout); parameter integer AddrWidth = 1; parameter integer DataWidth = 1; input clk; input enb; input signed [DataWidth - 1:0] wr_din; input [AddrWidth - 1:0] wr_addr; input wr_en; input [AddrWidth - 1:0] rd_addr; output signed [DataWidth - 1:0] dout; generate {cases} begin: g_behav // fallback: original behavioral RAM (unlisted param combo) reg [DataWidth - 1:0] ram [2**AddrWidth - 1:0]; reg [DataWidth - 1:0] data_int; integer i; initial begin for (i=0; i<=2**AddrWidth - 1; i=i+1) ram[i] = 0; data_int = 0; end always @(posedge clk) if (enb == 1'b1) begin if (wr_en == 1'b1) ram[wr_addr] <= wr_din; data_int <= ram[rd_addr]; end assign dout = data_int; end endgenerate endmodule ''' CASE = ''' if (AddrWidth == {aw} && DataWidth == {dw}) begin: g_a{aw}d{dw} wire [{dwm}:0] doutb_i; {ip} u_bram ( .clka(clk), .ena(enb), .wea(wr_en), .addra(wr_addr), .dina(wr_din), .clkb(clk), .enb(enb), .addrb(rd_addr), .doutb(doutb_i)); assign dout = doutb_i; end else ''' def main(): ap = argparse.ArgumentParser() ap.add_argument('hdlsrc') ap.add_argument('outdir', help='FIXED output dir (e.g. /vivado/rtl_ip/)') ap.add_argument('--manifest', default=None) a = ap.parse_args() hdlsrc = os.path.abspath(a.hdlsrc) rtl_ip = os.path.abspath(a.outdir) man = json.load(open(a.manifest or os.path.join(rtl_ip, 'manifest.json'))) combos = sorted({(r['AddrWidth'], r['DataWidth']) for r in man['ram'] if r['AddrWidth'] and r['DataWidth']}) if not combos: print('no SimpleDualPortRAM instantiations found'); return ipman_p = os.path.join(rtl_ip, 'ip_manifest.json') ipman = json.load(open(ipman_p)) if os.path.exists(ipman_p) else {'ips': []} have = {ip['name'] for ip in ipman['ips']} cases = '' for aw, dw in combos: ip = f'bram_sdp_a{aw}d{dw}' cases += CASE.format(aw=aw, dw=dw, dwm=dw - 1, ip=ip) if ip not in have: ipman['ips'].append({'type': 'blk_mem_gen', 'name': ip, 'width': dw, 'depth': 2 ** aw, 'mode': 'sdp_readfirst_commonclk'}) # rewrite every SimpleDualPortRAM_*.v module file present in rtl_ip n = 0 for fn in os.listdir(rtl_ip): if re.match(r'SimpleDualPortRAM_\w+\.v$', fn): mod = fn[:-2] open(os.path.join(rtl_ip, fn), 'w').write(WRAP.format(mod=mod, cases=cases)) print('wrapped', mod, 'combos:', combos) n += 1 json.dump(ipman, open(ipman_p, 'w'), indent=1) with open(os.path.join(rtl_ip, 'ip_manifest.tcl'), 'w') as f: for ip in ipman['ips']: f.write('lappend ips {%s}\n' % ' '.join(f'{k} {{{v}}}' for k, v in ip.items())) print(f'done: {n} RAM module(s) wrapped, {len(combos)} blk_mem_gen config(s)') if __name__ == '__main__': main()