blob: fd9e789891d6aa69aef51839052325c1a2b830e2 [file] [log] [blame] [edit]
#!/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()