#!/usr/bin/env python3

# Copyright 2021 ETH Zurich and University of Bologna.
# Solderpad Hardware License, Version 0.51, see LICENSE for details.
# SPDX-License-Identifier: SHL-0.51

# Author: Samuel Riedel, ETH Zurich

from string import Template
from math import log2
import argparse
import os.path
import sys

parser = argparse.ArgumentParser(
    description='Convert binary file to verilog rom')
parser.add_argument(
    'filename',
    metavar='filename.img',
    nargs=1,
    help='filename of input binary')
parser.add_argument(
    "--output",
    nargs="?",
    metavar='file',
    help="Name of output file (without extension)",
    default=None)
parser.add_argument(
    "--datawidth",
    nargs="?",
    metavar='int',
    help="Bootram's data width (default: 128)",
    default=128)
parser.add_argument(
    '--sv',
    dest='systemverilog',
    action='store_true',
    help="Generate SystemVerilog file")
parser.set_defaults(systemverilog=False)
parser.add_argument(
    '--header',
    dest='header',
    action='store_true',
    help="Generate C header file")
parser.set_defaults(header=False)

args = parser.parse_args()
file = args.filename[0]

# check that file exists
if not os.path.isfile(file):
    sys.exit("File {} does not exist.".format(file))

filename = os.path.splitext(file)[0]

output = args.output
if output is None:
    output = filename

DataWidth = int(args.datawidth)
if DataWidth < 32:
    sys.exit('DataWidth must be larger than 32')
if DataWidth % 32:
    sys.exit('DataWidth must be multiple of 32')
ByteWidth = int(DataWidth / 8)

DataOffset = int(log2(ByteWidth))

license = """\
// Copyright 2021 ETH Zurich and University of Bologna.
// Solderpad Hardware License, Version 0.51, see LICENSE for details.
// SPDX-License-Identifier: SHL-0.51
//
// Description: Automatically generated bootrom
//
// Generated by hardware/scripts/generate_bootrom.py

"""

module = """\
module $filename #(
  /* Automatically generated. DO NOT CHANGE! */
  parameter int unsigned DataWidth = $DataWidth,
  parameter int unsigned AddrWidth = 32
) (
  input  logic                 clk_i,
  input  logic                 req_i,
  input  logic [AddrWidth-1:0] addr_i,
  output logic [DataWidth-1:0] rdata_o
);
  localparam int RomSize = $size;
  localparam int AddrBits = RomSize > 1 ? $$clog2(RomSize) : 1;

  const logic [RomSize-1:0][DataWidth-1:0] mem = {
$content
  };

  logic [AddrBits-1:0] addr_q;

  always_ff @(posedge clk_i) begin
    if (req_i) begin
      addr_q <= addr_i[AddrBits-1+$DataOffset:$DataOffset];
    end
  end

  // this prevents spurious Xes from propagating into
  // the speculative fetch stage of the core
  assign rdata_o = (addr_q < RomSize) ? mem[addr_q] : '0;
endmodule
"""

c_var = """\
// Auto-generated code

const int reset_vec_size = $size;

uint32_t reset_vec[reset_vec_size] = {
$content
};
"""


def read_bin():
    with open(filename + ".img", 'rb') as f:
        rom = bytes.hex(f.read())
        rom = list(map(''.join, zip(rom[::2], rom[1::2])))
    # align to datawidth bits
    align = (int((len(rom) + ByteWidth - 1) / ByteWidth)) * ByteWidth
    for i in range(len(rom), align):
        rom.append("00")
    return rom


rom = read_bin()

""" Generate C header file for simulator
"""
if args.header:
    with open(output + ".h", "w") as f:
        rom_str = ""
        # process in junks of 32 bit (4 byte)
        for i in range(0, int(len(rom) / 4)):
            rom_str += "    0x" + "".join(rom[i * 4:i * 4 + 4][::-1]) + ",\n"
        # remove the trailing comma
        rom_str = rom_str[:-2]
        s = Template(c_var)
        f.write(s.substitute(filename=os.path.basename(
            filename), size=int(len(rom) / 4), content=rom_str))
        f.close()

""" Generate SystemVerilog bootcode for FPGA and ASIC
"""
if args.systemverilog:
    with open(output + ".sv", "w") as f:
        rom_str = ""
        # process in junks of DataWidth bit (DataWidth/8 byte)
        for i in reversed(range(int(len(rom) / ByteWidth))):
            rom_str += "    {}'h".format(DataWidth)
            for b in reversed(range(int(ByteWidth / 4))):
                rom_str += "".join(rom[i * ByteWidth + b * 4:i *
                                       ByteWidth + b * 4 + 4][::-1]) + "_"
            rom_str = rom_str[:-1]
            rom_str += ",\n"
        # remove the trailing comma
        rom_str = rom_str[:-2]
        f.write(license)
        s = Template(module)
        f.write(s.substitute(filename=os.path.basename(filename), size=int(
            len(rom) / ByteWidth), content=rom_str, DataWidth=DataWidth,
            DataOffset=DataOffset))
