diff options
author | Paul Selkirk <paul@psgd.org> | 2015-11-13 17:03:52 -0500 |
---|---|---|
committer | Paul Selkirk <paul@psgd.org> | 2015-11-13 17:03:52 -0500 |
commit | b1e661bf4f8a715743222977067c1cf560408b40 (patch) | |
tree | dbda524f94b591a9952577303f768f96f8d5ae88 /config/config.py | |
parent | 5ad8554e49ed204ffe5242493b16d7735cadb4e6 (diff) | |
parent | 47508ec70ea2c85cb1541b1c3a214439357ad735 (diff) |
Merge branch 'config_core_selector'
Diffstat (limited to 'config/config.py')
-rwxr-xr-x | config/config.py | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/config/config.py b/config/config.py new file mode 100755 index 0000000..0f3818f --- /dev/null +++ b/config/config.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python + +""" +Generate core_selector.v and core_vfiles.mk 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 +# contiguous "cores" with a 10-bit composite register selector. + +# At present, TRNG core's internal multiplexer doesn't allocate cores +# contiguously, there's a gap, and one just has to know what the +# offsets are. Current theory is that we'll fix the TRNG core to get +# rid of this problem, but for now the workaround requires this script +# to know the magic offsets for the high 4 bits of the 12-bit TRNG +# address: +# +# 0x0: trng +# 0x5: entropy1 (avalanche) +# 0x6: entropy2 (rosc) +# 0xa: mixer +# 0xb: csprng + +# The modexps6 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 modexps6 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. +# +# - 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) + + 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( + 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 = {} + + # Class variable mapping core name to subclass for special cases. + + special_class = {} + + # Class variable recording whether we need a one-cycle delay to + # compensate for block memories. + + need_one_cycle_delay = True + + def __init__(self, name): + self.name = name + self.core_number = None + self.vfiles = [] + self.reset_high = False + 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 + + @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) + self.error_wire = cfg.getboolean(self.name, "error_wire", self.error_wire) + self.block_memory = cfg.getboolean(self.name, "block_memory", self.block_memory) + + @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)" + + @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_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): + 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 with slots for 15 sub-cores, + most of which are empty. This is a bit of a mess. + + 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 + different templates, and fiddle with addresses a bit. + + Mux numbers have to be dug out of the TRNG Verilog source. + """ + + # TRNG subcore name -> internal mux number. + subcore_parameters = dict(avalanche_entropy = 0x5, + rosc_entropy = 0x6, + trng_mixer = 0xa, + trng_csprng = 0xb) + + def __init__(self, name): + super(TRNGCore, self).__init__(name) + self.subcores = tuple(SubCore(name, self) + for name in sorted(self.subcore_parameters, + key = lambda x: self.subcore_parameters[x])) + + def assign_core_number(self, n): + n = super(TRNGCore, self).assign_core_number(n) + for subcore in self.subcores: + subcore.assign_core_number(self.core_number + self.subcore_parameters[subcore.name]) + return n + 15 + + @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, core0 = 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}){core.error_port} + ); + +{core.one_cycle_delay} + +""" + +# 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} + 9'h03); + 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.upper_instance_name} + 9'h0f); + 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) + ); + +{core.one_cycle_delay} + +""" + +# 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_ADDR_{core.upper_instance_name}: + begin + sys_read_data_mux = {core0.mux_data_reg}; + sys_error_mux = {core0.mux_error_reg}; + 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 = {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 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() |