add minerva source from https://github.com/lambdaconcept/minerva
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 11 Mar 2020 16:10:51 +0000 (16:10 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 11 Mar 2020 16:13:25 +0000 (16:13 +0000)
34 files changed:
src/soc/minerva/LICENSE.txt [new file with mode: 0644]
src/soc/minerva/__init__.py [new file with mode: 0644]
src/soc/minerva/cache.py [new file with mode: 0644]
src/soc/minerva/cli.py [new file with mode: 0644]
src/soc/minerva/core.py [new file with mode: 0644]
src/soc/minerva/csr.py [new file with mode: 0644]
src/soc/minerva/isa.py [new file with mode: 0644]
src/soc/minerva/stage.py [new file with mode: 0644]
src/soc/minerva/test/__init__.py [new file with mode: 0644]
src/soc/minerva/test/test_cache.py [new file with mode: 0644]
src/soc/minerva/test/test_units_divider.py [new file with mode: 0644]
src/soc/minerva/test/test_units_multiplier.py [new file with mode: 0644]
src/soc/minerva/units/__init__.py [new file with mode: 0644]
src/soc/minerva/units/adder.py [new file with mode: 0644]
src/soc/minerva/units/compare.py [new file with mode: 0644]
src/soc/minerva/units/debug/__init__.py [new file with mode: 0644]
src/soc/minerva/units/debug/controller.py [new file with mode: 0644]
src/soc/minerva/units/debug/dmi.py [new file with mode: 0644]
src/soc/minerva/units/debug/jtag.py [new file with mode: 0644]
src/soc/minerva/units/debug/regfile.py [new file with mode: 0644]
src/soc/minerva/units/debug/top.py [new file with mode: 0644]
src/soc/minerva/units/debug/wbmaster.py [new file with mode: 0644]
src/soc/minerva/units/decoder.py [new file with mode: 0644]
src/soc/minerva/units/divider.py [new file with mode: 0644]
src/soc/minerva/units/exception.py [new file with mode: 0644]
src/soc/minerva/units/fetch.py [new file with mode: 0644]
src/soc/minerva/units/loadstore.py [new file with mode: 0644]
src/soc/minerva/units/logic.py [new file with mode: 0644]
src/soc/minerva/units/multiplier.py [new file with mode: 0644]
src/soc/minerva/units/predict.py [new file with mode: 0644]
src/soc/minerva/units/rvficon.py [new file with mode: 0644]
src/soc/minerva/units/shifter.py [new file with mode: 0644]
src/soc/minerva/units/trigger.py [new file with mode: 0644]
src/soc/minerva/wishbone.py [new file with mode: 0644]

diff --git a/src/soc/minerva/LICENSE.txt b/src/soc/minerva/LICENSE.txt
new file mode 100644 (file)
index 0000000..d9d87bc
--- /dev/null
@@ -0,0 +1,28 @@
+Copyright (C) 2018-2019 LambdaConcept
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. 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.
+
+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 OWNER 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.
+
+
+Other authors retain ownership of their contributions. If a submission can
+reasonably be considered independently copyrightable, it's yours and we
+encourage you to claim it with appropriate copyright notices. This submission
+then falls under the "otherwise noted" category. All submissions are strongly
+encouraged to use the two-clause BSD license reproduced above.
diff --git a/src/soc/minerva/__init__.py b/src/soc/minerva/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/soc/minerva/cache.py b/src/soc/minerva/cache.py
new file mode 100644 (file)
index 0000000..5cdc1e8
--- /dev/null
@@ -0,0 +1,142 @@
+from nmigen import *
+from nmigen.asserts import *
+from nmigen.lib.coding import Encoder
+from nmigen.utils import log2_int
+
+
+__all__ = ["L1Cache"]
+
+
+class L1Cache(Elaboratable):
+    def __init__(self, nways, nlines, nwords, base, limit):
+        if not nlines or nlines & nlines-1:
+            raise ValueError("nlines must be a power of 2, not {!r}".format(nlines))
+        if nwords not in {4, 8, 16}:
+            raise ValueError("nwords must be 4, 8 or 16, not {!r}".format(nwords))
+        if nways not in {1, 2}:
+            raise ValueError("nways must be 1 or 2, not {!r}".format(nways))
+
+        self.nways = nways
+        self.nlines = nlines
+        self.nwords = nwords
+        self.base = base
+        self.limit = limit
+
+        offsetbits = log2_int(nwords)
+        linebits = log2_int(nlines)
+        tagbits = log2_int(limit-base) - log2_int(nlines) - log2_int(nwords) - 2
+
+        self.s1_addr = Record([("offset", offsetbits), ("line", linebits), ("tag", tagbits)])
+        self.s1_flush = Signal()
+        self.s1_stall = Signal()
+        self.s1_valid = Signal()
+        self.s2_addr = Record.like(self.s1_addr)
+        self.s2_re = Signal()
+        self.s2_evict = Signal()
+        self.s2_valid = Signal()
+        self.bus_valid = Signal()
+        self.bus_error = Signal()
+        self.bus_rdata = Signal(32)
+
+        self.s2_miss = Signal()
+        self.s2_rdata = Signal(32)
+        self.bus_re = Signal()
+        self.bus_addr = Record.like(self.s1_addr)
+        self.bus_last = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        ways = Array(Record([("data",   self.nwords * 32),
+                             ("tag",    self.s2_addr.tag.shape()),
+                             ("valid",  1),
+                             ("bus_re", 1)])
+                     for _ in range(self.nways))
+
+        if self.nways == 1:
+            way_lru = Const(0)
+        elif self.nways == 2:
+            way_lru = Signal()
+            with m.If(self.bus_re & self.bus_valid & self.bus_last & ~self.bus_error):
+                m.d.sync += way_lru.eq(~way_lru)
+
+        m.d.comb += ways[way_lru].bus_re.eq(self.bus_re)
+
+        way_hit = m.submodules.way_hit = Encoder(self.nways)
+        for j, way in enumerate(ways):
+            m.d.comb += way_hit.i[j].eq((way.tag == self.s2_addr.tag) & way.valid)
+
+        m.d.comb += [
+            self.s2_miss.eq(way_hit.n),
+            self.s2_rdata.eq(ways[way_hit.o].data.word_select(self.s2_addr.offset, 32))
+        ]
+
+        with m.FSM() as fsm:
+            last_offset = Signal.like(self.s2_addr.offset)
+
+            with m.State("CHECK"):
+                with m.If(self.s2_re & self.s2_miss & self.s2_valid):
+                    m.d.sync += [
+                        self.bus_addr.eq(self.s2_addr),
+                        self.bus_re.eq(1),
+                        last_offset.eq(self.s2_addr.offset - 1)
+                    ]
+                    m.next = "REFILL"
+
+            with m.State("REFILL"):
+                m.d.comb += self.bus_last.eq(self.bus_addr.offset == last_offset)
+                with m.If(self.bus_valid):
+                    m.d.sync += self.bus_addr.offset.eq(self.bus_addr.offset + 1)
+                with m.If(self.bus_valid & self.bus_last | self.bus_error):
+                    m.d.sync += self.bus_re.eq(0)
+                with m.If(~self.bus_re & ~self.s1_stall):
+                    m.next = "CHECK"
+
+        if platform == "formal":
+            with m.If(Initial()):
+                m.d.comb += Assume(fsm.ongoing("CHECK"))
+
+        for way in ways:
+            valid_lines = Signal(self.nlines)
+
+            with m.If(self.s1_flush & self.s1_valid):
+                m.d.sync += valid_lines.eq(0)
+            with m.Elif(way.bus_re & self.bus_error):
+                m.d.sync += valid_lines.bit_select(self.bus_addr.line, 1).eq(0)
+            with m.Elif(way.bus_re & self.bus_valid & self.bus_last):
+                m.d.sync += valid_lines.bit_select(self.bus_addr.line, 1).eq(1)
+            with m.Elif(self.s2_evict & self.s2_valid & (way.tag == self.s2_addr.tag)):
+                m.d.sync += valid_lines.bit_select(self.s2_addr.line, 1).eq(0)
+
+            tag_mem = Memory(width=len(way.tag), depth=self.nlines)
+            tag_rp = tag_mem.read_port()
+            tag_wp = tag_mem.write_port()
+            m.submodules += tag_rp, tag_wp
+
+            data_mem = Memory(width=len(way.data), depth=self.nlines)
+            data_rp = data_mem.read_port()
+            data_wp = data_mem.write_port(granularity=32)
+            m.submodules += data_rp, data_wp
+
+            m.d.comb += [
+                tag_rp.addr.eq(Mux(self.s1_stall, self.s2_addr.line, self.s1_addr.line)),
+                data_rp.addr.eq(Mux(self.s1_stall, self.s2_addr.line, self.s1_addr.line)),
+
+                tag_wp.addr.eq(self.bus_addr.line),
+                tag_wp.en.eq(way.bus_re & self.bus_valid & self.bus_last),
+                tag_wp.data.eq(self.bus_addr.tag),
+
+                data_wp.addr.eq(self.bus_addr.line),
+                data_wp.en.bit_select(self.bus_addr.offset, 1).eq(way.bus_re & self.bus_valid),
+                data_wp.data.eq(self.bus_rdata << self.bus_addr.offset*32),
+
+                way.valid.eq(valid_lines.bit_select(self.s2_addr.line, 1)),
+                way.tag.eq(tag_rp.data),
+                way.data.eq(data_rp.data)
+            ]
+
+            if platform == "formal":
+                with m.If(Initial()):
+                    m.d.comb += Assume(~valid_lines.bool())
+
+        return m
diff --git a/src/soc/minerva/cli.py b/src/soc/minerva/cli.py
new file mode 100644 (file)
index 0000000..6b38cd4
--- /dev/null
@@ -0,0 +1,114 @@
+import argparse
+import warnings
+from nmigen import cli
+
+from minerva.core import Minerva
+
+
+def main():
+    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+    parser.add_argument("--reset-addr",
+            type=lambda s: int(s, 16), default="0x00000000",
+            help="reset vector address")
+
+    parser.add_argument("--with-icache",
+            default=False, action="store_true",
+            help="enable the instruction cache")
+    parser.add_argument("--with-dcache",
+            default=False, action="store_true",
+            help="enable the data cache")
+    parser.add_argument("--with-muldiv",
+            default=False, action="store_true",
+            help="enable RV32M support")
+    parser.add_argument("--with-debug",
+            default=False, action="store_true",
+            help="enable the Debug Module")
+    parser.add_argument("--with-trigger",
+            default=False, action="store_true",
+            help="enable the Trigger Module")
+    parser.add_argument("--with-rvfi",
+            default=False, action="store_true",
+            help="enable the riscv-formal interface")
+
+    icache_group = parser.add_argument_group("icache options")
+    icache_group.add_argument("--icache-nways",
+            type=int, choices=[1, 2], default=1,
+            help="number of ways")
+    icache_group.add_argument("--icache-nlines",
+            type=int, default=128,
+            help="number of lines")
+    icache_group.add_argument("--icache-nwords",
+            type=int, choices=[4, 8, 16], default=4,
+            help="number of words in a line")
+    icache_group.add_argument("--icache-base",
+            type=lambda s: int(s, 16), default="0x00000000",
+            help="base address")
+    icache_group.add_argument("--icache-limit",
+            type=lambda s: int(s, 16), default="0x80000000",
+            help="limit address")
+
+    dcache_group = parser.add_argument_group("dcache options")
+    dcache_group.add_argument("--dcache-nways",
+            type=int, choices=[1, 2], default=1,
+            help="number of ways")
+    dcache_group.add_argument("--dcache-nlines",
+            type=int, default=128,
+            help="number of lines")
+    dcache_group.add_argument("--dcache-nwords",
+            type=int, choices=[4, 8, 16], default=4,
+            help="number of words in a line")
+    dcache_group.add_argument("--dcache-base",
+            type=lambda s: int(s, 16), default="0x00000000",
+            help="base address")
+    dcache_group.add_argument("--dcache-limit",
+            type=lambda s: int(s, 16), default="0x80000000",
+            help="limit address")
+
+    trigger_group = parser.add_argument_group("trigger options")
+    trigger_group.add_argument("--nb-triggers",
+            type=int, default=8,
+            help="number of triggers")
+
+    cli.main_parser(parser)
+
+    args = parser.parse_args()
+
+    if args.with_debug and not args.with_trigger:
+        warnings.warn("Support for hardware breakpoints requires --with-trigger")
+
+    cpu = Minerva(args.reset_addr,
+            args.with_icache, args.icache_nways, args.icache_nlines, args.icache_nwords,
+            args.icache_base, args.icache_limit,
+            args.with_dcache, args.dcache_nways, args.dcache_nlines, args.dcache_nwords,
+            args.dcache_base, args.dcache_limit,
+            args.with_muldiv,
+            args.with_debug,
+            args.with_trigger, args.nb_triggers,
+            args.with_rvfi)
+
+    ports = [
+        cpu.external_interrupt, cpu.timer_interrupt, cpu.software_interrupt,
+        cpu.ibus.ack, cpu.ibus.adr, cpu.ibus.bte, cpu.ibus.cti, cpu.ibus.cyc, cpu.ibus.dat_r,
+        cpu.ibus.dat_w, cpu.ibus.sel, cpu.ibus.stb, cpu.ibus.we, cpu.ibus.err,
+        cpu.dbus.ack, cpu.dbus.adr, cpu.dbus.bte, cpu.dbus.cti, cpu.dbus.cyc, cpu.dbus.dat_r,
+        cpu.dbus.dat_w, cpu.dbus.sel, cpu.dbus.stb, cpu.dbus.we, cpu.dbus.err
+    ]
+
+    if args.with_debug:
+        ports += [cpu.jtag.tck, cpu.jtag.tdi, cpu.jtag.tdo, cpu.jtag.tms]
+
+    if args.with_rvfi:
+        ports += [
+            cpu.rvfi.valid, cpu.rvfi.order, cpu.rvfi.insn, cpu.rvfi.trap, cpu.rvfi.halt,
+            cpu.rvfi.intr, cpu.rvfi.mode, cpu.rvfi.ixl, cpu.rvfi.rs1_addr, cpu.rvfi.rs2_addr,
+            cpu.rvfi.rs1_rdata, cpu.rvfi.rs2_rdata, cpu.rvfi.rd_addr, cpu.rvfi.rd_wdata,
+            cpu.rvfi.pc_rdata, cpu.rvfi.pc_wdata, cpu.rvfi.mem_addr, cpu.rvfi.mem_rmask,
+            cpu.rvfi.mem_wmask, cpu.rvfi.mem_rdata, cpu.rvfi.mem_wdata
+        ]
+
+    cli.main_runner(parser, args, cpu, name="minerva_cpu", ports=ports)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/soc/minerva/core.py b/src/soc/minerva/core.py
new file mode 100644 (file)
index 0000000..95dbb1b
--- /dev/null
@@ -0,0 +1,806 @@
+from functools import reduce
+from operator import or_
+from itertools import tee
+
+from nmigen import *
+from nmigen.lib.coding import PriorityEncoder
+
+from .isa import *
+from .stage import *
+from .csr import *
+
+from .units.adder import *
+from .units.compare import *
+from .units.debug import *
+from .units.decoder import *
+from .units.divider import *
+from .units.exception import *
+from .units.fetch import *
+from .units.rvficon import *
+from .units.loadstore import *
+from .units.logic import *
+from .units.multiplier import *
+from .units.predict import *
+from .units.shifter import *
+from .units.trigger import *
+
+from .units.debug.jtag import jtag_layout
+from .wishbone import wishbone_layout
+
+
+__all__ = ["Minerva"]
+
+
+_af_layout = [
+    ("pc",      (33, True)),
+]
+
+
+_fd_layout = [
+    ("pc",               32),
+    ("instruction",      32),
+    ("fetch_error",       1),
+    ("fetch_badaddr",    30)
+]
+
+
+_dx_layout = [
+    ("pc",                  32),
+    ("instruction",         32),
+    ("fetch_error",          1),
+    ("fetch_badaddr",       30),
+    ("illegal",              1),
+    ("rd",                   5),
+    ("rs1",                  5),
+    ("rd_we",                1),
+    ("rs1_re",               1),
+    ("src1",                32),
+    ("src2",                32),
+    ("immediate",           32),
+    ("bypass_x",             1),
+    ("bypass_m",             1),
+    ("funct3",               3),
+    ("load",                 1),
+    ("store",                1),
+    ("adder",                1),
+    ("adder_sub",            1),
+    ("logic",                1),
+    ("multiply",             1),
+    ("divide",               1),
+    ("shift",                1),
+    ("direction",            1),
+    ("sext",                 1),
+    ("jump",                 1),
+    ("compare",              1),
+    ("branch",               1),
+    ("branch_target",       32),
+    ("branch_predict_taken", 1),
+    ("fence_i",              1),
+    ("csr",                  1),
+    ("csr_adr",             12),
+    ("csr_we",               1),
+    ("ecall",                1),
+    ("ebreak",               1),
+    ("mret",                 1),
+]
+
+
+_xm_layout = [
+    ("pc",                  32),
+    ("instruction",         32),
+    ("fetch_error",          1),
+    ("fetch_badaddr",       30),
+    ("illegal",              1),
+    ("loadstore_misaligned", 1),
+    ("ecall",                1),
+    ("ebreak",               1),
+    ("rd",                   5),
+    ("rd_we",                1),
+    ("bypass_m",             1),
+    ("funct3",               3),
+    ("result",              32),
+    ("shift",                1),
+    ("load",                 1),
+    ("store",                1),
+    ("store_data",          32),
+    ("compare",              1),
+    ("multiply",             1),
+    ("divide",               1),
+    ("condition_met",        1),
+    ("branch_target",       32),
+    ("branch_taken",         1),
+    ("branch_predict_taken", 1),
+    ("csr",                  1),
+    ("csr_adr",             12),
+    ("csr_we",               1),
+    ("csr_result",          32),
+    ("mret",                 1),
+    ("exception",            1)
+]
+
+
+_mw_layout = [
+    ("pc",                32),
+    ("rd",                 5),
+    ("rd_we",              1),
+    ("funct3",             3),
+    ("result",            32),
+    ("load",               1),
+    ("load_data",         32),
+    ("multiply",           1),
+    ("exception",          1)
+]
+
+
+class Minerva(Elaboratable):
+    def __init__(self, reset_address=0x00000000,
+                with_icache=False,
+                icache_nways=1, icache_nlines=256, icache_nwords=8, icache_base=0, icache_limit=2**31,
+                with_dcache=False,
+                dcache_nways=1, dcache_nlines=256, dcache_nwords=8, dcache_base=0, dcache_limit=2**31,
+                with_muldiv=False,
+                with_debug=False,
+                with_trigger=False, nb_triggers=8,
+                with_rvfi=False):
+        self.external_interrupt = Signal(32)
+        self.timer_interrupt = Signal()
+        self.software_interrupt = Signal()
+        self.ibus = Record(wishbone_layout)
+        self.dbus = Record(wishbone_layout)
+
+        if with_debug:
+            self.jtag = Record(jtag_layout)
+
+        if with_rvfi:
+            self.rvfi = Record(rvfi_layout)
+
+        self.reset_address = reset_address
+        self.with_icache   = with_icache
+        self.icache_args   = icache_nways, icache_nlines, icache_nwords, icache_base, icache_limit
+        self.with_dcache   = with_dcache
+        self.dcache_args   = dcache_nways, dcache_nlines, dcache_nwords, dcache_base, dcache_limit
+        self.with_muldiv   = with_muldiv
+        self.with_debug    = with_debug
+        self.with_trigger  = with_trigger
+        self.nb_triggers   = nb_triggers
+        self.with_rvfi     = with_rvfi
+
+    def elaborate(self, platform):
+        cpu = Module()
+
+        # pipeline stages
+
+        a = cpu.submodules.a = Stage(None, _af_layout)
+        f = cpu.submodules.f = Stage(_af_layout, _fd_layout)
+        d = cpu.submodules.d = Stage(_fd_layout, _dx_layout)
+        x = cpu.submodules.x = Stage(_dx_layout, _xm_layout)
+        m = cpu.submodules.m = Stage(_xm_layout, _mw_layout)
+        w = cpu.submodules.w = Stage(_mw_layout, None)
+        stages = a, f, d, x, m, w
+
+        sources, sinks = tee(stages)
+        next(sinks)
+        for s1, s2 in zip(sources, sinks):
+            cpu.d.comb += s1.source.connect(s2.sink)
+
+        a.source.pc.reset = self.reset_address - 4
+        cpu.d.comb += a.valid.eq(Const(1))
+
+        # units
+
+        pc_sel    = cpu.submodules.pc_sel    = PCSelector()
+        data_sel  = cpu.submodules.data_sel  = DataSelector()
+        adder     = cpu.submodules.adder     = Adder()
+        compare   = cpu.submodules.compare   = CompareUnit()
+        decoder   = cpu.submodules.decoder   = InstructionDecoder(self.with_muldiv)
+        exception = cpu.submodules.exception = ExceptionUnit()
+        logic     = cpu.submodules.logic     = LogicUnit()
+        predict   = cpu.submodules.predict   = BranchPredictor()
+        shifter   = cpu.submodules.shifter   = Shifter()
+
+        if self.with_icache:
+            fetch = cpu.submodules.fetch = CachedFetchUnit(*self.icache_args)
+        else:
+            fetch = cpu.submodules.fetch = BareFetchUnit()
+
+        if self.with_dcache:
+            loadstore = cpu.submodules.loadstore = CachedLoadStoreUnit(*self.dcache_args)
+        else:
+            loadstore = cpu.submodules.loadstore = BareLoadStoreUnit()
+
+        if self.with_muldiv:
+            multiplier = Multiplier() if not self.with_rvfi else DummyMultiplier()
+            divider    = Divider()    if not self.with_rvfi else DummyDivider()
+            cpu.submodules.multiplier = multiplier
+            cpu.submodules.divider    = divider
+
+        if self.with_debug:
+            debug = cpu.submodules.debug = DebugUnit()
+
+        if self.with_trigger:
+            trigger = cpu.submodules.trigger = TriggerUnit(self.nb_triggers)
+
+        if self.with_rvfi:
+            rvficon = cpu.submodules.rvficon = RVFIController()
+
+        # register files
+
+        gprf = Memory(width=32, depth=32)
+        gprf_rp1 = gprf.read_port()
+        gprf_rp2 = gprf.read_port()
+        gprf_wp  = gprf.write_port()
+        cpu.submodules += gprf_rp1, gprf_rp2, gprf_wp
+
+        csrf = cpu.submodules.csrf = CSRFile()
+        csrf_rp = csrf.read_port()
+        csrf_wp = csrf.write_port()
+
+        csrf.add_csrs(exception.iter_csrs())
+        if self.with_debug:
+            csrf.add_csrs(debug.iter_csrs())
+        if self.with_trigger:
+            csrf.add_csrs(trigger.iter_csrs())
+
+        # pipeline logic
+
+        cpu.d.comb += [
+            pc_sel.f_pc.eq(f.sink.pc),
+            pc_sel.d_pc.eq(d.sink.pc),
+            pc_sel.d_branch_predict_taken.eq(predict.d_branch_taken & ~predict.d_fetch_misaligned),
+            pc_sel.d_branch_target.eq(predict.d_branch_target),
+            pc_sel.d_valid.eq(d.valid),
+            pc_sel.x_pc.eq(x.sink.pc),
+            pc_sel.x_fence_i.eq(x.sink.fence_i),
+            pc_sel.x_valid.eq(x.valid),
+            pc_sel.m_branch_predict_taken.eq(m.sink.branch_predict_taken),
+            pc_sel.m_branch_taken.eq(m.sink.branch_taken),
+            pc_sel.m_branch_target.eq(m.sink.branch_target),
+            pc_sel.m_exception.eq(exception.m_raise),
+            pc_sel.m_mret.eq(m.sink.mret),
+            pc_sel.m_valid.eq(m.valid),
+            pc_sel.mtvec_r_base.eq(exception.mtvec.r.base),
+            pc_sel.mepc_r_base.eq(exception.mepc.r.base)
+        ]
+
+        cpu.d.comb += [
+            fetch.a_pc.eq(pc_sel.a_pc),
+            fetch.a_stall.eq(a.stall),
+            fetch.a_valid.eq(a.valid),
+            fetch.f_stall.eq(f.stall),
+            fetch.f_valid.eq(f.valid),
+            fetch.ibus.connect(self.ibus)
+        ]
+
+        m.stall_on(fetch.a_busy & a.valid)
+        m.stall_on(fetch.f_busy & f.valid)
+
+        if self.with_icache:
+            flush_icache = x.sink.fence_i & x.valid & ~x.stall
+            if self.with_debug:
+                flush_icache |= debug.resumereq
+
+            cpu.d.comb += [
+                fetch.a_flush.eq(flush_icache),
+                fetch.f_pc.eq(f.sink.pc)
+            ]
+
+        cpu.d.comb += [
+            decoder.instruction.eq(d.sink.instruction)
+        ]
+
+        if self.with_debug:
+            with cpu.If(debug.halt & debug.halted):
+                cpu.d.comb += gprf_rp1.addr.eq(debug.gprf_addr)
+            with cpu.Elif(~d.stall):
+                cpu.d.comb += gprf_rp1.addr.eq(fetch.f_instruction[15:20])
+            with cpu.Else():
+                cpu.d.comb += gprf_rp1.addr.eq(decoder.rs1)
+
+            cpu.d.comb += debug.gprf_dat_r.eq(gprf_rp1.data)
+        else:
+            with cpu.If(~d.stall):
+                cpu.d.comb += gprf_rp1.addr.eq(fetch.f_instruction[15:20])
+            with cpu.Else():
+                cpu.d.comb += gprf_rp1.addr.eq(decoder.rs1)
+
+        with cpu.If(~d.stall):
+            cpu.d.comb += gprf_rp2.addr.eq(fetch.f_instruction[20:25])
+        with cpu.Else():
+            cpu.d.comb += gprf_rp2.addr.eq(decoder.rs2)
+
+        with cpu.If(~f.stall):
+            cpu.d.sync += csrf_rp.addr.eq(fetch.f_instruction[20:32])
+        cpu.d.comb += csrf_rp.en.eq(decoder.csr & d.valid)
+
+        # CSR set/clear instructions are translated to logic operations.
+        x_csr_set_clear = x.sink.funct3[1]
+        x_csr_clear = x_csr_set_clear & x.sink.funct3[0]
+        x_csr_fmt_i = x.sink.funct3[2]
+        x_csr_src1 = Mux(x_csr_fmt_i, x.sink.rs1, x.sink.src1)
+        x_csr_src1 = Mux(x_csr_clear, ~x_csr_src1, x_csr_src1)
+        x_csr_logic_op = x.sink.funct3 | 0b100
+
+        cpu.d.comb += [
+            logic.op.eq(Mux(x.sink.csr, x_csr_logic_op, x.sink.funct3)),
+            logic.src1.eq(Mux(x.sink.csr, x_csr_src1, x.sink.src1)),
+            logic.src2.eq(x.sink.src2)
+        ]
+
+        cpu.d.comb += [
+            adder.sub.eq(x.sink.adder & x.sink.adder_sub | x.sink.compare | x.sink.branch),
+            adder.src1.eq(x.sink.src1),
+            adder.src2.eq(Mux(x.sink.store, x.sink.immediate, x.sink.src2))
+        ]
+
+        if self.with_muldiv:
+            cpu.d.comb += [
+                multiplier.x_op.eq(x.sink.funct3),
+                multiplier.x_src1.eq(x.sink.src1),
+                multiplier.x_src2.eq(x.sink.src2),
+                multiplier.x_stall.eq(x.stall),
+                multiplier.m_stall.eq(m.stall)
+            ]
+
+            cpu.d.comb += [
+                divider.x_op.eq(x.sink.funct3),
+                divider.x_src1.eq(x.sink.src1),
+                divider.x_src2.eq(x.sink.src2),
+                divider.x_valid.eq(x.sink.valid),
+                divider.x_stall.eq(x.stall)
+            ]
+
+            m.stall_on(divider.m_busy)
+
+        cpu.d.comb += [
+            shifter.x_direction.eq(x.sink.direction),
+            shifter.x_sext.eq(x.sink.sext),
+            shifter.x_shamt.eq(x.sink.src2),
+            shifter.x_src1.eq(x.sink.src1),
+            shifter.x_stall.eq(x.stall)
+        ]
+
+        cpu.d.comb += [
+            # compare.op is shared by compare and branch instructions.
+            compare.op.eq(Mux(x.sink.compare, x.sink.funct3 << 1, x.sink.funct3)),
+            compare.zero.eq(x.sink.src1 == x.sink.src2),
+            compare.negative.eq(adder.result[-1]),
+            compare.overflow.eq(adder.overflow),
+            compare.carry.eq(adder.carry)
+        ]
+
+        cpu.d.comb += [
+            exception.external_interrupt.eq(self.external_interrupt),
+            exception.timer_interrupt.eq(self.timer_interrupt),
+            exception.software_interrupt.eq(self.software_interrupt),
+            exception.m_fetch_misaligned.eq(m.sink.branch_taken & m.sink.branch_target[:2].bool()),
+            exception.m_fetch_error.eq(m.sink.fetch_error),
+            exception.m_fetch_badaddr.eq(m.sink.fetch_badaddr),
+            exception.m_load_misaligned.eq(m.sink.load & m.sink.loadstore_misaligned),
+            exception.m_load_error.eq(loadstore.m_load_error),
+            exception.m_store_misaligned.eq(m.sink.store & m.sink.loadstore_misaligned),
+            exception.m_store_error.eq(loadstore.m_store_error),
+            exception.m_loadstore_badaddr.eq(loadstore.m_badaddr),
+            exception.m_branch_target.eq(m.sink.branch_target),
+            exception.m_illegal.eq(m.sink.illegal),
+            exception.m_ecall.eq(m.sink.ecall),
+            exception.m_pc.eq(m.sink.pc),
+            exception.m_instruction.eq(m.sink.instruction),
+            exception.m_result.eq(m.sink.result),
+            exception.m_mret.eq(m.sink.mret),
+            exception.m_stall.eq(m.sink.stall),
+            exception.m_valid.eq(m.valid)
+        ]
+
+        m_ebreak = m.sink.ebreak
+        if self.with_debug:
+            # If dcsr.ebreakm is set, EBREAK instructions enter Debug Mode.
+            # We do not want to raise an exception in this case because Debug Mode
+            # should be invisible to software execution.
+            m_ebreak &= ~debug.dcsr_ebreakm
+        if self.with_trigger:
+            m_ebreak |= trigger.trap
+        cpu.d.comb += exception.m_ebreak.eq(m_ebreak)
+
+        m.kill_on(m.source.exception & m.source.valid)
+
+        cpu.d.comb += [
+            data_sel.x_offset.eq(adder.result[:2]),
+            data_sel.x_funct3.eq(x.sink.funct3),
+            data_sel.x_store_operand.eq(x.sink.src2),
+            data_sel.w_offset.eq(w.sink.result[:2]),
+            data_sel.w_funct3.eq(w.sink.funct3),
+            data_sel.w_load_data.eq(w.sink.load_data)
+        ]
+
+        cpu.d.comb += [
+            loadstore.x_addr.eq(adder.result),
+            loadstore.x_mask.eq(data_sel.x_mask),
+            loadstore.x_load.eq(x.sink.load),
+            loadstore.x_store.eq(x.sink.store),
+            loadstore.x_store_data.eq(data_sel.x_store_data),
+            loadstore.x_stall.eq(x.stall),
+            loadstore.x_valid.eq(x.valid),
+            loadstore.m_stall.eq(m.stall),
+            loadstore.m_valid.eq(m.valid)
+        ]
+
+        m.stall_on(loadstore.x_busy & x.valid)
+        m.stall_on(loadstore.m_busy & m.valid)
+
+        if self.with_dcache:
+            if self.with_debug:
+                cpu.d.comb += loadstore.x_flush.eq(debug.resumereq)
+
+            cpu.d.comb += [
+                loadstore.x_fence_i.eq(x.sink.fence_i),
+                loadstore.m_addr.eq(m.sink.result),
+                loadstore.m_load.eq(m.sink.load),
+                loadstore.m_store.eq(m.sink.store),
+            ]
+
+            x.stall_on(loadstore.x_busy & x.valid)
+
+        for s in a, f:
+            s.kill_on(x.sink.fence_i & x.valid)
+
+        if self.with_debug:
+            with cpu.If(debug.halt & debug.halted):
+                cpu.d.comb += debug.dbus.connect(self.dbus)
+            with cpu.Else():
+                cpu.d.comb += loadstore.dbus.connect(self.dbus)
+        else:
+            cpu.d.comb += loadstore.dbus.connect(self.dbus)
+
+        # RAW hazard management
+
+        x_raw_rs1 = Signal()
+        m_raw_rs1 = Signal()
+        w_raw_rs1 = Signal()
+        x_raw_rs2 = Signal()
+        m_raw_rs2 = Signal()
+        w_raw_rs2 = Signal()
+
+        x_raw_csr = Signal()
+        m_raw_csr = Signal()
+
+        x_lock = Signal()
+        m_lock = Signal()
+
+        cpu.d.comb += [
+            x_raw_rs1.eq((x.sink.rd != 0) & (x.sink.rd == decoder.rs1) & x.sink.rd_we),
+            m_raw_rs1.eq((m.sink.rd != 0) & (m.sink.rd == decoder.rs1) & m.sink.rd_we),
+            w_raw_rs1.eq((w.sink.rd != 0) & (w.sink.rd == decoder.rs1) & w.sink.rd_we),
+
+            x_raw_rs2.eq((x.sink.rd != 0) & (x.sink.rd == decoder.rs2) & x.sink.rd_we),
+            m_raw_rs2.eq((m.sink.rd != 0) & (m.sink.rd == decoder.rs2) & m.sink.rd_we),
+            w_raw_rs2.eq((w.sink.rd != 0) & (w.sink.rd == decoder.rs2) & w.sink.rd_we),
+
+            x_raw_csr.eq((x.sink.csr_adr == decoder.immediate) & x.sink.csr_we),
+            m_raw_csr.eq((m.sink.csr_adr == decoder.immediate) & m.sink.csr_we),
+
+            x_lock.eq(~x.sink.bypass_x & (decoder.rs1_re & x_raw_rs1 | decoder.rs2_re & x_raw_rs2) | decoder.csr & x_raw_csr),
+            m_lock.eq(~m.sink.bypass_m & (decoder.rs1_re & m_raw_rs1 | decoder.rs2_re & m_raw_rs2) | decoder.csr & m_raw_csr)
+        ]
+
+        if self.with_debug:
+            d.stall_on((x_lock & x.valid | m_lock & m.valid) & d.valid & ~debug.dcsr_step)
+        else:
+            d.stall_on((x_lock & x.valid | m_lock & m.valid) & d.valid)
+
+        # result selection
+
+        x_result = Signal(32)
+        m_result = Signal(32)
+        w_result = Signal(32)
+        x_csr_result = Signal(32)
+
+        with cpu.If(x.sink.jump):
+            cpu.d.comb += x_result.eq(x.sink.pc + 4)
+        with cpu.Elif(x.sink.logic):
+            cpu.d.comb += x_result.eq(logic.result)
+        with cpu.Elif(x.sink.csr):
+            cpu.d.comb += x_result.eq(x.sink.src2)
+        with cpu.Else():
+            cpu.d.comb += x_result.eq(adder.result)
+
+        with cpu.If(m.sink.compare):
+            cpu.d.comb += m_result.eq(m.sink.condition_met)
+        if self.with_muldiv:
+            with cpu.Elif(m.sink.divide):
+                cpu.d.comb += m_result.eq(divider.m_result)
+        with cpu.Elif(m.sink.shift):
+            cpu.d.comb += m_result.eq(shifter.m_result)
+        with cpu.Else():
+            cpu.d.comb += m_result.eq(m.sink.result)
+
+        with cpu.If(w.sink.load):
+            cpu.d.comb += w_result.eq(data_sel.w_load_result)
+        if self.with_muldiv:
+            with cpu.Elif(w.sink.multiply):
+                cpu.d.comb += w_result.eq(multiplier.w_result)
+        with cpu.Else():
+            cpu.d.comb += w_result.eq(w.sink.result)
+
+        with cpu.If(x_csr_set_clear):
+            cpu.d.comb += x_csr_result.eq(logic.result)
+        with cpu.Else():
+            cpu.d.comb += x_csr_result.eq(x_csr_src1)
+
+        cpu.d.comb += [
+            csrf_wp.en.eq(m.sink.csr & m.sink.csr_we & m.valid & ~exception.m_raise & ~m.stall),
+            csrf_wp.addr.eq(m.sink.csr_adr),
+            csrf_wp.data.eq(m.sink.csr_result)
+        ]
+
+        if self.with_debug:
+            with cpu.If(debug.halt & debug.halted):
+                cpu.d.comb += [
+                    gprf_wp.addr.eq(debug.gprf_addr),
+                    gprf_wp.en.eq(debug.gprf_we),
+                    gprf_wp.data.eq(debug.gprf_dat_w)
+                ]
+            with cpu.Else():
+                cpu.d.comb += [
+                    gprf_wp.en.eq((w.sink.rd != 0) & w.sink.rd_we & w.valid & ~w.sink.exception),
+                    gprf_wp.addr.eq(w.sink.rd),
+                    gprf_wp.data.eq(w_result)
+                ]
+        else:
+            cpu.d.comb += [
+                gprf_wp.en.eq((w.sink.rd != 0) & w.sink.rd_we & w.valid),
+                gprf_wp.addr.eq(w.sink.rd),
+                gprf_wp.data.eq(w_result)
+            ]
+
+        # D stage operand selection
+
+        d_src1 = Signal(32)
+        d_src2 = Signal(32)
+
+        with cpu.If(decoder.lui):
+            cpu.d.comb += d_src1.eq(0)
+        with cpu.Elif(decoder.auipc):
+            cpu.d.comb += d_src1.eq(d.sink.pc)
+        with cpu.Elif(decoder.rs1_re & (decoder.rs1 == 0)):
+            cpu.d.comb += d_src1.eq(0)
+        with cpu.Elif(x_raw_rs1 & x.valid):
+            cpu.d.comb += d_src1.eq(x_result)
+        with cpu.Elif(m_raw_rs1 & m.valid):
+            cpu.d.comb += d_src1.eq(m_result)
+        with cpu.Elif(w_raw_rs1 & w.valid):
+            cpu.d.comb += d_src1.eq(w_result)
+        with cpu.Else():
+            cpu.d.comb += d_src1.eq(gprf_rp1.data)
+
+        with cpu.If(decoder.csr):
+            cpu.d.comb += d_src2.eq(csrf_rp.data)
+        with cpu.Elif(~decoder.rs2_re):
+            cpu.d.comb += d_src2.eq(decoder.immediate)
+        with cpu.Elif(decoder.rs2 == 0):
+            cpu.d.comb += d_src2.eq(0)
+        with cpu.Elif(x_raw_rs2 & x.valid):
+            cpu.d.comb += d_src2.eq(x_result)
+        with cpu.Elif(m_raw_rs2 & m.valid):
+            cpu.d.comb += d_src2.eq(m_result)
+        with cpu.Elif(w_raw_rs2 & w.valid):
+            cpu.d.comb += d_src2.eq(w_result)
+        with cpu.Else():
+            cpu.d.comb += d_src2.eq(gprf_rp2.data)
+
+        # branch prediction
+
+        cpu.d.comb += [
+            predict.d_branch.eq(decoder.branch),
+            predict.d_jump.eq(decoder.jump),
+            predict.d_offset.eq(decoder.immediate),
+            predict.d_pc.eq(d.sink.pc),
+            predict.d_rs1_re.eq(decoder.rs1_re)
+        ]
+
+        a.kill_on(predict.d_branch_taken & ~predict.d_fetch_misaligned & d.valid)
+        for s in a, f:
+            s.kill_on(m.sink.branch_predict_taken & ~m.sink.branch_taken & m.valid)
+        for s in a, f, d:
+            s.kill_on(~m.sink.branch_predict_taken & m.sink.branch_taken & m.valid)
+            s.kill_on((exception.m_raise | m.sink.mret) & m.valid)
+
+        # debug unit
+
+        if self.with_debug:
+            cpu.d.comb += [
+                debug.jtag.connect(self.jtag),
+                debug.x_pc.eq(x.sink.pc),
+                debug.x_ebreak.eq(x.sink.ebreak),
+                debug.x_stall.eq(x.stall),
+                debug.m_branch_taken.eq(m.sink.branch_taken),
+                debug.m_branch_target.eq(m.sink.branch_target),
+                debug.m_mret.eq(m.sink.mret),
+                debug.m_exception.eq(exception.m_raise),
+                debug.m_pc.eq(m.sink.pc),
+                debug.m_valid.eq(m.valid),
+                debug.mepc_r_base.eq(exception.mepc.r.base),
+                debug.mtvec_r_base.eq(exception.mtvec.r.base)
+            ]
+
+            if self.with_trigger:
+                cpu.d.comb += debug.trigger_haltreq.eq(trigger.haltreq)
+            else:
+                cpu.d.comb += debug.trigger_haltreq.eq(Const(0))
+
+            csrf_debug_rp = csrf.read_port()
+            csrf_debug_wp = csrf.write_port()
+            cpu.d.comb += [
+                csrf_debug_rp.addr.eq(debug.csrf_addr),
+                csrf_debug_rp.en.eq(debug.csrf_re),
+                debug.csrf_dat_r.eq(csrf_debug_rp.data),
+                csrf_debug_wp.addr.eq(debug.csrf_addr),
+                csrf_debug_wp.en.eq(debug.csrf_we),
+                csrf_debug_wp.data.eq(debug.csrf_dat_w)
+            ]
+
+            x.stall_on(debug.halt)
+            m.stall_on(debug.dcsr_step & m.valid & ~debug.halt)
+            for s in a, f, d, x:
+                s.kill_on(debug.killall)
+
+            halted = x.stall & ~reduce(or_, (s.valid for s in (m, w)))
+            cpu.d.sync += debug.halted.eq(halted)
+
+            with cpu.If(debug.resumereq):
+                with cpu.If(~debug.dbus_busy):
+                    cpu.d.comb += debug.resumeack.eq(1)
+                    cpu.d.sync += a.source.pc.eq(debug.dpc_value - 4)
+
+        if self.with_trigger:
+            cpu.d.comb += [
+                trigger.x_pc.eq(x.sink.pc),
+                trigger.x_valid.eq(x.valid),
+            ]
+
+        if self.with_rvfi:
+            cpu.d.comb += [
+                rvficon.d_insn.eq(decoder.instruction),
+                rvficon.d_rs1_addr.eq(Mux(decoder.rs1_re, decoder.rs1, 0)),
+                rvficon.d_rs2_addr.eq(Mux(decoder.rs2_re, decoder.rs2, 0)),
+                rvficon.d_rs1_rdata.eq(Mux(decoder.rs1_re, d_src1, 0)),
+                rvficon.d_rs2_rdata.eq(Mux(decoder.rs2_re, d_src2, 0)),
+                rvficon.d_stall.eq(d.stall),
+                rvficon.x_mem_addr.eq(loadstore.x_addr[2:] << 2),
+                rvficon.x_mem_wmask.eq(Mux(loadstore.x_store, loadstore.x_mask, 0)),
+                rvficon.x_mem_rmask.eq(Mux(loadstore.x_load, loadstore.x_mask, 0)),
+                rvficon.x_mem_wdata.eq(loadstore.x_store_data),
+                rvficon.x_stall.eq(x.stall),
+                rvficon.m_mem_rdata.eq(loadstore.m_load_data),
+                rvficon.m_fetch_misaligned.eq(exception.m_fetch_misaligned),
+                rvficon.m_illegal_insn.eq(m.sink.illegal),
+                rvficon.m_load_misaligned.eq(exception.m_load_misaligned),
+                rvficon.m_store_misaligned.eq(exception.m_store_misaligned),
+                rvficon.m_exception.eq(exception.m_raise),
+                rvficon.m_mret.eq(m.sink.mret),
+                rvficon.m_branch_taken.eq(m.sink.branch_taken),
+                rvficon.m_branch_target.eq(m.sink.branch_target),
+                rvficon.m_pc_rdata.eq(m.sink.pc),
+                rvficon.m_stall.eq(m.stall),
+                rvficon.m_valid.eq(m.valid),
+                rvficon.w_rd_addr.eq(Mux(gprf_wp.en, gprf_wp.addr, 0)),
+                rvficon.w_rd_wdata.eq(Mux(gprf_wp.en, gprf_wp.data, 0)),
+                rvficon.mtvec_r_base.eq(exception.mtvec.r.base),
+                rvficon.mepc_r_value.eq(exception.mepc.r),
+                rvficon.rvfi.connect(self.rvfi)
+            ]
+
+        # pipeline registers
+
+        # A/F
+        with cpu.If(~a.stall):
+            cpu.d.sync += a.source.pc.eq(fetch.a_pc)
+
+        # F/D
+        with cpu.If(~f.stall):
+            cpu.d.sync += [
+                f.source.pc.eq(f.sink.pc),
+                f.source.instruction.eq(fetch.f_instruction),
+                f.source.fetch_error.eq(fetch.f_fetch_error),
+                f.source.fetch_badaddr.eq(fetch.f_badaddr)
+            ]
+
+        # D/X
+        with cpu.If(~d.stall):
+            cpu.d.sync += [
+                d.source.pc.eq(d.sink.pc),
+                d.source.instruction.eq(d.sink.instruction),
+                d.source.fetch_error.eq(d.sink.fetch_error),
+                d.source.fetch_badaddr.eq(d.sink.fetch_badaddr),
+                d.source.illegal.eq(decoder.illegal),
+                d.source.rd.eq(decoder.rd),
+                d.source.rs1.eq(decoder.rs1),
+                d.source.rd_we.eq(decoder.rd_we),
+                d.source.rs1_re.eq(decoder.rs1_re),
+                d.source.immediate.eq(decoder.immediate),
+                d.source.bypass_x.eq(decoder.bypass_x),
+                d.source.bypass_m.eq(decoder.bypass_m),
+                d.source.funct3.eq(decoder.funct3),
+                d.source.load.eq(decoder.load),
+                d.source.store.eq(decoder.store),
+                d.source.adder.eq(decoder.adder),
+                d.source.adder_sub.eq(decoder.adder_sub),
+                d.source.compare.eq(decoder.compare),
+                d.source.logic.eq(decoder.logic),
+                d.source.shift.eq(decoder.shift),
+                d.source.direction.eq(decoder.direction),
+                d.source.sext.eq(decoder.sext),
+                d.source.jump.eq(decoder.jump),
+                d.source.branch.eq(decoder.branch),
+                d.source.fence_i.eq(decoder.fence_i),
+                d.source.csr.eq(decoder.csr),
+                d.source.csr_adr.eq(decoder.immediate),
+                d.source.csr_we.eq(decoder.csr_we),
+                d.source.ecall.eq(decoder.ecall),
+                d.source.ebreak.eq(decoder.ebreak),
+                d.source.mret.eq(decoder.mret),
+                d.source.src1.eq(d_src1),
+                d.source.src2.eq(d_src2),
+                d.source.branch_predict_taken.eq(predict.d_branch_taken & ~predict.d_fetch_misaligned),
+                d.source.branch_target.eq(predict.d_branch_target)
+            ]
+            if self.with_muldiv:
+                cpu.d.sync += [
+                    d.source.multiply.eq(decoder.multiply),
+                    d.source.divide.eq(decoder.divide)
+                ]
+
+        # X/M
+        with cpu.If(~x.stall):
+            cpu.d.sync += [
+                x.source.pc.eq(x.sink.pc),
+                x.source.instruction.eq(x.sink.instruction),
+                x.source.fetch_error.eq(x.sink.fetch_error),
+                x.source.fetch_badaddr.eq(x.sink.fetch_badaddr),
+                x.source.illegal.eq(x.sink.illegal),
+                x.source.loadstore_misaligned.eq(data_sel.x_misaligned),
+                x.source.ecall.eq(x.sink.ecall),
+                x.source.ebreak.eq(x.sink.ebreak),
+                x.source.rd.eq(x.sink.rd),
+                x.source.rd_we.eq(x.sink.rd_we),
+                x.source.bypass_m.eq(x.sink.bypass_m | x.sink.bypass_x),
+                x.source.funct3.eq(x.sink.funct3),
+                x.source.load.eq(x.sink.load),
+                x.source.store.eq(x.sink.store),
+                x.source.store_data.eq(loadstore.x_store_data),
+                x.source.compare.eq(x.sink.compare),
+                x.source.shift.eq(x.sink.shift),
+                x.source.mret.eq(x.sink.mret),
+                x.source.condition_met.eq(compare.condition_met),
+                x.source.branch_taken.eq(x.sink.jump | x.sink.branch & compare.condition_met),
+                x.source.branch_target.eq(Mux(x.sink.jump & x.sink.rs1_re, adder.result[1:] << 1, x.sink.branch_target)),
+                x.source.branch_predict_taken.eq(x.sink.branch_predict_taken),
+                x.source.csr.eq(x.sink.csr),
+                x.source.csr_adr.eq(x.sink.csr_adr),
+                x.source.csr_we.eq(x.sink.csr_we),
+                x.source.csr_result.eq(x_csr_result),
+                x.source.result.eq(x_result)
+            ]
+            if self.with_muldiv:
+                cpu.d.sync += [
+                    x.source.multiply.eq(x.sink.multiply),
+                    x.source.divide.eq(x.sink.divide)
+                ]
+
+        # M/W
+        with cpu.If(~m.stall):
+            cpu.d.sync += [
+                m.source.pc.eq(m.sink.pc),
+                m.source.rd.eq(m.sink.rd),
+                m.source.load.eq(m.sink.load),
+                m.source.funct3.eq(m.sink.funct3),
+                m.source.load_data.eq(loadstore.m_load_data),
+                m.source.rd_we.eq(m.sink.rd_we),
+                m.source.result.eq(m_result),
+                m.source.exception.eq(exception.m_raise)
+            ]
+            if self.with_muldiv:
+                cpu.d.sync += [
+                    m.source.multiply.eq(m.sink.multiply)
+                ]
+
+        return cpu
diff --git a/src/soc/minerva/csr.py b/src/soc/minerva/csr.py
new file mode 100644 (file)
index 0000000..56b1049
--- /dev/null
@@ -0,0 +1,94 @@
+from enum import Enum
+from collections import OrderedDict
+
+from nmigen import *
+from nmigen.hdl.rec import *
+from nmigen.utils import bits_for
+
+
+__all__ = ["CSRAccess", "CSR", "AutoCSR", "CSRFile"]
+
+
+CSRAccess = Enum("CSRAccess", ("WIRI", "WPRI", "WLRL", "WARL"))
+
+
+class CSR():
+    def __init__(self, addr, description, name):
+        fields = []
+        mask = 0
+        offset = 0
+        for name, shape, access in description:
+            if isinstance(shape, int):
+                shape = shape, False
+            nbits, signed = shape
+            fields.append((name, shape))
+            if access in {CSRAccess.WLRL, CSRAccess.WARL}:
+                mask |= ((1 << nbits) - 1) << offset
+            offset += nbits
+
+        self.addr = addr
+        self.rmask = self.wmask = Const(mask)
+        self.r = Record(fields)
+        self.w = Record(fields)
+        self.re = Signal()
+        self.we = Signal()
+
+
+class AutoCSR():
+    def iter_csrs(self):
+        for v in vars(self).values():
+            if isinstance(v, CSR):
+                yield v
+            elif hasattr(v, "iter_csrs"):
+                yield from v.iter_csrs()
+
+
+class CSRFile(Elaboratable):
+    def __init__(self, width=32, depth=2**12):
+        self.width = width
+        self.depth = depth
+        self._csr_map = OrderedDict()
+        self._read_ports = []
+        self._write_ports = []
+
+    def add_csrs(self, csrs):
+        for csr in csrs:
+            if not isinstance(csr, CSR):
+                raise TypeError("Object {!r} is not a CSR".format(csr))
+            if csr.addr in self._csr_map:
+                raise ValueError("CSR address 0x{:x} has already been allocated"
+                                 .format(csr.addr))
+            self._csr_map[csr.addr] = csr
+
+    def read_port(self):
+        port = Record([("addr", bits_for(self.depth)), ("en", 1), ("data", self.width)])
+        self._read_ports.append(port)
+        return port
+
+    def write_port(self):
+        port = Record([("addr", bits_for(self.depth)), ("en", 1), ("data", self.width)])
+        self._write_ports.append(port)
+        return port
+
+    def elaborate(self, platform):
+        m = Module()
+
+        for rp in self._read_ports:
+            with m.Switch(rp.addr):
+                for addr, csr in self._csr_map.items():
+                    with m.Case(addr):
+                        m.d.comb += [
+                            csr.re.eq(rp.en),
+                            rp.data.eq(csr.r & csr.rmask)
+                        ]
+
+        for wp in self._write_ports:
+            with m.Switch(wp.addr):
+                for addr, csr in self._csr_map.items():
+                    with m.Case(addr):
+                        m.d.comb += [
+                            csr.we.eq(wp.en),
+                            csr.w.eq(wp.data & csr.wmask)
+                        ]
+
+        return m
diff --git a/src/soc/minerva/isa.py b/src/soc/minerva/isa.py
new file mode 100644 (file)
index 0000000..b821278
--- /dev/null
@@ -0,0 +1,220 @@
+from .csr import *
+
+__all__ = [
+    "Opcode", "Funct3", "Funct7", "Funct12", "CSRIndex", "Cause",
+    "flat_layout", "misa_layout", "mstatus_layout", "mtvec_layout", "mepc_layout",
+    "mip_layout", "mie_layout", "mcause_layout", "dcsr_layout", "tdata1_layout"
+]
+
+class Opcode:
+    LUI       = 0b01101
+    AUIPC     = 0b00101
+    JAL       = 0b11011
+    JALR      = 0b11001
+    BRANCH    = 0b11000
+    LOAD      = 0b00000
+    STORE     = 0b01000
+    OP_IMM_32 = 0b00100
+    OP_32     = 0b01100
+    MISC_MEM  = 0b00011
+    SYSTEM    = 0b11100
+
+
+class Funct3:
+    BEQ  = B  = ADD  = FENCE  = PRIV   = MUL    = 0b000
+    BNE  = H  = SLL  = FENCEI = CSRRW  = MULH   = 0b001
+    _    = W  = SLT  = _      = CSRRS  = MULHSU = 0b010
+    _    = _  = SLTU = _      = CSRRC  = MULHU  = 0b011
+    BLT  = BU = XOR  = _      = _      = DIV    = 0b100
+    BGE  = HU = SR   = _      = CSRRWI = DIVU   = 0b101
+    BLTU = _  = OR   = _      = CSRRSI = REM    = 0b110
+    BGEU = _  = AND  = _      = CSRRCI = REMU   = 0b111
+
+
+class Funct7:
+    SRL = ADD = 0b0000000
+    MULDIV    = 0b0000001
+    SRA = SUB = 0b0100000
+
+
+class Funct12:
+    ECALL  = 0b000000000000
+    EBREAK = 0b000000000001
+    MRET   = 0b001100000010
+    WFI    = 0b000100000101
+
+
+class CSRIndex:
+    MVENDORID   = 0xF11
+    MARCHID     = 0xF12
+    MIMPID      = 0xF13
+    MHARTID     = 0xF14
+    MSTATUS     = 0x300
+    MISA        = 0x301
+    MEDELEG     = 0x302
+    MIDELEG     = 0x303
+    MIE         = 0x304
+    MTVEC       = 0x305
+    MCOUTEREN   = 0x306
+    MSCRATCH    = 0x340
+    MEPC        = 0x341
+    MCAUSE      = 0x342
+    MTVAL       = 0x343
+    MIP         = 0x344
+    # ยตarch specific
+    IRQ_MASK    = 0x330
+    IRQ_PENDING = 0x360
+    # trigger module
+    TSELECT     = 0x7a0
+    TDATA1      = 0x7a1
+    TDATA2      = 0x7a2
+    TDATA3      = 0x7a3
+    TINFO       = 0x7a4
+    MCONTEXT    = 0x7a8
+    # debug module
+    DCSR        = 0x7b0
+    DPC         = 0x7b1
+
+
+class Cause:
+    FETCH_MISALIGNED     = 0
+    FETCH_ACCESS_FAULT   = 1
+    ILLEGAL_INSTRUCTION  = 2
+    BREAKPOINT           = 3
+    LOAD_MISALIGNED      = 4
+    LOAD_ACCESS_FAULT    = 5
+    STORE_MISALIGNED     = 6
+    STORE_ACCESS_FAULT   = 7
+    ECALL_FROM_U         = 8
+    ECALL_FROM_S         = 9
+    ECALL_FROM_M         = 11
+    FETCH_PAGE_FAULT     = 12
+    LOAD_PAGE_FAULT      = 13
+    STORE_PAGE_FAULT     = 15
+    # interrupts
+    U_SOFTWARE_INTERRUPT = 0
+    S_SOFTWARE_INTERRUPT = 1
+    M_SOFTWARE_INTERRUPT = 3
+    U_TIMER_INTERRUPT    = 4
+    S_TIMER_INTERRUPT    = 5
+    M_TIMER_INTERRUPT    = 7
+    U_EXTERNAL_INTERRUPT = 8
+    S_EXTERNAL_INTERRUPT = 9
+    M_EXTERNAL_INTERRUPT = 11
+
+
+# CSR layouts
+
+flat_layout = [
+    ("value", 32, CSRAccess.WARL)
+]
+
+
+misa_layout = [
+    ("extensions", 26, CSRAccess.WARL),
+    ("wiri0",       4, CSRAccess.WIRI),
+    ("mxl",         2, CSRAccess.WARL)
+]
+
+
+mstatus_layout = [
+    ("uie",   1, CSRAccess.WARL), # User Interrupt Enable
+    ("sie",   1, CSRAccess.WARL), # Supervisor Interrupt Enable
+    ("wpri0", 1, CSRAccess.WPRI),
+    ("mie",   1, CSRAccess.WARL), # Machine Interrupt Enable
+    ("upie",  1, CSRAccess.WARL), # User Previous Interrupt Enable
+    ("spie",  1, CSRAccess.WARL), # Supervisor Previous Interrupt Enable
+    ("wpri1", 1, CSRAccess.WPRI),
+    ("mpie",  1, CSRAccess.WARL), # Machine Previous Interrupt Enable
+    ("spp",   1, CSRAccess.WARL), # Supervisor Previous Privilege
+    ("wpri2", 2, CSRAccess.WPRI),
+    ("mpp",   2, CSRAccess.WARL), # Machine Previous Privilege
+    ("fs",    2, CSRAccess.WARL), # FPU Status
+    ("xs",    2, CSRAccess.WARL), # user-mode eXtensions Status
+    ("mprv",  1, CSRAccess.WARL), # Modify PRiVilege
+    ("sum",   1, CSRAccess.WARL), # Supervisor User Memory access
+    ("mxr",   1, CSRAccess.WARL), # Make eXecutable Readable
+    ("tvm",   1, CSRAccess.WARL), # Trap Virtual Memory
+    ("tw",    1, CSRAccess.WARL), # Timeout Wait
+    ("tsr",   1, CSRAccess.WARL), # Trap SRET
+    ("wpri3", 8, CSRAccess.WPRI),
+    ("sd",    1, CSRAccess.WARL)  # State Dirty (set if XS or FS are set to dirty)
+]
+
+
+mtvec_layout = [
+    ("mode",  2, CSRAccess.WARL),
+    ("base", 30, CSRAccess.WARL)
+]
+
+
+mepc_layout = [
+    ("zero",  2, CSRAccess.WIRI),  # 16-bit instructions are not supported
+    ("base", 30, CSRAccess.WARL)
+]
+
+
+mip_layout = [
+    ("usip",   1, CSRAccess.WARL),
+    ("ssip",   1, CSRAccess.WARL),
+    ("wiri0",  1, CSRAccess.WIRI),
+    ("msip",   1, CSRAccess.WARL),
+    ("utip",   1, CSRAccess.WARL),
+    ("stip",   1, CSRAccess.WARL),
+    ("wiri1",  1, CSRAccess.WIRI),
+    ("mtip",   1, CSRAccess.WARL),
+    ("ueip",   1, CSRAccess.WARL),
+    ("seip",   1, CSRAccess.WARL),
+    ("wiri2",  1, CSRAccess.WIRI),
+    ("meip",   1, CSRAccess.WARL),
+    ("wiri3", 20, CSRAccess.WIRI)
+]
+
+
+mie_layout = [
+    ("usie",   1, CSRAccess.WARL),
+    ("ssie",   1, CSRAccess.WARL),
+    ("wpri0",  1, CSRAccess.WPRI),
+    ("msie",   1, CSRAccess.WARL),
+    ("utie",   1, CSRAccess.WARL),
+    ("stie",   1, CSRAccess.WARL),
+    ("wpri1",  1, CSRAccess.WPRI),
+    ("mtie",   1, CSRAccess.WARL),
+    ("ueie",   1, CSRAccess.WARL),
+    ("seie",   1, CSRAccess.WARL),
+    ("wpri2",  1, CSRAccess.WPRI),
+    ("meie",   1, CSRAccess.WARL),
+    ("wpri3", 20, CSRAccess.WPRI)
+]
+
+
+mcause_layout = [
+    ("ecode",    31, CSRAccess.WARL),
+    ("interrupt", 1, CSRAccess.WARL)
+]
+
+
+dcsr_layout = [
+    ("prv",        2, CSRAccess.WARL), # Privilege level before Debug Mode was entered
+    ("step",       1, CSRAccess.WARL), # Execute a single instruction and re-enter Debug Mode
+    ("nmip",       1, CSRAccess.WLRL), # A non-maskable interrupt is pending
+    ("mprven",     1, CSRAccess.WARL), # Use mstatus.mprv in Debug Mode
+    ("zero0",      1, CSRAccess.WPRI),
+    ("cause",      3, CSRAccess.WLRL), # Explains why Debug Mode was entered
+    ("stoptime",   1, CSRAccess.WARL), # Stop timer increment during Debug Mode
+    ("stopcount",  1, CSRAccess.WARL), # Stop counter increment during Debug Mode
+    ("stepie",     1, CSRAccess.WARL), # Enable interrupts during single stepping
+    ("ebreaku",    1, CSRAccess.WARL), # EBREAKs in U-mode enter Debug Mode
+    ("ebreaks",    1, CSRAccess.WARL), # EBREAKs in S-mode enter Debug Mode
+    ("zero1",      1, CSRAccess.WPRI),
+    ("ebreakm",    1, CSRAccess.WARL), # EBREAKs in M-mode enter Debug Mode
+    ("zero2",     12, CSRAccess.WPRI),
+    ("xdebugver",  4, CSRAccess.WLRL)  # External Debug specification version
+]
+
+
+tdata1_layout = [
+    ("data",  27, CSRAccess.WARL),
+    ("dmode",  1, CSRAccess.WARL),
+    ("type",   4, CSRAccess.WARL)
+]
diff --git a/src/soc/minerva/stage.py b/src/soc/minerva/stage.py
new file mode 100644 (file)
index 0000000..33d941d
--- /dev/null
@@ -0,0 +1,100 @@
+from functools import reduce
+from operator import or_
+
+from nmigen import *
+from nmigen.hdl.rec import *
+
+
+__all__ = ["Stage"]
+
+
+def _make_m2s(layout):
+    r = []
+    for f in layout:
+        if isinstance(f[1], (int, tuple)):
+            r.append((f[0], f[1], DIR_FANOUT))
+        else:
+            r.append((f[0], _make_m2s(f[1])))
+    return r
+
+
+class _EndpointDescription:
+    def __init__(self, payload_layout):
+        self.payload_layout = payload_layout
+
+    def get_full_layout(self):
+        reserved = {"valid", "stall", "kill"}
+        attributed = set()
+        for f in self.payload_layout:
+            if f[0] in attributed:
+                raise ValueError(f[0] + " already attributed in payload layout")
+            if f[0] in reserved:
+                raise ValueError(f[0] + " cannot be used in endpoint layout")
+            attributed.add(f[0])
+
+        full_layout = [
+            ("valid", 1, DIR_FANOUT),
+            ("stall", 1, DIR_FANIN),
+            ("kill",  1, DIR_FANOUT),
+            ("payload", _make_m2s(self.payload_layout))
+        ]
+        return full_layout
+
+
+class _Endpoint(Record):
+    def __init__(self, layout):
+        self.description = _EndpointDescription(layout)
+        super().__init__(self.description.get_full_layout())
+
+    def __getattr__(self, name):
+        try:
+            return super().__getattr__(name)
+        except AttributeError:
+            return self.fields["payload"][name]
+
+
+class Stage(Elaboratable):
+    def __init__(self, sink_layout, source_layout):
+        self.kill = Signal()
+        self.stall = Signal()
+        self.valid = Signal()
+
+        if sink_layout is None and source_layout is None:
+            raise ValueError
+        if sink_layout is not None:
+            self.sink = _Endpoint(sink_layout)
+        if source_layout is not None:
+            self.source = _Endpoint(source_layout)
+
+        self._kill_sources = []
+        self._stall_sources = []
+
+    def kill_on(self, cond):
+        self._kill_sources.append(cond)
+
+    def stall_on(self, cond):
+        self._stall_sources.append(cond)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        if hasattr(self, "sink"):
+            m.d.comb += [
+                self.valid.eq(self.sink.valid & ~self.sink.kill),
+                self.sink.stall.eq(self.stall)
+            ]
+
+        if hasattr(self, "source"):
+            with m.If(~self.stall):
+                m.d.sync += self.source.valid.eq(self.valid)
+            with m.Elif(~self.source.stall | self.kill):
+                m.d.sync += self.source.valid.eq(0)
+            self.stall_on(self.source.stall)
+            m.d.comb += [
+                self.source.kill.eq(self.kill),
+                self.kill.eq(reduce(or_, self._kill_sources, 0))
+            ]
+
+        m.d.comb += self.stall.eq(reduce(or_, self._stall_sources, 0))
+
+        return m
diff --git a/src/soc/minerva/test/__init__.py b/src/soc/minerva/test/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/soc/minerva/test/test_cache.py b/src/soc/minerva/test/test_cache.py
new file mode 100644 (file)
index 0000000..e52f034
--- /dev/null
@@ -0,0 +1,91 @@
+from nmigen import *
+from nmigen.utils import log2_int
+from nmigen.test.utils import *
+from nmigen.asserts import *
+
+from ..cache import L1Cache
+
+
+class L1CacheSpec(Elaboratable):
+    """
+    A cache read at a given address must either:
+    - miss and start a cache refill;
+    - hit and return the last refilled value for that address.
+    """
+    def __init__(self, cache):
+        self.cache = cache
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.dut = cache = self.cache
+
+        addr  = AnyConst(cache.s1_addr.shape())
+        s1_re = AnySeq(1)
+
+        with m.If(~cache.s1_stall):
+            m.d.sync += [
+                cache.s2_addr.eq(cache.s1_addr),
+                cache.s2_re.eq(s1_re),
+                cache.s2_valid.eq(cache.s1_valid)
+            ]
+
+        m.d.comb += Assume(cache.s1_valid.implies(cache.s1_addr == addr))
+        with m.If(cache.s2_re & cache.s2_miss & cache.s2_valid):
+            m.d.comb += Assume(cache.s1_stall)
+
+        spec_s2_rdata = Signal(32)
+        spec_bus_re   = Signal()
+        spec_bus_addr = Record.like(cache.bus_addr)
+        spec_bus_last = Signal()
+        last_offset   = Signal.like(cache.s2_addr.offset)
+
+        with m.If(~Past(cache.s1_stall)):
+            with m.If(cache.s2_re & cache.s2_miss & cache.s2_valid):
+                m.d.sync += [
+                    spec_bus_re.eq(1),
+                    spec_bus_addr.eq(cache.s2_addr),
+                    last_offset.eq(cache.s2_addr.offset - 1)
+                ]
+
+        with m.If(cache.bus_re):
+            with m.If(cache.bus_valid):
+                # Lines are refilled with an incremental burst that starts at the missed address
+                # and wraps around the offset bits.
+                m.d.sync += spec_bus_addr.offset.eq(spec_bus_addr.offset + 1)
+                with m.If((cache.bus_addr == cache.s2_addr) & ~cache.bus_error):
+                    m.d.sync += spec_s2_rdata.eq(cache.bus_rdata)
+            # A burst ends when all words in the line have been refilled, or an error occured.
+            m.d.comb += spec_bus_last.eq(cache.bus_addr.offset == last_offset)
+            with m.If(cache.bus_valid & cache.bus_last | cache.bus_error):
+                m.d.sync += spec_bus_re.eq(0)
+
+        with m.If(cache.s2_re & cache.s2_valid & ~cache.s2_miss):
+            m.d.comb += Assert(cache.s2_rdata == spec_s2_rdata)
+
+        m.d.comb += Assert(cache.bus_re == spec_bus_re)
+        with m.If(cache.bus_re):
+            m.d.comb += [
+                Assert(cache.bus_addr == spec_bus_addr),
+                Assert(cache.bus_last == spec_bus_last)
+            ]
+
+        with m.If(Initial()):
+            m.d.comb += [
+                Assume(cache.s1_stall),
+                Assume(~cache.s2_valid),
+                Assume(~cache.bus_re),
+                Assume(~spec_bus_re)
+            ]
+
+        return m
+
+
+class L1CacheTestCase(FHDLTestCase):
+    def check(self, cache):
+        self.assertFormal(L1CacheSpec(cache), mode="bmc", depth=10)
+
+    def test_direct_mapped(self):
+        self.check(L1Cache(nways=1, nlines=2, nwords=4, base=0, limit=1024))
+
+    def test_2_ways(self):
+        self.check(L1Cache(nways=2, nlines=2, nwords=4, base=0, limit=1024))
diff --git a/src/soc/minerva/test/test_units_divider.py b/src/soc/minerva/test/test_units_divider.py
new file mode 100644 (file)
index 0000000..5f34beb
--- /dev/null
@@ -0,0 +1,163 @@
+from nmigen import *
+from nmigen.back.pysim import *
+from nmigen.test.utils import *
+
+from ..units.divider import *
+from ..isa import Funct3
+
+
+def test_op(funct3, src1, src2, result):
+    def test(self):
+        with Simulator(self.dut) as sim:
+            def process():
+                yield self.dut.x_op.eq(funct3)
+                yield self.dut.x_src1.eq(src1)
+                yield self.dut.x_src2.eq(src2)
+                yield self.dut.x_valid.eq(1)
+                yield self.dut.x_stall.eq(0)
+                yield Tick()
+                yield self.dut.x_valid.eq(0)
+                yield Tick()
+                while (yield self.dut.m_busy):
+                    yield Tick()
+                self.assertEqual((yield self.dut.m_result), result)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+            sim.run()
+    return test
+
+
+class DividerTestCase(FHDLTestCase):
+    def setUp(self):
+        self.dut = Divider()
+
+    # Test cases are taken from the riscv-compliance testbench:
+    # https://github.com/riscv/riscv-compliance/tree/master/riscv-test-suite/rv32im
+
+    # DIV ------------------------------------------------------------------------
+
+    test_div_0   = test_op(Funct3.DIV,  0x00000000, 0x00000000, result=0xffffffff)
+    test_div_1   = test_op(Funct3.DIV,  0x00000000, 0x00000001, result=0x00000000)
+    test_div_2   = test_op(Funct3.DIV,  0x00000000, 0xffffffff, result=0x00000000)
+    test_div_3   = test_op(Funct3.DIV,  0x00000000, 0x7fffffff, result=0x00000000)
+    test_div_4   = test_op(Funct3.DIV,  0x00000000, 0x80000000, result=0x00000000)
+
+    test_div_5   = test_op(Funct3.DIV,  0x00000001, 0x00000000, result=0xffffffff)
+    test_div_6   = test_op(Funct3.DIV,  0x00000001, 0x00000001, result=0x00000001)
+    test_div_7   = test_op(Funct3.DIV,  0x00000001, 0xffffffff, result=0xffffffff)
+    test_div_8   = test_op(Funct3.DIV,  0x00000001, 0x7fffffff, result=0x00000000)
+    test_div_9   = test_op(Funct3.DIV,  0x00000001, 0x80000000, result=0x00000000)
+
+    test_div_10  = test_op(Funct3.DIV,  0xffffffff, 0x00000000, result=0xffffffff)
+    test_div_11  = test_op(Funct3.DIV,  0xffffffff, 0x00000001, result=0xffffffff)
+    test_div_12  = test_op(Funct3.DIV,  0xffffffff, 0xffffffff, result=0x00000001)
+    test_div_13  = test_op(Funct3.DIV,  0xffffffff, 0x7fffffff, result=0x00000000)
+    test_div_14  = test_op(Funct3.DIV,  0xffffffff, 0x80000000, result=0x00000000)
+
+    test_div_15  = test_op(Funct3.DIV,  0x7fffffff, 0x00000000, result=0xffffffff)
+    test_div_16  = test_op(Funct3.DIV,  0x7fffffff, 0x00000001, result=0x7fffffff)
+    test_div_17  = test_op(Funct3.DIV,  0x7fffffff, 0xffffffff, result=0x80000001)
+    test_div_18  = test_op(Funct3.DIV,  0x7fffffff, 0x7fffffff, result=0x00000001)
+    test_div_19  = test_op(Funct3.DIV,  0x7fffffff, 0x80000000, result=0x00000000)
+
+    test_div_20  = test_op(Funct3.DIV,  0x80000000, 0x00000000, result=0xffffffff)
+    test_div_21  = test_op(Funct3.DIV,  0x80000000, 0x00000001, result=0x80000000)
+    test_div_22  = test_op(Funct3.DIV,  0x80000000, 0xffffffff, result=0x80000000)
+    test_div_23  = test_op(Funct3.DIV,  0x80000000, 0x7fffffff, result=0xffffffff)
+    test_div_24  = test_op(Funct3.DIV,  0x80000000, 0x80000000, result=0x00000001)
+
+    # DIVU -----------------------------------------------------------------------
+
+    test_divu_0  = test_op(Funct3.DIVU, 0x00000000, 0x00000000, result=0xffffffff)
+    test_divu_1  = test_op(Funct3.DIVU, 0x00000000, 0x00000001, result=0x00000000)
+    test_divu_2  = test_op(Funct3.DIVU, 0x00000000, 0xffffffff, result=0x00000000)
+    test_divu_3  = test_op(Funct3.DIVU, 0x00000000, 0x7fffffff, result=0x00000000)
+    test_divu_4  = test_op(Funct3.DIVU, 0x00000000, 0x80000000, result=0x00000000)
+
+    test_divu_5  = test_op(Funct3.DIVU, 0x00000001, 0x00000000, result=0xffffffff)
+    test_divu_6  = test_op(Funct3.DIVU, 0x00000001, 0x00000001, result=0x00000001)
+    test_divu_7  = test_op(Funct3.DIVU, 0x00000001, 0xffffffff, result=0x00000000)
+    test_divu_8  = test_op(Funct3.DIVU, 0x00000001, 0x7fffffff, result=0x00000000)
+    test_divu_9  = test_op(Funct3.DIVU, 0x00000001, 0x80000000, result=0x00000000)
+
+    test_divu_10 = test_op(Funct3.DIVU, 0xffffffff, 0x00000000, result=0xffffffff)
+    test_divu_11 = test_op(Funct3.DIVU, 0xffffffff, 0x00000001, result=0xffffffff)
+    test_divu_12 = test_op(Funct3.DIVU, 0xffffffff, 0xffffffff, result=0x00000001)
+    test_divu_13 = test_op(Funct3.DIVU, 0xffffffff, 0x7fffffff, result=0x00000002)
+    test_divu_14 = test_op(Funct3.DIVU, 0xffffffff, 0x80000000, result=0x00000001)
+
+    test_divu_15 = test_op(Funct3.DIVU, 0x7fffffff, 0x00000000, result=0xffffffff)
+    test_divu_16 = test_op(Funct3.DIVU, 0x7fffffff, 0x00000001, result=0x7fffffff)
+    test_divu_17 = test_op(Funct3.DIVU, 0x7fffffff, 0xffffffff, result=0x00000000)
+    test_divu_18 = test_op(Funct3.DIVU, 0x7fffffff, 0x7fffffff, result=0x00000001)
+    test_divu_19 = test_op(Funct3.DIVU, 0x7fffffff, 0x80000000, result=0x00000000)
+
+    test_divu_20 = test_op(Funct3.DIVU, 0x80000000, 0x00000000, result=0xffffffff)
+    test_divu_21 = test_op(Funct3.DIVU, 0x80000000, 0x00000001, result=0x80000000)
+    test_divu_22 = test_op(Funct3.DIVU, 0x80000000, 0xffffffff, result=0x00000000)
+    test_divu_23 = test_op(Funct3.DIVU, 0x80000000, 0x7fffffff, result=0x00000001)
+    test_divu_24 = test_op(Funct3.DIVU, 0x80000000, 0x80000000, result=0x00000001)
+
+    # REM ------------------------------------------------------------------------
+
+    test_rem_0   = test_op(Funct3.REM,  0x00000000, 0x00000000, result=0x00000000)
+    test_rem_1   = test_op(Funct3.REM,  0x00000000, 0x00000001, result=0x00000000)
+    test_rem_2   = test_op(Funct3.REM,  0x00000000, 0xffffffff, result=0x00000000)
+    test_rem_3   = test_op(Funct3.REM,  0x00000000, 0x7fffffff, result=0x00000000)
+    test_rem_4   = test_op(Funct3.REM,  0x00000000, 0x80000000, result=0x00000000)
+
+    test_rem_5   = test_op(Funct3.REM,  0x00000001, 0x00000000, result=0x00000001)
+    test_rem_6   = test_op(Funct3.REM,  0x00000001, 0x00000001, result=0x00000000)
+    test_rem_7   = test_op(Funct3.REM,  0x00000001, 0xffffffff, result=0x00000000)
+    test_rem_8   = test_op(Funct3.REM,  0x00000001, 0x7fffffff, result=0x00000001)
+    test_rem_9   = test_op(Funct3.REM,  0x00000001, 0x80000000, result=0x00000001)
+
+    test_rem_10  = test_op(Funct3.REM,  0xffffffff, 0x00000000, result=0xffffffff)
+    test_rem_11  = test_op(Funct3.REM,  0xffffffff, 0x00000001, result=0x00000000)
+    test_rem_12  = test_op(Funct3.REM,  0xffffffff, 0xffffffff, result=0x00000000)
+    test_rem_13  = test_op(Funct3.REM,  0xffffffff, 0x7fffffff, result=0xffffffff)
+    test_rem_14  = test_op(Funct3.REM,  0xffffffff, 0x80000000, result=0xffffffff)
+
+    test_rem_15  = test_op(Funct3.REM,  0x7fffffff, 0x00000000, result=0x7fffffff)
+    test_rem_16  = test_op(Funct3.REM,  0x7fffffff, 0x00000001, result=0x00000000)
+    test_rem_17  = test_op(Funct3.REM,  0x7fffffff, 0xffffffff, result=0x00000000)
+    test_rem_18  = test_op(Funct3.REM,  0x7fffffff, 0x7fffffff, result=0x00000000)
+    test_rem_19  = test_op(Funct3.REM,  0x7fffffff, 0x80000000, result=0x7fffffff)
+
+    test_rem_20  = test_op(Funct3.REM,  0x80000000, 0x00000000, result=0x80000000)
+    test_rem_21  = test_op(Funct3.REM,  0x80000000, 0x00000001, result=0x00000000)
+    test_rem_22  = test_op(Funct3.REM,  0x80000000, 0xffffffff, result=0x00000000)
+    test_rem_23  = test_op(Funct3.REM,  0x80000000, 0x7fffffff, result=0xffffffff)
+    test_rem_24  = test_op(Funct3.REM,  0x80000000, 0x80000000, result=0x00000000)
+
+    # REMU -----------------------------------------------------------------------
+
+    test_remu_0  = test_op(Funct3.REMU, 0x00000000, 0x00000000, result=0x00000000)
+    test_remu_1  = test_op(Funct3.REMU, 0x00000000, 0x00000001, result=0x00000000)
+    test_remu_2  = test_op(Funct3.REMU, 0x00000000, 0xffffffff, result=0x00000000)
+    test_remu_3  = test_op(Funct3.REMU, 0x00000000, 0x7fffffff, result=0x00000000)
+    test_remu_4  = test_op(Funct3.REMU, 0x00000000, 0x80000000, result=0x00000000)
+
+    test_remu_5  = test_op(Funct3.REMU, 0x00000001, 0x00000000, result=0x00000001)
+    test_remu_6  = test_op(Funct3.REMU, 0x00000001, 0x00000001, result=0x00000000)
+    test_remu_7  = test_op(Funct3.REMU, 0x00000001, 0xffffffff, result=0x00000001)
+    test_remu_8  = test_op(Funct3.REMU, 0x00000001, 0x7fffffff, result=0x00000001)
+    test_remu_9  = test_op(Funct3.REMU, 0x00000001, 0x80000000, result=0x00000001)
+
+    test_remu_10 = test_op(Funct3.REMU, 0xffffffff, 0x00000000, result=0xffffffff)
+    test_remu_11 = test_op(Funct3.REMU, 0xffffffff, 0x00000001, result=0x00000000)
+    test_remu_12 = test_op(Funct3.REMU, 0xffffffff, 0xffffffff, result=0x00000000)
+    test_remu_13 = test_op(Funct3.REMU, 0xffffffff, 0x7fffffff, result=0x00000001)
+    test_remu_14 = test_op(Funct3.REMU, 0xffffffff, 0x80000000, result=0x7fffffff)
+
+    test_remu_15 = test_op(Funct3.REMU, 0x7fffffff, 0x00000000, result=0x7fffffff)
+    test_remu_16 = test_op(Funct3.REMU, 0x7fffffff, 0x00000001, result=0x00000000)
+    test_remu_17 = test_op(Funct3.REMU, 0x7fffffff, 0xffffffff, result=0x7fffffff)
+    test_remu_18 = test_op(Funct3.REMU, 0x7fffffff, 0x7fffffff, result=0x00000000)
+    test_remu_19 = test_op(Funct3.REMU, 0x7fffffff, 0x80000000, result=0x7fffffff)
+
+    test_remu_20 = test_op(Funct3.REMU, 0x80000000, 0x00000000, result=0x80000000)
+    test_remu_21 = test_op(Funct3.REMU, 0x80000000, 0x00000001, result=0x00000000)
+    test_remu_22 = test_op(Funct3.REMU, 0x80000000, 0xffffffff, result=0x80000000)
+    test_remu_23 = test_op(Funct3.REMU, 0x80000000, 0x7fffffff, result=0x00000001)
+    test_remu_24 = test_op(Funct3.REMU, 0x80000000, 0x80000000, result=0x00000000)
diff --git a/src/soc/minerva/test/test_units_multiplier.py b/src/soc/minerva/test/test_units_multiplier.py
new file mode 100644 (file)
index 0000000..e4b2963
--- /dev/null
@@ -0,0 +1,161 @@
+from nmigen import *
+from nmigen.back.pysim import *
+from nmigen.test.utils import *
+
+from ..units.multiplier import *
+from ..isa import Funct3
+
+
+def test_op(funct3, src1, src2, result):
+    def test(self):
+        with Simulator(self.dut) as sim:
+            def process():
+                yield self.dut.x_op.eq(funct3)
+                yield self.dut.x_src1.eq(src1)
+                yield self.dut.x_src2.eq(src2)
+                yield self.dut.x_stall.eq(0)
+                yield Tick()
+                yield self.dut.m_stall.eq(0)
+                yield Tick()
+                yield Tick()
+                self.assertEqual((yield self.dut.w_result), result)
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+            sim.run()
+    return test
+
+
+class MultiplierTestCase(FHDLTestCase):
+    def setUp(self):
+        self.dut = Multiplier()
+
+    # Test cases are taken from the riscv-compliance testbench:
+    # https://github.com/riscv/riscv-compliance/tree/master/riscv-test-suite/rv32im
+
+    # MUL ----------------------------------------------------------------------------
+
+    test_mul_0     = test_op(Funct3.MUL,    0x00000000, 0x00000000, result=0x00000000)
+    test_mul_1     = test_op(Funct3.MUL,    0x00000000, 0x00000001, result=0x00000000)
+    test_mul_2     = test_op(Funct3.MUL,    0x00000000, 0xffffffff, result=0x00000000)
+    test_mul_3     = test_op(Funct3.MUL,    0x00000000, 0x7fffffff, result=0x00000000)
+    test_mul_4     = test_op(Funct3.MUL,    0x00000000, 0x80000000, result=0x00000000)
+
+    test_mul_5     = test_op(Funct3.MUL,    0x00000001, 0x00000000, result=0x00000000)
+    test_mul_6     = test_op(Funct3.MUL,    0x00000001, 0x00000001, result=0x00000001)
+    test_mul_7     = test_op(Funct3.MUL,    0x00000001, 0xffffffff, result=0xffffffff)
+    test_mul_8     = test_op(Funct3.MUL,    0x00000001, 0x7fffffff, result=0x7fffffff)
+    test_mul_9     = test_op(Funct3.MUL,    0x00000001, 0x80000000, result=0x80000000)
+
+    test_mul_10    = test_op(Funct3.MUL,    0xffffffff, 0x00000000, result=0x00000000)
+    test_mul_11    = test_op(Funct3.MUL,    0xffffffff, 0x00000001, result=0xffffffff)
+    test_mul_12    = test_op(Funct3.MUL,    0xffffffff, 0xffffffff, result=0x00000001)
+    test_mul_13    = test_op(Funct3.MUL,    0xffffffff, 0x7fffffff, result=0x80000001)
+    test_mul_14    = test_op(Funct3.MUL,    0xffffffff, 0x80000000, result=0x80000000)
+
+    test_mul_15    = test_op(Funct3.MUL,    0x7fffffff, 0x00000000, result=0x00000000)
+    test_mul_16    = test_op(Funct3.MUL,    0x7fffffff, 0x00000001, result=0x7fffffff)
+    test_mul_17    = test_op(Funct3.MUL,    0x7fffffff, 0xffffffff, result=0x80000001)
+    test_mul_18    = test_op(Funct3.MUL,    0x7fffffff, 0x7fffffff, result=0x00000001)
+    test_mul_19    = test_op(Funct3.MUL,    0x7fffffff, 0x80000000, result=0x80000000)
+
+    test_mul_20    = test_op(Funct3.MUL,    0x80000000, 0x00000000, result=0x00000000)
+    test_mul_21    = test_op(Funct3.MUL,    0x80000000, 0x00000001, result=0x80000000)
+    test_mul_22    = test_op(Funct3.MUL,    0x80000000, 0xffffffff, result=0x80000000)
+    test_mul_23    = test_op(Funct3.MUL,    0x80000000, 0x7fffffff, result=0x80000000)
+    test_mul_24    = test_op(Funct3.MUL,    0x80000000, 0x80000000, result=0x00000000)
+
+    # MULH ---------------------------------------------------------------------------
+
+    test_mulh_0    = test_op(Funct3.MULH,   0x00000000, 0x00000000, result=0x00000000)
+    test_mulh_1    = test_op(Funct3.MULH,   0x00000000, 0x00000001, result=0x00000000)
+    test_mulh_2    = test_op(Funct3.MULH,   0x00000000, 0xffffffff, result=0x00000000)
+    test_mulh_3    = test_op(Funct3.MULH,   0x00000000, 0x7fffffff, result=0x00000000)
+    test_mulh_4    = test_op(Funct3.MULH,   0x00000000, 0x80000000, result=0x00000000)
+
+    test_mulh_5    = test_op(Funct3.MULH,   0x00000001, 0x00000000, result=0x00000000)
+    test_mulh_6    = test_op(Funct3.MULH,   0x00000001, 0x00000001, result=0x00000000)
+    test_mulh_7    = test_op(Funct3.MULH,   0x00000001, 0xffffffff, result=0xffffffff)
+    test_mulh_8    = test_op(Funct3.MULH,   0x00000001, 0x7fffffff, result=0x00000000)
+    test_mulh_9    = test_op(Funct3.MULH,   0x00000001, 0x80000000, result=0xffffffff)
+
+    test_mulh_10   = test_op(Funct3.MULH,   0xffffffff, 0x00000000, result=0x00000000)
+    test_mulh_11   = test_op(Funct3.MULH,   0xffffffff, 0x00000001, result=0xffffffff)
+    test_mulh_12   = test_op(Funct3.MULH,   0xffffffff, 0xffffffff, result=0x00000000)
+    test_mulh_13   = test_op(Funct3.MULH,   0xffffffff, 0x7fffffff, result=0xffffffff)
+    test_mulh_14   = test_op(Funct3.MULH,   0xffffffff, 0x80000000, result=0x00000000)
+
+    test_mulh_15   = test_op(Funct3.MULH,   0x7fffffff, 0x00000000, result=0x00000000)
+    test_mulh_16   = test_op(Funct3.MULH,   0x7fffffff, 0x00000001, result=0x00000000)
+    test_mulh_17   = test_op(Funct3.MULH,   0x7fffffff, 0xffffffff, result=0xffffffff)
+    test_mulh_18   = test_op(Funct3.MULH,   0x7fffffff, 0x7fffffff, result=0x3fffffff)
+    test_mulh_19   = test_op(Funct3.MULH,   0x7fffffff, 0x80000000, result=0xc0000000)
+
+    test_mulh_20   = test_op(Funct3.MULH,   0x80000000, 0x00000000, result=0x00000000)
+    test_mulh_21   = test_op(Funct3.MULH,   0x80000000, 0x00000001, result=0xffffffff)
+    test_mulh_22   = test_op(Funct3.MULH,   0x80000000, 0xffffffff, result=0x00000000)
+    test_mulh_23   = test_op(Funct3.MULH,   0x80000000, 0x7fffffff, result=0xc0000000)
+    test_mulh_24   = test_op(Funct3.MULH,   0x80000000, 0x80000000, result=0x40000000)
+
+    # MULHSU -------------------------------------------------------------------------
+
+    test_mulhsu_0  = test_op(Funct3.MULHSU, 0x00000000, 0x00000000, result=0x00000000)
+    test_mulhsu_1  = test_op(Funct3.MULHSU, 0x00000000, 0x00000001, result=0x00000000)
+    test_mulhsu_2  = test_op(Funct3.MULHSU, 0x00000000, 0xffffffff, result=0x00000000)
+    test_mulhsu_3  = test_op(Funct3.MULHSU, 0x00000000, 0x7fffffff, result=0x00000000)
+    test_mulhsu_4  = test_op(Funct3.MULHSU, 0x00000000, 0x80000000, result=0x00000000)
+
+    test_mulhsu_5  = test_op(Funct3.MULHSU, 0x00000001, 0x00000000, result=0x00000000)
+    test_mulhsu_6  = test_op(Funct3.MULHSU, 0x00000001, 0x00000001, result=0x00000000)
+    test_mulhsu_7  = test_op(Funct3.MULHSU, 0x00000001, 0xffffffff, result=0x00000000)
+    test_mulhsu_8  = test_op(Funct3.MULHSU, 0x00000001, 0x7fffffff, result=0x00000000)
+    test_mulhsu_9  = test_op(Funct3.MULHSU, 0x00000001, 0x80000000, result=0x00000000)
+
+    test_mulhsu_10 = test_op(Funct3.MULHSU, 0xffffffff, 0x00000000, result=0x00000000)
+    test_mulhsu_11 = test_op(Funct3.MULHSU, 0xffffffff, 0x00000001, result=0xffffffff)
+    test_mulhsu_12 = test_op(Funct3.MULHSU, 0xffffffff, 0xffffffff, result=0xffffffff)
+    test_mulhsu_13 = test_op(Funct3.MULHSU, 0xffffffff, 0x7fffffff, result=0xffffffff)
+    test_mulhsu_14 = test_op(Funct3.MULHSU, 0xffffffff, 0x80000000, result=0xffffffff)
+
+    test_mulhsu_15 = test_op(Funct3.MULHSU, 0x7fffffff, 0x00000000, result=0x00000000)
+    test_mulhsu_16 = test_op(Funct3.MULHSU, 0x7fffffff, 0x00000001, result=0x00000000)
+    test_mulhsu_17 = test_op(Funct3.MULHSU, 0x7fffffff, 0xffffffff, result=0x7ffffffe)
+    test_mulhsu_18 = test_op(Funct3.MULHSU, 0x7fffffff, 0x7fffffff, result=0x3fffffff)
+    test_mulhsu_19 = test_op(Funct3.MULHSU, 0x7fffffff, 0x80000000, result=0x3fffffff)
+
+    test_mulhsu_20 = test_op(Funct3.MULHSU, 0x80000000, 0x00000000, result=0x00000000)
+    test_mulhsu_21 = test_op(Funct3.MULHSU, 0x80000000, 0x00000001, result=0xffffffff)
+    test_mulhsu_22 = test_op(Funct3.MULHSU, 0x80000000, 0xffffffff, result=0x80000000)
+    test_mulhsu_23 = test_op(Funct3.MULHSU, 0x80000000, 0x7fffffff, result=0xc0000000)
+    test_mulhsu_24 = test_op(Funct3.MULHSU, 0x80000000, 0x80000000, result=0xc0000000)
+
+    # MULHU --------------------------------------------------------------------------
+
+    test_mulhu_0   = test_op(Funct3.MULHU,  0x00000000, 0x00000000, result=0x00000000)
+    test_mulhu_1   = test_op(Funct3.MULHU,  0x00000000, 0x00000001, result=0x00000000)
+    test_mulhu_2   = test_op(Funct3.MULHU,  0x00000000, 0xffffffff, result=0x00000000)
+    test_mulhu_3   = test_op(Funct3.MULHU,  0x00000000, 0x7fffffff, result=0x00000000)
+    test_mulhu_4   = test_op(Funct3.MULHU,  0x00000000, 0x80000000, result=0x00000000)
+
+    test_mulhu_5   = test_op(Funct3.MULHU,  0x00000001, 0x00000000, result=0x00000000)
+    test_mulhu_6   = test_op(Funct3.MULHU,  0x00000001, 0x00000001, result=0x00000000)
+    test_mulhu_7   = test_op(Funct3.MULHU,  0x00000001, 0xffffffff, result=0x00000000)
+    test_mulhu_8   = test_op(Funct3.MULHU,  0x00000001, 0x7fffffff, result=0x00000000)
+    test_mulhu_9   = test_op(Funct3.MULHU,  0x00000001, 0x80000000, result=0x00000000)
+
+    test_mulhu_10  = test_op(Funct3.MULHU,  0xffffffff, 0x00000000, result=0x00000000)
+    test_mulhu_11  = test_op(Funct3.MULHU,  0xffffffff, 0x00000001, result=0x00000000)
+    test_mulhu_12  = test_op(Funct3.MULHU,  0xffffffff, 0xffffffff, result=0xfffffffe)
+    test_mulhu_13  = test_op(Funct3.MULHU,  0xffffffff, 0x7fffffff, result=0x7ffffffe)
+    test_mulhu_14  = test_op(Funct3.MULHU,  0xffffffff, 0x80000000, result=0x7fffffff)
+
+    test_mulhu_15  = test_op(Funct3.MULHU,  0x7fffffff, 0x00000000, result=0x00000000)
+    test_mulhu_16  = test_op(Funct3.MULHU,  0x7fffffff, 0x00000001, result=0x00000000)
+    test_mulhu_17  = test_op(Funct3.MULHU,  0x7fffffff, 0xffffffff, result=0x7ffffffe)
+    test_mulhu_18  = test_op(Funct3.MULHU,  0x7fffffff, 0x7fffffff, result=0x3fffffff)
+    test_mulhu_19  = test_op(Funct3.MULHU,  0x7fffffff, 0x80000000, result=0x3fffffff)
+
+    test_mulhu_20  = test_op(Funct3.MULHU,  0x80000000, 0x00000000, result=0x00000000)
+    test_mulhu_21  = test_op(Funct3.MULHU,  0x80000000, 0x00000001, result=0x00000000)
+    test_mulhu_22  = test_op(Funct3.MULHU,  0x80000000, 0xffffffff, result=0x7fffffff)
+    test_mulhu_23  = test_op(Funct3.MULHU,  0x80000000, 0x7fffffff, result=0x3fffffff)
+    test_mulhu_24  = test_op(Funct3.MULHU,  0x80000000, 0x80000000, result=0x40000000)
diff --git a/src/soc/minerva/units/__init__.py b/src/soc/minerva/units/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/soc/minerva/units/adder.py b/src/soc/minerva/units/adder.py
new file mode 100644 (file)
index 0000000..2971d98
--- /dev/null
@@ -0,0 +1,31 @@
+from nmigen import *
+
+
+__all__ = ["Adder"]
+
+
+class Adder(Elaboratable):
+    def __init__(self):
+        self.sub = Signal()
+        self.src1 = Signal(32)
+        self.src2 = Signal(32)
+
+        self.result = Signal(32)
+        self.carry = Signal()
+        self.overflow = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.If(self.sub):
+            m.d.comb += [
+                Cat(self.result, self.carry).eq(self.src1 - self.src2),
+                self.overflow.eq((self.src1[-1] != self.src2[-1]) & (self.result[-1] == self.src2[-1]))
+            ]
+        with m.Else():
+            m.d.comb += [
+                Cat(self.result, self.carry).eq(self.src1 + self.src2),
+                self.overflow.eq(~self.src1[-1] & self.src2[-1] & self.result[-1])
+            ]
+
+        return m
diff --git a/src/soc/minerva/units/compare.py b/src/soc/minerva/units/compare.py
new file mode 100644 (file)
index 0000000..b11cdfe
--- /dev/null
@@ -0,0 +1,36 @@
+from nmigen import *
+
+from ..isa import Funct3
+
+
+__all__ = ["CompareUnit"]
+
+
+class CompareUnit(Elaboratable):
+    def __init__(self):
+        self.op = Signal(3)
+        self.zero = Signal()
+        self.negative = Signal()
+        self.overflow = Signal()
+        self.carry = Signal()
+
+        self.condition_met = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.Switch(self.op):
+            with m.Case(Funct3.BEQ):
+                m.d.comb += self.condition_met.eq(self.zero)
+            with m.Case(Funct3.BNE):
+                m.d.comb += self.condition_met.eq(~self.zero)
+            with m.Case(Funct3.BLT):
+                m.d.comb += self.condition_met.eq(~self.zero & (self.negative != self.overflow))
+            with m.Case(Funct3.BGE):
+                m.d.comb += self.condition_met.eq(self.negative == self.overflow)
+            with m.Case(Funct3.BLTU):
+                m.d.comb += self.condition_met.eq(~self.zero & self.carry)
+            with m.Case(Funct3.BGEU):
+                m.d.comb += self.condition_met.eq(~self.carry)
+
+        return m
diff --git a/src/soc/minerva/units/debug/__init__.py b/src/soc/minerva/units/debug/__init__.py
new file mode 100644 (file)
index 0000000..08dee6d
--- /dev/null
@@ -0,0 +1 @@
+from .top import *
diff --git a/src/soc/minerva/units/debug/controller.py b/src/soc/minerva/units/debug/controller.py
new file mode 100644 (file)
index 0000000..31641ba
--- /dev/null
@@ -0,0 +1,201 @@
+from nmigen import *
+from nmigen.lib.coding import PriorityEncoder
+
+from ...csr import *
+from ...isa import *
+from ...wishbone import wishbone_layout
+from .dmi import DebugReg, Command, Error, Version, cmd_access_reg_layout
+
+
+__all__ = ["DebugController"]
+
+
+class HaltCause:
+    EBREAK        = 1
+    TRIGGER       = 0
+    HALTREQ       = 3
+    STEP          = 4
+    RESET_HALTREQ = 2
+
+
+cause_map = [2, 1, 5, 3, 4]
+
+
+class DebugController(Elaboratable, AutoCSR):
+    def __init__(self, debugrf):
+        self.dcsr = CSR(0x7b0, dcsr_layout, name="dcsr")
+        self.dpc  = CSR(0x7b1, flat_layout, name="dpc")
+
+        self.dmstatus   = debugrf.reg_port(DebugReg.DMSTATUS)
+        self.dmcontrol  = debugrf.reg_port(DebugReg.DMCONTROL)
+        self.hartinfo   = debugrf.reg_port(DebugReg.HARTINFO)
+        self.abstractcs = debugrf.reg_port(DebugReg.ABSTRACTCS)
+        self.command    = debugrf.reg_port(DebugReg.COMMAND)
+        self.data0      = debugrf.reg_port(DebugReg.DATA0)
+
+        self.trigger_haltreq = Signal()
+
+        self.x_pc = Signal(32)
+        self.x_ebreak = Signal()
+        self.x_stall = Signal()
+
+        self.m_branch_taken = Signal()
+        self.m_branch_target = Signal(32)
+        self.m_mret = Signal()
+        self.m_exception = Signal()
+        self.m_pc = Signal(32)
+        self.m_valid = Signal()
+        self.mepc_r_base = Signal(30)
+        self.mtvec_r_base = Signal(30)
+
+        self.halt = Signal()
+        self.halted = Signal()
+        self.killall = Signal()
+        self.resumereq = Signal()
+        self.resumeack = Signal()
+
+        self.gprf_addr = Signal(5)
+        self.gprf_re = Signal()
+        self.gprf_dat_r = Signal(32)
+        self.gprf_we = Signal()
+        self.gprf_dat_w = Signal(32)
+
+        self.csrf_addr = Signal(12)
+        self.csrf_re = Signal()
+        self.csrf_dat_r = Signal(32)
+        self.csrf_we = Signal()
+        self.csrf_dat_w = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        for csr in self.dcsr, self.dpc:
+            with m.If(csr.we):
+                m.d.sync += csr.r.eq(csr.w)
+
+        with m.If(self.dmcontrol.update):
+            m.d.sync += [
+                self.dmcontrol.w.dmactive.eq(self.dmcontrol.r.dmactive),
+                self.dmcontrol.w.ndmreset.eq(self.dmcontrol.r.ndmreset),
+                self.dmcontrol.w.hartselhi.eq(self.dmcontrol.r.hartselhi),
+                self.dmcontrol.w.hartsello.eq(self.dmcontrol.r.hartsello),
+                self.dmcontrol.w.hasel.eq(self.dmcontrol.r.hasel),
+                self.dmcontrol.w.hartreset.eq(self.dmcontrol.r.hartreset),
+                self.dmcontrol.w.resumereq.eq(self.dmcontrol.r.resumereq)
+            ]
+
+        with m.If(self.abstractcs.update):
+            m.d.sync += self.abstractcs.w.cmderr.eq(self.abstractcs.r.cmderr)
+
+        m.d.comb += [
+            self.dmstatus.w.version.eq(Version.V013),
+            self.dmstatus.w.authenticated.eq(1),
+            self.resumereq.eq(self.dmcontrol.w.resumereq)
+        ]
+
+        m_breakpoint = Signal()
+        with m.If(~self.x_stall):
+            m.d.comb += m_breakpoint.eq(self.x_ebreak & self.dcsr.r.ebreakm)
+
+        halt_pe = m.submodules.halt_pe = PriorityEncoder(5)
+        m.d.comb += [
+            halt_pe.i[HaltCause.EBREAK].eq(m_breakpoint & self.m_valid),
+            halt_pe.i[HaltCause.TRIGGER].eq(self.trigger_haltreq),
+            halt_pe.i[HaltCause.HALTREQ].eq(self.dmcontrol.r.haltreq),
+            halt_pe.i[HaltCause.STEP].eq(self.dcsr.r.step & self.m_valid),
+        ]
+
+        with m.FSM():
+            with m.State("RUN"):
+                m.d.comb += self.dmstatus.w.allrunning.eq(1)
+                with m.If(~halt_pe.n):
+                    m.d.sync += [
+                        self.halt.eq(1),
+                        self.dcsr.r.cause.eq(Array(cause_map)[halt_pe.o]),
+                        self.dcsr.r.stepie.eq(1)
+                    ]
+                    with m.If(halt_pe.o == HaltCause.EBREAK):
+                        m.d.sync += self.dpc.r.eq(self.m_pc)
+                    with m.Elif(self.m_exception & self.m_valid):
+                        m.d.sync += self.dpc.r.eq(self.mtvec_r_base << 30)
+                    with m.Elif(self.m_mret & self.m_valid):
+                        m.d.sync += self.dpc.r.eq(self.mepc_r_base << 30)
+                    with m.Elif(self.m_branch_taken & self.m_valid):
+                        m.d.sync += self.dpc.r.eq(self.m_branch_target)
+                    with m.Else():
+                        m.d.sync += self.dpc.r.eq(self.x_pc)
+                    m.next = "HALTING"
+
+            with m.State("HALTING"):
+                with m.If(self.halted):
+                    m.d.comb += self.killall.eq(1)
+                    m.d.sync += self.dmstatus.w.allhalted.eq(1)
+                    m.next = "WAIT"
+
+            with m.State("WAIT"):
+                with m.If(self.dmcontrol.w.resumereq):
+                    m.next = "RESUME"
+                with m.Elif(self.command.update):
+                    m.d.sync += self.abstractcs.w.busy.eq(1)
+                    m.next = "COMMAND:START"
+
+            with m.State("RESUME"):
+                with m.If(self.resumeack):
+                    m.d.sync += [
+                        self.dmcontrol.w.resumereq.eq(0),
+                        self.dmstatus.w.allresumeack.eq(1),
+                        self.halt.eq(0),
+                        self.dmstatus.w.allhalted.eq(0)
+                    ]
+                    m.next = "RUN"
+
+            with m.State("COMMAND:START"):
+                with m.Switch(self.command.r.cmdtype):
+                    with m.Case(Command.ACCESS_REG):
+                        control = Record(cmd_access_reg_layout)
+                        m.d.comb += control.eq(self.command.r.control)
+                        m.d.comb += self.gprf_addr.eq(control.regno)
+                        m.next = "COMMAND:ACCESS-REG"
+                    with m.Case():
+                        m.d.sync += self.abstractcs.w.cmderr.eq(Error.UNSUPPORTED)
+                        m.next = "COMMAND:DONE"
+
+            with m.State("COMMAND:ACCESS-REG"):
+                control = Record(cmd_access_reg_layout)
+                m.d.comb += control.eq(self.command.r.control)
+                with m.If(control.postexec | (control.aarsize != 2) | control.aarpostincrement):
+                    # Unsupported parameters.
+                    m.d.sync += self.abstractcs.w.cmderr.eq(Error.EXCEPTION)
+                with m.Elif((control.regno >= 0x0000) & (control.regno < 0x1000)):
+                    with m.If(control.transfer):
+                        m.d.comb += self.csrf_addr.eq(control.regno)
+                        with m.If(control.write):
+                            m.d.comb += [
+                                self.csrf_we.eq(1),
+                                self.csrf_dat_w.eq(self.data0.r)
+                            ]
+                        with m.Else():
+                            m.d.comb += self.csrf_re.eq(1)
+                            m.d.sync += self.data0.w.eq(self.csrf_dat_r)
+                    m.d.sync += self.abstractcs.w.cmderr.eq(Error.NONE)
+                with m.Elif((control.regno >= 0x1000) & (control.regno < 0x1020)):
+                    with m.If(control.transfer):
+                        m.d.comb += self.gprf_addr.eq(control.regno)
+                        with m.If(control.write):
+                            m.d.comb += [
+                                self.gprf_we.eq(1),
+                                self.gprf_dat_w.eq(self.data0.r)
+                            ]
+                        with m.Else():
+                            m.d.sync += self.data0.w.eq(self.gprf_dat_r)
+                    m.d.sync += self.abstractcs.w.cmderr.eq(Error.NONE)
+                with m.Else():
+                    # Unknown register number.
+                    m.d.sync += self.abstractcs.w.cmderr.eq(Error.EXCEPTION)
+                m.next = "COMMAND:DONE"
+
+            with m.State("COMMAND:DONE"):
+                m.d.sync += self.abstractcs.w.busy.eq(0)
+                m.next = "WAIT"
+
+        return m
diff --git a/src/soc/minerva/units/debug/dmi.py b/src/soc/minerva/units/debug/dmi.py
new file mode 100644 (file)
index 0000000..4298b2d
--- /dev/null
@@ -0,0 +1,147 @@
+from enum import Enum
+
+
+__all__ = [
+    "Version", "Command", "Error", "RegMode", "DmiOp", "DebugReg", "dmstatus_layout",
+    "dmcontrol_layout", "abstractcs_layout", "cmd_access_reg_layout", "command_layout",
+    "sbcs_layout", "flat_layout"
+]
+
+
+class Version:
+    NONE  = 0
+    V011  = 1
+    V013  = 2
+    OTHER = 15
+
+
+class Command:
+    ACCESS_REG   = 0
+    QUICK_ACCESS = 1
+    ACCESS_MEM   = 2
+
+
+class Error:
+    NONE        = 0
+    BUSY        = 1
+    UNSUPPORTED = 2
+    EXCEPTION   = 3
+    HALT_RESUME = 4
+
+
+RegMode = Enum("RegMode", ("R", "W", "W1", "RW", "RW1C", "WARL"))
+
+
+class DmiOp:
+    NOP   = 0
+    READ  = 1
+    WRITE = 2
+
+
+# Debug registers
+
+class DebugReg:
+    DATA0      = 0x04
+    DMCONTROL  = 0x10
+    DMSTATUS   = 0x11
+    HARTINFO   = 0x12
+    ABSTRACTCS = 0x16
+    COMMAND    = 0x17
+    PROGBUF0   = 0x20
+    SBCS       = 0x38
+    SBADDRESS0 = 0x39
+    SBDATA0    = 0x3c
+
+
+dmstatus_layout = [
+    ("version",           4, RegMode.R,    Version.V013),
+    ("confstrptrvalid",   1, RegMode.R,    False),
+    ("hasresethaltreq",   1, RegMode.R,    False),
+    ("authbusy",          1, RegMode.R,    False),
+    ("authenticated",     1, RegMode.R,    True),
+    ("anyhalted",         1, RegMode.R,    False),
+    ("allhalted",         1, RegMode.R,    False),
+    ("anyrunning",        1, RegMode.R,    False),
+    ("allrunning",        1, RegMode.R,    False),
+    ("anyunavail",        1, RegMode.R,    False),
+    ("allunavail",        1, RegMode.R,    False),
+    ("anynonexistent",    1, RegMode.R,    False),
+    ("allnonexistent",    1, RegMode.R,    False),
+    ("anyresumeack",      1, RegMode.R,    False),
+    ("allresumeack",      1, RegMode.R,    False),
+    ("anyhavereset",      1, RegMode.R,    False),
+    ("allhavereset",      1, RegMode.R,    False),
+    ("zero0",             2, RegMode.R,    0),
+    ("impebreak",         1, RegMode.R,    False),
+    ("zero1",             9, RegMode.R,    0)
+]
+
+
+dmcontrol_layout = [
+    ("dmactive",          1, RegMode.RW,   False),
+    ("ndmreset",          1, RegMode.RW,   False),
+    ("clrresethaltreq",   1, RegMode.W1,   False),
+    ("setresethaltreq",   1, RegMode.W1,   False),
+    ("zero0",             2, RegMode.R,    0),
+    ("hartselhi",        10, RegMode.RW,   0),
+    ("hartsello",        10, RegMode.RW,   0),
+    ("hasel",             1, RegMode.RW,   False),
+    ("zero1",             1, RegMode.R,    0),
+    ("ackhavereset",      1, RegMode.W1,   False),
+    ("hartreset",         1, RegMode.RW,   False),
+    ("resumereq",         1, RegMode.W1,   False),
+    ("haltreq",           1, RegMode.W,    False)
+]
+
+
+abstractcs_layout = [
+    ("datacount",         4, RegMode.R,    1),
+    ("zero0",             4, RegMode.R,    0),
+    ("cmderr",            3, RegMode.RW1C, 0),
+    ("zero1",             1, RegMode.R,    0),
+    ("busy",              1, RegMode.R,    False),
+    ("zero2",            11, RegMode.R,    0),
+    ("progbufsize",       5, RegMode.R,    0),
+    ("zero3",             3, RegMode.R,    0)
+]
+
+
+cmd_access_reg_layout = [
+    ("regno",            16),
+    ("write",             1),
+    ("transfer",          1),
+    ("postexec",          1),
+    ("aarpostincrement",  1),
+    ("aarsize",           3),
+    ("zero0",             1),
+]
+
+
+command_layout = [
+    ("control",          24, RegMode.W,    0),
+    ("cmdtype",           8, RegMode.W,    Command.ACCESS_REG)
+]
+
+
+sbcs_layout = [
+    ("sbaccess8",         1, RegMode.R,    True),
+    ("sbaccess16",        1, RegMode.R,    True),
+    ("sbaccess32",        1, RegMode.R,    True),
+    ("sbaccess64",        1, RegMode.R,    False),
+    ("sbaccess128",       1, RegMode.R,    False),
+    ("sbasize",           7, RegMode.R,    32),
+    ("sberror",           3, RegMode.RW1C, 0),
+    ("sbreadondata",      1, RegMode.RW,   False),
+    ("sbautoincrement",   1, RegMode.RW,   False),
+    ("sbaccess",          3, RegMode.RW,   2),
+    ("sbreadonaddr",      1, RegMode.RW,   False),
+    ("sbbusy",            1, RegMode.R,    False),
+    ("sbbusyerror",       1, RegMode.RW1C, False),
+    ("zero0",             6, RegMode.R,    0),
+    ("sbversion",         3, RegMode.R,    1)
+]
+
+
+flat_layout = [
+    ("value",            32, RegMode.RW,   0)
+]
diff --git a/src/soc/minerva/units/debug/jtag.py b/src/soc/minerva/units/debug/jtag.py
new file mode 100644 (file)
index 0000000..e979789
--- /dev/null
@@ -0,0 +1,41 @@
+from nmigen.hdl.rec import *
+
+
+__all__ = ["jtag_layout", "JTAGReg", "dtmcs_layout", "dmi_layout"]
+
+
+jtag_layout = [
+    ("tck",  1, DIR_FANIN),
+    ("tdi",  1, DIR_FANIN),
+    ("tdo",  1, DIR_FANOUT),
+    ("tms",  1, DIR_FANIN),
+    ("trst", 1, DIR_FANIN) # TODO
+]
+
+
+class JTAGReg:
+    BYPASS = 0x00
+    IDCODE = 0x01
+    DTMCS  = 0x10
+    DMI    = 0x11
+
+
+# JTAG register layouts
+
+dtmcs_layout = [
+    ("version",       4),
+    ("abits",         6),
+    ("dmistat",       2),
+    ("idle",          3),
+    ("zero0",         1),
+    ("dmireset",      1),
+    ("dmihardreset",  1),
+    ("zero1",        14)
+]
+
+
+dmi_layout = [
+    ("op",    2),
+    ("data", 32),
+    ("addr",  6)
+]
diff --git a/src/soc/minerva/units/debug/regfile.py b/src/soc/minerva/units/debug/regfile.py
new file mode 100644 (file)
index 0000000..a27e3bd
--- /dev/null
@@ -0,0 +1,102 @@
+from nmigen import *
+from nmigen.hdl.rec import *
+
+from .dmi import *
+
+
+__all__ = ["DebugRegisterFile"]
+
+
+class DmiOp:
+    NOP   = 0
+    READ  = 1
+    WRITE = 2
+
+
+class DmiResult:
+    OK   = 0
+    FAIL = 2
+    BUSY = 3
+
+
+reg_map = {
+    DebugReg.DMSTATUS:   dmstatus_layout,
+    DebugReg.DMCONTROL:  dmcontrol_layout,
+    DebugReg.HARTINFO:   flat_layout,
+    DebugReg.ABSTRACTCS: abstractcs_layout,
+    DebugReg.COMMAND:    command_layout,
+    DebugReg.SBCS:       sbcs_layout,
+    DebugReg.SBADDRESS0: flat_layout,
+    DebugReg.SBDATA0:    flat_layout,
+    DebugReg.DATA0:      flat_layout
+}
+
+
+class DebugRegisterFile(Elaboratable):
+    def __init__(self, dmi):
+        self.dmi = dmi
+        self.ports = dict()
+
+    def reg_port(self, addr):
+        if addr not in reg_map:
+            raise ValueError("Unknown register {:x}.".format(addr))
+        if addr in self.ports:
+            raise ValueError("Register {:x} has already been allocated.".format(addr))
+        layout = [f[:2] for f in reg_map[addr]]
+        port = Record([("r", layout), ("w", layout), ("update", 1), ("capture", 1)])
+        for name, shape, mode, reset in reg_map[addr]:
+            getattr(port.r, name).reset = reset
+            getattr(port.w, name).reset = reset
+        self.ports[addr] = port
+        return port
+
+    def elaborate(self, platform):
+        m = Module()
+
+        def do_read(addr, port):
+            rec = Record(port.w.layout)
+            m.d.sync += self.dmi.r.data.eq(rec)
+            for name, shape, mode, reset in reg_map[addr]:
+                dst = getattr(rec, name)
+                src = getattr(port.w, name)
+                if mode in {RegMode.R, RegMode.RW, RegMode.RW1C}:
+                    m.d.comb += dst.eq(src)
+                else:
+                    m.d.comb += dst.eq(Const(0))
+            m.d.sync += port.capture.eq(1)
+
+        def do_write(addr, port):
+            rec = Record(port.r.layout)
+            m.d.comb += rec.eq(self.dmi.w.data)
+            for name, shape, mode, reset in reg_map[addr]:
+                dst = getattr(port.r, name)
+                src = getattr(rec, name)
+                if mode in {RegMode.W, RegMode.RW}:
+                    m.d.sync += dst.eq(src)
+                elif mode is RegMode.W1:
+                    m.d.sync += dst.eq(getattr(port.w, name) | src)
+                elif mode is RegMode.RW1C:
+                    m.d.sync += dst.eq(getattr(port.w, name) & ~src)
+
+            m.d.sync += port.update.eq(1)
+
+        with m.If(self.dmi.update):
+            with m.Switch(self.dmi.w.addr):
+                for addr, port in self.ports.items():
+                    with m.Case(addr):
+                        with m.If(self.dmi.w.op == DmiOp.READ):
+                            do_read(addr, port)
+                        with m.Elif(self.dmi.w.op == DmiOp.WRITE):
+                            do_write(addr, port)
+                        m.d.sync += self.dmi.r.op.eq(DmiResult.OK)
+                with m.Case():
+                    # Invalid register.
+                    m.d.sync += self.dmi.r.op.eq(DmiResult.FAIL)
+
+        for port in self.ports.values():
+            with m.If(port.update):
+                m.d.sync += port.update.eq(0)
+            with m.If(port.capture):
+                m.d.sync += port.capture.eq(0)
+
+        return m
diff --git a/src/soc/minerva/units/debug/top.py b/src/soc/minerva/units/debug/top.py
new file mode 100644 (file)
index 0000000..e5cf404
--- /dev/null
@@ -0,0 +1,124 @@
+from nmigen import *
+from nmigen.hdl.rec import *
+
+
+from ...csr import *
+from ...isa import *
+from ...wishbone import wishbone_layout
+from .controller import *
+from .dmi import *
+from .jtag import *
+from .regfile import *
+from .wbmaster import *
+
+
+__all__ = ["DebugUnit"]
+
+
+jtag_regs = {
+    JTAGReg.IDCODE: [("value", 32)],
+    JTAGReg.DTMCS:  dtmcs_layout,
+    JTAGReg.DMI:    dmi_layout
+}
+
+
+class DebugUnit(Elaboratable, AutoCSR):
+    def __init__(self):
+        self.jtag = Record(jtag_layout)
+        self.dbus = Record(wishbone_layout)
+
+        self.trigger_haltreq = Signal()
+
+        self.x_ebreak = Signal()
+        self.x_pc = Signal(32)
+        self.x_stall = Signal()
+
+        self.m_branch_taken = Signal()
+        self.m_branch_target = Signal(32)
+        self.m_mret = Signal()
+        self.m_exception = Signal()
+        self.m_pc = Signal(32)
+        self.m_valid = Signal()
+        self.mepc_r_base = Signal(30)
+        self.mtvec_r_base = Signal(30)
+
+        self.dcsr_step = Signal()
+        self.dcsr_ebreakm = Signal()
+        self.dpc_value = Signal(32)
+
+        self.halt = Signal()
+        self.halted = Signal()
+        self.killall = Signal()
+        self.resumereq = Signal()
+        self.resumeack = Signal()
+
+        self.dbus_busy = Signal()
+
+        self.csrf_addr = Signal(12)
+        self.csrf_re = Signal()
+        self.csrf_dat_r = Signal(32)
+        self.csrf_we = Signal()
+        self.csrf_dat_w = Signal(32)
+
+        self.gprf_addr = Signal(5)
+        self.gprf_re = Signal()
+        self.gprf_dat_r = Signal(32)
+        self.gprf_we = Signal()
+        self.gprf_dat_w = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        from jtagtap import JTAGTap
+        tap        = m.submodules.tap        = JTAGTap(jtag_regs)
+        regfile    = m.submodules.regfile    = DebugRegisterFile(tap.regs[JTAGReg.DMI])
+        controller = m.submodules.controller = DebugController(regfile)
+        wbmaster   = m.submodules.wbmaster   = DebugWishboneMaster(regfile)
+
+        m.d.comb += [
+            tap.port.connect(self.jtag),
+            tap.regs[JTAGReg.IDCODE].r.eq(0x10e31913), # Usurpate a Spike core for now.
+            tap.regs[JTAGReg.DTMCS].r.eq(0x61) # (abits=6, version=1) TODO
+        ]
+
+        m.d.comb += [
+            controller.trigger_haltreq.eq(self.trigger_haltreq),
+
+            controller.x_ebreak.eq(self.x_ebreak),
+            controller.x_pc.eq(self.x_pc),
+            controller.x_stall.eq(self.x_stall),
+
+            controller.m_branch_taken.eq(self.m_branch_taken),
+            controller.m_branch_target.eq(self.m_branch_target),
+            controller.m_pc.eq(self.m_pc),
+            controller.m_valid.eq(self.m_valid),
+
+            self.halt.eq(controller.halt),
+            controller.halted.eq(self.halted),
+            self.killall.eq(controller.killall),
+            self.resumereq.eq(controller.resumereq),
+            controller.resumeack.eq(self.resumeack),
+
+            self.dcsr_step.eq(controller.dcsr.r.step),
+            self.dcsr_ebreakm.eq(controller.dcsr.r.ebreakm),
+            self.dpc_value.eq(controller.dpc.r.value),
+
+            self.csrf_addr.eq(controller.csrf_addr),
+            self.csrf_re.eq(controller.csrf_re),
+            controller.csrf_dat_r.eq(self.csrf_dat_r),
+            self.csrf_we.eq(controller.csrf_we),
+            self.csrf_dat_w.eq(controller.csrf_dat_w),
+
+            self.gprf_addr.eq(controller.gprf_addr),
+            self.gprf_re.eq(controller.gprf_re),
+            controller.gprf_dat_r.eq(self.gprf_dat_r),
+            self.gprf_we.eq(controller.gprf_we),
+            self.gprf_dat_w.eq(controller.gprf_dat_w),
+        ]
+
+        m.d.comb += [
+            wbmaster.bus.connect(self.dbus),
+            self.dbus_busy.eq(wbmaster.dbus_busy)
+        ]
+
+        return m
diff --git a/src/soc/minerva/units/debug/wbmaster.py b/src/soc/minerva/units/debug/wbmaster.py
new file mode 100644 (file)
index 0000000..a471156
--- /dev/null
@@ -0,0 +1,126 @@
+from functools import reduce
+from operator import or_
+
+from nmigen import *
+from nmigen.hdl.rec import *
+
+from ...wishbone import wishbone_layout
+from .dmi import *
+
+
+__all__ = ["BusError", "AccessSize", "DebugWishboneMaster"]
+
+
+class BusError:
+    NONE        = 0
+    TIMEOUT     = 1
+    BAD_ADDRESS = 2
+    MISALIGNED  = 3
+    BAD_SIZE    = 4
+    OTHER       = 7
+
+
+class AccessSize:
+    BYTE = 0
+    HALF = 1
+    WORD = 2
+
+
+class DebugWishboneMaster(Elaboratable):
+    def __init__(self, debugrf):
+        self.bus = Record(wishbone_layout)
+
+        self.dbus_busy = Signal()
+
+        self.sbcs       = debugrf.reg_port(DebugReg.SBCS)
+        self.sbaddress0 = debugrf.reg_port(DebugReg.SBADDRESS0)
+        self.sbdata0    = debugrf.reg_port(DebugReg.SBDATA0)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        addr = self.sbaddress0.w.value
+        size = self.sbcs.r.sbaccess
+
+        width = Signal(6)
+        m.d.comb += width.eq((1<<size)*8)
+
+        sbbusyerror = self.sbcs.w.sbbusyerror
+        sberror = self.sbcs.w.sberror
+        m.d.comb += self.dbus_busy.eq(self.sbcs.w.sbbusy)
+
+        m.d.comb += [
+            self.sbcs.w.sbaccess8.eq(1),
+            self.sbcs.w.sbaccess16.eq(1),
+            self.sbcs.w.sbaccess32.eq(1),
+            self.sbcs.w.sbasize.eq(32),
+            self.sbcs.w.sbversion.eq(1)
+        ]
+
+        with m.If(self.sbcs.update):
+            m.d.sync += [
+                self.sbcs.w.sbbusyerror.eq(self.sbcs.r.sbbusyerror),
+                self.sbcs.w.sberror.eq(self.sbcs.r.sberror)
+            ]
+
+        we = Signal()
+        re = Signal()
+
+        with m.If(self.sbdata0.update):
+            with m.If(self.sbcs.w.sbbusy):
+                m.d.sync += self.sbcs.w.sbbusyerror.eq(1)
+            with m.Else():
+                m.d.sync += we.eq(~sberror.bool())
+
+        with m.If(self.sbdata0.capture):
+            with m.If(self.sbcs.w.sbbusy):
+                m.d.sync += self.sbcs.w.sbbusyerror.eq(1)
+            with m.Else():
+                m.d.sync += re.eq(self.sbcs.r.sbreadondata & ~sberror.bool())
+
+        with m.If(self.sbaddress0.update):
+            with m.If(self.sbcs.w.sbbusy):
+                m.d.sync += self.sbcs.w.sbbusyerror.eq(1)
+            with m.Else():
+                m.d.sync += [
+                    re.eq(self.sbcs.r.sbreadonaddr & ~sberror.bool()),
+                    self.sbaddress0.w.value.eq(self.sbaddress0.r.value)
+                ]
+
+        with m.FSM():
+            with m.State("IDLE"):
+                with m.If(we | re):
+                    m.d.sync += we.eq(0), re.eq(0)
+                    with m.If(size > AccessSize.WORD):
+                        m.d.sync += sberror.eq(BusError.BAD_SIZE)
+                    with m.Elif((addr & (1<<size)-1) != 0):
+                        m.d.sync += sberror.eq(BusError.MISALIGNED)
+                    with m.Else():
+                        m.d.sync += [
+                            self.bus.cyc.eq(1),
+                            self.bus.stb.eq(1),
+                            self.bus.adr.eq(addr[2:]),
+                            self.bus.we.eq(we),
+                            self.bus.sel.eq((1<<(1<<size))-1 << addr[:2]),
+                            self.bus.dat_w.eq((self.sbdata0.r & (1<<width)-1) << addr[:2]*8)
+                        ]
+                        m.next = "BUSY"
+
+            with m.State("BUSY"):
+                m.d.comb += self.sbcs.w.sbbusy.eq(1)
+                with m.If(self.bus.ack | self.bus.err):
+                    m.d.sync += [
+                        self.bus.cyc.eq(0),
+                        self.bus.stb.eq(0),
+                        self.bus.we.eq(0),
+                    ]
+                    with m.If(self.bus.err):
+                        m.d.sync += sberror.eq(BusError.OTHER)
+                    with m.Else():
+                        with m.If(~self.bus.we):
+                            m.d.sync += self.sbdata0.w.eq((self.bus.dat_r >> addr[:2]*8) & (1<<width)-1)
+                        with m.If(self.sbcs.r.sbautoincrement):
+                            m.d.sync += addr.eq(addr + (1<<size))
+                    m.next = "IDLE"
+
+        return m
diff --git a/src/soc/minerva/units/decoder.py b/src/soc/minerva/units/decoder.py
new file mode 100644 (file)
index 0000000..0c75d50
--- /dev/null
@@ -0,0 +1,261 @@
+from functools import reduce
+from itertools import starmap
+from operator import or_
+
+from nmigen import *
+
+from ..isa import Opcode, Funct3, Funct7, Funct12
+
+
+__all__ = ["InstructionDecoder"]
+
+
+class Type:
+    R = 0
+    I = 1
+    S = 2
+    B = 3
+    U = 4
+    J = 5
+
+
+class InstructionDecoder(Elaboratable):
+    def __init__(self, with_muldiv):
+        self.with_muldiv = with_muldiv
+
+        self.instruction = Signal(32)
+
+        self.rd = Signal(5)
+        self.rd_we = Signal()
+        self.rs1 = Signal(5)
+        self.rs1_re = Signal()
+        self.rs2 = Signal(5)
+        self.rs2_re = Signal()
+        self.immediate = Signal((32, True))
+        self.bypass_x = Signal()
+        self.bypass_m = Signal()
+        self.load = Signal()
+        self.store = Signal()
+        self.fence_i = Signal()
+        self.adder = Signal()
+        self.adder_sub = Signal()
+        self.logic = Signal()
+        self.multiply = Signal()
+        self.divide = Signal()
+        self.shift = Signal()
+        self.direction = Signal()
+        self.sext = Signal()
+        self.lui = Signal()
+        self.auipc = Signal()
+        self.jump = Signal()
+        self.branch = Signal()
+        self.compare = Signal()
+        self.csr = Signal()
+        self.csr_we = Signal()
+        self.privileged = Signal()
+        self.ecall = Signal()
+        self.ebreak = Signal()
+        self.mret = Signal()
+        self.funct3 = Signal(3)
+        self.illegal = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        opcode = Signal(5)
+        funct3 = Signal(3)
+        funct7 = Signal(7)
+        funct12 = Signal(12)
+
+        iimm12 = Signal((12, True))
+        simm12 = Signal((12, True))
+        bimm12 = Signal((13, True))
+        uimm20 = Signal(20)
+        jimm20 = Signal((21, True))
+
+        insn = self.instruction
+        fmt = Signal(range(Type.J + 1))
+
+        m.d.comb += [
+            opcode.eq(insn[2:7]),
+            funct3.eq(insn[12:15]),
+            funct7.eq(insn[25:32]),
+            funct12.eq(insn[20:32]),
+
+            iimm12.eq(insn[20:32]),
+            simm12.eq(Cat(insn[7:12], insn[25:32])),
+            bimm12.eq(Cat(0, insn[8:12], insn[25:31], insn[7], insn[31])),
+            uimm20.eq(insn[12:32]),
+            jimm20.eq(Cat(0, insn[21:31], insn[20], insn[12:20], insn[31])),
+        ]
+
+        with m.Switch(opcode):
+            with m.Case(Opcode.LUI):
+                m.d.comb += fmt.eq(Type.U)
+            with m.Case(Opcode.AUIPC):
+                m.d.comb += fmt.eq(Type.U)
+            with m.Case(Opcode.JAL):
+                m.d.comb += fmt.eq(Type.J)
+            with m.Case(Opcode.JALR):
+                m.d.comb += fmt.eq(Type.I)
+            with m.Case(Opcode.BRANCH):
+                m.d.comb += fmt.eq(Type.B)
+            with m.Case(Opcode.LOAD):
+                m.d.comb += fmt.eq(Type.I)
+            with m.Case(Opcode.STORE):
+                m.d.comb += fmt.eq(Type.S)
+            with m.Case(Opcode.OP_IMM_32):
+                m.d.comb += fmt.eq(Type.I)
+            with m.Case(Opcode.OP_32):
+                m.d.comb += fmt.eq(Type.R)
+            with m.Case(Opcode.MISC_MEM):
+                m.d.comb += fmt.eq(Type.I)
+            with m.Case(Opcode.SYSTEM):
+                m.d.comb += fmt.eq(Type.I)
+
+        with m.Switch(fmt):
+            with m.Case(Type.I):
+                m.d.comb += self.immediate.eq(iimm12)
+            with m.Case(Type.S):
+                m.d.comb += self.immediate.eq(simm12)
+            with m.Case(Type.B):
+                m.d.comb += self.immediate.eq(bimm12)
+            with m.Case(Type.U):
+                m.d.comb += self.immediate.eq(uimm20 << 12)
+            with m.Case(Type.J):
+                m.d.comb += self.immediate.eq(jimm20)
+
+        m.d.comb += [
+            self.rd.eq(insn[7:12]),
+            self.rs1.eq(insn[15:20]),
+            self.rs2.eq(insn[20:25]),
+
+            self.rd_we.eq(reduce(or_, (fmt == T for T in (Type.R, Type.I, Type.U, Type.J)))),
+            self.rs1_re.eq(reduce(or_, (fmt == T for T in (Type.R, Type.I, Type.S, Type.B)))),
+            self.rs2_re.eq(reduce(or_, (fmt == T for T in (Type.R, Type.S, Type.B)))),
+
+            self.funct3.eq(funct3)
+        ]
+
+        def matcher(encodings):
+            return reduce(or_, starmap(
+                lambda opc, f3=None, f7=None, f12=None:
+                    (opcode  == opc if opc is not None else 1) \
+                  & (funct3  == f3  if f3  is not None else 1) \
+                  & (funct7  == f7  if f7  is not None else 1) \
+                  & (funct12 == f12 if f12 is not None else 1),
+                encodings))
+
+        m.d.comb += [
+            self.compare.eq(matcher([
+                (Opcode.OP_IMM_32, Funct3.SLT,  None), # slti
+                (Opcode.OP_IMM_32, Funct3.SLTU, None), # sltiu
+                (Opcode.OP_32,     Funct3.SLT,  0),    # slt
+                (Opcode.OP_32,     Funct3.SLTU, 0)     # sltu
+            ])),
+            self.branch.eq(matcher([
+                (Opcode.BRANCH, Funct3.BEQ,  None), # beq
+                (Opcode.BRANCH, Funct3.BNE,  None), # bne
+                (Opcode.BRANCH, Funct3.BLT,  None), # blt
+                (Opcode.BRANCH, Funct3.BGE,  None), # bge
+                (Opcode.BRANCH, Funct3.BLTU, None), # bltu
+                (Opcode.BRANCH, Funct3.BGEU, None)  # bgeu
+            ])),
+
+            self.adder.eq(matcher([
+                (Opcode.OP_IMM_32, Funct3.ADD, None),       # addi
+                (Opcode.OP_32,     Funct3.ADD, Funct7.ADD), # add
+                (Opcode.OP_32,     Funct3.ADD, Funct7.SUB)  # sub
+            ])),
+            self.adder_sub.eq(self.rs2_re & (funct7 == Funct7.SUB)),
+
+            self.logic.eq(matcher([
+                (Opcode.OP_IMM_32, Funct3.XOR, None), # xori
+                (Opcode.OP_IMM_32, Funct3.OR,  None), # ori
+                (Opcode.OP_IMM_32, Funct3.AND, None), # andi
+                (Opcode.OP_32,     Funct3.XOR, 0),    # xor
+                (Opcode.OP_32,     Funct3.OR,  0),    # or
+                (Opcode.OP_32,     Funct3.AND, 0)     # and
+            ])),
+        ]
+
+        if self.with_muldiv:
+            m.d.comb += [
+                self.multiply.eq(matcher([
+                    (Opcode.OP_32, Funct3.MUL,    Funct7.MULDIV), # mul
+                    (Opcode.OP_32, Funct3.MULH,   Funct7.MULDIV), # mulh
+                    (Opcode.OP_32, Funct3.MULHSU, Funct7.MULDIV), # mulhsu
+                    (Opcode.OP_32, Funct3.MULHU,  Funct7.MULDIV), # mulhu
+                ])),
+
+                self.divide.eq(matcher([
+                    (Opcode.OP_32, Funct3.DIV,  Funct7.MULDIV), # div
+                    (Opcode.OP_32, Funct3.DIVU, Funct7.MULDIV), # divu
+                    (Opcode.OP_32, Funct3.REM,  Funct7.MULDIV), # rem
+                    (Opcode.OP_32, Funct3.REMU, Funct7.MULDIV)  # remu
+                ])),
+            ]
+
+        m.d.comb += [
+            self.shift.eq(matcher([
+                (Opcode.OP_IMM_32, Funct3.SLL, 0),          # slli
+                (Opcode.OP_IMM_32, Funct3.SR,  Funct7.SRL), # srli
+                (Opcode.OP_IMM_32, Funct3.SR,  Funct7.SRA), # srai
+                (Opcode.OP_32,     Funct3.SLL, 0),          # sll
+                (Opcode.OP_32,     Funct3.SR,  Funct7.SRL), # srl
+                (Opcode.OP_32,     Funct3.SR,  Funct7.SRA)  # sra
+            ])),
+            self.direction.eq(funct3 == Funct3.SR),
+            self.sext.eq(funct7 == Funct7.SRA),
+
+            self.lui.eq(opcode == Opcode.LUI),
+            self.auipc.eq(opcode == Opcode.AUIPC),
+
+            self.jump.eq(matcher([
+                (Opcode.JAL,  None), # jal
+                (Opcode.JALR, 0)     # jalr
+            ])),
+
+            self.load.eq(matcher([
+                (Opcode.LOAD, Funct3.B),  # lb
+                (Opcode.LOAD, Funct3.BU), # lbu
+                (Opcode.LOAD, Funct3.H),  # lh
+                (Opcode.LOAD, Funct3.HU), # lhu
+                (Opcode.LOAD, Funct3.W)   # lw
+            ])),
+            self.store.eq(matcher([
+                (Opcode.STORE, Funct3.B), # sb
+                (Opcode.STORE, Funct3.H), # sh
+                (Opcode.STORE, Funct3.W)  # sw
+            ])),
+
+            self.fence_i.eq(matcher([
+                (Opcode.MISC_MEM, Funct3.FENCEI) # fence.i
+            ])),
+
+            self.csr.eq(matcher([
+                (Opcode.SYSTEM, Funct3.CSRRW),  # csrrw
+                (Opcode.SYSTEM, Funct3.CSRRS),  # csrrs
+                (Opcode.SYSTEM, Funct3.CSRRC),  # csrrc
+                (Opcode.SYSTEM, Funct3.CSRRWI), # csrrwi
+                (Opcode.SYSTEM, Funct3.CSRRSI), # csrrsi
+                (Opcode.SYSTEM, Funct3.CSRRCI)  # csrrci
+            ])),
+            self.csr_we.eq(~funct3[1] | (self.rs1 != 0)),
+
+            self.privileged.eq((opcode == Opcode.SYSTEM) & (funct3 == Funct3.PRIV)),
+            self.ecall.eq(self.privileged & (funct12 == Funct12.ECALL)),
+            self.ebreak.eq(self.privileged & (funct12 == Funct12.EBREAK)),
+            self.mret.eq(self.privileged & (funct12 == Funct12.MRET)),
+
+            self.bypass_x.eq(self.adder | self.logic | self.lui | self.auipc | self.csr),
+            self.bypass_m.eq(self.compare | self.divide | self.shift),
+
+            self.illegal.eq((self.instruction[:2] != 0b11) | ~reduce(or_, (
+                self.compare, self.branch, self.adder, self.logic, self.multiply, self.divide, self.shift,
+                self.lui, self.auipc, self.jump, self.load, self.store,
+                self.csr, self.ecall, self.ebreak, self.mret
+            )))
+        ]
+
+        return m
diff --git a/src/soc/minerva/units/divider.py b/src/soc/minerva/units/divider.py
new file mode 100644 (file)
index 0000000..6a98927
--- /dev/null
@@ -0,0 +1,144 @@
+from nmigen import *
+
+from ..isa import Funct3
+
+
+__all__ = ["DividerInterface", "Divider", "DummyDivider"]
+
+
+class DividerInterface:
+    def __init__(self):
+        self.x_op     = Signal(3)
+        self.x_src1   = Signal(32)
+        self.x_src2   = Signal(32)
+        self.x_valid  = Signal()
+        self.x_stall  = Signal()
+
+        self.m_result = Signal(32)
+        self.m_busy   = Signal()
+
+
+class Divider(DividerInterface, Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        x_enable  = Signal()
+        x_modulus = Signal()
+        x_signed  = Signal()
+
+        with m.Switch(self.x_op):
+            with m.Case(Funct3.DIV):
+                m.d.comb += x_enable.eq(1), x_signed.eq(1)
+            with m.Case(Funct3.DIVU):
+                m.d.comb += x_enable.eq(1)
+            with m.Case(Funct3.REM):
+                m.d.comb += x_enable.eq(1), x_modulus.eq(1), x_signed.eq(1)
+            with m.Case(Funct3.REMU):
+                m.d.comb += x_enable.eq(1), x_modulus.eq(1)
+
+        x_negative = Signal()
+        with m.If(x_modulus):
+            m.d.comb += x_negative.eq(x_signed & self.x_src1[31])
+        with m.Else():
+            m.d.comb += x_negative.eq(x_signed & (self.x_src1[31] ^ self.x_src2[31]))
+
+        x_dividend = Signal(32)
+        x_divisor  = Signal(32)
+        m.d.comb += [
+            x_dividend.eq(Mux(x_signed & self.x_src1[31], -self.x_src1, self.x_src1)),
+            x_divisor.eq(Mux(x_signed & self.x_src2[31], -self.x_src2, self.x_src2))
+        ]
+
+        m_modulus  = Signal()
+        m_negative = Signal()
+
+        timer      = Signal(range(33), reset=32)
+        quotient   = Signal(32)
+        divisor    = Signal(32)
+        remainder  = Signal(32)
+        difference = Signal(33)
+
+        with m.FSM() as fsm:
+            with m.State("IDLE"):
+                with m.If(x_enable & self.x_valid & ~self.x_stall):
+                    m.d.sync += [
+                        m_modulus.eq(x_modulus),
+                        m_negative.eq(x_negative)
+                    ]
+                    with m.If(x_divisor == 0):
+                        # Division by zero
+                        m.d.sync += [
+                            quotient.eq(-1),
+                            remainder.eq(self.x_src1)
+                        ]
+                    with m.Elif(x_signed & (self.x_src1 == -2**31) & (self.x_src2 == -1)):
+                        # Signed overflow
+                        m.d.sync += [
+                            quotient.eq(self.x_src1),
+                            remainder.eq(0)
+                        ]
+                    with m.Elif(x_dividend == 0):
+                        m.d.sync += [
+                            quotient.eq(0),
+                            remainder.eq(0)
+                        ]
+                    with m.Else():
+                        m.d.sync += [
+                            quotient.eq(x_dividend),
+                            remainder.eq(0),
+                            divisor.eq(x_divisor),
+                            timer.eq(timer.reset)
+                        ]
+                        m.next = "DIVIDE"
+
+            with m.State("DIVIDE"):
+                m.d.comb += self.m_busy.eq(1)
+                with m.If(timer != 0):
+                    m.d.sync += timer.eq(timer - 1)
+                    m.d.comb += difference.eq(Cat(quotient[31], remainder) - divisor)
+                    with m.If(difference[32]):
+                        m.d.sync += [
+                            remainder.eq(Cat(quotient[31], remainder)),
+                            quotient.eq(Cat(0, quotient))
+                        ]
+                    with m.Else():
+                        m.d.sync += [
+                            remainder.eq(difference),
+                            quotient.eq(Cat(1, quotient))
+                        ]
+                with m.Else():
+                    m.d.sync += [
+                        quotient.eq(Mux(m_negative, -quotient, quotient)),
+                        remainder.eq(Mux(m_negative, -remainder, remainder))
+                    ]
+                    m.next = "IDLE"
+
+        m.d.comb += self.m_result.eq(Mux(m_modulus, remainder, quotient))
+
+        return m
+
+
+class DummyDivider(DividerInterface, Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        x_result = Signal.like(self.m_result)
+
+        with m.Switch(self.x_op):
+            # As per the RVFI specification (ยง "Alternative Arithmetic Operations").
+            # https://github.com/SymbioticEDA/riscv-formal/blob/master/docs/rvfi.md
+            with m.Case(Funct3.DIV):
+                m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x7f8529ec))
+            with m.Case(Funct3.DIVU):
+                m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x10e8fd70))
+            with m.Case(Funct3.REM):
+                m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x8da68fa5))
+            with m.Case(Funct3.REMU):
+                m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x3138d0e1))
+
+        with m.If(~self.x_stall):
+            m.d.sync += self.m_result.eq(x_result)
+
+        m.d.comb += self.m_busy.eq(C(0))
+
+        return m
diff --git a/src/soc/minerva/units/exception.py b/src/soc/minerva/units/exception.py
new file mode 100644 (file)
index 0000000..0d66b47
--- /dev/null
@@ -0,0 +1,121 @@
+from nmigen import *
+from nmigen.lib.coding import PriorityEncoder
+
+from ..csr import *
+from ..isa import *
+
+
+__all__ = ["ExceptionUnit"]
+
+
+class ExceptionUnit(Elaboratable, AutoCSR):
+    def __init__(self):
+        self.mstatus     = CSR(0x300, mstatus_layout, name="mstatus")
+        self.misa        = CSR(0x301, misa_layout, name="misa") # FIXME move elsewhere
+        self.mie         = CSR(0x304, mie_layout, name="mie")
+        self.mtvec       = CSR(0x305, mtvec_layout, name="mtvec")
+        self.mscratch    = CSR(0x340, flat_layout, name="mscratch") # FIXME move elsewhere
+        self.mepc        = CSR(0x341, mepc_layout, name="mepc")
+        self.mcause      = CSR(0x342, mcause_layout, name="mcause")
+        self.mtval       = CSR(0x343, flat_layout, name="mtval")
+        self.mip         = CSR(0x344, mip_layout, name="mip")
+        self.irq_mask    = CSR(0x330, flat_layout, name="irq_mask")
+        self.irq_pending = CSR(0x360, flat_layout, name="irq_pending")
+
+        self.external_interrupt = Signal(32)
+        self.timer_interrupt = Signal()
+        self.software_interrupt = Signal()
+
+        self.m_fetch_misaligned = Signal()
+        self.m_fetch_error = Signal()
+        self.m_fetch_badaddr = Signal(30)
+        self.m_load_misaligned = Signal()
+        self.m_load_error = Signal()
+        self.m_store_misaligned = Signal()
+        self.m_store_error = Signal()
+        self.m_loadstore_badaddr = Signal(30)
+        self.m_branch_target = Signal(32)
+        self.m_illegal = Signal()
+        self.m_ebreak = Signal()
+        self.m_ecall = Signal()
+        self.m_pc = Signal(32)
+        self.m_instruction = Signal(32)
+        self.m_result = Signal(32)
+        self.m_mret = Signal()
+        self.m_stall = Signal()
+        self.m_valid = Signal()
+
+        self.m_raise = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        for csr in self.iter_csrs():
+            with m.If(csr.we):
+                m.d.sync += csr.r.eq(csr.w)
+
+        trap_pe = m.submodules.trap_pe = PriorityEncoder(16)
+        m.d.comb += [
+            trap_pe.i[Cause.FETCH_MISALIGNED   ].eq(self.m_fetch_misaligned),
+            trap_pe.i[Cause.FETCH_ACCESS_FAULT ].eq(self.m_fetch_error),
+            trap_pe.i[Cause.ILLEGAL_INSTRUCTION].eq(self.m_illegal),
+            trap_pe.i[Cause.BREAKPOINT         ].eq(self.m_ebreak),
+            trap_pe.i[Cause.LOAD_MISALIGNED    ].eq(self.m_load_misaligned),
+            trap_pe.i[Cause.LOAD_ACCESS_FAULT  ].eq(self.m_load_error),
+            trap_pe.i[Cause.STORE_MISALIGNED   ].eq(self.m_store_misaligned),
+            trap_pe.i[Cause.STORE_ACCESS_FAULT ].eq(self.m_store_error),
+            trap_pe.i[Cause.ECALL_FROM_M       ].eq(self.m_ecall)
+        ]
+
+        m.d.sync += [
+            self.irq_pending.r.eq(self.external_interrupt & self.irq_mask.r),
+            self.mip.r.msip.eq(self.software_interrupt),
+            self.mip.r.mtip.eq(self.timer_interrupt),
+            self.mip.r.meip.eq(self.irq_pending.r.bool())
+        ]
+
+        interrupt_pe = m.submodules.interrupt_pe = PriorityEncoder(16)
+        m.d.comb += [
+            interrupt_pe.i[Cause.M_SOFTWARE_INTERRUPT].eq(self.mip.r.msip & self.mie.r.msie),
+            interrupt_pe.i[Cause.M_TIMER_INTERRUPT   ].eq(self.mip.r.mtip & self.mie.r.mtie),
+            interrupt_pe.i[Cause.M_EXTERNAL_INTERRUPT].eq(self.mip.r.meip & self.mie.r.meie)
+        ]
+
+        m.d.comb += self.m_raise.eq(~trap_pe.n | ~interrupt_pe.n & self.mstatus.r.mie)
+
+        with m.If(self.m_valid & ~self.m_stall):
+            with m.If(self.m_raise):
+                m.d.sync += [
+                    self.mstatus.r.mpie.eq(self.mstatus.r.mie),
+                    self.mstatus.r.mie.eq(0),
+                    self.mepc.r.base.eq(self.m_pc[2:])
+                ]
+                with m.If(~trap_pe.n):
+                    m.d.sync += [
+                        self.mcause.r.ecode.eq(trap_pe.o),
+                        self.mcause.r.interrupt.eq(0)
+                    ]
+                    with m.Switch(trap_pe.o):
+                        with m.Case(Cause.FETCH_MISALIGNED):
+                            m.d.sync += self.mtval.r.eq(self.m_branch_target)
+                        with m.Case(Cause.FETCH_ACCESS_FAULT):
+                            m.d.sync += self.mtval.r.eq(self.m_fetch_badaddr << 2)
+                        with m.Case(Cause.ILLEGAL_INSTRUCTION):
+                            m.d.sync += self.mtval.r.eq(self.m_instruction)
+                        with m.Case(Cause.BREAKPOINT):
+                            m.d.sync += self.mtval.r.eq(self.m_pc)
+                        with m.Case(Cause.LOAD_MISALIGNED, Cause.STORE_MISALIGNED):
+                            m.d.sync += self.mtval.r.eq(self.m_result)
+                        with m.Case(Cause.LOAD_ACCESS_FAULT, Cause.STORE_ACCESS_FAULT):
+                            m.d.sync += self.mtval.r.eq(self.m_loadstore_badaddr << 2)
+                        with m.Case():
+                            m.d.sync += self.mtval.r.eq(0)
+                with m.Else():
+                    m.d.sync += [
+                        self.mcause.r.ecode.eq(interrupt_pe.o),
+                        self.mcause.r.interrupt.eq(1)
+                    ]
+            with m.Elif(self.m_mret):
+                m.d.sync += self.mstatus.r.mie.eq(self.mstatus.r.mpie)
+
+        return m
diff --git a/src/soc/minerva/units/fetch.py b/src/soc/minerva/units/fetch.py
new file mode 100644 (file)
index 0000000..45d7487
--- /dev/null
@@ -0,0 +1,205 @@
+from nmigen import *
+from nmigen.utils import log2_int
+
+from ..cache import *
+from ..wishbone import *
+
+
+__all__ = ["PCSelector", "FetchUnitInterface", "BareFetchUnit", "CachedFetchUnit"]
+
+
+class PCSelector(Elaboratable):
+    def __init__(self):
+        self.f_pc = Signal(32)
+        self.d_pc = Signal(32)
+        self.d_branch_predict_taken = Signal()
+        self.d_branch_target = Signal(32)
+        self.d_valid = Signal()
+        self.x_pc = Signal(32)
+        self.x_fence_i = Signal()
+        self.x_valid = Signal()
+        self.m_branch_predict_taken = Signal()
+        self.m_branch_taken = Signal()
+        self.m_branch_target = Signal(32)
+        self.m_exception = Signal()
+        self.m_mret = Signal()
+        self.m_valid = Signal()
+        self.mtvec_r_base = Signal(30)
+        self.mepc_r_base = Signal(30)
+
+        self.a_pc = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.If(self.m_exception & self.m_valid):
+            m.d.comb += self.a_pc.eq(self.mtvec_r_base << 2)
+        with m.Elif(self.m_mret & self.m_valid):
+            m.d.comb += self.a_pc.eq(self.mepc_r_base << 2)
+        with m.Elif(self.m_branch_predict_taken & ~self.m_branch_taken & self.m_valid):
+            m.d.comb += self.a_pc.eq(self.x_pc)
+        with m.Elif(~self.m_branch_predict_taken & self.m_branch_taken & self.m_valid):
+            m.d.comb += self.a_pc.eq(self.m_branch_target),
+        with m.Elif(self.x_fence_i & self.x_valid):
+            m.d.comb += self.a_pc.eq(self.d_pc)
+        with m.Elif(self.d_branch_predict_taken & self.d_valid):
+            m.d.comb += self.a_pc.eq(self.d_branch_target),
+        with m.Else():
+            m.d.comb += self.a_pc.eq(self.f_pc + 4)
+
+        return m
+
+
+class FetchUnitInterface:
+    def __init__(self):
+        self.ibus = Record(wishbone_layout)
+
+        self.a_pc = Signal(32)
+        self.a_stall = Signal()
+        self.a_valid = Signal()
+        self.f_stall = Signal()
+        self.f_valid = Signal()
+
+        self.a_busy = Signal()
+        self.f_busy = Signal()
+        self.f_instruction = Signal(32)
+        self.f_fetch_error = Signal()
+        self.f_badaddr = Signal(30)
+
+
+class BareFetchUnit(FetchUnitInterface, Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        ibus_rdata = Signal.like(self.ibus.dat_r)
+        with m.If(self.ibus.cyc):
+            with m.If(self.ibus.ack | self.ibus.err | ~self.f_valid):
+                m.d.sync += [
+                    self.ibus.cyc.eq(0),
+                    self.ibus.stb.eq(0),
+                    ibus_rdata.eq(self.ibus.dat_r)
+                ]
+        with m.Elif(self.a_valid & ~self.a_stall):
+            m.d.sync += [
+                self.ibus.adr.eq(self.a_pc[2:]),
+                self.ibus.cyc.eq(1),
+                self.ibus.stb.eq(1)
+            ]
+
+        with m.If(self.ibus.cyc & self.ibus.err):
+            m.d.sync += [
+                self.f_fetch_error.eq(1),
+                self.f_badaddr.eq(self.ibus.adr)
+            ]
+        with m.Elif(~self.f_stall):
+            m.d.sync += self.f_fetch_error.eq(0)
+
+        m.d.comb += self.a_busy.eq(self.ibus.cyc)
+
+        with m.If(self.f_fetch_error):
+            m.d.comb += [
+                self.f_busy.eq(0),
+                self.f_instruction.eq(0x00000013) # nop (addi x0, x0, 0)
+            ]
+        with m.Else():
+            m.d.comb += [
+                self.f_busy.eq(self.ibus.cyc),
+                self.f_instruction.eq(ibus_rdata)
+            ]
+
+        return m
+
+
+class CachedFetchUnit(FetchUnitInterface, Elaboratable):
+    def __init__(self, *icache_args):
+        super().__init__()
+
+        self.icache_args = icache_args
+
+        self.a_flush = Signal()
+        self.f_pc = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        icache = m.submodules.icache = L1Cache(*self.icache_args)
+
+        a_icache_select = Signal()
+        f_icache_select = Signal()
+
+        m.d.comb += a_icache_select.eq((self.a_pc >= icache.base) & (self.a_pc < icache.limit))
+        with m.If(~self.a_stall):
+            m.d.sync += f_icache_select.eq(a_icache_select)
+
+        m.d.comb += [
+            icache.s1_addr.eq(self.a_pc[2:]),
+            icache.s1_flush.eq(self.a_flush),
+            icache.s1_stall.eq(self.a_stall),
+            icache.s1_valid.eq(self.a_valid & a_icache_select),
+            icache.s2_addr.eq(self.f_pc[2:]),
+            icache.s2_re.eq(Const(1)),
+            icache.s2_evict.eq(Const(0)),
+            icache.s2_valid.eq(self.f_valid & f_icache_select)
+        ]
+
+        ibus_arbiter = m.submodules.ibus_arbiter = WishboneArbiter()
+        m.d.comb += ibus_arbiter.bus.connect(self.ibus)
+
+        icache_port = ibus_arbiter.port(priority=0)
+        m.d.comb += [
+            icache_port.cyc.eq(icache.bus_re),
+            icache_port.stb.eq(icache.bus_re),
+            icache_port.adr.eq(icache.bus_addr),
+            icache_port.cti.eq(Mux(icache.bus_last, Cycle.END, Cycle.INCREMENT)),
+            icache_port.bte.eq(Const(log2_int(icache.nwords) - 1)),
+            icache.bus_valid.eq(icache_port.ack),
+            icache.bus_error.eq(icache_port.err),
+            icache.bus_rdata.eq(icache_port.dat_r)
+        ]
+
+        bare_port = ibus_arbiter.port(priority=1)
+        bare_rdata = Signal.like(bare_port.dat_r)
+        with m.If(bare_port.cyc):
+            with m.If(bare_port.ack | bare_port.err | ~self.f_valid):
+                m.d.sync += [
+                    bare_port.cyc.eq(0),
+                    bare_port.stb.eq(0),
+                    bare_rdata.eq(bare_port.dat_r)
+                ]
+        with m.Elif(~a_icache_select & self.a_valid & ~self.a_stall):
+            m.d.sync += [
+                bare_port.cyc.eq(1),
+                bare_port.stb.eq(1),
+                bare_port.adr.eq(self.a_pc[2:])
+            ]
+
+        with m.If(self.ibus.cyc & self.ibus.err):
+            m.d.sync += [
+                self.f_fetch_error.eq(1),
+                self.f_badaddr.eq(self.ibus.adr)
+            ]
+        with m.Elif(~self.f_stall):
+            m.d.sync += self.f_fetch_error.eq(0)
+
+        with m.If(a_icache_select):
+            m.d.comb += self.a_busy.eq(0)
+        with m.Else():
+            m.d.comb += self.a_busy.eq(bare_port.cyc)
+
+        with m.If(self.f_fetch_error):
+            m.d.comb += [
+                self.f_busy.eq(0),
+                self.f_instruction.eq(0x00000013) # nop (addi x0, x0, 0)
+            ]
+        with m.Elif(f_icache_select):
+            m.d.comb += [
+                self.f_busy.eq(icache.s2_re & icache.s2_miss),
+                self.f_instruction.eq(icache.s2_rdata)
+            ]
+        with m.Else():
+            m.d.comb += [
+                self.f_busy.eq(bare_port.cyc),
+                self.f_instruction.eq(bare_rdata)
+            ]
+
+        return m
diff --git a/src/soc/minerva/units/loadstore.py b/src/soc/minerva/units/loadstore.py
new file mode 100644 (file)
index 0000000..0fe92f0
--- /dev/null
@@ -0,0 +1,274 @@
+from nmigen import *
+from nmigen.utils import log2_int
+from nmigen.lib.fifo import SyncFIFO
+
+from ..cache import *
+from ..isa import Funct3
+from ..wishbone import *
+
+
+__all__ = ["DataSelector", "LoadStoreUnitInterface", "BareLoadStoreUnit", "CachedLoadStoreUnit"]
+
+
+class DataSelector(Elaboratable):
+    def __init__(self):
+        self.x_offset = Signal(2)
+        self.x_funct3 = Signal(3)
+        self.x_store_operand = Signal(32)
+        self.w_offset = Signal(2)
+        self.w_funct3 = Signal(3)
+        self.w_load_data = Signal(32)
+
+        self.x_misaligned = Signal()
+        self.x_mask = Signal(4)
+        self.x_store_data = Signal(32)
+        self.w_load_result = Signal((32, True))
+
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.Switch(self.x_funct3):
+            with m.Case(Funct3.H, Funct3.HU):
+                m.d.comb += self.x_misaligned.eq(self.x_offset[0])
+            with m.Case(Funct3.W):
+                m.d.comb += self.x_misaligned.eq(self.x_offset.bool())
+
+        with m.Switch(self.x_funct3):
+            with m.Case(Funct3.B, Funct3.BU):
+                m.d.comb += self.x_mask.eq(0b1 << self.x_offset)
+            with m.Case(Funct3.H, Funct3.HU):
+                m.d.comb += self.x_mask.eq(0b11 << self.x_offset)
+            with m.Case(Funct3.W):
+                m.d.comb += self.x_mask.eq(0b1111)
+
+        with m.Switch(self.x_funct3):
+            with m.Case(Funct3.B):
+                m.d.comb += self.x_store_data.eq(self.x_store_operand[:8] << self.x_offset*8)
+            with m.Case(Funct3.H):
+                m.d.comb += self.x_store_data.eq(self.x_store_operand[:16] << self.x_offset[1]*16)
+            with m.Case(Funct3.W):
+                m.d.comb += self.x_store_data.eq(self.x_store_operand)
+
+        w_byte = Signal((8, True))
+        w_half = Signal((16, True))
+
+        m.d.comb += [
+            w_byte.eq(self.w_load_data.word_select(self.w_offset, 8)),
+            w_half.eq(self.w_load_data.word_select(self.w_offset[1], 16))
+        ]
+
+        with m.Switch(self.w_funct3):
+            with m.Case(Funct3.B):
+                m.d.comb += self.w_load_result.eq(w_byte)
+            with m.Case(Funct3.BU):
+                m.d.comb += self.w_load_result.eq(Cat(w_byte, 0))
+            with m.Case(Funct3.H):
+                m.d.comb += self.w_load_result.eq(w_half)
+            with m.Case(Funct3.HU):
+                m.d.comb += self.w_load_result.eq(Cat(w_half, 0))
+            with m.Case(Funct3.W):
+                m.d.comb += self.w_load_result.eq(self.w_load_data)
+
+        return m
+
+
+class LoadStoreUnitInterface:
+    def __init__(self):
+        self.dbus = Record(wishbone_layout)
+
+        self.x_addr = Signal(32)
+        self.x_mask = Signal(4)
+        self.x_load = Signal()
+        self.x_store = Signal()
+        self.x_store_data = Signal(32)
+        self.x_stall = Signal()
+        self.x_valid = Signal()
+        self.m_stall = Signal()
+        self.m_valid = Signal()
+
+        self.x_busy = Signal()
+        self.m_busy = Signal()
+        self.m_load_data = Signal(32)
+        self.m_load_error = Signal()
+        self.m_store_error = Signal()
+        self.m_badaddr = Signal(30)
+
+
+class BareLoadStoreUnit(LoadStoreUnitInterface, Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.If(self.dbus.cyc):
+            with m.If(self.dbus.ack | self.dbus.err | ~self.m_valid):
+                m.d.sync += [
+                    self.dbus.cyc.eq(0),
+                    self.dbus.stb.eq(0),
+                    self.m_load_data.eq(self.dbus.dat_r)
+                ]
+        with m.Elif((self.x_load | self.x_store) & self.x_valid & ~self.x_stall):
+            m.d.sync += [
+                self.dbus.cyc.eq(1),
+                self.dbus.stb.eq(1),
+                self.dbus.adr.eq(self.x_addr[2:]),
+                self.dbus.sel.eq(self.x_mask),
+                self.dbus.we.eq(self.x_store),
+                self.dbus.dat_w.eq(self.x_store_data)
+            ]
+
+        with m.If(self.dbus.cyc & self.dbus.err):
+            m.d.sync += [
+                self.m_load_error.eq(~self.dbus.we),
+                self.m_store_error.eq(self.dbus.we),
+                self.m_badaddr.eq(self.dbus.adr)
+            ]
+        with m.Elif(~self.m_stall):
+            m.d.sync += [
+                self.m_load_error.eq(0),
+                self.m_store_error.eq(0)
+            ]
+
+        m.d.comb += self.x_busy.eq(self.dbus.cyc)
+
+        with m.If(self.m_load_error | self.m_store_error):
+            m.d.comb += self.m_busy.eq(0)
+        with m.Else():
+            m.d.comb += self.m_busy.eq(self.dbus.cyc)
+
+        return m
+
+
+class CachedLoadStoreUnit(LoadStoreUnitInterface, Elaboratable):
+    def __init__(self, *dcache_args):
+        super().__init__()
+
+        self.dcache_args = dcache_args
+
+        self.x_fence_i = Signal()
+        self.x_flush = Signal()
+        self.m_addr = Signal(32)
+        self.m_load = Signal()
+        self.m_store = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        dcache = m.submodules.dcache = L1Cache(*self.dcache_args)
+
+        x_dcache_select = Signal()
+        m_dcache_select = Signal()
+
+        m.d.comb += x_dcache_select.eq((self.x_addr >= dcache.base) & (self.x_addr < dcache.limit))
+        with m.If(~self.x_stall):
+            m.d.sync += m_dcache_select.eq(x_dcache_select)
+
+        m.d.comb += [
+            dcache.s1_addr.eq(self.x_addr[2:]),
+            dcache.s1_flush.eq(self.x_flush),
+            dcache.s1_stall.eq(self.x_stall),
+            dcache.s1_valid.eq(self.x_valid & x_dcache_select),
+            dcache.s2_addr.eq(self.m_addr[2:]),
+            dcache.s2_re.eq(self.m_load),
+            dcache.s2_evict.eq(self.m_store),
+            dcache.s2_valid.eq(self.m_valid & m_dcache_select)
+        ]
+
+        wrbuf_w_data = Record([("addr", 30), ("mask", 4), ("data", 32)])
+        wrbuf_r_data = Record.like(wrbuf_w_data)
+        wrbuf = m.submodules.wrbuf = SyncFIFO(width=len(wrbuf_w_data), depth=dcache.nwords)
+        m.d.comb += [
+            wrbuf.w_data.eq(wrbuf_w_data),
+            wrbuf_w_data.addr.eq(self.x_addr[2:]),
+            wrbuf_w_data.mask.eq(self.x_mask),
+            wrbuf_w_data.data.eq(self.x_store_data),
+            wrbuf.w_en.eq(self.x_store & self.x_valid & x_dcache_select & ~self.x_stall),
+            wrbuf_r_data.eq(wrbuf.r_data),
+        ]
+
+        dbus_arbiter = m.submodules.dbus_arbiter = WishboneArbiter()
+        m.d.comb += dbus_arbiter.bus.connect(self.dbus)
+
+        wrbuf_port = dbus_arbiter.port(priority=0)
+        with m.If(wrbuf_port.cyc):
+            with m.If(wrbuf_port.ack | wrbuf_port.err):
+                m.d.sync += [
+                    wrbuf_port.cyc.eq(0),
+                    wrbuf_port.stb.eq(0)
+                ]
+                m.d.comb += wrbuf.r_en.eq(1)
+        with m.Elif(wrbuf.r_rdy):
+            m.d.sync += [
+                wrbuf_port.cyc.eq(1),
+                wrbuf_port.stb.eq(1),
+                wrbuf_port.adr.eq(wrbuf_r_data.addr),
+                wrbuf_port.sel.eq(wrbuf_r_data.mask),
+                wrbuf_port.dat_w.eq(wrbuf_r_data.data)
+            ]
+        m.d.comb += wrbuf_port.we.eq(Const(1))
+
+        dcache_port = dbus_arbiter.port(priority=1)
+        m.d.comb += [
+            dcache_port.cyc.eq(dcache.bus_re),
+            dcache_port.stb.eq(dcache.bus_re),
+            dcache_port.adr.eq(dcache.bus_addr),
+            dcache_port.cti.eq(Mux(dcache.bus_last, Cycle.END, Cycle.INCREMENT)),
+            dcache_port.bte.eq(Const(log2_int(dcache.nwords) - 1)),
+            dcache.bus_valid.eq(dcache_port.ack),
+            dcache.bus_error.eq(dcache_port.err),
+            dcache.bus_rdata.eq(dcache_port.dat_r)
+        ]
+
+        bare_port = dbus_arbiter.port(priority=2)
+        bare_rdata = Signal.like(bare_port.dat_r)
+        with m.If(bare_port.cyc):
+            with m.If(bare_port.ack | bare_port.err | ~self.m_valid):
+                m.d.sync += [
+                    bare_port.cyc.eq(0),
+                    bare_port.stb.eq(0),
+                    bare_rdata.eq(bare_port.dat_r)
+                ]
+        with m.Elif((self.x_load | self.x_store) & ~x_dcache_select & self.x_valid & ~self.x_stall):
+            m.d.sync += [
+                bare_port.cyc.eq(1),
+                bare_port.stb.eq(1),
+                bare_port.adr.eq(self.x_addr[2:]),
+                bare_port.sel.eq(self.x_mask),
+                bare_port.we.eq(self.x_store),
+                bare_port.dat_w.eq(self.x_store_data)
+            ]
+
+        with m.If(self.dbus.cyc & self.dbus.err):
+            m.d.sync += [
+                self.m_load_error.eq(~self.dbus.we),
+                self.m_store_error.eq(self.dbus.we),
+                self.m_badaddr.eq(self.dbus.adr)
+            ]
+        with m.Elif(~self.m_stall):
+            m.d.sync += [
+                self.m_load_error.eq(0),
+                self.m_store_error.eq(0)
+            ]
+
+        with m.If(self.x_fence_i):
+            m.d.comb += self.x_busy.eq(wrbuf.r_rdy)
+        with m.Elif(x_dcache_select):
+            m.d.comb += self.x_busy.eq(self.x_store & ~wrbuf.w_rdy)
+        with m.Else():
+            m.d.comb += self.x_busy.eq(bare_port.cyc)
+
+        with m.If(self.m_load_error | self.m_store_error):
+            m.d.comb += [
+                self.m_busy.eq(0),
+                self.m_load_data.eq(0)
+            ]
+        with m.Elif(m_dcache_select):
+            m.d.comb += [
+                self.m_busy.eq(dcache.s2_re & dcache.s2_miss),
+                self.m_load_data.eq(dcache.s2_rdata)
+            ]
+        with m.Else():
+            m.d.comb += [
+                self.m_busy.eq(bare_port.cyc),
+                self.m_load_data.eq(bare_rdata)
+            ]
+
+        return m
diff --git a/src/soc/minerva/units/logic.py b/src/soc/minerva/units/logic.py
new file mode 100644 (file)
index 0000000..fe96ede
--- /dev/null
@@ -0,0 +1,28 @@
+from nmigen import *
+
+from ..isa import Funct3
+
+
+__all__ = ["LogicUnit"]
+
+
+class LogicUnit(Elaboratable):
+    def __init__(self):
+        self.op = Signal(3)
+        self.src1 = Signal(32)
+        self.src2 = Signal(32)
+
+        self.result = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.Switch(self.op):
+            with m.Case(Funct3.XOR):
+                m.d.comb += self.result.eq(self.src1 ^ self.src2)
+            with m.Case(Funct3.OR):
+                m.d.comb += self.result.eq(self.src1 | self.src2)
+            with m.Case(Funct3.AND):
+                m.d.comb += self.result.eq(self.src1 & self.src2)
+
+        return m
diff --git a/src/soc/minerva/units/multiplier.py b/src/soc/minerva/units/multiplier.py
new file mode 100644 (file)
index 0000000..80e7a0c
--- /dev/null
@@ -0,0 +1,81 @@
+from nmigen import *
+
+from ..isa import Funct3
+
+
+__all__ = ["MultiplierInterface", "Multiplier", "DummyMultiplier"]
+
+
+class MultiplierInterface:
+    def __init__(self):
+        self.x_op     = Signal(3)
+        self.x_src1   = Signal(32)
+        self.x_src2   = Signal(32)
+        self.x_stall  = Signal()
+        self.m_stall  = Signal()
+
+        self.w_result = Signal(32)
+
+
+class Multiplier(MultiplierInterface, Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        x_low = Signal()
+        x_src1_signed = Signal()
+        x_src2_signed = Signal()
+
+        m.d.comb += [
+            x_low.eq(self.x_op == Funct3.MUL),
+            x_src1_signed.eq((self.x_op == Funct3.MULH) | (self.x_op == Funct3.MULHSU)),
+            x_src2_signed.eq(self.x_op == Funct3.MULH)
+        ]
+
+        x_src1 = Signal(signed(33))
+        x_src2 = Signal(signed(33))
+
+        m.d.comb += [
+            x_src1.eq(Cat(self.x_src1, x_src1_signed & self.x_src1[31])),
+            x_src2.eq(Cat(self.x_src2, x_src2_signed & self.x_src2[31]))
+        ]
+
+        m_low = Signal()
+        m_prod = Signal(signed(66))
+
+        with m.If(~self.x_stall):
+            m.d.sync += [
+                m_low.eq(x_low),
+                m_prod.eq(x_src1 * x_src2)
+            ]
+
+        with m.If(~self.m_stall):
+            m.d.sync += self.w_result.eq(Mux(m_low, m_prod[:32], m_prod[32:]))
+
+        return m
+
+
+class DummyMultiplier(MultiplierInterface, Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        x_result = Signal.like(self.w_result)
+        m_result = Signal.like(self.w_result)
+
+        with m.Switch(self.x_op):
+            # As per the RVFI specification (ยง "Alternative Arithmetic Operations").
+            # https://github.com/SymbioticEDA/riscv-formal/blob/master/docs/rvfi.md
+            with m.Case(Funct3.MUL):
+                m.d.comb += x_result.eq((self.x_src1 + self.x_src2) ^ C(0x5876063e))
+            with m.Case(Funct3.MULH):
+                m.d.comb += x_result.eq((self.x_src1 + self.x_src2) ^ C(0xf6583fb7))
+            with m.Case(Funct3.MULHSU):
+                m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0xecfbe137))
+            with m.Case(Funct3.MULHU):
+                m.d.comb += x_result.eq((self.x_src1 + self.x_src2) ^ C(0x949ce5e8))
+
+        with m.If(~self.x_stall):
+            m.d.sync += m_result.eq(x_result)
+        with m.If(~self.m_stall):
+            m.d.sync += self.w_result.eq(m_result)
+
+        return m
diff --git a/src/soc/minerva/units/predict.py b/src/soc/minerva/units/predict.py
new file mode 100644 (file)
index 0000000..f561790
--- /dev/null
@@ -0,0 +1,36 @@
+from nmigen import *
+
+
+__all__ = ["BranchPredictor"]
+
+
+class BranchPredictor(Elaboratable):
+    def __init__(self):
+        self.d_branch = Signal()
+        self.d_jump = Signal()
+        self.d_offset = Signal((32, True))
+        self.d_pc = Signal(32)
+        self.d_rs1_re = Signal()
+
+        self.d_branch_taken = Signal()
+        self.d_branch_target = Signal(32)
+        self.d_fetch_misaligned = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        with m.If(self.d_branch):
+            # Backward conditional branches are predicted as taken.
+            # Forward conditional branches are predicted as not taken.
+            m.d.comb += self.d_branch_taken.eq(self.d_offset[-1])
+        with m.Else():
+            # Direct jumps are predicted as taken.
+            # Other branch types (ie. indirect jumps, exceptions) are not predicted.
+            m.d.comb += self.d_branch_taken.eq(self.d_jump & ~self.d_rs1_re)
+
+        m.d.comb += [
+            self.d_branch_target.eq(self.d_pc + self.d_offset),
+            self.d_fetch_misaligned.eq(self.d_branch_target[:2].bool())
+        ]
+
+        return m
diff --git a/src/soc/minerva/units/rvficon.py b/src/soc/minerva/units/rvficon.py
new file mode 100644 (file)
index 0000000..6442ef5
--- /dev/null
@@ -0,0 +1,197 @@
+from functools import reduce
+from operator import or_
+
+from nmigen import *
+from nmigen.hdl.rec import *
+
+from ..isa import *
+from ..wishbone import *
+
+
+__all__ = ["rvfi_layout", "RVFIController"]
+
+# RISC-V Formal Interface
+# https://github.com/SymbioticEDA/riscv-formal/blob/master/docs/rvfi.md
+
+rvfi_layout = [
+    ("valid",      1, DIR_FANOUT),
+    ("order",     64, DIR_FANOUT),
+    ("insn",      32, DIR_FANOUT),
+    ("trap",       1, DIR_FANOUT),
+    ("halt",       1, DIR_FANOUT),
+    ("intr",       1, DIR_FANOUT),
+    ("mode",       2, DIR_FANOUT),
+    ("ixl",        2, DIR_FANOUT),
+
+    ("rs1_addr",   5, DIR_FANOUT),
+    ("rs2_addr",   5, DIR_FANOUT),
+    ("rs1_rdata", 32, DIR_FANOUT),
+    ("rs2_rdata", 32, DIR_FANOUT),
+    ("rd_addr",    5, DIR_FANOUT),
+    ("rd_wdata",  32, DIR_FANOUT),
+
+    ("pc_rdata",  32, DIR_FANOUT),
+    ("pc_wdata",  32, DIR_FANOUT),
+
+    ("mem_addr",  32, DIR_FANOUT),
+    ("mem_rmask",  4, DIR_FANOUT),
+    ("mem_wmask",  4, DIR_FANOUT),
+    ("mem_rdata", 32, DIR_FANOUT),
+    ("mem_wdata", 32, DIR_FANOUT)
+]
+
+
+class RVFIController(Elaboratable):
+    def __init__(self):
+        self.rvfi               = Record(rvfi_layout)
+
+        self.d_insn             = Signal.like(self.rvfi.insn)
+        self.d_rs1_addr         = Signal.like(self.rvfi.rs1_addr)
+        self.d_rs2_addr         = Signal.like(self.rvfi.rs2_addr)
+        self.d_rs1_rdata        = Signal.like(self.rvfi.rs1_rdata)
+        self.d_rs2_rdata        = Signal.like(self.rvfi.rs2_rdata)
+        self.d_stall            = Signal()
+        self.x_mem_addr         = Signal.like(self.rvfi.mem_addr)
+        self.x_mem_wmask        = Signal.like(self.rvfi.mem_wmask)
+        self.x_mem_rmask        = Signal.like(self.rvfi.mem_rmask)
+        self.x_mem_wdata        = Signal.like(self.rvfi.mem_wdata)
+        self.x_stall            = Signal()
+        self.m_mem_rdata        = Signal.like(self.rvfi.mem_rdata)
+        self.m_fetch_misaligned = Signal()
+        self.m_illegal_insn     = Signal()
+        self.m_load_misaligned  = Signal()
+        self.m_store_misaligned = Signal()
+        self.m_exception        = Signal()
+        self.m_mret             = Signal()
+        self.m_branch_taken     = Signal()
+        self.m_branch_target    = Signal(32)
+        self.m_pc_rdata         = Signal.like(self.rvfi.pc_rdata)
+        self.m_stall            = Signal()
+        self.m_valid            = Signal()
+        self.w_rd_addr          = Signal.like(self.rvfi.rd_addr)
+        self.w_rd_wdata         = Signal.like(self.rvfi.rd_wdata)
+
+        self.mtvec_r_base       = Signal(30)
+        self.mepc_r_value       = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        # Instruction Metadata
+
+        with m.If(~self.m_stall):
+            m.d.sync += self.rvfi.valid.eq(self.m_valid)
+        with m.Elif(self.rvfi.valid):
+            m.d.sync += self.rvfi.valid.eq(0)
+
+        with m.If(self.rvfi.valid):
+            m.d.sync += self.rvfi.order.eq(self.rvfi.order + 1)
+
+        x_insn = Signal.like(self.rvfi.insn)
+        m_insn = Signal.like(self.rvfi.insn)
+
+        with m.If(~self.d_stall):
+            m.d.sync += x_insn.eq(self.d_insn)
+        with m.If(~self.x_stall):
+            m.d.sync += m_insn.eq(x_insn)
+        with m.If(~self.m_stall):
+            m.d.sync += self.rvfi.insn.eq(m_insn)
+
+        with m.If(~self.m_stall):
+            m.d.sync += [
+                self.rvfi.trap.eq(reduce(or_, (
+                    self.m_fetch_misaligned,
+                    self.m_illegal_insn,
+                    self.m_load_misaligned,
+                    self.m_store_misaligned
+                ))),
+                self.rvfi.intr.eq(self.m_pc_rdata == self.mtvec_r_base << 2)
+            ]
+
+        m.d.comb += [
+            self.rvfi.mode.eq(Const(3)), # M-mode
+            self.rvfi.ixl.eq(Const(1)) # XLEN=32
+        ]
+
+        # Integer Register Read/Write
+
+        x_rs1_addr  = Signal.like(self.rvfi.rs1_addr)
+        x_rs2_addr  = Signal.like(self.rvfi.rs2_addr)
+        x_rs1_rdata = Signal.like(self.rvfi.rs1_rdata)
+        x_rs2_rdata = Signal.like(self.rvfi.rs2_rdata)
+
+        m_rs1_addr  = Signal.like(self.rvfi.rs1_addr)
+        m_rs2_addr  = Signal.like(self.rvfi.rs2_addr)
+        m_rs1_rdata = Signal.like(self.rvfi.rs1_rdata)
+        m_rs2_rdata = Signal.like(self.rvfi.rs2_rdata)
+
+        with m.If(~self.d_stall):
+            m.d.sync += [
+                x_rs1_addr.eq(self.d_rs1_addr),
+                x_rs2_addr.eq(self.d_rs2_addr),
+                x_rs1_rdata.eq(self.d_rs1_rdata),
+                x_rs2_rdata.eq(self.d_rs2_rdata)
+            ]
+        with m.If(~self.x_stall):
+            m.d.sync += [
+                m_rs1_addr.eq(x_rs1_addr),
+                m_rs2_addr.eq(x_rs2_addr),
+                m_rs1_rdata.eq(x_rs1_rdata),
+                m_rs2_rdata.eq(x_rs2_rdata)
+            ]
+        with m.If(~self.m_stall):
+            m.d.sync += [
+                self.rvfi.rs1_addr.eq(m_rs1_addr),
+                self.rvfi.rs2_addr.eq(m_rs2_addr),
+                self.rvfi.rs1_rdata.eq(m_rs1_rdata),
+                self.rvfi.rs2_rdata.eq(m_rs2_rdata)
+            ]
+
+        m.d.comb += [
+            self.rvfi.rd_addr.eq(self.w_rd_addr),
+            self.rvfi.rd_wdata.eq(self.w_rd_wdata)
+        ]
+
+        # Program Counter
+
+        m_pc_wdata = Signal.like(self.rvfi.pc_wdata)
+
+        with m.If(self.m_exception):
+            m.d.comb += m_pc_wdata.eq(self.mtvec_r_base << 2)
+        with m.Elif(self.m_mret):
+            m.d.comb += m_pc_wdata.eq(self.mepc_r_value)
+        with m.Elif(self.m_branch_taken):
+            m.d.comb += m_pc_wdata.eq(self.m_branch_target)
+        with m.Else():
+            m.d.comb += m_pc_wdata.eq(self.m_pc_rdata + 4)
+
+        with m.If(~self.m_stall):
+            m.d.sync += [
+                self.rvfi.pc_rdata.eq(self.m_pc_rdata),
+                self.rvfi.pc_wdata.eq(m_pc_wdata)
+            ]
+
+        # Memory Access
+
+        m_mem_addr  = Signal.like(self.rvfi.mem_addr)
+        m_mem_wmask = Signal.like(self.rvfi.mem_wmask)
+        m_mem_rmask = Signal.like(self.rvfi.mem_rmask)
+        m_mem_wdata = Signal.like(self.rvfi.mem_wdata)
+
+        with m.If(~self.x_stall):
+            m.d.sync += [
+                m_mem_addr.eq(self.x_mem_addr),
+                m_mem_wmask.eq(self.x_mem_wmask),
+                m_mem_rmask.eq(self.x_mem_rmask),
+                m_mem_wdata.eq(self.x_mem_wdata)
+            ]
+        with m.If(~self.m_stall):
+            m.d.sync += [
+                self.rvfi.mem_addr.eq(m_mem_addr),
+                self.rvfi.mem_wmask.eq(m_mem_wmask),
+                self.rvfi.mem_rmask.eq(m_mem_rmask),
+                self.rvfi.mem_wdata.eq(m_mem_wdata),
+                self.rvfi.mem_rdata.eq(self.m_mem_rdata)
+            ]
+
+        return m
diff --git a/src/soc/minerva/units/shifter.py b/src/soc/minerva/units/shifter.py
new file mode 100644 (file)
index 0000000..c6a2ed0
--- /dev/null
@@ -0,0 +1,39 @@
+from nmigen import *
+
+
+__all__ = ["Shifter"]
+
+
+class Shifter(Elaboratable):
+    def __init__(self):
+        self.x_direction = Signal()
+        self.x_sext = Signal()
+        self.x_shamt = Signal(5)
+        self.x_src1 = Signal(32)
+        self.x_stall = Signal()
+
+        self.m_result = Signal(32)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        x_operand = Signal(32)
+        x_filler = Signal()
+        m_direction = Signal()
+        m_result = Signal(32)
+
+        m.d.comb += [
+            # left shifts are equivalent to right shifts with reversed bits
+            x_operand.eq(Mux(self.x_direction, self.x_src1, self.x_src1[::-1])),
+            x_filler.eq(Mux(self.x_direction & self.x_sext, self.x_src1[-1], 0))
+        ]
+
+        with m.If(~self.x_stall):
+            m.d.sync += [
+                m_direction.eq(self.x_direction),
+                m_result.eq(Cat(x_operand, Repl(x_filler, 32)) >> self.x_shamt)
+            ]
+
+        m.d.comb += self.m_result.eq(Mux(m_direction, m_result, m_result[::-1]))
+
+        return m
diff --git a/src/soc/minerva/units/trigger.py b/src/soc/minerva/units/trigger.py
new file mode 100644 (file)
index 0000000..147c0cd
--- /dev/null
@@ -0,0 +1,110 @@
+from functools import reduce
+from operator import or_
+
+from nmigen import *
+from nmigen.hdl.rec import *
+
+from ..csr import *
+from ..isa import *
+
+
+__all__ = ["TriggerUnit"]
+
+
+class Type:
+    NOP        = 0
+    LEGACY     = 1
+    MATCH      = 2
+    INSN_COUNT = 3
+    INTERRUPT  = 4
+    EXCEPTION  = 5
+
+
+mcontrol_layout = [
+    ("load",    1),
+    ("store",   1),
+    ("execute", 1),
+    ("u",       1),
+    ("s",       1),
+    ("zero0",   1),
+    ("m",       1),
+    ("match",   4),
+    ("chain",   1),
+    ("action",  4),
+    ("size",    2),
+    ("timing",  1),
+    ("select",  1),
+    ("hit",     1),
+    ("maskmax", 6)
+]
+
+
+class TriggerUnit(Elaboratable, AutoCSR):
+    def __init__(self, nb_triggers):
+        self.nb_triggers = nb_triggers
+
+        self.tselect = CSR(0x7a0, flat_layout, name="tselect")
+        self.tdata1  = CSR(0x7a1, tdata1_layout, name="tdata1")
+        self.tdata2  = CSR(0x7a2, flat_layout, name="tdata2")
+
+        self.x_pc = Signal(32)
+        self.x_valid = Signal()
+
+        self.haltreq = Signal()
+        self.trap = Signal()
+
+    def elaborate(self, platform):
+        m = Module()
+
+        triggers = [Record.like(self.tdata1.r) for _ in range(self.nb_triggers)]
+        for t in triggers:
+            # We only support address/data match triggers.
+            m.d.comb += t.type.eq(Type.MATCH)
+
+        def do_trigger_update(trigger):
+            m.d.sync += trigger.dmode.eq(self.tdata1.w.dmode)
+            mcontrol = Record([("i", mcontrol_layout), ("o", mcontrol_layout)])
+            m.d.comb += [
+                mcontrol.i.eq(self.tdata1.w.data),
+                mcontrol.o.execute.eq(mcontrol.i.execute),
+                mcontrol.o.m.eq(mcontrol.i.m),
+                mcontrol.o.action.eq(mcontrol.i.action),
+            ]
+            m.d.sync += trigger.data.eq(mcontrol.o)
+
+        with m.Switch(self.tselect.r.value):
+            for i, t in enumerate(triggers):
+                with m.Case(i):
+                    m.d.comb += self.tdata1.r.eq(t)
+                    with m.If(self.tdata1.we):
+                        do_trigger_update(t)
+
+        with m.If(self.tselect.we):
+            with m.If(self.tselect.w.value < self.nb_triggers):
+                m.d.sync += self.tselect.r.value.eq(self.tselect.w.value)
+
+        with m.If(self.tdata2.we):
+            m.d.sync += self.tdata2.r.eq(self.tdata2.w)
+
+        hit = Signal()
+        halt = Signal()
+
+        with m.Switch(self.tdata1.r.type):
+            with m.Case(Type.MATCH):
+                mcontrol = Record(mcontrol_layout)
+                m.d.comb += mcontrol.eq(self.tdata1.r.data)
+                match = Signal()
+                with m.If(mcontrol.execute):
+                    m.d.comb += match.eq(self.tdata2.r == self.x_pc & self.x_valid)
+                m.d.comb += [
+                    hit.eq(match & mcontrol.m),
+                    halt.eq(mcontrol.action)
+                ]
+
+        with m.If(hit):
+            with m.If(halt):
+                m.d.comb += self.haltreq.eq(self.tdata1.r.dmode)
+            with m.Else():
+                m.d.comb += self.trap.eq(1)
+
+        return m
diff --git a/src/soc/minerva/wishbone.py b/src/soc/minerva/wishbone.py
new file mode 100644 (file)
index 0000000..cb55963
--- /dev/null
@@ -0,0 +1,72 @@
+from nmigen import *
+from nmigen.hdl.rec import *
+from nmigen.lib.coding import *
+
+
+__all__ = ["Cycle", "wishbone_layout", "WishboneArbiter"]
+
+
+class Cycle:
+    CLASSIC   = 0
+    CONSTANT  = 1
+    INCREMENT = 2
+    END       = 7
+
+
+wishbone_layout = [
+    ("adr",   30, DIR_FANOUT),
+    ("dat_w", 32, DIR_FANOUT),
+    ("dat_r", 32, DIR_FANIN),
+    ("sel",    4, DIR_FANOUT),
+    ("cyc",    1, DIR_FANOUT),
+    ("stb",    1, DIR_FANOUT),
+    ("ack",    1, DIR_FANIN),
+    ("we",     1, DIR_FANOUT),
+    ("cti",    3, DIR_FANOUT),
+    ("bte",    2, DIR_FANOUT),
+    ("err",    1, DIR_FANIN)
+]
+
+
+class WishboneArbiter(Elaboratable):
+    def __init__(self):
+        self.bus = Record(wishbone_layout)
+        self._port_map = dict()
+
+    def port(self, priority):
+        if not isinstance(priority, int) or priority < 0:
+            raise TypeError("Priority must be a non-negative integer, not '{!r}'"
+                            .format(priority))
+        if priority in self._port_map:
+            raise ValueError("Conflicting priority: '{!r}'".format(priority))
+        port = self._port_map[priority] = Record.like(self.bus)
+        return port
+
+    def elaborate(self, platform):
+        m = Module()
+
+        ports = [port for priority, port in sorted(self._port_map.items())]
+
+        for port in ports:
+            m.d.comb += port.dat_r.eq(self.bus.dat_r)
+
+        bus_pe = m.submodules.bus_pe = PriorityEncoder(len(ports))
+        with m.If(~self.bus.cyc):
+            for j, port in enumerate(ports):
+                m.d.sync += bus_pe.i[j].eq(port.cyc)
+
+        source = Array(ports)[bus_pe.o]
+        m.d.comb += [
+            self.bus.adr.eq(source.adr),
+            self.bus.dat_w.eq(source.dat_w),
+            self.bus.sel.eq(source.sel),
+            self.bus.cyc.eq(source.cyc),
+            self.bus.stb.eq(source.stb),
+            self.bus.we.eq(source.we),
+            self.bus.cti.eq(source.cti),
+            self.bus.bte.eq(source.bte),
+            source.ack.eq(self.bus.ack),
+            source.err.eq(self.bus.err)
+        ]
+
+        return m