| #!/usr/bin/env python3 | 
 | """ | 
 | This script allows to convert HEX file with firmware to a verilog ROM | 
 | implementation. The HEX file must be generated using the following command: | 
 |  | 
 |  "objcopy -O verilog <input ELF> <output HEX>". | 
 |  | 
 | The script generates a series of verilog case statements. Those statements are | 
 | then injected to a template file and as a result a new verilog file is written. | 
 |  | 
 | The verilog template file name is hard coded as "progmem.v.template". The | 
 | output file with the ROM is named "progmem.v". | 
 |  | 
 | Important! The script assumes that the firmware is placed at 0x00100000 address | 
 | and will convert to verilog content beginning at that address only ! | 
 |  | 
 | """ | 
 | import argparse | 
 | import sys | 
 |  | 
 | # ============================================================================= | 
 |  | 
 |  | 
 | class Templates: | 
 |  | 
 |     memory_case = """ | 
 | module progmem | 
 | ( | 
 | // Closk & reset | 
 | input  wire         clk, | 
 | input  wire         rstn, | 
 |  | 
 | // PicoRV32 bus interface | 
 | input  wire         valid, | 
 | output wire         ready, | 
 | input  wire [31:0]  addr, | 
 | output wire [31:0]  rdata | 
 | ); | 
 |  | 
 | // ============================================================================ | 
 |  | 
 | localparam  MEM_SIZE_BITS   = {mem_size}; // In 32-bit words | 
 | localparam  MEM_SIZE        = 1 << MEM_SIZE_BITS; | 
 | localparam  MEM_ADDR_MASK   = 32'h0010_0000; | 
 |  | 
 | // ============================================================================ | 
 |  | 
 | wire [MEM_SIZE_BITS-1:0]    mem_addr; | 
 | reg  [31:0]                 mem_data; | 
 |  | 
 | always @(posedge clk) | 
 |     case (mem_addr) | 
 |  | 
 | {mem_data} | 
 |  | 
 |     default:    mem_data <= 32'hDEADBEEF; | 
 |  | 
 |     endcase | 
 |  | 
 | // ============================================================================ | 
 |  | 
 | reg o_ready; | 
 |  | 
 | always @(posedge clk or negedge rstn) | 
 |     if (!rstn)  o_ready <= 1'd0; | 
 |     else        o_ready <= valid && ((addr & MEM_ADDR_MASK) != 0); | 
 |  | 
 | // Output connectins | 
 | assign ready    = o_ready; | 
 | assign rdata    = mem_data; | 
 | assign mem_addr = addr[MEM_SIZE_BITS+1:2]; | 
 |  | 
 | endmodule | 
 | """ | 
 |  | 
 |     memory_initial = """ | 
 | module progmem | 
 | ( | 
 | // Closk & reset | 
 | input  wire         clk, | 
 | input  wire         rstn, | 
 |  | 
 | // PicoRV32 bus interface | 
 | input  wire         valid, | 
 | output wire         ready, | 
 | input  wire [31:0]  addr, | 
 | output wire [31:0]  rdata | 
 | ); | 
 |  | 
 | // ============================================================================ | 
 |  | 
 | localparam  MEM_SIZE_BITS   = {mem_size}; // In 32-bit words | 
 | localparam  MEM_SIZE        = 1 << MEM_SIZE_BITS; | 
 | localparam  MEM_ADDR_MASK   = 32'h0010_0000; | 
 |  | 
 | // ============================================================================ | 
 |  | 
 | wire [MEM_SIZE_BITS-1:0]    mem_addr; | 
 | reg  [31:0]                 mem_data; | 
 | reg  [31:0]                 mem[0:MEM_SIZE]; | 
 |  | 
 | initial begin | 
 | {mem_data} | 
 | end | 
 |  | 
 | always @(posedge clk) | 
 |     mem_data <= mem[mem_addr]; | 
 |  | 
 | // ============================================================================ | 
 |  | 
 | reg o_ready; | 
 |  | 
 | always @(posedge clk or negedge rstn) | 
 |     if (!rstn)  o_ready <= 1'd0; | 
 |     else        o_ready <= valid && ((addr & MEM_ADDR_MASK) != 0); | 
 |  | 
 | // Output connectins | 
 | assign ready    = o_ready; | 
 | assign rdata    = mem_data; | 
 | assign mem_addr = addr[MEM_SIZE_BITS+1:2]; | 
 |  | 
 | endmodule | 
 | """ | 
 |  | 
 |  | 
 | # ============================================================================= | 
 |  | 
 |  | 
 | def load_hex_file(file_name): | 
 |     """ | 
 |     Loads a "HEX" file generated using the command: | 
 |     'objcopy -O verilog firmware.elf firmware.hex' | 
 |     """ | 
 |  | 
 |     sys.stderr.write("Loading 'HEX' from: " + file_name + "\n") | 
 |  | 
 |     # Load and parse HEX data | 
 |     sections = {} | 
 |     section = 0  # If no '@' is specified the code will end up at addr. 0 | 
 |     hex_data = [] | 
 |  | 
 |     with open(file_name, "r") as fp: | 
 |         for line in fp.readlines(): | 
 |  | 
 |             # Address, create new section | 
 |             if line.startswith("@"): | 
 |                 section = int(line[1:], 16) | 
 |                 hex_data = [] | 
 |                 sections[section] = hex_data | 
 |                 continue | 
 |  | 
 |             # Data, append to current section | 
 |             else: | 
 |                 hex_data += line.split() | 
 |  | 
 |     # Convert section data to bytes | 
 |     for section in sections.keys(): | 
 |         sections[section] = bytes([int(s, 16) for s in sections[section]]) | 
 |  | 
 |     # Dump sections | 
 |     sys.stderr.write("Sections:\n") | 
 |     for section in sections.keys(): | 
 |         length = len(sections[section]) | 
 |         sys.stderr.write( | 
 |             " @%08X - @%08X, %d bytes\n" % (section, section + length, length) | 
 |         ) | 
 |  | 
 |     return sections | 
 |  | 
 |  | 
 | def modify_code_templte(sections, rom_style): | 
 |     """ | 
 |     Modifies verilog ROM template by inserting case statements with the | 
 |     ROM content. Requires the sections dict to contain a section beginning | 
 |     at 0x00100000 address. | 
 |  | 
 |     Returns a string with the verilog code | 
 |     """ | 
 |  | 
 |     # Get section at 0x00100000 | 
 |     data = sections[0x00100000] | 
 |  | 
 |     # Pad to make length a multiply of 4 | 
 |     if len(data) % 4: | 
 |         dummy_cnt = ((len(data) // 4) + 1) * 4 - len(data) | 
 |         data += bytes(dummy_cnt) | 
 |  | 
 |     # Determine memory size bits (in words) | 
 |     mem_size_bits = len(data).bit_length() - 2 | 
 |     sys.stderr.write("ROM size (words): %d bits\n" % mem_size_bits) | 
 |  | 
 |     # Encode verilog case statements | 
 |     if rom_style == "case": | 
 |  | 
 |         # Generate statements | 
 |         case_statements = "" | 
 |         for i in range(len(data) // 4): | 
 |  | 
 |             # Little endian | 
 |             data_word = data[4 * i + 0] | 
 |             data_word |= data[4 * i + 1] << 8 | 
 |             data_word |= data[4 * i + 2] << 16 | 
 |             data_word |= data[4 * i + 3] << 24 | 
 |  | 
 |             statement = "    'h%04X: mem_data <= 32'h%08X;\n" % (i, data_word) | 
 |             case_statements += statement | 
 |  | 
 |         # Return the code | 
 |         return Templates.memory_case.format( | 
 |             mem_size=mem_size_bits, mem_data=case_statements | 
 |         ) | 
 |  | 
 |     # Encode data as initial statements for a verilog array | 
 |     if rom_style == "initial": | 
 |  | 
 |         # Generate statements | 
 |         initial_statements = "" | 
 |         for i in range(len(data) // 4): | 
 |  | 
 |             # Little endian | 
 |             data_word = data[4 * i + 0] | 
 |             data_word |= data[4 * i + 1] << 8 | 
 |             data_word |= data[4 * i + 2] << 16 | 
 |             data_word |= data[4 * i + 3] << 24 | 
 |  | 
 |             statement = "    mem['h%04X] <= 32'h%08X;\n" % (i, data_word) | 
 |             initial_statements += statement | 
 |  | 
 |         # Return the code | 
 |         return Templates.memory_initial.format( | 
 |             mem_size=mem_size_bits, mem_data=initial_statements | 
 |         ) | 
 |  | 
 |     # Error | 
 |     sys.stdout.write("Invalid ROM style '%s'\n" % rom_style) | 
 |     return "" | 
 |  | 
 |  | 
 | # ============================================================================= | 
 |  | 
 |  | 
 | def main(): | 
 |  | 
 |     # Argument parser | 
 |     parser = argparse.ArgumentParser(description=__doc__) | 
 |     parser.add_argument("hex", type=str, help="Input HEX file") | 
 |     parser.add_argument( | 
 |         "--rom-style", type=str, default="case", help="ROM style" | 
 |     ) | 
 |  | 
 |     args = parser.parse_args() | 
 |  | 
 |     # Load HEX | 
 |     sections = load_hex_file(args.hex) | 
 |  | 
 |     # Generate verilog code | 
 |     code = modify_code_templte(sections, args.rom_style) | 
 |  | 
 |     # Output verilog code | 
 |     sys.stdout.write(code) | 
 |     sys.stdout.flush() | 
 |  | 
 |  | 
 | # ============================================================================= | 
 |  | 
 | if __name__ == "__main__": | 
 |     main() |