| #!/usr/bin/env python3 | |
| """ | |
| This script generates a verilog module which implements ROM memory. Content | |
| of the memory is also generated. Different styles of ROM implementation are | |
| available. | |
| """ | |
| import sys | |
| import argparse | |
| import math | |
| import random | |
| # ============================================================================= | |
| class Templates: | |
| # ============================================================================= | |
| memory_inferred_bram = """ | |
| module rom # | |
| ( | |
| parameter ROM_SIZE_BITS = {mem_size} // Size in 32-bit words | |
| ) | |
| ( | |
| // Closk & reset | |
| input wire CLK, | |
| input wire RST, | |
| // ROM interface | |
| input wire I_STB, | |
| input wire [ROM_SIZE_BITS-1:0] I_ADR, | |
| output wire O_STB, | |
| output wire [31:0] O_DAT | |
| ); | |
| // ============================================================================ | |
| localparam ROM_SIZE = (1<<ROM_SIZE_BITS); | |
| reg [31:0] rom [0:ROM_SIZE-1]; | |
| reg rom_stb; | |
| reg [31:0] rom_dat; | |
| always @(posedge CLK) | |
| rom_dat <= rom[I_ADR]; | |
| always @(posedge CLK or posedge RST) | |
| if (RST) rom_stb <= 1'd0; | |
| else rom_stb <= I_STB; | |
| assign O_STB = rom_stb; | |
| assign O_DAT = rom_dat; | |
| // ============================================================================ | |
| initial begin | |
| {mem_data} | |
| end | |
| // ============================================================================ | |
| endmodule | |
| """ | |
| # ============================================================================= | |
| memory_explicit_dram64 = """ | |
| module rom # | |
| ( | |
| parameter ROM_SIZE_BITS = {mem_size} // Size in 32-bit words | |
| ) | |
| ( | |
| // Closk & reset | |
| input wire CLK, | |
| input wire RST, | |
| // ROM interface | |
| input wire I_STB, | |
| input wire [ROM_SIZE_BITS-1:0] I_ADR, | |
| output wire O_STB, | |
| output wire [31:0] O_DAT | |
| ); | |
| // ============================================================================ | |
| // DRAM aggregation logic | |
| {dram_agg_code} | |
| assign O_STB = rom_stb; | |
| assign O_DAT = dram_data_0; // FIXME: Hard coded ! | |
| // ============================================================================ | |
| // DRAMs | |
| {dram_code} | |
| endmodule | |
| """ | |
| # ============================================================================= | |
| dram_row_reg = """ | |
| wire [{cols_minus_one}:0] {data_w}; | |
| reg [{cols_minus_one}:0] {data_r}; | |
| wire [{mem_size_bits_minus_one}:0] {addr}; | |
| always @(posedge CLK) | |
| {data_r} <= {data_w}; | |
| assign {addr} = I_ADR; // FIXME: Hard coded ! | |
| """ | |
| ram64x1d = """ | |
| RAM64X1D # | |
| ( | |
| .INIT ({init}) | |
| ) | |
| dram_{row}_{col} | |
| ( | |
| .WCLK (CLK), | |
| .WE (1'b0), .D(1'b0), | |
| .DPRA0 (1'b0), .DPRA1(1'b0), .DPRA2(1'b0), | |
| .DPRA3 (1'b0), .DPRA4(1'b0), .DPRA5(1'b0), | |
| .A0 ({addr}[0]), .A1 ({addr}[1]), .A2 ({addr}[2]), | |
| .A3 ({addr}[3]), .A4 ({addr}[4]), .A5 ({addr}[5]), | |
| .SPO ({data}) | |
| ); | |
| """ | |
| # ============================================================================= | |
| def generate_inferred_bram(rom_data): | |
| # Log2 memory size | |
| mem_size_bits = int(math.ceil(math.log2(len(rom_data)))) | |
| # Case statements | |
| case_statements = "" | |
| for i, data_word in enumerate(rom_data): | |
| case_statements += " rom['h%04X] <= 32'h%08X;\n" % (i, data_word) | |
| return Templates.memory_inferred_bram.format( | |
| mem_size=mem_size_bits, mem_data=case_statements | |
| ) | |
| def generate_explicit_dram(rom_data, dram_size_bits=6): | |
| # Log2 memory size | |
| mem_size_bits = int(math.ceil(math.log2(len(rom_data)))) | |
| # DRAM size | |
| dram_size = int(math.pow(2, dram_size_bits)) | |
| # Memory width is fixed to 32-bits. It is the number of lets say "horizontal" | |
| # DRAMS that need to be used | |
| mem_width = 32 | |
| # Memory height is the number of DRAMs required to store 1 bit of the | |
| # whole data set. It has to be an integer multiply of the DRAM size. | |
| mem_height = int(math.ceil(len(rom_data) / dram_size)) | |
| # FIXME: Only one row works -> 64 words. Need more time to code hierarchical | |
| # muxes to join them. | |
| assert (mem_height == 1) | |
| sys.stderr.write("dram_size = %d\n" % dram_size) | |
| sys.stderr.write("mem_height = %d\n" % mem_height) | |
| # Generate "rows" of DRAMs | |
| dram_code = "" | |
| wire_code = "" | |
| for row in range(mem_height): | |
| # Row aggregation | |
| wire_code += Templates.dram_row_reg.format( | |
| cols_minus_one=mem_width - 1, | |
| mem_size_bits_minus_one=dram_size_bits - 1, | |
| data_r="dram_data_%d" % (row), | |
| data_w="dram_data_%d_w" % (row), | |
| addr="dram_addr_%d" % (row) | |
| ) | |
| # Cut data range | |
| i0 = row * dram_size | |
| i1 = min(len(rom_data), i0 + dram_size) | |
| sz = i1 - i0 | |
| data_chunk = [0] * dram_size | |
| data_chunk[:sz] = rom_data[i0:i1] | |
| # Generate "row" of DRAMS | |
| for col in range(mem_width): | |
| bit = col | |
| init_data = [ | |
| bool(data_chunk[i] & (1 << bit)) for i in range(dram_size) | |
| ] | |
| init_str = "".join(["%c" % "1" if b else "0" for b in init_data]) | |
| init_str = "%d'b" % dram_size + init_str[::-1] | |
| code = Templates.ram64x1d.format( | |
| init=init_str, | |
| col=col, | |
| row=row, | |
| addr="dram_addr_%d" % (row), | |
| data="dram_data_%d_w[%d]" % (row, col) | |
| ) | |
| dram_code += code | |
| # Add final wire bindings | |
| wire_code += """ | |
| assign rom_dat = dram_data_0; // FIXME: Hard coded ! | |
| reg rom_stb; | |
| always @(posedge CLK or posedge RST) | |
| if (RST) rom_stb <= 1'd0; | |
| else rom_stb <= I_STB; | |
| """ | |
| return Templates.memory_explicit_dram64.format( | |
| mem_size=mem_size_bits, | |
| dram_agg_code=wire_code, | |
| dram_code=dram_code, | |
| ) | |
| # ============================================================================= | |
| def generate_rom_data(word_count): | |
| """ | |
| Generates a list of 32-bit words that are to be put into ROM memory | |
| """ | |
| rom_data = [] | |
| random.seed(123456, version=2) # A fixed seed | |
| for i in range(word_count): | |
| # # Simple sequence of monotonically increasing numbers | |
| # v0 = 2*i | |
| # v1 = 2*i+1 | |
| # Pseudo-random numbers | |
| v0 = int(random.random() * 65536.0) | |
| v1 = int(random.random() * 65536.0) | |
| # Pack as big endian | |
| data_word = v0 << 16 | |
| data_word |= v1 | |
| rom_data.append(data_word) | |
| return rom_data | |
| # ============================================================================= | |
| def main(): | |
| # Argument parser | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "--rom-style", | |
| type=str, | |
| default="bram", | |
| help="ROM style (\"bram\",\"dram64\")" | |
| ) | |
| args = parser.parse_args() | |
| # Generate BRAM | |
| if args.rom_style == "bram": | |
| rom_data = generate_rom_data(512) | |
| print(generate_inferred_bram(rom_data)) | |
| elif args.rom_style == "bram36": | |
| rom_data = generate_rom_data(1024) | |
| print(generate_inferred_bram(rom_data)) | |
| # Generate DRAM64 | |
| elif args.rom_style == "dram64": | |
| rom_data = generate_rom_data(64) | |
| print(generate_explicit_dram(rom_data)) | |
| # Error | |
| else: | |
| sys.stderr.write("Invalid ROM style '%s'" % args.rom_style) | |
| exit(-1) | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| main() |