#!/usr/bin/env python3 """ Generate core_selector.v and core_vfiles.mk for a set of cores. """ #======================================================================= # Copyright (c) 2015-2017, 2019 NORDUnet A/S All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # - Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # - Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # - Neither the name of the NORDUnet nor the names of its contributors may # be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #======================================================================= def main(): """ Parse arguments and config file, generate core list, generate output. """ from argparse import ArgumentParser, FileType, ArgumentDefaultsHelpFormatter from sys import exit parser = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter) parser.add_argument("-d", "--debug", help = "enable debugging", action = "store_true") parser.add_argument("-c", "--config", help = "configuration file", default = "core.cfg", type = FileType("r")) parser.add_argument("-b", "--board", help = "config file 'board' section") parser.add_argument("-p", "--project", help = "config file 'project' section") parser.add_argument("-v", "--verilog", help = "verilog output file",default = "core_selector.v", type = FileType("w")) parser.add_argument("-m", "--makefile",help = "output makefile", default = "core_vfiles.mk", type = FileType("w")) parser.add_argument("core", help = "name(s) of core(s)", nargs = "*") args = parser.parse_args() try: cfg = RawConfigParser() cfg.read_file(args.config) board = args.board or cfg.get("default", "board") board_section = "board " + board Core.bus_name = cfg.get(board_section, "bus name") Core.bus_width = cfg.getint(board_section, "bus width") Core.bus_max = Core.bus_width - 1 Core.addr_width = Core.bus_width - 8 Core.addr_max = Core.addr_width - 1 Core.extra_wires = cfg.get(board_section, "extra wires") Core.modexp = cfg.get(board_section, "modexp") if Core.extra_wires: # restore formatting Core.extra_wires = Core.extra_wires.replace("\n", "\n ") + "\n" if args.core: cores = args.core else: project = args.project or cfg.get("default", "project") cores = cfg.get("project " + project, "cores").split() for core in cfg.getvalues(board_section, "requires"): try: (c1, c2) = core.split("/") if c1 not in cores and c2 not in cores: cores.append(c2) except ValueError: if core not in cores: cores.append(core) cores.insert(0, "board_regs") cores.insert(1, "comm_regs") cores = tuple(Core(core) for core in cores) for core in cores: core.configure(cfg) core_number = 0 for core in cores: core_number = core.assign_core_number(core_number) for i, core in enumerate(cores): core.assign_seq_number(i) # On the unused piece of code below: we really should not try to # optimize out the delay. This may have worked earlier, when we only # had a small set of simple cores. There are a lot of complex cores # by now, so the readback multiplexer gets pretty wide and will never # meet timing if we make it purely combinatorial. Moreover, it turns # out that additional delays are necessary to make it work at higher # clock speeds. if False: # For some reason, attempting to optimize out the delay # code entirely results in a non-working bitstream. Don't # know why, disabling the optimization works, so just do # that for now. Core.need_one_cycle_delay = any(core.block_memory for core in cores) # longest core/subcore instance name max_name_len = 0 for core in cores: if len(core.instance_name) > max_name_len: max_name_len = len(core.instance_name) for subcore in core.subcores: if len(subcore.instance_name) > max_name_len: max_name_len = len(subcore.instance_name) args.verilog.write(createModule_template.format( core_count = len(cores), core = cores[0], addrs = "".join(core.createAddr(max_name_len) for core in cores), insts = "".join(core.createInstance() for core in cores), muxes = "".join(core.createMux() for core in cores) )) args.makefile.write(listVfiles_template.format( vfiles = "".join(core.listVfiles() for core in cores))) except Exception as e: if args.debug: raise exit(str(e)) import configparser class RawConfigParser(configparser.RawConfigParser): """ RawConfigParser with a few extensions. """ def getboolean(self, section, option, default = False): if self.has_option(section, option): return super().getboolean(section, option) else: return default def getvalues(self, section, option): if self.has_option(section, option): for value in self.get(section, option).split(): yield value def get(self, section, option, default = "", **kwargs): try: return super().get(section = section, option = option, **kwargs) except configparser.NoSectionError: if section in ("core board_regs", "core comm_regs"): return default else: raise except configparser.NoOptionError: return default class Core(object): """ Data and methods for a generic core. We can use this directly for most cores, a few are weird and require subclassing to override particular methods. """ # Class variable tracking how many times a particular core has # been instantiated. This controls instance numbering. _instance_count = {} # Class variable recording whether we need a one-cycle delay to # compensate for block memories. need_one_cycle_delay = True def __init__(self, name): if Core.modexp and name == "modexp": name = Core.modexp self.name = name self.cfg_section = "core " + name self.core_number = None self.seq_number = None self.vfiles = [] self.error_wire = True self.block_memory = False self.instance_number = self._instance_count.get(name, 0) self._instance_count[name] = self.instance_number + 1 self.subcores = [] self.blocks = 1 self.dummy = False self._parameters = dict() self.reset_name = "reset_n" def assign_core_number(self, n): self.core_number = n for i, subcore in enumerate(self.subcores): subcore.assign_core_number(n + i + 1) return n + self.blocks def assign_seq_number(self, n): self.seq_number = n def configure(self, cfg): if self.instance_number == 0: self.vfiles.extend(cfg.getvalues(self.cfg_section, "vfiles")) for required in cfg.getvalues(self.cfg_section, "requires"): if required not in self._instance_count: self.vfiles.extend(cfg.getvalues("core " + required, "vfiles")) self.error_wire = cfg.getboolean(self.cfg_section, "error wire", self.error_wire) self.block_memory = cfg.getboolean(self.cfg_section, "block memory", self.block_memory) self.extra_ports = cfg.get(self.cfg_section, "extra ports") if self.extra_ports: self.extra_ports = self.extra_ports.replace("\n", "\n ") + "\n" self.blocks = int(cfg.get(self.cfg_section, "core blocks") or 1) self.block_max = self.blocks - 1 if self.blocks > 1: try: self.block_bits = {4:2, 8:3, 16:4, 32:5}[self.blocks] except KeyError: raise ValueError("In [{}]: unexpected value \"core blocks = {}\"".format(self.cfg_section, self.blocks)) self.block_bit_max = self.block_bits - 1 for subcore in cfg.getvalues(self.cfg_section, "subcores"): self.subcores.append(SubCore(subcore, self)) if len(self.subcores) > self.blocks - 1: raise ValueError("In [{}]: number of subcores exceeds size of \"core blocks\"".format(self.cfg_section)) self.module_name = cfg.get(self.cfg_section, "module name") or self.name self.dummy = cfg.get(self.cfg_section, "dummy") if self.dummy: self.dummy = self.dummy.replace("\n", "\n ") + "\n" if cfg.has_section(self.cfg_section): for option in cfg.options(self.cfg_section): if option.startswith("parameter "): self._parameters[option[len("parameter"):].upper().strip()] = cfg.get(self.cfg_section, option) self.reset_name = cfg.get(self.cfg_section, "reset name", self.reset_name) @property def instance_name(self): if self._instance_count[self.name] > 1: return "{}_{}".format(self.name, self.instance_number) else: return self.name @property def upper_instance_name(self): return self.instance_name.upper() @property def error_wire_decl(self): return "\n wire error_{core.instance_name};".format(core = self) if self.error_wire else "" @property def error_port(self): return ",\n .error(error_{core.instance_name})".format(core = self) if self.error_wire else "" @property def one_cycle_delay(self): return one_cycle_delay_template.format(core = self) if self.need_one_cycle_delay and not self.block_memory else "" @property def extra_pipeline_stage(self): return extra_pipeline_stage_template.format(core = self) @property def mux_core_addr(self): if self.blocks == 1 or self.subcores: return "CORE_ADDR_{core.upper_instance_name}".format(core=self) else: return ",\n ".join("CORE_ADDR_{core.upper_instance_name} + {core.addr_width}'h{0:04X}".format(i, core=self) for i in range(self.blocks)) @property def reg_data_out(self): return "reg_read_data_" + self.instance_name @property def comb_data_out(self): return "comb_read_data_" + self.instance_name @property def wire_data_out(self): return self.comb_data_out if self.need_one_cycle_delay and not self.block_memory else self.reg_data_out @property def pipe_data_out(self): return "pipe_read_data_" + self.instance_name @property def mux_error_reg(self): return "error_" + self.instance_name if self.error_wire else "0" @property def parameters(self): if self._parameters: return "#( {} ) ".format(", ".join(".{} ({})".format(k, v) for k, v in self._parameters.items())) else: return "" def createInstance(self): template = createInstance_template_dummy if self.dummy else createInstance_template_generic if self.blocks == 1 else createInstance_template_multi_block return template.format(core = self) def createAddr(self, max_name_len): if self.dummy: return "" return createAddr_template.format(core = self, name_pad = max_name_len) + "".join(subcore.createAddr(max_name_len) for subcore in self.subcores) def createMux(self): if self.dummy: return "" return createMux_template.format(core = self, core0 = self) + "".join(subcore.createMux() for subcore in self.subcores) def listVfiles(self): return "".join(" \\\n\t$(CORE_TREE)/" + vfile for vfile in self.vfiles) class SubCore(Core): """" Override mux handling for TRNG's sub-cores. """ def __init__(self, name, parent): super(SubCore, self).__init__(name) self.parent = parent def createMux(self): return createMux_template.format(core = self, core0 = self.parent) # Templates (format strings), here instead of inline in the functions # that use them, both because some of these are shared between # multiple functions and because it's easier to read these (and get # the indentation right) when the're separate. # Template used by .createAddr() methods. createAddr_template = """\ localparam CORE_ADDR_{core.upper_instance_name:{name_pad}s} = {core.addr_width}'h{core.core_number:02x}; """ # Template used by Core.createInstance(). createInstance_template_generic = """\ //---------------------------------------------------------------- // {core.upper_instance_name} //---------------------------------------------------------------- wire enable_{core.instance_name} = (addr_core_num == CORE_ADDR_{core.upper_instance_name}); wire [31: 0] {core.wire_data_out};{core.error_wire_decl} reg select_{core.instance_name} = 1'b0; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg write_{core.instance_name} = 1'b0; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [31: 0] write_data_{core.instance_name}; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [ 7: 0] addr_{core.instance_name}; always @(posedge sys_clk) begin select_{core.instance_name} <= enable_{core.instance_name} && sys_{core.bus_name}_cs; write_{core.instance_name} <= sys_{core.bus_name}_wr; write_data_{core.instance_name} <= sys_write_data; addr_{core.instance_name} <= addr_core_reg; end {core.module_name} {core.parameters}{core.instance_name}_inst ( .clk(sys_clk), .{core.reset_name}(sys_rst_n_fanout[{core.seq_number}]), {core.extra_ports} .cs(select_{core.instance_name}), .we(write_{core.instance_name}), .address(addr_{core.instance_name}), .write_data(write_data_{core.instance_name}), .read_data({core.wire_data_out}){core.error_port} ); {core.one_cycle_delay} {core.extra_pipeline_stage} """ # Template used for multi-block cores (modexp and trng). This is different # enough from the base template that it's easier to make this separate. createInstance_template_multi_block = """\ //---------------------------------------------------------------- // {core.upper_instance_name} //---------------------------------------------------------------- wire enable_{core.instance_name} = (addr_core_num >= CORE_ADDR_{core.upper_instance_name}) && (addr_core_num <= (CORE_ADDR_{core.upper_instance_name} + {core.addr_width}'h{core.block_max:02x})); wire [31: 0] {core.wire_data_out};{core.error_wire_decl} wire [{core.block_bit_max:>2}: 0] prefix_{core.instance_name} = addr_core_num[{core.block_bit_max}:0] - CORE_ADDR_{core.upper_instance_name}[{core.block_bit_max}:0]; reg select_{core.instance_name} = 1'b0; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg write_{core.instance_name} = 1'b0; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [ 31: 0] write_data_{core.instance_name}; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [{core.block_bits}+7: 0] addr_{core.instance_name}; always @(posedge sys_clk) begin select_{core.instance_name} <= enable_{core.instance_name} && sys_{core.bus_name}_cs; write_{core.instance_name} <= sys_{core.bus_name}_wr; write_data_{core.instance_name} <= sys_write_data; addr_{core.instance_name} <= {{prefix_{core.instance_name}, addr_core_reg}}; end {core.module_name} {core.parameters}{core.instance_name}_inst ( .clk(sys_clk), .{core.reset_name}(sys_rst_n_fanout[{core.seq_number}]), {core.extra_ports} .cs(select_{core.instance_name}), .we(write_{core.instance_name}), .address(addr_{core.instance_name}), .write_data(write_data_{core.instance_name}), .read_data({core.wire_data_out}){core.error_port} ); {core.one_cycle_delay} {core.extra_pipeline_stage} """ createInstance_template_dummy = """\ //---------------------------------------------------------------- // {core.upper_instance_name} //---------------------------------------------------------------- {core.dummy} """ # Template for one-cycle delay code. one_cycle_delay_template = """\ (* SHREG_EXTRACT="NO" *) reg [31: 0] {core.reg_data_out}; always @(posedge sys_clk) {core.reg_data_out} <= {core.wire_data_out}; """ # Template for an extra delay cycle code. extra_pipeline_stage_template = """\ (* SHREG_EXTRACT="NO" *) reg [31: 0] {core.pipe_data_out}; always @(posedge sys_clk) {core.pipe_data_out} <= {core.reg_data_out}; """ # Template for .createMux() methods. createMux_template = """\ {core.mux_core_addr}: begin sys_read_data_mux <= {core0.pipe_data_out}; sys_error_mux <= {core0.mux_error_reg}; end """ # Top-level (createModule) template. createModule_template = """\ // NOTE: This file is generated; do not edit. module core_selector ( input wire sys_clk, input wire sys_rst_n, input wire [{core.bus_max}: 0] sys_{core.bus_name}_addr, input wire sys_{core.bus_name}_wr, input wire sys_{core.bus_name}_rd, output wire [31: 0] sys_read_data, input wire [31: 0] sys_write_data, output wire sys_error, {core.extra_wires} input wire noise, output wire [ 7 :0] debug ); //---------------------------------------------------------------- // Localized Resets Generator //---------------------------------------------------------------- wire [{core_count}-1:0] sys_rst_n_fanout; reset_replicator # ( .SHREG_WIDTH(8), .FANOUT_WIDTH({core_count}) ) reset_replicator_inst ( .sys_clk_in (sys_clk), .sys_rst_n_in (sys_rst_n), .sys_rst_n_out (sys_rst_n_fanout) ); //---------------------------------------------------------------- // Address Decoder //---------------------------------------------------------------- // upper {core.addr_width} bits specify core being addressed // lower 8 bits specify register offset in core wire [{core.addr_max:>2}: 0] addr_core_num = sys_{core.bus_name}_addr[{core.bus_max}: 8]; wire [ 7: 0] addr_core_reg = sys_{core.bus_name}_addr[ 7: 0]; //---------------------------------------------------------------- // Core Address Table //---------------------------------------------------------------- {addrs} //---------------------------------------------------------------- // Core Instances //---------------------------------------------------------------- wire sys_{core.bus_name}_cs = sys_{core.bus_name}_rd || sys_{core.bus_name}_wr; {insts} //---------------------------------------------------------------- // Output (Read Data) Multiplexer //---------------------------------------------------------------- (* SHREG_EXTRACT="NO" *) reg sys_{core.bus_name}_cs_dly1 = 1'b0; (* SHREG_EXTRACT="NO" *) reg sys_{core.bus_name}_cs_dly2 = 1'b0; (* SHREG_EXTRACT="NO" *) reg sys_{core.bus_name}_cs_dly3 = 1'b0; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [{core.addr_max:>2}: 0] addr_core_num_dly1; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [{core.addr_max:>2}: 0] addr_core_num_dly2; (* SHREG_EXTRACT="NO" *) (* EQUIVALENT_REGISTER_REMOVAL="NO" *) reg [{core.addr_max:>2}: 0] addr_core_num_dly3; always @(posedge sys_clk) begin sys_{core.bus_name}_cs_dly1 <= sys_{core.bus_name}_cs; sys_{core.bus_name}_cs_dly2 <= sys_{core.bus_name}_cs_dly1; sys_{core.bus_name}_cs_dly3 <= sys_{core.bus_name}_cs_dly2; end always @(posedge sys_clk) begin if (sys_{core.bus_name}_cs) addr_core_num_dly1 <= addr_core_num; if (sys_{core.bus_name}_cs_dly1) addr_core_num_dly2 <= addr_core_num_dly1; if (sys_{core.bus_name}_cs_dly2) addr_core_num_dly3 <= addr_core_num_dly2; end reg [31: 0] sys_read_data_mux; reg sys_error_mux; assign sys_read_data = sys_read_data_mux; assign sys_error = sys_error_mux; always @(posedge sys_clk) if (sys_{core.bus_name}_cs_dly3) case (addr_core_num_dly3) {muxes} default: begin sys_read_data_mux <= {{32{{1'b0}}}}; sys_error_mux <= 1'b1; end endcase endmodule //====================================================================== // EOF core_selector.v //====================================================================== """ # Template for makefile snippet listing Verilog source files. listVfiles_template = """\ # NOTE: This file is generated; do not edit by hand. vfiles +={vfiles} """ # Run main program. if __name__ == "__main__": main()