#!/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 ConfigParser import RawConfigParser
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)
for core in cores[2:]:
core.add_vfiles(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))
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.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 add_vfiles(self, cfg):
if self.instance_number == 0:
self.vfiles = cfg.get(self.name, "vfiles").split()
if cfg.has_option(self.name, "requires"):
for required in cfg.get(self.name, "requires").split():
if required not in self._instance_count:
self.vfiles.extend(cfg.get(required, "vfiles").split())
@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)"
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 InvertedResetCore(Core):
"""
Core which inverts the reset signal. Seems to vary by author.
No, I don't know why we don't just pick one convention or the other.
"""
@property
def reset_pin(self):
return ".reset_n(~sys_rst)"
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(InvertedResetCore):
"""
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.
# Moving the reset-high/reset-low logic to the config file should simplify this.
Core.special_class.update(
trng = TRNGCore,
aes = InvertedResetCore,
chacha = InvertedResetCore,
sha1 = InvertedResetCore,
sha256 = InvertedResetCore,
sha512 = InvertedResetCore,
modexp = InvertedResetCore,
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})
);
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 error_{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}),
.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()