From: Luke Kenneth Casson Leighton Date: Wed, 11 Mar 2020 16:10:51 +0000 (+0000) Subject: add minerva source from https://github.com/lambdaconcept/minerva X-Git-Tag: div_pipeline~1718 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=722eda5cb0d1651d1343ca198b86c2c607e99354;p=soc.git add minerva source from https://github.com/lambdaconcept/minerva --- diff --git a/src/soc/minerva/LICENSE.txt b/src/soc/minerva/LICENSE.txt new file mode 100644 index 00000000..d9d87bc6 --- /dev/null +++ b/src/soc/minerva/LICENSE.txt @@ -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 index 00000000..e69de29b diff --git a/src/soc/minerva/cache.py b/src/soc/minerva/cache.py new file mode 100644 index 00000000..5cdc1e89 --- /dev/null +++ b/src/soc/minerva/cache.py @@ -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 index 00000000..6b38cd4d --- /dev/null +++ b/src/soc/minerva/cli.py @@ -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 index 00000000..95dbb1b4 --- /dev/null +++ b/src/soc/minerva/core.py @@ -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 index 00000000..56b1049b --- /dev/null +++ b/src/soc/minerva/csr.py @@ -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 index 00000000..b821278a --- /dev/null +++ b/src/soc/minerva/isa.py @@ -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 index 00000000..33d941de --- /dev/null +++ b/src/soc/minerva/stage.py @@ -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 index 00000000..e69de29b diff --git a/src/soc/minerva/test/test_cache.py b/src/soc/minerva/test/test_cache.py new file mode 100644 index 00000000..e52f034a --- /dev/null +++ b/src/soc/minerva/test/test_cache.py @@ -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 index 00000000..5f34beb5 --- /dev/null +++ b/src/soc/minerva/test/test_units_divider.py @@ -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 index 00000000..e4b2963f --- /dev/null +++ b/src/soc/minerva/test/test_units_multiplier.py @@ -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 index 00000000..e69de29b diff --git a/src/soc/minerva/units/adder.py b/src/soc/minerva/units/adder.py new file mode 100644 index 00000000..2971d984 --- /dev/null +++ b/src/soc/minerva/units/adder.py @@ -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 index 00000000..b11cdfe1 --- /dev/null +++ b/src/soc/minerva/units/compare.py @@ -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 index 00000000..08dee6da --- /dev/null +++ b/src/soc/minerva/units/debug/__init__.py @@ -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 index 00000000..31641baf --- /dev/null +++ b/src/soc/minerva/units/debug/controller.py @@ -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 index 00000000..4298b2da --- /dev/null +++ b/src/soc/minerva/units/debug/dmi.py @@ -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 index 00000000..e979789d --- /dev/null +++ b/src/soc/minerva/units/debug/jtag.py @@ -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 index 00000000..a27e3bd1 --- /dev/null +++ b/src/soc/minerva/units/debug/regfile.py @@ -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 index 00000000..e5cf4041 --- /dev/null +++ b/src/soc/minerva/units/debug/top.py @@ -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 index 00000000..a4711568 --- /dev/null +++ b/src/soc/minerva/units/debug/wbmaster.py @@ -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< AccessSize.WORD): + m.d.sync += sberror.eq(BusError.BAD_SIZE) + with m.Elif((addr & (1<> addr[:2]*8) & (1<= 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 index 00000000..0fe92f0c --- /dev/null +++ b/src/soc/minerva/units/loadstore.py @@ -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 index 00000000..fe96ede1 --- /dev/null +++ b/src/soc/minerva/units/logic.py @@ -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 index 00000000..80e7a0c0 --- /dev/null +++ b/src/soc/minerva/units/multiplier.py @@ -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 index 00000000..f5617903 --- /dev/null +++ b/src/soc/minerva/units/predict.py @@ -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 index 00000000..6442ef51 --- /dev/null +++ b/src/soc/minerva/units/rvficon.py @@ -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 index 00000000..c6a2ed0f --- /dev/null +++ b/src/soc/minerva/units/shifter.py @@ -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 index 00000000..147c0cd0 --- /dev/null +++ b/src/soc/minerva/units/trigger.py @@ -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 index 00000000..cb559630 --- /dev/null +++ b/src/soc/minerva/wishbone.py @@ -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