| #!/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() |