From 8fd190d498cc68a8b51b4141c1ed6f12ef2ec2dd Mon Sep 17 00:00:00 2001 From: Paul Selkirk Date: Tue, 17 Jan 2017 23:43:46 -0500 Subject: Rename and rewrite the core_selector generator. By moving most of the domain-specific knowledge from the script to the config file, we can a) merge back the fork in platform/alpha, and b) remove the special-case code that has to know about the modexp and trng cores. --- config/core_config.py | 474 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100755 config/core_config.py (limited to 'config/core_config.py') diff --git a/config/core_config.py b/config/core_config.py new file mode 100755 index 0000000..3f75243 --- /dev/null +++ b/config/core_config.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python + +""" +Generate core_selector.v and core_vfiles.mk for a set of cores. +""" + +#======================================================================= +# Copyright (c) 2015-2017, 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. +#======================================================================= + +# The modexpa7 core drags in a one clock cycle delay to other cores, +# to compensate for the extra clock cycle consumed by the block +# memories used in the modexpa7 core. We probably want a general +# solution for this, because we're going to run into this problem for +# any core that handles arguments big enough to require block memory. + +# To Do: +# +# - Consider automating the one-clock-cycle delay stuff by adding +# another boolean flag to the config file. Default would be no +# delay, if any included core sets the "I use block memories" flag, +# all other cores would get the delay. Slightly tedious but +# something we can calculate easily enough, and probably an +# improvement over wiring in the delay when nothing needs it. + +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.readfp(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 = int(cfg.get(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) + + 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) + + args.verilog.write(createModule_template.format( + core = cores[0], + addrs = "".join(core.createAddr() 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, e: + if args.debug: + raise + exit(str(e)) + + +try: + import ConfigParser as configparser +except ImportError: + import configparser + +class RawConfigParser(configparser.RawConfigParser): + """ + RawConfigParser with a few extensions. + """ + + def getboolean(self, section, option, default = False): + if self.has_option(section, option): + # RawConfigParser is an old-stle class, super() doesn't work, feh. + return configparser.RawConfigParser.getboolean(self, 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): + try: + return configparser.RawConfigParser.get(self, section, option) + except configparser.NoSectionError: + if section in ("core board_regs", "core comm_regs"): + return "" + else: + raise + except configparser.NoOptionError: + return "" + + +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.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 + + 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 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}[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" + + @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 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} + {0}".format(i, core=self) for i in range(self.blocks)) + + @property + def mux_data_reg(self): + return "read_data_" + self.instance_name + ("_reg" if self.need_one_cycle_delay and not self.block_memory else "") + + @property + def mux_error_reg(self): + return "error_" + self.instance_name if self.error_wire else "0" + + 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): + if self.dummy: + return "" + return createAddr_template.format(core = self) + "".join(subcore.createAddr() 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:21s} = {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] read_data_{core.instance_name};{core.error_wire_decl} + + {core.module_name} {core.instance_name}_inst + ( + .clk(sys_clk), + .reset_n(sys_rst_n), +{core.extra_ports} + .cs(enable_{core.instance_name} & (sys_{core.bus_name}_rd | sys_{core.bus_name}_wr)), + .we(sys_{core.bus_name}_wr), + + .address(addr_core_reg), + .write_data(sys_write_data), + .read_data(read_data_{core.instance_name}){core.error_port} + ); + +{core.one_cycle_delay} + +""" + +# 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] read_data_{core.instance_name};{core.error_wire_decl} + wire [{core.block_bit_max}:0] {core.instance_name}_prefix = addr_core_num[{core.block_bit_max}:0] - CORE_ADDR_{core.upper_instance_name}; + + {core.module_name} {core.instance_name}_inst + ( + .clk(sys_clk), + .reset_n(sys_rst_n), +{core.extra_ports} + .cs(enable_{core.instance_name} & (sys_{core.bus_name}_rd | sys_{core.bus_name}_wr)), + .we(sys_{core.bus_name}_wr), + + .address({{{core.instance_name}_prefix, addr_core_reg}}), + .write_data(sys_write_data), + .read_data(read_data_{core.instance_name}){core.error_port} + ); + +{core.one_cycle_delay} + +""" + +createInstance_template_dummy = """\ + //---------------------------------------------------------------- + // {core.upper_instance_name} + //---------------------------------------------------------------- +{core.dummy} +""" + +# Template for one-cycle delay code. + +one_cycle_delay_template = """\ + reg [31: 0] read_data_{core.instance_name}_reg; + always @(posedge sys_clk) + read_data_{core.instance_name}_reg <= read_data_{core.instance_name}; +""" + +# Template for .createMux() methods. + +createMux_template = """\ + {core.mux_core_addr}: + begin + sys_read_data_mux = {core0.mux_data_reg}; + 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 + ); + + + //---------------------------------------------------------------- + // Address Decoder + //---------------------------------------------------------------- + // upper {core.addr_width} bits specify core being addressed + wire [{core.addr_max:>2}: 0] addr_core_num = sys_{core.bus_name}_addr[{core.bus_max}: 8]; + // lower 8 bits specify register offset in core + wire [ 7: 0] addr_core_reg = sys_{core.bus_name}_addr[ 7: 0]; + + + //---------------------------------------------------------------- + // Core Address Table + //---------------------------------------------------------------- +{addrs} + +{insts} + //---------------------------------------------------------------- + // Output (Read Data) Multiplexer + //---------------------------------------------------------------- + reg [31: 0] sys_read_data_mux; + assign sys_read_data = sys_read_data_mux; + reg sys_error_mux; + assign sys_error = sys_error_mux; + + always @* + + case (addr_core_num) +{muxes} + default: + begin + sys_read_data_mux = {{32{{1'b0}}}}; + sys_error_mux = 1; + 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() -- cgit v1.2.3