#!/usr/bin/env python """ Generate core_selector.v for a set of cores. """ # History of cryptech bus addressing scheme, as best I understand it. # # The old old addressing scheme that Joachim and Paul came up with # was: # # 3 bits of segment selector [16:14] # 6 bits of core selector [13:8] # 8 bits of register selector [7:0] # # modexp6s needed more register bits than that, so Pavel changed # addressing within the math segment to: # # 3 bits of segment selector [16:14] # 4 bits of core selector [13:10] # 10 bits of register selector [9:0] # # Meanwhile, Paul eliminated segments entirely when writing the # ancestor of this script, resulting in: # # 9 bits of core selector [16:8] # 8 bits of register selector [7:0] # # Taking Pavel's and Paul's changes together, we'd get: # # 7 bits of core selector [16:10] # 10 bits of register selector [9:0] # # Except that this would waste space for most cores, and make things # very confusing for the TRNG cores. So, instead, we keep Paul's # two-level (no segment) scheme and handle modexps6 as a set of four # consecutive "cores" with a 10-bit composite register selector. # The modexps6 core also 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 modexps6 core. # To Do: # # - Move reset-high/reset-low to a boolean variable in the config # file, simplify Core classes accordingly. # # - 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. # # - Rename script and its config file to something more meaningful. # # - Figure out whether this really belongs in the novena repository at # all, seems more generic than that. 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("-s", "--section", help = "config file section") parser.add_argument("-c", "--config", help = "configuration file", default = "config.cfg", type = FileType("r")) parser.add_argument("--verilog", help = "verilog output file", default = "core_selector.v", type = FileType("w")) parser.add_argument("--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) if args.core: cores = args.core else: section = args.section or cfg.get("default", "default-section") cores = cfg.get(section, "cores").split() cores.insert(0, "board_regs") cores.insert(1, "comm_regs") cores = tuple(Core.new(core) for core in cores) core_number = 0 for core in cores: core_number = core.assign_core_number(core_number) cores[0].reset_high = True cores[1].reset_high = True for core in cores: core.configure(cfg) args.verilog.write(createModule_template.format( 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 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 = {} # Map from core name to subclass for the special case cores. special_class = {} def __init__(self, name): self.name = name self.core_number = None self.vfiles = [] self.reset_high = False self.instance_number = self._instance_count.get(name, 0) self._instance_count[name] = self.instance_number + 1 @classmethod def new(cls, name): return cls.special_class.get(name, cls)(name) def assign_core_number(self, n): self.core_number = n return n + 1 def configure(self, cfg): if self.instance_number == 0: self.vfiles.extend(cfg.getvalues(self.name, "vfiles")) for required in cfg.getvalues(self.name, "requires"): if required not in self._instance_count: self.vfiles.extend(cfg.getvalues(required, "vfiles")) self.reset_high = cfg.getboolean(self.name, "reset_high", self.reset_high) @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 reset_pin(self): return ".rst(sys_rst)" if self.reset_high else ".reset_n(~sys_rst)" def createInstance(self): return createInstance_template_generic.format(core = self) def createAddr(self): return createAddr_template.format(core = self) def createMux(self): return createMux_template.format(core = self, core0 = self) 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) class TRNGCore(Core): """ The TRNG core has an internal mux and a collection of sub-cores. Mostly this means that our method calls have to iterate over all of the subcores after handling the base TRNG core, but we also use a different instance template in the hope that it is easier to read. """ subcore_names = ("avalanche_entropy", "rosc_entropy", "trng_mixer", "trng_csprng") def __init__(self, name): super(TRNGCore, self).__init__(name) self.subcores = tuple(SubCore(name, self) for name in self.subcore_names) def assign_core_number(self, n): n = super(TRNGCore, self).assign_core_number(n) for subcore in self.subcores: n = subcore.assign_core_number(n) return n @property def last_subcore_upper_instance_name(self): return self.subcores[-1].upper_instance_name def createInstance(self): return createInstance_template_TRNG.format(core = self) def createAddr(self): return super(TRNGCore, self).createAddr() + "".join(subcore.createAddr() for subcore in self.subcores) def createMux(self): return super(TRNGCore, self).createMux() + "".join(subcore.createMux() for subcore in self.subcores) class ModExpS6Core(Core): """ ModExpS6 core consumes as much space as four ordinary cores, and uses different templates to handle the differences in timing and addressing. """ def assign_core_number(self, n): n = super(ModExpS6Core, self).assign_core_number(n) return n + 3 def createInstance(self): return createInstance_template_ModExpS6.format(core = self) def createMux(self): return createMux_modexps6_template.format(core = self) # Hook special classes in as handlers for the cores that require them. Core.special_class.update( trng = TRNGCore, modexps6 = ModExpS6Core) # 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} = 9'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}; wire error_{core.instance_name}; {core.name} {core.instance_name}_inst ( .clk(sys_clk), {core.reset_pin}, .cs(enable_{core.instance_name} & (sys_eim_rd | sys_eim_wr)), .we(sys_eim_wr), .address(addr_core_reg), .write_data(sys_write_data), .read_data(read_data_{core.instance_name}), .error(error_{core.instance_name}) ); 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 used by ModExpS6Core.createInstance(). This is different # enough from the base template that it's easier to make this separate. createInstance_template_ModExpS6 = """\ //---------------------------------------------------------------- // {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} + 3); wire [31: 0] read_data_{core.instance_name}; wire [1:0] {core.instance_name}_prefix = addr_core_num[1:0] - CORE_ADDR_{core.upper_instance_name}; {core.name}_wrapper {core.instance_name}_inst ( .clk(sys_clk), {core.reset_pin}, .cs(enable_{core.instance_name} & (sys_eim_rd | sys_eim_wr)), .we(sys_eim_wr), .address({{{core.instance_name}_prefix, addr_core_reg}}), .write_data(sys_write_data), .read_data(read_data_{core.instance_name}) ); """ # Template used by TRNGCore.createInstance(); this is different enough # from the generic template that it's (probably) clearer to have this # separate. createInstance_template_TRNG = """\ //---------------------------------------------------------------- // {core.upper_instance_name} //---------------------------------------------------------------- wire enable_{core.instance_name} = (addr_core_num >= CORE_ADDR_{core.upper_instance_name}) && (addr_core_num <= CORE_ADDR_{core.last_subcore_upper_instance_name}); wire [31: 0] read_data_{core.instance_name}; wire error_{core.instance_name}; wire [3:0] {core.instance_name}_prefix = addr_core_num[3:0] - CORE_ADDR_{core.upper_instance_name}; {core.name} {core.instance_name}_inst ( .clk(sys_clk), {core.reset_pin}, .cs(enable_{core.instance_name} & (sys_eim_rd | sys_eim_wr)), .we(sys_eim_wr), .address({{{core.instance_name}_prefix, addr_core_reg}}), .write_data(sys_write_data), .read_data(read_data_{core.instance_name}), .error(error_{core.instance_name}), .avalanche_noise(noise), .debug(debug) ); 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_ADDR_{core.upper_instance_name}: begin sys_read_data_mux = read_data_{core0.instance_name}_reg; sys_error_mux = error_{core0.instance_name}; end """ # Template for ModExpS6.createMux() method. createMux_modexps6_template = """\ CORE_ADDR_{core.upper_instance_name} + 0, CORE_ADDR_{core.upper_instance_name} + 1, CORE_ADDR_{core.upper_instance_name} + 2, CORE_ADDR_{core.upper_instance_name} + 3: begin sys_read_data_mux = read_data_{core.instance_name}; sys_error_mux = error_{core.instance_name}; end """ # Top-level (createModule) template. createModule_template = """\ // NOTE: This file is generated; do not edit by hand. module core_selector ( input wire sys_clk, input wire sys_rst, input wire [16: 0] sys_eim_addr, input wire sys_eim_wr, input wire sys_eim_rd, output wire [31: 0] sys_read_data, input wire [31: 0] sys_write_data, output wire sys_error, input wire noise, output wire [7 : 0] debug ); //---------------------------------------------------------------- // Address Decoder //---------------------------------------------------------------- // upper 9 bits specify core being addressed wire [ 8: 0] addr_core_num = sys_eim_addr[16: 8]; // lower 8 bits specify register offset in core wire [ 7: 0] addr_core_reg = sys_eim_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()