aboutsummaryrefslogtreecommitdiff
AgeCommit message (Expand)Author
2016-10-26Version 0.1 of a set of HSM unit tests, using the Python RPC API.Rob Austein
2016-10-26Add PyCrypto-based mixed-mode support to Python RPC client.Rob Austein
2016-10-26Fix pure-remote-mode hal_rpc_pkey_{sign,verify}().Rob Austein
2016-10-25Uppercase HAL_DIGEST_ALGORITHM_ symbols for API consistency.Rob Austein
2016-10-24Flesh out key object access control.Rob Austein
2016-10-24Make previous_uuid an input-only argument to hal_rpc_pkey_match().Rob Austein
2016-10-21Tweak enum handling to handle more of the C enum definition syntax.Rob Austein
2016-10-21Better enum handling, more readable RPC methods.Rob Austein
2016-10-20Fix HAL_KEY_TYPE_* symbols, add Attribute class.Rob Austein
2016-10-19Add handle objects to make API a bit more Pythonic.Rob Austein
2016-10-19Shake first round of bugs out of hal_rpc_pkey_match().Rob Austein
2016-10-19Use correct RPC function code in hash_get_digest_algorithm_id().Rob Austein
2016-10-19First cut at Python interface to native libhal RPC.Rob Austein
2016-10-16Debug keystore attribute code; handle name properly in ks_index_replace().Rob Austein
2016-10-15Fencepost error in ks_heapsort().Rob Austein
2016-10-14Keystore attribute code. Not really tested.Rob Austein
2016-10-09Per-session objects in ks_volatile; more untested ks_attribute code.Rob Austein
2016-10-07Fix session handle arguments in RPC calls.Rob Austein
2016-10-07Stop whining about POSIX strnlen() function.Rob Austein
2016-10-07Checkpoint along the way to adding keystore attribute support.Rob Austein
2016-09-30Multi-block object support in keystore.Rob Austein
2016-09-27Redesign ks_flash block header.Rob Austein
2016-09-27Write updated PIN block before updating index.Rob Austein
2016-09-27Fix swapped memmove() arguments in hal_ks_index_replace().Rob Austein
2016-09-27Add hal_ks_index_replace().Rob Austein
2016-09-26More ks_flash cleanup.Rob Austein
2016-09-26Rewrite block_erase_maybe() to run the "maybe" check in constant time.Rob Austein
2016-09-23Use subsectors instead of sectors in keystore.Rob Austein
2016-09-20Restructure hal_slip_recv_char so caller can pass in a char directly.Paul Selkirk
2016-09-20Clean up the defines around rpc client, software hash cores, etc.Paul Selkirk
2016-09-16Debug new ks_flash code.Rob Austein
2016-09-16Revised ks_flash. Compiles, not yet tested.Rob Austein
2016-09-13Cleanup prior to rewriting ks_flash.c.Rob Austein
2016-09-12CRC-32 code for use in ks_flash, stm32 DFU, possibly elsewhere.Rob Austein
2016-09-12Doh, allow keystore reinitialization after unclean reboot.Rob Austein
2016-09-11Explicit initialization of keystore drivers instead of guessing.Rob Austein
2016-09-09Portable fix for ks_find() fencepost error.Rob Austein
2016-09-09Rewrite ks_volatile driver to use new ks_index infrastructure.Rob Austein
2016-09-09Fencepost error in ks_find().Rob Austein
2016-09-09Missed a few instances of type-based key naming in keystore drivers.Rob Austein
2016-09-09Simplify hal_rpc_pkey_find() by removing `type` argument.Rob Austein
2016-09-08New keystore index internal API. Compiles, not yet integrated or tested.Rob Austein
2016-09-03Plug pkey handle leak.Rob Austein
2016-09-02Test both in-memory and on-flash keystores.Rob Austein
2016-09-02Fencepost error in hal_uuid_format().Rob Austein
2016-09-02Code to convert between text and internal forms of UUIDs.Rob Austein
2016-09-01Move in-memory keystore from client to server. Whack with club until compiles.Rob Austein
2016-09-01Revised keystore API, part one. Not usable yet.Rob Austein
2016-08-23Merge branch 'master' of git.cryptech.is:sw/libhalPaul Selkirk
2016-08-16Lock RPC device after opening it.Rob Austein
a id='n387' href='#n387'>387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
#!/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()