├── chisel_memory_lower ├── __init__.py ├── parser.py ├── generate.py ├── xilinx.py ├── utils.py └── arm.py ├── requirements.in ├── sim.tcl ├── .gitignore ├── example.conf ├── requirements.txt ├── mem_1rw_xilinx.v ├── mem_1rw_arm.v ├── mem_1r1w_xilinx.v ├── mem_1r1w_masked_32x64_xilinx.v ├── mem_1r1w_masked_48x64_xilinx.v ├── mem_1r1w_masked_32x136_xilinx.v ├── README.md ├── LICENSE ├── arm.yaml ├── mem_1r1w_masked_32x64_arm.v ├── mem_1r1w_masked_32x136_arm.v ├── mem_1r1w_masked_48x64_arm.v └── mem_1r1w_arm.v /chisel_memory_lower/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | click 2 | pyyaml -------------------------------------------------------------------------------- /sim.tcl: -------------------------------------------------------------------------------- 1 | open_vcd 2 | log_vcd * 3 | run all 4 | exit 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | venv/ 3 | *.prj 4 | *.sh 5 | xsim.* 6 | xelab.* 7 | *.jou 8 | *.log 9 | *.wdb 10 | *.vcd 11 | *_tb.v 12 | csrc/ 13 | simv 14 | simv.daidir 15 | ucli.key 16 | rf2_*.v -------------------------------------------------------------------------------- /example.conf: -------------------------------------------------------------------------------- 1 | name mem_1rw depth 48 width 64 ports rw 2 | name mem_1r1w depth 48 width 64 ports write,read 3 | name mem_1r1w_masked_32x64 depth 32 width 64 ports mwrite,read mask_gran 8 4 | name mem_1r1w_masked_48x64 depth 48 width 64 ports mwrite,read mask_gran 8 5 | name mem_1r1w_masked_32x136 depth 32 width 136 ports mwrite,read mask_gran 8 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | --index-url https://mirrors.bfsu.edu.cn/pypi/web/simple 8 | 9 | click==8.1.3 10 | # via -r requirements.in 11 | pyyaml==6.0 12 | # via -r requirements.in 13 | -------------------------------------------------------------------------------- /chisel_memory_lower/parser.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from typing import List 3 | 4 | 5 | Config = namedtuple( 6 | 'Config', 'name, depth, width, ports, mask_gran', defaults=(None,) * 5) 7 | 8 | 9 | def parse(content: str) -> List[Config]: 10 | configs = [] 11 | for line in content.splitlines(): 12 | parts = line.split(' ') 13 | parts = list(filter(lambda s: len(s) > 0, parts)) 14 | if len(parts) % 2 != 0: 15 | continue 16 | info = {} 17 | for i in range(len(parts) // 2): 18 | key = parts[i * 2] 19 | value = parts[i * 2 + 1] 20 | info[key] = value 21 | config = Config(**info) 22 | configs.append(config) 23 | return configs 24 | -------------------------------------------------------------------------------- /mem_1rw_xilinx.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: xilinx 3 | // Config(name='mem_1rw', depth='48', width='64', ports='rw', mask_gran=None) 4 | `timescale 1ns/1ps 5 | module mem_1rw ( 6 | input [5:0] RW0_addr, 7 | input RW0_en, 8 | input RW0_clk, 9 | input RW0_wmode, 10 | input [63:0] RW0_wdata, 11 | output [63:0] RW0_rdata 12 | ); 13 | 14 | xpm_memory_spram #( 15 | .ADDR_WIDTH_A(6), 16 | .BYTE_WRITE_WIDTH_A(64), 17 | .MEMORY_SIZE(3072), 18 | .READ_DATA_WIDTH_A(64), 19 | .READ_LATENCY_A(1), 20 | .WRITE_DATA_WIDTH_A(64) 21 | ) xpm_memory_spram_inst ( 22 | .douta(RW0_rdata), 23 | .addra(RW0_addr), 24 | .clka(RW0_clk), 25 | .dina(RW0_wdata), 26 | .ena(RW0_en), 27 | .rsta(1'b0), 28 | .wea(RW0_wmode) 29 | ); 30 | endmodule 31 | -------------------------------------------------------------------------------- /mem_1rw_arm.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: arm 3 | // Config(name='mem_1rw', depth='48', width='64', ports='rw', mask_gran=None) 4 | `timescale 1ns/1ps 5 | module mem_1rw ( 6 | input [5:0] RW0_addr, 7 | input RW0_en, 8 | input RW0_clk, 9 | input RW0_wmode, 10 | input [63:0] RW0_wdata, 11 | output [63:0] RW0_rdata 12 | ); 13 | 14 | wire rw_addr_match_0 = (RW0_addr >> 7) == 0; 15 | wire [63:0] read_data_0; 16 | wire [63:0] read_partial_0_0; 17 | assign read_data_0[63:0] = read_partial_0_0; 18 | assign RW0_rdata = read_data_0; 19 | sram_1rw_128X64 inst_0_0 ( 20 | .A({1'b0, RW0_addr[5:0]}), 21 | .CEN(~(RW0_en && rw_addr_match_0)), 22 | .WEN(~RW0_wmode), 23 | .CLK(RW0_clk), 24 | .D(RW0_wdata[63:0]), 25 | .Q(read_partial_0_0), 26 | .EMA(3'd3), 27 | .EMAW(2'd2), 28 | .RET1N(1'b1) 29 | ); 30 | endmodule 31 | -------------------------------------------------------------------------------- /mem_1r1w_xilinx.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: xilinx 3 | // Config(name='mem_1r1w', depth='48', width='64', ports='write,read', mask_gran=None) 4 | `timescale 1ns/1ps 5 | module mem_1r1w ( 6 | input [5:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [63:0] R0_data, 10 | input [5:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [63:0] W0_data 14 | ); 15 | 16 | xpm_memory_sdpram #( 17 | .ADDR_WIDTH_A(6), 18 | .ADDR_WIDTH_B(6), 19 | .BYTE_WRITE_WIDTH_A(64), 20 | .MEMORY_SIZE(3072), 21 | .READ_DATA_WIDTH_B(64), 22 | .READ_LATENCY_B(1), 23 | .WRITE_DATA_WIDTH_A(64) 24 | ) xpm_memory_sdpram_inst ( 25 | .dina(W0_data), 26 | .addra(W0_addr), 27 | .ena(W0_en), 28 | .wea(W0_en), 29 | .clka(W0_clk), 30 | .addrb(R0_addr), 31 | .clkb(R0_clk), 32 | .enb(R0_en), 33 | .doutb(R0_data), 34 | .rstb(1'b0) 35 | ); 36 | endmodule 37 | -------------------------------------------------------------------------------- /mem_1r1w_masked_32x64_xilinx.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: xilinx 3 | // Config(name='mem_1r1w_masked_32x64', depth='32', width='64', ports='mwrite,read', mask_gran='8') 4 | `timescale 1ns/1ps 5 | module mem_1r1w_masked_32x64 ( 6 | input [4:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [63:0] R0_data, 10 | input [4:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [63:0] W0_data, 14 | input [7:0] W0_mask 15 | ); 16 | 17 | xpm_memory_sdpram #( 18 | .ADDR_WIDTH_A(5), 19 | .ADDR_WIDTH_B(5), 20 | .BYTE_WRITE_WIDTH_A(8), 21 | .MEMORY_SIZE(2048), 22 | .READ_DATA_WIDTH_B(64), 23 | .READ_LATENCY_B(1), 24 | .WRITE_DATA_WIDTH_A(64) 25 | ) xpm_memory_sdpram_inst ( 26 | .dina(W0_data), 27 | .addra(W0_addr), 28 | .ena(W0_en), 29 | .wea(W0_mask), 30 | .clka(W0_clk), 31 | .addrb(R0_addr), 32 | .clkb(R0_clk), 33 | .enb(R0_en), 34 | .doutb(R0_data), 35 | .rstb(1'b0) 36 | ); 37 | endmodule 38 | -------------------------------------------------------------------------------- /mem_1r1w_masked_48x64_xilinx.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: xilinx 3 | // Config(name='mem_1r1w_masked_48x64', depth='48', width='64', ports='mwrite,read', mask_gran='8') 4 | `timescale 1ns/1ps 5 | module mem_1r1w_masked_48x64 ( 6 | input [5:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [63:0] R0_data, 10 | input [5:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [63:0] W0_data, 14 | input [7:0] W0_mask 15 | ); 16 | 17 | xpm_memory_sdpram #( 18 | .ADDR_WIDTH_A(6), 19 | .ADDR_WIDTH_B(6), 20 | .BYTE_WRITE_WIDTH_A(8), 21 | .MEMORY_SIZE(3072), 22 | .READ_DATA_WIDTH_B(64), 23 | .READ_LATENCY_B(1), 24 | .WRITE_DATA_WIDTH_A(64) 25 | ) xpm_memory_sdpram_inst ( 26 | .dina(W0_data), 27 | .addra(W0_addr), 28 | .ena(W0_en), 29 | .wea(W0_mask), 30 | .clka(W0_clk), 31 | .addrb(R0_addr), 32 | .clkb(R0_clk), 33 | .enb(R0_en), 34 | .doutb(R0_data), 35 | .rstb(1'b0) 36 | ); 37 | endmodule 38 | -------------------------------------------------------------------------------- /mem_1r1w_masked_32x136_xilinx.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: xilinx 3 | // Config(name='mem_1r1w_masked_32x136', depth='32', width='136', ports='mwrite,read', mask_gran='8') 4 | `timescale 1ns/1ps 5 | module mem_1r1w_masked_32x136 ( 6 | input [4:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [135:0] R0_data, 10 | input [4:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [135:0] W0_data, 14 | input [16:0] W0_mask 15 | ); 16 | 17 | xpm_memory_sdpram #( 18 | .ADDR_WIDTH_A(5), 19 | .ADDR_WIDTH_B(5), 20 | .BYTE_WRITE_WIDTH_A(8), 21 | .MEMORY_SIZE(4352), 22 | .READ_DATA_WIDTH_B(136), 23 | .READ_LATENCY_B(1), 24 | .WRITE_DATA_WIDTH_A(136) 25 | ) xpm_memory_sdpram_inst ( 26 | .dina(W0_data), 27 | .addra(W0_addr), 28 | .ena(W0_en), 29 | .wea(W0_mask), 30 | .clka(W0_clk), 31 | .addrb(R0_addr), 32 | .clkb(R0_clk), 33 | .enb(R0_en), 34 | .doutb(R0_data), 35 | .rstb(1'b0) 36 | ); 37 | endmodule 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chisel-memory-lower 2 | 3 | Lower chisel3 memory Blackbox to macros. 4 | 5 | Targets: 6 | 7 | - Xilinx 8 | - ARM Memory IP 9 | 10 | Usage: 11 | 12 | ```shell 13 | python3 -m chisel_memory_lower.generate xilinx example.conf --tb 14 | python3 -m chisel_memory_lower.generate arm example.conf --arm-config arm.yaml --tb 15 | ``` 16 | 17 | ## Read under Write 18 | 19 | Chisel allows three read under write behaviors, `Undefined`, `ReadFirst` or `WriteFirst`. For 1R1W RAM, The behavior is: 20 | 21 | - `SyncReadMem()`: unspecified in FIRRTL, `WriteFirst` in behavior model 22 | - `SyncReadMem(Undefined)`: unspecified in FIRRTL, `WriteFirst` in behavior model 23 | - `SyncReadMem(ReadFirst)`: `old` in FIRRTL, `ReadFirst` in behavior model 24 | - `SyncReadMem(WriteFirst)`: `new` in FIRRTL, `WriteFirst` in behavior model 25 | 26 | However, XPM only supports `Undefined`(NO_CHANGE & WRITE_FIRST, generates `x`) and `ReadFirst`(READ_FIRST). ARM Memory IP only supports `Undefined`(generates `x`). Thus the behavior is not guaranteed when lowering. 27 | -------------------------------------------------------------------------------- /chisel_memory_lower/generate.py: -------------------------------------------------------------------------------- 1 | import click 2 | from chisel_memory_lower.parser import Config, parse 3 | import chisel_memory_lower.xilinx 4 | import chisel_memory_lower.arm 5 | 6 | 7 | @click.command() 8 | @click.argument('target') 9 | @click.argument('config', type=click.Path(exists=True)) 10 | @click.option('-a', '--arm-config', 'arm_config', help="Configuration file for IP") 11 | @click.option('-t', '--tb', is_flag=True, help="Generate testbench") 12 | @click.option('-v', '--vivado-path', 'vivado_path', help="Path to vivado installation", default="/opt/Xilinx/Vivado/2020.2") 13 | def generate(target: str, config: str, arm_config: str, tb: bool, vivado_path: str): 14 | content = open(config, 'r').read() 15 | for mem in parse(content): 16 | print(f'Generating {mem}') 17 | if target == "xilinx": 18 | chisel_memory_lower.xilinx.generate(mem, tb, vivado_path) 19 | elif target == "arm": 20 | chisel_memory_lower.arm.generate(mem, arm_config, tb) 21 | else: 22 | assert False 23 | 24 | 25 | if __name__ == '__main__': 26 | generate() 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jiajie Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /arm.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/vortexgpgpu/vortex/blob/master/hw/syn/synopsys/models/memory/cln28hpm/rf2_32x128_wm1/rf2_32x128_wm1.v 2 | ip: 3 | - name: rf2_32x128_wm1 4 | cost: 8780 # cell area 5 | width: 128 6 | depth: 32 7 | type: 1r1w_masked 8 | constants: 9 | COLLDISN: 1'b1 10 | DFTRAMBYP: 1'b0 11 | EMAA: 3'd3 12 | EMAB: 3'd3 13 | EMASA: 1'b0 14 | RET1N: 1'b1 15 | SEA: 1'b0 16 | SEB: 1'b0 17 | SIA: 2'b0 18 | SIB: 2'b0 19 | TAA: 5'b0 20 | TAB: 5'b0 21 | TCENA: 1'b0 22 | TCENB: 1'b0 23 | TDB: "128'b0" 24 | TENA: 1'b1 25 | TENB: 1'b1 26 | TWENB: "{128{1'b1}}" 27 | ports: &1r1w_mask 28 | - type: r 29 | addr: AA 30 | clock: CLKA 31 | enable_n: CENA 32 | data: QA 33 | - type: w 34 | addr: AB 35 | clock: CLKB 36 | enable_n: CENB 37 | data: DB 38 | mask_n: WENB 39 | # https://github.com/vortexgpgpu/vortex/blob/master/hw/syn/synopsys/models/memory/cln28hpm/rf2_32x19_wm0/rf2_32x19_wm0.v 40 | - name: rf2_32x19_wm0 41 | cost: 2136 # cell area 42 | width: 19 43 | depth: 32 44 | type: 1r1w 45 | constants: 46 | COLLDISN: 1'b1 47 | DFTRAMBYP: 1'b0 48 | EMAA: 3'd3 49 | EMAB: 3'd3 50 | EMASA: 1'b0 51 | RET1N: 1'b1 52 | SEA: 1'b0 53 | SEB: 1'b0 54 | SIA: 2'b0 55 | SIB: 2'b0 56 | TAA: 5'b0 57 | TAB: 5'b0 58 | TCENA: 1'b0 59 | TCENB: 1'b0 60 | TDB: "19'b0" 61 | TENA: 1'b1 62 | TENB: 1'b1 63 | ports: &1r1w 64 | - type: r 65 | addr: AA 66 | clock: CLKA 67 | enable_n: CENA 68 | data: QA 69 | - type: w 70 | addr: AB 71 | clock: CLKB 72 | enable_n: CENB 73 | data: DB 74 | # https://github.com/vortexgpgpu/vortex/blob/master/hw/syn/synopsys/models/memory/cln28hpm/rf2_256x19_wm0/rf2_256x19_wm0.v 75 | - name: rf2_256x19_wm0 76 | cost: 5188 # cell area 77 | width: 19 78 | depth: 256 79 | type: 1r1w 80 | ports: *1r1w -------------------------------------------------------------------------------- /mem_1r1w_masked_32x64_arm.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: arm 3 | // Config(name='mem_1r1w_masked_32x64', depth='32', width='64', ports='mwrite,read', mask_gran='8') 4 | `timescale 1ns/1ps 5 | module mem_1r1w_masked_32x64 ( 6 | input [4:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [63:0] R0_data, 10 | input [4:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [63:0] W0_data, 14 | input [7:0] W0_mask 15 | ); 16 | 17 | wire read_addr_match_0 = (R0_addr >> 5) == 0; 18 | wire write_addr_match_0 = (W0_addr >> 5) == 0; 19 | wire [63:0] read_data_0; 20 | wire [127:0] read_partial_0_0; 21 | assign read_data_0[63:0] = read_partial_0_0; 22 | assign R0_data = read_data_0; 23 | rf2_32x128_wm1 inst_0_0 ( 24 | .AA(R0_addr[4:0]), 25 | .CENA(~(R0_en && read_addr_match_0)), 26 | .CLKA(R0_clk), 27 | .QA(read_partial_0_0), 28 | .AB(W0_addr[4:0]), 29 | .CENB(~(W0_en && write_addr_match_0)), 30 | .CLKB(W0_clk), 31 | .DB({64'b0, W0_data[63:0]}), 32 | .WENB(~({1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0]})), 33 | .COLLDISN(1'b1), 34 | .DFTRAMBYP(1'b0), 35 | .EMAA(3'd3), 36 | .EMAB(3'd3), 37 | .EMASA(1'b0), 38 | .RET1N(1'b1), 39 | .SEA(1'b0), 40 | .SEB(1'b0), 41 | .SIA(2'b0), 42 | .SIB(2'b0), 43 | .TAA(5'b0), 44 | .TAB(5'b0), 45 | .TCENA(1'b0), 46 | .TCENB(1'b0), 47 | .TDB(128'b0), 48 | .TENA(1'b1), 49 | .TENB(1'b1), 50 | .TWENB({128{1'b1}}) 51 | ); 52 | endmodule 53 | -------------------------------------------------------------------------------- /mem_1r1w_masked_32x136_arm.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: arm 3 | // Config(name='mem_1r1w_masked_32x136', depth='32', width='136', ports='mwrite,read', mask_gran='8') 4 | `timescale 1ns/1ps 5 | module mem_1r1w_masked_32x136 ( 6 | input [4:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [135:0] R0_data, 10 | input [4:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [135:0] W0_data, 14 | input [16:0] W0_mask 15 | ); 16 | 17 | wire read_addr_match_0 = (R0_addr >> 5) == 0; 18 | wire write_addr_match_0 = (W0_addr >> 5) == 0; 19 | wire [135:0] read_data_0; 20 | wire [127:0] read_partial_0_0; 21 | assign read_data_0[67:0] = read_partial_0_0; 22 | wire [127:0] read_partial_1_0; 23 | assign read_data_0[135:68] = read_partial_1_0; 24 | assign R0_data = read_data_0; 25 | rf2_32x128_wm1 inst_0_0 ( 26 | .AA(R0_addr[4:0]), 27 | .CENA(~(R0_en && read_addr_match_0)), 28 | .CLKA(R0_clk), 29 | .QA(read_partial_0_0), 30 | .AB(W0_addr[4:0]), 31 | .CENB(~(W0_en && write_addr_match_0)), 32 | .CLKB(W0_clk), 33 | .DB({60'b0, W0_data[67:0]}), 34 | .WENB(~({1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, W0_mask[8], W0_mask[8], W0_mask[8], W0_mask[8], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0]})), 35 | .COLLDISN(1'b1), 36 | .DFTRAMBYP(1'b0), 37 | .EMAA(3'd3), 38 | .EMAB(3'd3), 39 | .EMASA(1'b0), 40 | .RET1N(1'b1), 41 | .SEA(1'b0), 42 | .SEB(1'b0), 43 | .SIA(2'b0), 44 | .SIB(2'b0), 45 | .TAA(5'b0), 46 | .TAB(5'b0), 47 | .TCENA(1'b0), 48 | .TCENB(1'b0), 49 | .TDB(128'b0), 50 | .TENA(1'b1), 51 | .TENB(1'b1), 52 | .TWENB({128{1'b1}}) 53 | ); 54 | rf2_32x128_wm1 inst_1_0 ( 55 | .AA(R0_addr[4:0]), 56 | .CENA(~(R0_en && read_addr_match_0)), 57 | .CLKA(R0_clk), 58 | .QA(read_partial_1_0), 59 | .AB(W0_addr[4:0]), 60 | .CENB(~(W0_en && write_addr_match_0)), 61 | .CLKB(W0_clk), 62 | .DB({60'b0, W0_data[135:68]}), 63 | .WENB(~({1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, W0_mask[16], W0_mask[16], W0_mask[16], W0_mask[16], W0_mask[16], W0_mask[16], W0_mask[16], W0_mask[16], W0_mask[15], W0_mask[15], W0_mask[15], W0_mask[15], W0_mask[15], W0_mask[15], W0_mask[15], W0_mask[15], W0_mask[14], W0_mask[14], W0_mask[14], W0_mask[14], W0_mask[14], W0_mask[14], W0_mask[14], W0_mask[14], W0_mask[13], W0_mask[13], W0_mask[13], W0_mask[13], W0_mask[13], W0_mask[13], W0_mask[13], W0_mask[13], W0_mask[12], W0_mask[12], W0_mask[12], W0_mask[12], W0_mask[12], W0_mask[12], W0_mask[12], W0_mask[12], W0_mask[11], W0_mask[11], W0_mask[11], W0_mask[11], W0_mask[11], W0_mask[11], W0_mask[11], W0_mask[11], W0_mask[10], W0_mask[10], W0_mask[10], W0_mask[10], W0_mask[10], W0_mask[10], W0_mask[10], W0_mask[10], W0_mask[9], W0_mask[9], W0_mask[9], W0_mask[9], W0_mask[9], W0_mask[9], W0_mask[9], W0_mask[9], W0_mask[8], W0_mask[8], W0_mask[8], W0_mask[8]})), 64 | .COLLDISN(1'b1), 65 | .DFTRAMBYP(1'b0), 66 | .EMAA(3'd3), 67 | .EMAB(3'd3), 68 | .EMASA(1'b0), 69 | .RET1N(1'b1), 70 | .SEA(1'b0), 71 | .SEB(1'b0), 72 | .SIA(2'b0), 73 | .SIB(2'b0), 74 | .TAA(5'b0), 75 | .TAB(5'b0), 76 | .TCENA(1'b0), 77 | .TCENB(1'b0), 78 | .TDB(128'b0), 79 | .TENA(1'b1), 80 | .TENB(1'b1), 81 | .TWENB({128{1'b1}}) 82 | ); 83 | endmodule 84 | -------------------------------------------------------------------------------- /mem_1r1w_masked_48x64_arm.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: arm 3 | // Config(name='mem_1r1w_masked_48x64', depth='48', width='64', ports='mwrite,read', mask_gran='8') 4 | `timescale 1ns/1ps 5 | module mem_1r1w_masked_48x64 ( 6 | input [5:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [63:0] R0_data, 10 | input [5:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [63:0] W0_data, 14 | input [7:0] W0_mask 15 | ); 16 | 17 | reg [0:0] read_addr_index_reg; 18 | always @ (posedge R0_clk) begin 19 | read_addr_index_reg <= R0_addr >> 5; 20 | end 21 | wire read_addr_match_0 = (R0_addr >> 5) == 0; 22 | wire write_addr_match_0 = (W0_addr >> 5) == 0; 23 | wire [63:0] read_data_0; 24 | wire [127:0] read_partial_0_0; 25 | assign read_data_0[63:0] = read_partial_0_0; 26 | wire read_addr_match_1 = (R0_addr >> 5) == 1; 27 | wire write_addr_match_1 = (W0_addr >> 5) == 1; 28 | wire [63:0] read_data_1; 29 | wire [127:0] read_partial_0_1; 30 | assign read_data_1[63:0] = read_partial_0_1; 31 | assign R0_data = ((read_addr_index_reg == 0) ? read_data_0 : ((read_addr_index_reg == 1) ? read_data_1 : 0)); 32 | rf2_32x128_wm1 inst_0_0 ( 33 | .AA(R0_addr[4:0]), 34 | .CENA(~(R0_en && read_addr_match_0)), 35 | .CLKA(R0_clk), 36 | .QA(read_partial_0_0), 37 | .AB(W0_addr[4:0]), 38 | .CENB(~(W0_en && write_addr_match_0)), 39 | .CLKB(W0_clk), 40 | .DB({64'b0, W0_data[63:0]}), 41 | .WENB(~({1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0]})), 42 | .COLLDISN(1'b1), 43 | .DFTRAMBYP(1'b0), 44 | .EMAA(3'd3), 45 | .EMAB(3'd3), 46 | .EMASA(1'b0), 47 | .RET1N(1'b1), 48 | .SEA(1'b0), 49 | .SEB(1'b0), 50 | .SIA(2'b0), 51 | .SIB(2'b0), 52 | .TAA(5'b0), 53 | .TAB(5'b0), 54 | .TCENA(1'b0), 55 | .TCENB(1'b0), 56 | .TDB(128'b0), 57 | .TENA(1'b1), 58 | .TENB(1'b1), 59 | .TWENB({128{1'b1}}) 60 | ); 61 | rf2_32x128_wm1 inst_0_1 ( 62 | .AA(R0_addr[4:0]), 63 | .CENA(~(R0_en && read_addr_match_1)), 64 | .CLKA(R0_clk), 65 | .QA(read_partial_0_1), 66 | .AB(W0_addr[4:0]), 67 | .CENB(~(W0_en && write_addr_match_1)), 68 | .CLKB(W0_clk), 69 | .DB({64'b0, W0_data[63:0]}), 70 | .WENB(~({1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, 1'b0, W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[7], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[6], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[5], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[4], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[3], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[2], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[1], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0], W0_mask[0]})), 71 | .COLLDISN(1'b1), 72 | .DFTRAMBYP(1'b0), 73 | .EMAA(3'd3), 74 | .EMAB(3'd3), 75 | .EMASA(1'b0), 76 | .RET1N(1'b1), 77 | .SEA(1'b0), 78 | .SEB(1'b0), 79 | .SIA(2'b0), 80 | .SIB(2'b0), 81 | .TAA(5'b0), 82 | .TAB(5'b0), 83 | .TCENA(1'b0), 84 | .TCENB(1'b0), 85 | .TDB(128'b0), 86 | .TENA(1'b1), 87 | .TENB(1'b1), 88 | .TWENB({128{1'b1}}) 89 | ); 90 | endmodule 91 | -------------------------------------------------------------------------------- /chisel_memory_lower/xilinx.py: -------------------------------------------------------------------------------- 1 | import os 2 | from chisel_memory_lower.utils import generate_header, generate_tb 3 | from chisel_memory_lower.parser import Config 4 | 5 | 6 | def generate(config: Config, tb: bool, vivado_path): 7 | with open(f'{config.name}_xilinx.v', 'w') as f: 8 | ports = set(config.ports.split(',')) 9 | depth = int(config.depth) 10 | width = int(config.width) 11 | addr_width = (depth-1).bit_length() 12 | header = generate_header(config, 'xilinx') 13 | print(header, file=f) 14 | 15 | if ports == {"rw"}: 16 | # 1RW 17 | print(f' xpm_memory_spram #(', file=f) 18 | print(f' .ADDR_WIDTH_A({addr_width}),', file=f) 19 | print(f' .BYTE_WRITE_WIDTH_A({width}),', file=f) 20 | print(f' .MEMORY_SIZE({width * depth}),', file=f) 21 | print(f' .READ_DATA_WIDTH_A({width}),', file=f) 22 | print(f' .READ_LATENCY_A(1),', file=f) 23 | print(f' .WRITE_DATA_WIDTH_A({width})', file=f) 24 | print(f' ) xpm_memory_spram_inst (', file=f) 25 | print(f' .douta(RW0_rdata),', file=f) 26 | print(f' .addra(RW0_addr),', file=f) 27 | print(f' .clka(RW0_clk),', file=f) 28 | print(f' .dina(RW0_wdata),', file=f) 29 | print(f' .ena(RW0_en),', file=f) 30 | print(f' .rsta(1\'b0),', file=f) 31 | print(f' .wea(RW0_wmode)', file=f) 32 | print(f' );', file=f) 33 | elif ports == {"read", "write"}: 34 | # 1R1W 35 | print(f' xpm_memory_sdpram #(', file=f) 36 | print(f' .ADDR_WIDTH_A({addr_width}),', file=f) 37 | print(f' .ADDR_WIDTH_B({addr_width}),', file=f) 38 | print(f' .BYTE_WRITE_WIDTH_A({width}),', file=f) 39 | print(f' .MEMORY_SIZE({width * depth}),', file=f) 40 | print(f' .READ_DATA_WIDTH_B({width}),', file=f) 41 | print(f' .READ_LATENCY_B(1),', file=f) 42 | print(f' .WRITE_DATA_WIDTH_A({width})', file=f) 43 | print(f' ) xpm_memory_sdpram_inst (', file=f) 44 | print(f' .dina(W0_data),', file=f) 45 | print(f' .addra(W0_addr),', file=f) 46 | print(f' .ena(W0_en),', file=f) 47 | print(f' .wea(W0_en),', file=f) 48 | print(f' .clka(W0_clk),', file=f) 49 | print(f' .addrb(R0_addr),', file=f) 50 | print(f' .clkb(R0_clk),', file=f) 51 | print(f' .enb(R0_en),', file=f) 52 | print(f' .doutb(R0_data),', file=f) 53 | print(f' .rstb(1\'b0)', file=f) 54 | print(f' );', file=f) 55 | elif ports == {"read", "mwrite"}: 56 | # 1R1W Masked 57 | mask_gran = int(config.mask_gran) 58 | print(f' xpm_memory_sdpram #(', file=f) 59 | print(f' .ADDR_WIDTH_A({addr_width}),', file=f) 60 | print(f' .ADDR_WIDTH_B({addr_width}),', file=f) 61 | print(f' .BYTE_WRITE_WIDTH_A({mask_gran}),', file=f) 62 | print(f' .MEMORY_SIZE({width * depth}),', file=f) 63 | print(f' .READ_DATA_WIDTH_B({width}),', file=f) 64 | print(f' .READ_LATENCY_B(1),', file=f) 65 | print(f' .WRITE_DATA_WIDTH_A({width})', file=f) 66 | print(f' ) xpm_memory_sdpram_inst (', file=f) 67 | print(f' .dina(W0_data),', file=f) 68 | print(f' .addra(W0_addr),', file=f) 69 | print(f' .ena(W0_en),', file=f) 70 | print(f' .wea(W0_mask),', file=f) 71 | print(f' .clka(W0_clk),', file=f) 72 | print(f' .addrb(R0_addr),', file=f) 73 | print(f' .clkb(R0_clk),', file=f) 74 | print(f' .enb(R0_en),', file=f) 75 | print(f' .doutb(R0_data),', file=f) 76 | print(f' .rstb(1\'b0)', file=f) 77 | print(f' );', file=f) 78 | print(f'endmodule', file=f) 79 | 80 | if tb: 81 | with open(f'{config.name}_xilinx.prj', 'w') as file: 82 | print(f'verilog work {config.name}_xilinx.v', file=file) 83 | print(f'verilog work {config.name}_xilinx_tb.v', file=file) 84 | print( 85 | f'sv work {vivado_path}/data/ip/xpm/xpm_memory/hdl/xpm_memory.sv', file=file) 86 | 87 | with open(f'{config.name}_xilinx.sh', 'w') as file: 88 | print(f'#!/bin/bash', file=file) 89 | print( 90 | f'export PATH={vivado_path}/bin:$PATH', file=file) 91 | print( 92 | f'xelab -debug all {config.name}_tb -prj {config.name}_xilinx.prj', file=file) 93 | print(f'xsim {config.name}_tb --tclbatch sim.tcl', file=file) 94 | os.chmod(f'{config.name}_xilinx.sh', 0o755) 95 | 96 | with open(f'{config.name}_xilinx_tb.v', 'w') as f: 97 | tb = generate_tb(config) 98 | print(tb, file=f) 99 | -------------------------------------------------------------------------------- /mem_1r1w_arm.v: -------------------------------------------------------------------------------- 1 | // Generate by chisel-memory-lower 2 | // Target: arm 3 | // Config(name='mem_1r1w', depth='48', width='64', ports='write,read', mask_gran=None) 4 | `timescale 1ns/1ps 5 | module mem_1r1w ( 6 | input [5:0] R0_addr, 7 | input R0_en, 8 | input R0_clk, 9 | output [63:0] R0_data, 10 | input [5:0] W0_addr, 11 | input W0_en, 12 | input W0_clk, 13 | input [63:0] W0_data 14 | ); 15 | 16 | reg [0:0] read_addr_index_reg; 17 | always @ (posedge R0_clk) begin 18 | read_addr_index_reg <= R0_addr >> 5; 19 | end 20 | wire read_addr_match_0 = (R0_addr >> 5) == 0; 21 | wire write_addr_match_0 = (W0_addr >> 5) == 0; 22 | wire [63:0] read_data_0; 23 | wire [18:0] read_partial_0_0; 24 | assign read_data_0[15:0] = read_partial_0_0; 25 | wire [18:0] read_partial_1_0; 26 | assign read_data_0[31:16] = read_partial_1_0; 27 | wire [18:0] read_partial_2_0; 28 | assign read_data_0[47:32] = read_partial_2_0; 29 | wire [18:0] read_partial_3_0; 30 | assign read_data_0[63:48] = read_partial_3_0; 31 | wire read_addr_match_1 = (R0_addr >> 5) == 1; 32 | wire write_addr_match_1 = (W0_addr >> 5) == 1; 33 | wire [63:0] read_data_1; 34 | wire [18:0] read_partial_0_1; 35 | assign read_data_1[15:0] = read_partial_0_1; 36 | wire [18:0] read_partial_1_1; 37 | assign read_data_1[31:16] = read_partial_1_1; 38 | wire [18:0] read_partial_2_1; 39 | assign read_data_1[47:32] = read_partial_2_1; 40 | wire [18:0] read_partial_3_1; 41 | assign read_data_1[63:48] = read_partial_3_1; 42 | assign R0_data = ((read_addr_index_reg == 0) ? read_data_0 : ((read_addr_index_reg == 1) ? read_data_1 : 0)); 43 | rf2_32x19_wm0 inst_0_0 ( 44 | .AA(R0_addr[4:0]), 45 | .CENA(~(R0_en && read_addr_match_0)), 46 | .CLKA(R0_clk), 47 | .QA(read_partial_0_0), 48 | .AB(W0_addr[4:0]), 49 | .CENB(~(W0_en && write_addr_match_0)), 50 | .CLKB(W0_clk), 51 | .DB({3'b0, W0_data[15:0]}), 52 | .COLLDISN(1'b1), 53 | .DFTRAMBYP(1'b0), 54 | .EMAA(3'd3), 55 | .EMAB(3'd3), 56 | .EMASA(1'b0), 57 | .RET1N(1'b1), 58 | .SEA(1'b0), 59 | .SEB(1'b0), 60 | .SIA(2'b0), 61 | .SIB(2'b0), 62 | .TAA(5'b0), 63 | .TAB(5'b0), 64 | .TCENA(1'b0), 65 | .TCENB(1'b0), 66 | .TDB(19'b0), 67 | .TENA(1'b1), 68 | .TENB(1'b1) 69 | ); 70 | rf2_32x19_wm0 inst_0_1 ( 71 | .AA(R0_addr[4:0]), 72 | .CENA(~(R0_en && read_addr_match_1)), 73 | .CLKA(R0_clk), 74 | .QA(read_partial_0_1), 75 | .AB(W0_addr[4:0]), 76 | .CENB(~(W0_en && write_addr_match_1)), 77 | .CLKB(W0_clk), 78 | .DB({3'b0, W0_data[15:0]}), 79 | .COLLDISN(1'b1), 80 | .DFTRAMBYP(1'b0), 81 | .EMAA(3'd3), 82 | .EMAB(3'd3), 83 | .EMASA(1'b0), 84 | .RET1N(1'b1), 85 | .SEA(1'b0), 86 | .SEB(1'b0), 87 | .SIA(2'b0), 88 | .SIB(2'b0), 89 | .TAA(5'b0), 90 | .TAB(5'b0), 91 | .TCENA(1'b0), 92 | .TCENB(1'b0), 93 | .TDB(19'b0), 94 | .TENA(1'b1), 95 | .TENB(1'b1) 96 | ); 97 | rf2_32x19_wm0 inst_1_0 ( 98 | .AA(R0_addr[4:0]), 99 | .CENA(~(R0_en && read_addr_match_0)), 100 | .CLKA(R0_clk), 101 | .QA(read_partial_1_0), 102 | .AB(W0_addr[4:0]), 103 | .CENB(~(W0_en && write_addr_match_0)), 104 | .CLKB(W0_clk), 105 | .DB({3'b0, W0_data[31:16]}), 106 | .COLLDISN(1'b1), 107 | .DFTRAMBYP(1'b0), 108 | .EMAA(3'd3), 109 | .EMAB(3'd3), 110 | .EMASA(1'b0), 111 | .RET1N(1'b1), 112 | .SEA(1'b0), 113 | .SEB(1'b0), 114 | .SIA(2'b0), 115 | .SIB(2'b0), 116 | .TAA(5'b0), 117 | .TAB(5'b0), 118 | .TCENA(1'b0), 119 | .TCENB(1'b0), 120 | .TDB(19'b0), 121 | .TENA(1'b1), 122 | .TENB(1'b1) 123 | ); 124 | rf2_32x19_wm0 inst_1_1 ( 125 | .AA(R0_addr[4:0]), 126 | .CENA(~(R0_en && read_addr_match_1)), 127 | .CLKA(R0_clk), 128 | .QA(read_partial_1_1), 129 | .AB(W0_addr[4:0]), 130 | .CENB(~(W0_en && write_addr_match_1)), 131 | .CLKB(W0_clk), 132 | .DB({3'b0, W0_data[31:16]}), 133 | .COLLDISN(1'b1), 134 | .DFTRAMBYP(1'b0), 135 | .EMAA(3'd3), 136 | .EMAB(3'd3), 137 | .EMASA(1'b0), 138 | .RET1N(1'b1), 139 | .SEA(1'b0), 140 | .SEB(1'b0), 141 | .SIA(2'b0), 142 | .SIB(2'b0), 143 | .TAA(5'b0), 144 | .TAB(5'b0), 145 | .TCENA(1'b0), 146 | .TCENB(1'b0), 147 | .TDB(19'b0), 148 | .TENA(1'b1), 149 | .TENB(1'b1) 150 | ); 151 | rf2_32x19_wm0 inst_2_0 ( 152 | .AA(R0_addr[4:0]), 153 | .CENA(~(R0_en && read_addr_match_0)), 154 | .CLKA(R0_clk), 155 | .QA(read_partial_2_0), 156 | .AB(W0_addr[4:0]), 157 | .CENB(~(W0_en && write_addr_match_0)), 158 | .CLKB(W0_clk), 159 | .DB({3'b0, W0_data[47:32]}), 160 | .COLLDISN(1'b1), 161 | .DFTRAMBYP(1'b0), 162 | .EMAA(3'd3), 163 | .EMAB(3'd3), 164 | .EMASA(1'b0), 165 | .RET1N(1'b1), 166 | .SEA(1'b0), 167 | .SEB(1'b0), 168 | .SIA(2'b0), 169 | .SIB(2'b0), 170 | .TAA(5'b0), 171 | .TAB(5'b0), 172 | .TCENA(1'b0), 173 | .TCENB(1'b0), 174 | .TDB(19'b0), 175 | .TENA(1'b1), 176 | .TENB(1'b1) 177 | ); 178 | rf2_32x19_wm0 inst_2_1 ( 179 | .AA(R0_addr[4:0]), 180 | .CENA(~(R0_en && read_addr_match_1)), 181 | .CLKA(R0_clk), 182 | .QA(read_partial_2_1), 183 | .AB(W0_addr[4:0]), 184 | .CENB(~(W0_en && write_addr_match_1)), 185 | .CLKB(W0_clk), 186 | .DB({3'b0, W0_data[47:32]}), 187 | .COLLDISN(1'b1), 188 | .DFTRAMBYP(1'b0), 189 | .EMAA(3'd3), 190 | .EMAB(3'd3), 191 | .EMASA(1'b0), 192 | .RET1N(1'b1), 193 | .SEA(1'b0), 194 | .SEB(1'b0), 195 | .SIA(2'b0), 196 | .SIB(2'b0), 197 | .TAA(5'b0), 198 | .TAB(5'b0), 199 | .TCENA(1'b0), 200 | .TCENB(1'b0), 201 | .TDB(19'b0), 202 | .TENA(1'b1), 203 | .TENB(1'b1) 204 | ); 205 | rf2_32x19_wm0 inst_3_0 ( 206 | .AA(R0_addr[4:0]), 207 | .CENA(~(R0_en && read_addr_match_0)), 208 | .CLKA(R0_clk), 209 | .QA(read_partial_3_0), 210 | .AB(W0_addr[4:0]), 211 | .CENB(~(W0_en && write_addr_match_0)), 212 | .CLKB(W0_clk), 213 | .DB({3'b0, W0_data[63:48]}), 214 | .COLLDISN(1'b1), 215 | .DFTRAMBYP(1'b0), 216 | .EMAA(3'd3), 217 | .EMAB(3'd3), 218 | .EMASA(1'b0), 219 | .RET1N(1'b1), 220 | .SEA(1'b0), 221 | .SEB(1'b0), 222 | .SIA(2'b0), 223 | .SIB(2'b0), 224 | .TAA(5'b0), 225 | .TAB(5'b0), 226 | .TCENA(1'b0), 227 | .TCENB(1'b0), 228 | .TDB(19'b0), 229 | .TENA(1'b1), 230 | .TENB(1'b1) 231 | ); 232 | rf2_32x19_wm0 inst_3_1 ( 233 | .AA(R0_addr[4:0]), 234 | .CENA(~(R0_en && read_addr_match_1)), 235 | .CLKA(R0_clk), 236 | .QA(read_partial_3_1), 237 | .AB(W0_addr[4:0]), 238 | .CENB(~(W0_en && write_addr_match_1)), 239 | .CLKB(W0_clk), 240 | .DB({3'b0, W0_data[63:48]}), 241 | .COLLDISN(1'b1), 242 | .DFTRAMBYP(1'b0), 243 | .EMAA(3'd3), 244 | .EMAB(3'd3), 245 | .EMASA(1'b0), 246 | .RET1N(1'b1), 247 | .SEA(1'b0), 248 | .SEB(1'b0), 249 | .SIA(2'b0), 250 | .SIB(2'b0), 251 | .TAA(5'b0), 252 | .TAB(5'b0), 253 | .TCENA(1'b0), 254 | .TCENB(1'b0), 255 | .TDB(19'b0), 256 | .TENA(1'b1), 257 | .TENB(1'b1) 258 | ); 259 | endmodule 260 | -------------------------------------------------------------------------------- /chisel_memory_lower/utils.py: -------------------------------------------------------------------------------- 1 | 2 | from io import StringIO 3 | import os 4 | import random 5 | from chisel_memory_lower.parser import Config 6 | 7 | 8 | def generate_header(config: Config, target: str) -> str: 9 | f = StringIO() 10 | ports = set(config.ports.split(',')) 11 | depth = int(config.depth) 12 | width = int(config.width) 13 | addr_width = (depth-1).bit_length() 14 | print(f'// Generate by chisel-memory-lower', file=f) 15 | print(f'// Target: {target}', file=f) 16 | print(f'// {config}', file=f) 17 | print(f'`timescale 1ns/1ps', file=f) 18 | print(f'module {config.name} (', file=f) 19 | if ports == {"rw"}: 20 | # 1RW 21 | print(f' input [{addr_width-1}:0] RW0_addr,', file=f) 22 | print(f' input RW0_en,', file=f) 23 | print(f' input RW0_clk,', file=f) 24 | print(f' input RW0_wmode,', file=f) 25 | print(f' input [{width-1}:0] RW0_wdata,', file=f) 26 | print(f' output [{width-1}:0] RW0_rdata', file=f) 27 | elif ports == {"mrw"}: 28 | # 1RW Masked 29 | mask_gran = int(config.mask_gran) 30 | print(f' input [{addr_width-1}:0] RW0_addr,', file=f) 31 | print(f' input RW0_en,', file=f) 32 | print(f' input RW0_clk,', file=f) 33 | print(f' input RW0_wmode,', file=f) 34 | print(f' input [{width-1}:0] RW0_wdata,', file=f) 35 | print(f' input [{width//mask_gran-1}:0] RW0_wmask,', file=f) 36 | print(f' output [{width-1}:0] RW0_rdata', file=f) 37 | elif ports == {"read", "write"}: 38 | # 1RW 39 | print(f' input [{addr_width-1}:0] R0_addr,', file=f) 40 | print(f' input R0_en,', file=f) 41 | print(f' input R0_clk,', file=f) 42 | print(f' output [{width-1}:0] R0_data,', file=f) 43 | print(f' input [{addr_width-1}:0] W0_addr,', file=f) 44 | print(f' input W0_en,', file=f) 45 | print(f' input W0_clk,', file=f) 46 | print(f' input [{width-1}:0] W0_data', file=f) 47 | elif ports == {"read", "mwrite"}: 48 | # 1RW Masked 49 | mask_gran = int(config.mask_gran) 50 | print(f' input [{addr_width-1}:0] R0_addr,', file=f) 51 | print(f' input R0_en,', file=f) 52 | print(f' input R0_clk,', file=f) 53 | print(f' output [{width-1}:0] R0_data,', file=f) 54 | print(f' input [{addr_width-1}:0] W0_addr,', file=f) 55 | print(f' input W0_en,', file=f) 56 | print(f' input W0_clk,', file=f) 57 | print(f' input [{width-1}:0] W0_data,', file=f) 58 | print(f' input [{width//mask_gran-1}:0] W0_mask', file=f) 59 | 60 | print(f');', file=f) 61 | return f.getvalue() 62 | 63 | 64 | def generate_tb(config: Config) -> str: 65 | f = StringIO() 66 | ports = set(config.ports.split(',')) 67 | depth = int(config.depth) 68 | width = int(config.width) 69 | mask_gran = config.mask_gran 70 | addr_width = (depth-1).bit_length() 71 | 72 | # generate transactions 73 | # r, w or rw for each cycle 74 | random.seed(0) 75 | trans = [] 76 | ram = [0] * depth 77 | for i in range(depth): 78 | ram[i] = random.randint(0, (1 << width) - 1) 79 | if "mwrite" in ports or "mrw" in ports: 80 | mask_gran = int(mask_gran) 81 | mask_bits = width // mask_gran 82 | wmask = (1 << mask_bits) - 1 83 | trans.append(("w", i, ram[i], wmask)) 84 | else: 85 | trans.append(("w", i, ram[i])) 86 | 87 | for i in range(1000): 88 | addr = random.randint(0, depth-1) 89 | data = random.randint(0, (1 << width) - 1) 90 | rand = random.randint(0, 9) 91 | if rand <= 3: 92 | # read 93 | trans.append(("r", addr, ram[addr])) 94 | elif rand < 9: 95 | if ports == {"read", "write"} and rand < 6: 96 | # read+write 97 | # allow x/read_first/write_first 98 | if rand == 5: 99 | # test read-write collision 100 | raddr = addr 101 | else: 102 | raddr = random.randint(0, depth-1) 103 | if raddr == addr: 104 | old = ram[addr] 105 | ram[addr] = data 106 | trans.append(("rw", addr, data, raddr, old, data)) 107 | else: 108 | ram[addr] = data 109 | trans.append(("rw", addr, data, raddr, ram[raddr])) 110 | else: 111 | # write 112 | if "mwrite" in ports or "mrw" in ports: 113 | # masked write 114 | mask_gran = int(mask_gran) 115 | mask_bits = width // mask_gran 116 | wmask = random.randint(0, (1 << mask_bits) - 1) 117 | bmask = 0 118 | for j in range(mask_bits): 119 | if wmask & (1 << j) != 0: 120 | bmask |= ((1 << mask_gran) - 1) << (mask_gran * j) 121 | ram[addr] = (ram[addr] & ~bmask) | (data & bmask) 122 | trans.append(("w", addr, data, wmask)) 123 | else: 124 | ram[addr] = data 125 | trans.append(("w", addr, data)) 126 | else: 127 | # bubble 128 | trans.append(("b")) 129 | 130 | print(f'`timescale 1ns/1ps', file=f) 131 | print(f'module {config.name}_tb (', file=f) 132 | print(f');', file=f) 133 | if ports == {"rw"} or ports == {"mrw"}: 134 | # 1RW or 1RW Masked 135 | print(f' reg [{addr_width-1}:0] RW0_addr;', file=f) 136 | print(f' reg RW0_en;', file=f) 137 | print(f' reg RW0_clk;', file=f) 138 | print(f' reg RW0_wmode;', file=f) 139 | print(f' reg [{width-1}:0] RW0_wdata;', file=f) 140 | print(f' wire [{width-1}:0] RW0_rdata;', file=f) 141 | if "mrw" in ports: 142 | print(f' reg [{width//int(mask_gran)-1}:0] RW0_wmask;', file=f) 143 | 144 | print(f' initial begin', file=f) 145 | print(f' RW0_clk = 1;', file=f) 146 | print(f' RW0_en = 0;', file=f) 147 | print(f' RW0_wmode = 0;', file=f) 148 | print(f' RW0_addr = 0;', file=f) 149 | print(f' RW0_wdata = 0;', file=f) 150 | if "mrw" in ports: 151 | print( 152 | f' RW0_wmask = {{{width//int(mask_gran)}{{1\'b1}}}};', file=f) 153 | print(f' #2;', file=f) 154 | for tx in trans: 155 | if tx[0] == "w": 156 | print(f' RW0_en = 1;', file=f) 157 | print(f' RW0_wmode = 1;', file=f) 158 | print(f' RW0_addr = {tx[1]};', file=f) 159 | print(f' RW0_wdata = \'h{tx[2]:x};', file=f) 160 | if "mrw" in ports: 161 | print(f' RW0_wmask = \'h{tx[3]:x};', file=f) 162 | print(f' #10;', file=f) 163 | elif tx[0] == "r": 164 | print(f' RW0_en = 1;', file=f) 165 | print(f' RW0_wmode = 0;', file=f) 166 | print(f' RW0_addr = {tx[1]};', file=f) 167 | print(f' #10;', file=f) 168 | print( 169 | f' if (RW0_rdata !== \'h{tx[2]:x}) begin', file=f) 170 | print(f' $display("ASSERTION FAILED");', file=f) 171 | print(f' $finish;', file=f) 172 | print(f' end', file=f) 173 | elif tx[0] == "b": 174 | print(f' RW0_en = 0;', file=f) 175 | print(f' #10;', file=f) 176 | 177 | print(f' $display("SIMULATION SUCCESS");', file=f) 178 | print(f' $finish;', file=f) 179 | print(f' end', file=f) 180 | 181 | print(f' always #5 RW0_clk = ~RW0_clk;', file=f) 182 | 183 | print(f' {config.name} inst (', file=f) 184 | print(f' .RW0_addr(RW0_addr),', file=f) 185 | print(f' .RW0_en(RW0_en),', file=f) 186 | print(f' .RW0_clk(RW0_clk),', file=f) 187 | print(f' .RW0_wmode(RW0_wmode),', file=f) 188 | print(f' .RW0_wdata(RW0_wdata),', file=f) 189 | if "mrw" in ports: 190 | print(f' .RW0_wmask(RW0_wmask),', file=f) 191 | print(f' .RW0_rdata(RW0_rdata)', file=f) 192 | print(f' );', file=f) 193 | elif ports == {"read", "write"} or ports == {"read", "mwrite"}: 194 | # 1R1W or 1R1W masked 195 | print(f' reg [{addr_width-1}:0] R0_addr;', file=f) 196 | print(f' reg R0_en;', file=f) 197 | print(f' reg R0_clk;', file=f) 198 | print(f' wire [{width-1}:0] R0_data;', file=f) 199 | print(f' reg [{addr_width-1}:0] W0_addr;', file=f) 200 | print(f' reg W0_en;', file=f) 201 | print(f' reg W0_clk;', file=f) 202 | print(f' reg [{width-1}:0] W0_data;', file=f) 203 | if "mwrite" in ports: 204 | print(f' reg [{width//int(mask_gran)-1}:0] W0_mask;', file=f) 205 | 206 | print(f' initial begin', file=f) 207 | print(f' R0_clk = 1;', file=f) 208 | print(f' R0_en = 0;', file=f) 209 | print(f' R0_addr = 0;', file=f) 210 | print(f' W0_clk = 1;', file=f) 211 | print(f' W0_en = 0;', file=f) 212 | print(f' W0_addr = 0;', file=f) 213 | print(f' W0_data = 0;', file=f) 214 | if "mwrite" in ports: 215 | print( 216 | f' W0_mask = {{{width//int(mask_gran)}{{1\'b1}}}};', file=f) 217 | print(f' #2;', file=f) 218 | for tx in trans: 219 | if tx[0] == "w": 220 | print(f' R0_en = 0;', file=f) 221 | print(f' W0_en = 1;', file=f) 222 | print(f' W0_addr = {tx[1]};', file=f) 223 | print(f' W0_data = \'h{tx[2]:x};', file=f) 224 | if "mwrite" in ports: 225 | print(f' W0_mask = \'h{tx[3]:x};', file=f) 226 | print(f' #10;', file=f) 227 | elif tx[0] == "r": 228 | print(f' R0_en = 1;', file=f) 229 | print(f' W0_en = 0;', file=f) 230 | print(f' R0_addr = {tx[1]};', file=f) 231 | print(f' #10;', file=f) 232 | print( 233 | f' if (R0_data !== \'h{tx[2]:x}) begin', file=f) 234 | print(f' $display("ASSERTION FAILED");', file=f) 235 | print(f' $finish;', file=f) 236 | print(f' end', file=f) 237 | elif tx[0] == "rw": 238 | print(f' R0_en = 1;', file=f) 239 | print(f' R0_addr = {tx[3]};', file=f) 240 | print(f' W0_en = 1;', file=f) 241 | print(f' W0_addr = {tx[1]};', file=f) 242 | print(f' W0_data = \'h{tx[2]:x};', file=f) 243 | print(f' #10;', file=f) 244 | if tx[1] == tx[3]: 245 | # conflict, allow x/old/new 246 | print( 247 | f' if (R0_data !== \'h{tx[4]:x} && R0_data !== \'h{tx[5]:x} && R0_data !== {{{width}{{1\'bx}}}}) begin', file=f) 248 | else: 249 | # not conflict 250 | print( 251 | f' if (R0_data !== \'h{tx[4]:x}) begin', file=f) 252 | print(f' $display("ASSERTION FAILED");', file=f) 253 | print(f' $finish;', file=f) 254 | print(f' end', file=f) 255 | else: 256 | print(f' R0_en = 0;', file=f) 257 | print(f' W0_en = 0;', file=f) 258 | print(f' #10;', file=f) 259 | 260 | print(f' $display("SIMULATION SUCCESS");', file=f) 261 | print(f' $finish;', file=f) 262 | print(f' end', file=f) 263 | 264 | print(f' always #5 R0_clk = ~R0_clk;', file=f) 265 | print(f' always #5 W0_clk = ~W0_clk;', file=f) 266 | 267 | print(f' {config.name} inst (', file=f) 268 | print(f' .R0_addr(R0_addr),', file=f) 269 | print(f' .R0_en(R0_en),', file=f) 270 | print(f' .R0_clk(R0_clk),', file=f) 271 | print(f' .R0_data(R0_data),', file=f) 272 | print(f' .W0_addr(W0_addr),', file=f) 273 | print(f' .W0_en(W0_en),', file=f) 274 | print(f' .W0_clk(W0_clk),', file=f) 275 | if "mwrite" in ports: 276 | print(f' .W0_mask(W0_mask),', file=f) 277 | print(f' .W0_data(W0_data)', file=f) 278 | print(f' );', file=f) 279 | print(f'endmodule', file=f) 280 | return f.getvalue() 281 | -------------------------------------------------------------------------------- /chisel_memory_lower/arm.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | from chisel_memory_lower.utils import generate_header, generate_tb 4 | from chisel_memory_lower.parser import Config 5 | from collections import namedtuple 6 | import yaml 7 | 8 | 9 | def generate(config: Config, arm_config: str, tb: bool): 10 | cfg = yaml.load(open(arm_config, 'r'), Loader=yaml.Loader) 11 | ip = cfg['ip'] 12 | ports = set(config.ports.split(',')) 13 | depth = int(config.depth) 14 | width = int(config.width) 15 | addr_width = (depth-1).bit_length() 16 | 17 | types = '' 18 | if ports == {"read", "write"}: 19 | # 1R1W 20 | types = ['1r1w', '1r1w_masked'] 21 | elif ports == {"read", "mwrite"}: 22 | # 1R1W Masked 23 | types = ['1r1w_masked'] 24 | elif ports == {"rw"}: 25 | # 1RW 26 | types = ['1rw', '1rw_masked'] 27 | elif ports == {"mrw"}: 28 | # 1RW Masked 29 | types = ['1rw_masked'] 30 | candidates = list(filter(lambda c: c['type'] in types, ip)) 31 | if len(candidates) > 0: 32 | with open(f'{config.name}_arm.v', 'w') as f: 33 | header = generate_header(config, 'arm') 34 | print(header, file=f) 35 | selected = min(candidates, key=lambda candidate: math.ceil(width / candidate['width']) * 36 | math.ceil(depth / candidate['depth']) * candidate['cost']) 37 | print(f"Using sram ip {selected['name']}") 38 | 39 | width_replicate = math.ceil(width / selected['width']) 40 | depth_replicate = math.ceil(depth / selected['depth']) 41 | selected_addr_width = (selected['depth']-1).bit_length() 42 | 43 | if addr_width > selected_addr_width: 44 | if ports == {"rw"} or ports == {"mrw"}: 45 | print( 46 | f' reg [{addr_width-selected_addr_width-1}:0] rw_addr_index_reg;', file=f) 47 | print(f' always @ (posedge RW0_clk) begin', file=f) 48 | print( 49 | f' rw_addr_index_reg <= RW0_addr >> {selected_addr_width};', file=f) 50 | print(f' end', file=f) 51 | else: 52 | print( 53 | f' reg [{addr_width-selected_addr_width-1}:0] read_addr_index_reg;', file=f) 54 | print(f' always @ (posedge R0_clk) begin', file=f) 55 | print( 56 | f' read_addr_index_reg <= R0_addr >> {selected_addr_width};', file=f) 57 | print(f' end', file=f) 58 | 59 | for j in range(depth_replicate): 60 | if ports == {"rw"} or ports == {"mrw"}: 61 | print( 62 | f' wire rw_addr_match_{j} = (RW0_addr >> {selected_addr_width}) == {j};', file=f) 63 | print(f' wire [{width-1}:0] read_data_{j};', file=f) 64 | for i in range(width_replicate): 65 | width_start = i * width // width_replicate 66 | width_end = (i+1) * width // width_replicate 67 | print( 68 | f' wire [{selected["width"]-1}:0] read_partial_{i}_{j};', file=f) 69 | print( 70 | f' assign read_data_{j}[{width_end-1}:{width_start}] = read_partial_{i}_{j};', file=f) 71 | else: 72 | print( 73 | f' wire read_addr_match_{j} = (R0_addr >> {selected_addr_width}) == {j};', file=f) 74 | print( 75 | f' wire write_addr_match_{j} = (W0_addr >> {selected_addr_width}) == {j};', file=f) 76 | print(f' wire [{width-1}:0] read_data_{j};', file=f) 77 | for i in range(width_replicate): 78 | width_start = i * width // width_replicate 79 | width_end = (i+1) * width // width_replicate 80 | print( 81 | f' wire [{selected["width"]-1}:0] read_partial_{i}_{j};', file=f) 82 | print( 83 | f' assign read_data_{j}[{width_end-1}:{width_start}] = read_partial_{i}_{j};', file=f) 84 | 85 | if addr_width > selected_addr_width: 86 | if ports == {"rw"} or ports == {"mrw"}: 87 | print(f' assign RW0_rdata = ', file=f, end='') 88 | for j in range(depth_replicate): 89 | print( 90 | f'((rw_addr_index_reg == {j}) ? read_data_{j} : ', file=f, end='') 91 | print(f'0{")" * depth_replicate};', file=f) 92 | else: 93 | print(f' assign R0_data = ', file=f, end='') 94 | for j in range(depth_replicate): 95 | print( 96 | f'((read_addr_index_reg == {j}) ? read_data_{j} : ', file=f, end='') 97 | print(f'0{")" * depth_replicate};', file=f) 98 | else: 99 | if ports == {"rw"} or ports == {"mrw"}: 100 | print(f' assign RW0_rdata = read_data_0;', file=f) 101 | else: 102 | print(f' assign R0_data = read_data_0;', file=f) 103 | 104 | for i in range(width_replicate): 105 | width_start = i * width // width_replicate 106 | width_end = (i+1) * width // width_replicate 107 | data_width = width_end - width_start 108 | for j in range(depth_replicate): 109 | print(f' {selected["name"]} inst_{i}_{j} (', file=f) 110 | pins = [] 111 | for port in selected["ports"]: 112 | sram_depth_bits = ( 113 | selected['depth']-1).bit_length() 114 | actual_depth_bits = min( 115 | sram_depth_bits, (depth-1).bit_length()) 116 | if port["type"] == "r": 117 | # connect addr 118 | addr = f"R0_addr[{actual_depth_bits-1}:0]" 119 | if actual_depth_bits < sram_depth_bits: 120 | addr = f"{{{sram_depth_bits - actual_depth_bits}'b0, {addr}}}" 121 | pins.append( 122 | (port["addr"], addr)) 123 | 124 | pins.append( 125 | (port["enable_n"], f"~(R0_en && read_addr_match_{j})")) 126 | pins.append((port["clock"], f"R0_clk")) 127 | pins.append( 128 | (port["data"], f"read_partial_{i}_{j}")) 129 | elif port["type"] == "w": 130 | # connect addr 131 | addr = f"W0_addr[{actual_depth_bits-1}:0]" 132 | if actual_depth_bits < sram_depth_bits: 133 | addr = f"{{{sram_depth_bits - actual_depth_bits}'b0, {addr}}}" 134 | pins.append( 135 | (port["addr"], addr)) 136 | 137 | pins.append( 138 | (port["enable_n"], f"~(W0_en && write_addr_match_{j})")) 139 | if "clock" in port: 140 | pins.append((port["clock"], f"W0_clk")) 141 | else: 142 | print("CAUTION! clock is missing in write port, please ensure R0_clk and W0_clk connect to the same signal.") 143 | 144 | # pad to full width 145 | data = f'W0_data[{width_end-1}:{width_start}]' 146 | if data_width < selected['width']: 147 | data = f"{{{selected['width'] - data_width}'b0, {data}}}" 148 | pins.append( 149 | (port["data"], data)) 150 | 151 | if "mask_n" in port: 152 | if ports == {"read", "mwrite"}: 153 | # 1R1W Masked 154 | bits = [] 155 | for bit in range(width_start, width_end): 156 | mask_bit = bit // int(config.mask_gran) 157 | bits.append(f'W0_mask[{mask_bit}]') 158 | 159 | # pad to full width 160 | bits += ["1'b0"] * \ 161 | (selected['width'] - data_width) 162 | 163 | rhs = ', '.join(reversed(bits)) 164 | pins.append( 165 | (port["mask_n"], f'~({{{rhs}}})')) 166 | else: 167 | # all mask enabled 168 | pins.append( 169 | (port["mask_n"], f'{{{selected["width"]}{{1\'b0}}}}')) 170 | elif port["type"] == "rw": 171 | # connect addr 172 | sram_depth_bits = ( 173 | selected['depth']-1).bit_length() 174 | actual_depth_bits = min( 175 | sram_depth_bits, (depth-1).bit_length()) 176 | addr = f"RW0_addr[{actual_depth_bits-1}:0]" 177 | if actual_depth_bits < sram_depth_bits: 178 | addr = f"{{{sram_depth_bits - actual_depth_bits}'b0, {addr}}}" 179 | pins.append( 180 | (port["addr"], addr)) 181 | 182 | pins.append( 183 | (port["enable_n"], f"~(RW0_en && rw_addr_match_{j})")) 184 | pins.append((port["write_n"], f"~RW0_wmode")) 185 | pins.append((port["clock"], f"RW0_clk")) 186 | # pad to full width 187 | wdata = f'RW0_wdata[{width_end-1}:{width_start}]' 188 | if data_width < selected['width']: 189 | wdata = f"{{{selected['width'] - data_width}'b0, {wdata}}}" 190 | pins.append( 191 | (port["wdata"], wdata)) 192 | pins.append( 193 | (port["rdata"], f"read_partial_{i}_{j}")) 194 | 195 | if "mask_n" in port: 196 | if ports == {"mrw"}: 197 | # 1RW Masked 198 | bits = [] 199 | for bit in range(width_start, width_end): 200 | mask_bit = bit // int(config.mask_gran) 201 | bits.append(f'RW0_wmask[{mask_bit}]') 202 | 203 | # pad to full width 204 | bits += ["1'b0"] * \ 205 | (selected['width'] - data_width) 206 | 207 | rhs = ', '.join(reversed(bits)) 208 | pins.append( 209 | (port["mask_n"], f'~({{{rhs}}})')) 210 | else: 211 | # all mask enabled 212 | pins.append( 213 | (port["mask_n"], f'{{{selected["width"]}{{1\'b0}}}}')) 214 | if "constants" in selected: 215 | for name in selected["constants"]: 216 | value = selected["constants"][name] 217 | pins.append((name, value)) 218 | 219 | for k in range(len(pins)): 220 | if k == len(pins)-1: 221 | end = '' 222 | else: 223 | end = ',' 224 | print( 225 | f' .{pins[k][0]}({pins[k][1]}){end}', file=f) 226 | print(f' );', file=f) 227 | print(f'endmodule', file=f) 228 | 229 | with open(f'{config.name}_arm.sh', 'w') as file: 230 | print(f'#!/bin/bash', file=file) 231 | print( 232 | f'vcs +vcs+dumpvars+dump.vcd -full64 {config.name}_arm.v {config.name}_arm_tb.v {selected["name"]}.v', file=file) 233 | print(f'./simv', file=file) 234 | os.chmod(f'{config.name}_arm.sh', 0o755) 235 | 236 | with open(f'{config.name}_arm_tb.v', 'w') as f: 237 | tb = generate_tb(config) 238 | print(tb, file=f) 239 | --------------------------------------------------------------------------------