Add Minerva core, to be integrated later
This commit is contained in:
parent
6459c71ac5
commit
1982668829
13
README.md
13
README.md
|
@ -6,13 +6,22 @@ A port of [riscv-formal](https://github.com/SymbioticEDA/riscv-formal) to nMigen
|
|||
|
||||
- [nMigen](https://github.com/m-labs/nmigen)
|
||||
|
||||
## Breakdown
|
||||
|
||||
_This section is currently a work in progress._
|
||||
|
||||
| Directory | Description |
|
||||
| --- | --- |
|
||||
| `insns` | Supported RISC-V instructions and ISAs |
|
||||
| `cores` | Example cores to be integrated with riscv-formal-nmigen (WIP) |
|
||||
|
||||
## Build
|
||||
|
||||
TODO
|
||||
This framework is not ready to be used to verify RISC-V compliant cores at the time of writing. Instructions for running the framework against such a core will be added once the framework is ready for use.
|
||||
|
||||
## Scope
|
||||
|
||||
As with the original riscv-formal, support is planned for the RV32I and RV64I base ISAs, as well as the M and C extensions and combinations thereof (e.g. RV32IM, RV64IMC).
|
||||
Support for the RV32I base ISA and RV32M extension are planned and well underway. Support for other ISAs in the original riscv-formal such as RV32C and their 64-bit counterparts may also be added in the future as time permits.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
/*.egg-info
|
||||
/.eggs
|
||||
|
||||
# tests
|
||||
**/test/spec_*/
|
||||
*.vcd
|
||||
*.gtkw
|
||||
|
||||
# misc user-created
|
||||
*.il
|
||||
*.v
|
||||
/build
|
|
@ -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.
|
|
@ -0,0 +1,91 @@
|
|||
# Minerva
|
||||
|
||||
## A 32-bit RISC-V soft processor
|
||||
|
||||
Minerva is a CPU core that currently implements the [RISC-V][1] RV32IM instruction set. Its microarchitecture is described in plain Python code using the [nMigen][2] toolbox.
|
||||
|
||||
### Quick start
|
||||
|
||||
Minerva currently requires Python 3.6+ and [nMigen][2] on its `master` branch.
|
||||
|
||||
python setup.py install
|
||||
python cli.py generate > minerva.v
|
||||
|
||||
To use Minerva in its minimal configuration, you need to wire the following ports to `minerva_cpu`:
|
||||
|
||||
* `clk`
|
||||
* `rst`
|
||||
* `ibus__*`
|
||||
* `dbus__*`
|
||||
* `external_interrupt`
|
||||
* `timer_interrupt`
|
||||
* `software_interrupt`
|
||||
|
||||
### Features
|
||||
|
||||
The microarchitecture of Minerva is largely inspired by the [LatticeMico32][3] processor.
|
||||
|
||||
Minerva is pipelined on 6 stages:
|
||||
|
||||
1. **Address**
|
||||
The address of the next instruction is calculated and sent to the instruction cache.
|
||||
2. **Fetch**
|
||||
The instruction is read from memory.
|
||||
3. **Decode**
|
||||
The instruction is decoded, and operands are either fetched from the register file or bypassed from the pipeline. Branches are predicted by the static branch predictor.
|
||||
4. **Execute**
|
||||
Simple instructions such as arithmetic and logical operations are completed at this stage.
|
||||
5. **Memory**
|
||||
More complicated instructions such as loads, stores and shifts require a second execution stage.
|
||||
6. **Writeback**
|
||||
Results produced by the instructions are written back to the register file.
|
||||
|
||||
![Pipeline Diagram Image](https://docs.google.com/drawings/d/e/2PACX-1vTMkQc8ZJoiJ2AOeFGMkK0QTNx1hSG5wDrG5seLdJ3i61E4ag7wH7VFey44qhvuXotvOKxOw-mFS-VE/pub?w=850&h=761)
|
||||
|
||||
The L1 data cache is coupled to a write buffer. Store transactions are in this case done to the write buffer instead of the data bus. This enables stores to proceed in one clock cycle if the buffer isn't full, without having to wait for the bus transaction to complete. Store transactions are then completed in the background as the write buffer gets emptied to the data bus.
|
||||
|
||||
### Configuration
|
||||
|
||||
The following parameters can be used to configure the Minerva core.
|
||||
|
||||
| Parameter | Default value | Description |
|
||||
| ----------------- | -------------- | -------------------------------------------------- |
|
||||
| `reset_address` | `0x00000000` | Reset vector address |
|
||||
| `with_icache` | `False` | Enable the instruction cache |
|
||||
| `icache_nways` | `1` | Number of ways in the instruction cache |
|
||||
| `icache_nlines` | `128` | Number of lines in the instruction cache |
|
||||
| `icache_nwords` | `4` | Number of words in a line of the instruction cache |
|
||||
| `icache_base` | `0x00000000` | Base of the instruction cache address space |
|
||||
| `icache_limit` | `0x80000000` | Limit of the instruction cache address space |
|
||||
| `with_dcache` | `False` | Enable the data cache |
|
||||
| `dcache_nways` | `1` | Number of ways in the data cache |
|
||||
| `dcache_nlines` | `128` | Number of lines in the data cache |
|
||||
| `dcache_nwords` | `4` | Number of words in a line of the data cache |
|
||||
| `dcache_base` | `0x00000000` | Base of the data cache address space |
|
||||
| `dcache_limit` | `0x80000000` | Limit of the data cache address space |
|
||||
| `with_muldiv` | `False` | Enable RV32M support |
|
||||
| `with_debug` | `False` | Enable the Debug Module |
|
||||
| `with_trigger` | `False` | Enable the Trigger Module |
|
||||
| `nb_triggers` | `8` | Number of triggers |
|
||||
| `with_rvfi` | `False` | Enable the riscv-formal interface |
|
||||
|
||||
### Possible improvements
|
||||
|
||||
In no particular order:
|
||||
|
||||
* RV64I
|
||||
* Floating Point Unit
|
||||
* Stateful branch prediction
|
||||
* MMU
|
||||
* ...
|
||||
|
||||
If you are interested in sponsoring new features or improvements, get in touch at contact [at] lambdaconcept.com .
|
||||
|
||||
### License
|
||||
|
||||
Minerva is released under the permissive two-clause BSD license.
|
||||
See LICENSE file for full copyright and license information.
|
||||
|
||||
[1]: https://riscv.org/specifications/
|
||||
[2]: https://github.com/m-labs/nmigen/
|
||||
[3]: https://github.com/m-labs/lm32/
|
|
@ -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=32,
|
||||
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=32,
|
||||
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()
|
|
@ -0,0 +1,178 @@
|
|||
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 isinstance(nlines, int):
|
||||
raise TypeError("nlines must be an integer, not {!r}".format(nlines))
|
||||
if nlines == 0 or nlines & nlines - 1:
|
||||
raise ValueError("nlines must be a power of 2, not {}".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))
|
||||
|
||||
if not isinstance(base, int):
|
||||
raise TypeError("base must be an integer, not {!r}".format(base))
|
||||
if base not in range(0, 2**32) or base & base - 1:
|
||||
raise ValueError("base must be 0 or a power of 2 (< 2**32), not {:#x}".format(base))
|
||||
if not isinstance(limit, int):
|
||||
raise TypeError("limit must be an integer, not {!r}".format(limit))
|
||||
if limit not in range(1, 2**32 + 1) or limit & limit - 1:
|
||||
raise ValueError("limit must be a power of 2 (<= 2**32), not {:#x}".format(limit))
|
||||
if base >= limit:
|
||||
raise ValueError("limit {:#x} must be greater than base {:#x}"
|
||||
.format(limit, base))
|
||||
|
||||
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) - linebits - offsetbits - 2
|
||||
|
||||
self.s1_addr = Record([("offset", offsetbits), ("line", linebits), ("tag", tagbits)])
|
||||
self.s1_stall = Signal()
|
||||
self.s1_valid = Signal()
|
||||
self.s2_addr = Record.like(self.s1_addr)
|
||||
self.s2_re = Signal()
|
||||
self.s2_flush = 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_flush_ack = 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))
|
||||
]
|
||||
|
||||
flush_line = Signal(range(self.nlines), reset=self.nlines - 1)
|
||||
with m.If(self.s1_valid & ~self.s1_stall):
|
||||
m.d.sync += self.s2_flush_ack.eq(0)
|
||||
|
||||
with m.FSM() as fsm:
|
||||
last_offset = Signal.like(self.s2_addr.offset)
|
||||
|
||||
with m.State("CHECK"):
|
||||
with m.If(self.s2_valid):
|
||||
with m.If(self.s2_flush & ~self.s2_flush_ack):
|
||||
m.d.sync += flush_line.eq(flush_line.reset)
|
||||
m.next = "FLUSH"
|
||||
with m.Elif(self.s2_re & self.s2_miss):
|
||||
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"
|
||||
|
||||
with m.State("FLUSH"):
|
||||
with m.If(flush_line == 0):
|
||||
m.d.sync += self.s2_flush_ack.eq(1)
|
||||
m.next = "CHECK"
|
||||
with m.Else():
|
||||
m.d.sync += flush_line.eq(flush_line - 1)
|
||||
|
||||
if platform == "formal":
|
||||
with m.If(Initial()):
|
||||
m.d.comb += Assume(fsm.ongoing("CHECK"))
|
||||
|
||||
for way in ways:
|
||||
tag_mem = Memory(width=1 + 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
|
||||
|
||||
mem_rp_addr = Signal.like(self.s1_addr.line)
|
||||
with m.If(self.s1_stall):
|
||||
m.d.comb += mem_rp_addr.eq(self.s2_addr.line)
|
||||
with m.Else():
|
||||
m.d.comb += mem_rp_addr.eq(self.s1_addr.line)
|
||||
|
||||
m.d.comb += [
|
||||
tag_rp.addr.eq(mem_rp_addr),
|
||||
data_rp.addr.eq(mem_rp_addr),
|
||||
Cat(way.tag, way.valid).eq(tag_rp.data),
|
||||
way.data.eq(data_rp.data),
|
||||
]
|
||||
|
||||
with m.If(fsm.ongoing("FLUSH")):
|
||||
m.d.comb += [
|
||||
tag_wp.addr.eq(flush_line),
|
||||
tag_wp.en.eq(1),
|
||||
tag_wp.data.eq(0),
|
||||
]
|
||||
with m.Elif(way.bus_re):
|
||||
m.d.comb += [
|
||||
tag_wp.addr.eq(self.bus_addr.line),
|
||||
tag_wp.en.eq(way.bus_re & self.bus_valid),
|
||||
tag_wp.data.eq(Cat(self.bus_addr.tag, self.bus_last & ~self.bus_error)),
|
||||
]
|
||||
with m.Else():
|
||||
m.d.comb += [
|
||||
tag_wp.addr.eq(self.s2_addr.line),
|
||||
tag_wp.en.eq(self.s2_evict & self.s2_valid & (way.tag == self.s2_addr.tag)),
|
||||
tag_wp.data.eq(0),
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
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(Repl(self.bus_rdata, self.nwords)),
|
||||
]
|
||||
|
||||
return m
|
|
@ -0,0 +1,812 @@
|
|||
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, WishboneArbiter
|
||||
|
||||
|
||||
__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),
|
||||
("store_operand", 32),
|
||||
("bypass_x", 1),
|
||||
("bypass_m", 1),
|
||||
("funct3", 3),
|
||||
("load", 1),
|
||||
("store", 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=32, icache_nwords=4, icache_base=0, icache_limit=2**31,
|
||||
with_dcache=False,
|
||||
dcache_nways=1, dcache_nlines=32, dcache_nwords=4, 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),
|
||||
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
|
||||
if self.with_debug:
|
||||
flush_icache |= debug.resumereq
|
||||
|
||||
cpu.d.comb += [
|
||||
fetch.f_pc.eq(f.sink.pc),
|
||||
fetch.a_flush.eq(flush_icache)
|
||||
]
|
||||
|
||||
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_sub),
|
||||
adder.src1.eq(x.sink.src1),
|
||||
adder.src2.eq(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_trigger_trap = Signal()
|
||||
with cpu.If(~x.stall):
|
||||
cpu.d.sync += m_trigger_trap.eq(trigger.x_trap)
|
||||
m_ebreak |= m_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.store_operand),
|
||||
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.m_flush.eq(debug.resumereq)
|
||||
|
||||
cpu.d.comb += [
|
||||
loadstore.x_fence_i.eq(x.sink.fence_i),
|
||||
loadstore.m_load.eq(m.sink.load),
|
||||
loadstore.m_store.eq(m.sink.store),
|
||||
]
|
||||
|
||||
for s in a, f:
|
||||
s.kill_on(x.sink.fence_i & x.valid)
|
||||
|
||||
if self.with_debug:
|
||||
cpu.submodules.dbus_arbiter = dbus_arbiter = WishboneArbiter()
|
||||
debug_dbus_port = dbus_arbiter.port(priority=0)
|
||||
loadstore_dbus_port = dbus_arbiter.port(priority=1)
|
||||
cpu.d.comb += [
|
||||
loadstore.dbus.connect(loadstore_dbus_port),
|
||||
debug.dbus.connect(debug_dbus_port),
|
||||
dbus_arbiter.bus.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.any() & (x.sink.rd == decoder.rs1) & x.sink.rd_we),
|
||||
m_raw_rs1.eq(m.sink.rd.any() & (m.sink.rd == decoder.rs1) & m.sink.rd_we),
|
||||
w_raw_rs1.eq(w.sink.rd.any() & (w.sink.rd == decoder.rs1) & w.sink.rd_we),
|
||||
|
||||
x_raw_rs2.eq(x.sink.rd.any() & (x.sink.rd == decoder.rs2) & x.sink.rd_we),
|
||||
m_raw_rs2.eq(m.sink.rd.any() & (m.sink.rd == decoder.rs2) & m.sink.rd_we),
|
||||
w_raw_rs2.eq(w.sink.rd.any() & (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.sink.valid):
|
||||
cpu.d.comb += d_src1.eq(x_result)
|
||||
with cpu.Elif(m_raw_rs1 & m.sink.valid):
|
||||
cpu.d.comb += d_src1.eq(m_result)
|
||||
with cpu.Elif(w_raw_rs1 & w.sink.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.sink.valid):
|
||||
cpu.d.comb += d_src2.eq(x_result)
|
||||
with cpu.Elif(m_raw_rs2 & m.sink.valid):
|
||||
cpu.d.comb += d_src2.eq(m_result)
|
||||
with cpu.Elif(w_raw_rs2 & w.sink.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 & 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.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_sub.eq(decoder.adder & decoder.adder_sub
|
||||
| decoder.compare | decoder.branch),
|
||||
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(Mux(decoder.store, decoder.immediate, d_src2)),
|
||||
d.source.store_operand.eq(d_src2),
|
||||
d.source.branch_predict_taken.eq(predict.d_branch_taken),
|
||||
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
|
|
@ -0,0 +1,98 @@
|
|||
from enum import Enum
|
||||
from collections import OrderedDict
|
||||
|
||||
from nmigen import *
|
||||
from nmigen.utils import bits_for
|
||||
|
||||
|
||||
__all__ = ["CSRAccess", "CSR", "AutoCSR", "CSRFile"]
|
||||
|
||||
|
||||
class CSRAccess(Enum):
|
||||
RW = 0
|
||||
RO = 1
|
||||
|
||||
|
||||
class CSR(Record):
|
||||
def __init__(self, addr, description, name=None, src_loc_at=0):
|
||||
fields = []
|
||||
mask = 0
|
||||
offset = 0
|
||||
for fname, shape, access in description:
|
||||
if isinstance(shape, int):
|
||||
shape = shape, False
|
||||
width, signed = shape
|
||||
fields.append((fname, shape))
|
||||
if access is CSRAccess.RW:
|
||||
mask |= ((1 << width) - 1) << offset
|
||||
offset += width
|
||||
|
||||
self.addr = addr
|
||||
self.mask = mask
|
||||
|
||||
super().__init__([
|
||||
("r", fields),
|
||||
("w", fields),
|
||||
("re", 1),
|
||||
("we", 1),
|
||||
], name=name, src_loc_at=1 + src_loc_at)
|
||||
|
||||
|
||||
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)], name="rp")
|
||||
self._read_ports.append(port)
|
||||
return port
|
||||
|
||||
def write_port(self):
|
||||
port = Record([("addr", bits_for(self.depth)), ("en", 1), ("data", self.width)], name="wp")
|
||||
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)
|
||||
]
|
||||
|
||||
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)
|
||||
for i in range(self.width):
|
||||
rw = (1 << i) & csr.mask
|
||||
m.d.comb += csr.w[i].eq(wp.data[i] if rw else csr.r[i])
|
||||
|
||||
return m
|
|
@ -0,0 +1,215 @@
|
|||
from .csr import *
|
||||
|
||||
|
||||
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.RW),
|
||||
]
|
||||
|
||||
|
||||
misa_layout = [
|
||||
("extensions", 26, CSRAccess.RW),
|
||||
("zero", 4, CSRAccess.RO),
|
||||
("mxl", 2, CSRAccess.RW),
|
||||
]
|
||||
|
||||
|
||||
mstatus_layout = [
|
||||
("uie", 1, CSRAccess.RO), # User Interrupt Enable
|
||||
("sie", 1, CSRAccess.RO), # Supervisor Interrupt Enable
|
||||
("zero0", 1, CSRAccess.RO),
|
||||
("mie", 1, CSRAccess.RW), # Machine Interrupt Enable
|
||||
("upie", 1, CSRAccess.RO), # User Previous Interrupt Enable
|
||||
("spie", 1, CSRAccess.RO), # Supervisor Previous Interrupt Enable
|
||||
("zero1", 1, CSRAccess.RO),
|
||||
("mpie", 1, CSRAccess.RW), # Machine Previous Interrupt Enable
|
||||
("spp", 1, CSRAccess.RO), # Supervisor Previous Privilege
|
||||
("zero2", 2, CSRAccess.RO),
|
||||
("mpp", 2, CSRAccess.RW), # Machine Previous Privilege
|
||||
("fs", 2, CSRAccess.RO), # FPU Status
|
||||
("xs", 2, CSRAccess.RO), # user-mode eXtensions Status
|
||||
("mprv", 1, CSRAccess.RO), # Modify PRiVilege
|
||||
("sum", 1, CSRAccess.RO), # Supervisor User Memory access
|
||||
("mxr", 1, CSRAccess.RO), # Make eXecutable Readable
|
||||
("tvm", 1, CSRAccess.RO), # Trap Virtual Memory
|
||||
("tw", 1, CSRAccess.RO), # Timeout Wait
|
||||
("tsr", 1, CSRAccess.RO), # Trap SRET
|
||||
("zero3", 8, CSRAccess.RO),
|
||||
("sd", 1, CSRAccess.RO), # State Dirty (set if XS or FS are set to dirty)
|
||||
]
|
||||
|
||||
|
||||
mtvec_layout = [
|
||||
("mode", 2, CSRAccess.RW),
|
||||
("base", 30, CSRAccess.RW),
|
||||
]
|
||||
|
||||
|
||||
mepc_layout = [
|
||||
("zero", 2, CSRAccess.RO), # 16-bit instructions are not supported
|
||||
("base", 30, CSRAccess.RW),
|
||||
]
|
||||
|
||||
|
||||
mip_layout = [
|
||||
("usip", 1, CSRAccess.RO),
|
||||
("ssip", 1, CSRAccess.RO),
|
||||
("zero0", 1, CSRAccess.RO),
|
||||
("msip", 1, CSRAccess.RW),
|
||||
("utip", 1, CSRAccess.RO),
|
||||
("stip", 1, CSRAccess.RO),
|
||||
("zero1", 1, CSRAccess.RO),
|
||||
("mtip", 1, CSRAccess.RW),
|
||||
("ueip", 1, CSRAccess.RO),
|
||||
("seip", 1, CSRAccess.RO),
|
||||
("zero2", 1, CSRAccess.RO),
|
||||
("meip", 1, CSRAccess.RW),
|
||||
("zero3", 20, CSRAccess.RO),
|
||||
]
|
||||
|
||||
|
||||
mie_layout = [
|
||||
("usie", 1, CSRAccess.RO),
|
||||
("ssie", 1, CSRAccess.RO),
|
||||
("zero0", 1, CSRAccess.RO),
|
||||
("msie", 1, CSRAccess.RW),
|
||||
("utie", 1, CSRAccess.RO),
|
||||
("stie", 1, CSRAccess.RO),
|
||||
("zero1", 1, CSRAccess.RO),
|
||||
("mtie", 1, CSRAccess.RW),
|
||||
("ueie", 1, CSRAccess.RO),
|
||||
("seie", 1, CSRAccess.RO),
|
||||
("zero2", 1, CSRAccess.RO),
|
||||
("meie", 1, CSRAccess.RW),
|
||||
("zero3", 20, CSRAccess.RO),
|
||||
]
|
||||
|
||||
|
||||
mcause_layout = [
|
||||
("ecode", 31, CSRAccess.RW),
|
||||
("interrupt", 1, CSRAccess.RW),
|
||||
]
|
||||
|
||||
|
||||
dcsr_layout = [
|
||||
("prv", 2, CSRAccess.RW), # Privilege level before Debug Mode was entered
|
||||
("step", 1, CSRAccess.RW), # Execute a single instruction and re-enter Debug Mode
|
||||
("nmip", 1, CSRAccess.RO), # A non-maskable interrupt is pending
|
||||
("mprven", 1, CSRAccess.RW), # Use mstatus.mprv in Debug Mode
|
||||
("zero0", 1, CSRAccess.RO),
|
||||
("cause", 3, CSRAccess.RO), # Explains why Debug Mode was entered
|
||||
("stoptime", 1, CSRAccess.RW), # Stop timer increment during Debug Mode
|
||||
("stopcount", 1, CSRAccess.RW), # Stop counter increment during Debug Mode
|
||||
("stepie", 1, CSRAccess.RW), # Enable interrupts during single stepping
|
||||
("ebreaku", 1, CSRAccess.RW), # EBREAKs in U-mode enter Debug Mode
|
||||
("ebreaks", 1, CSRAccess.RW), # EBREAKs in S-mode enter Debug Mode
|
||||
("zero1", 1, CSRAccess.RO),
|
||||
("ebreakm", 1, CSRAccess.RW), # EBREAKs in M-mode enter Debug Mode
|
||||
("zero2", 12, CSRAccess.RO),
|
||||
("xdebugver", 4, CSRAccess.RO), # External Debug specification version
|
||||
]
|
||||
|
||||
|
||||
tdata1_layout = [
|
||||
("data", 27, CSRAccess.RW),
|
||||
("dmode", 1, CSRAccess.RW),
|
||||
("type", 4, CSRAccess.RW),
|
||||
]
|
|
@ -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
|
|
@ -0,0 +1,97 @@
|
|||
from nmigen import *
|
||||
from nmigen.test.utils import *
|
||||
from nmigen.asserts import *
|
||||
|
||||
from ..cache import L1Cache
|
||||
|
||||
|
||||
class L1CacheSpec(Elaboratable):
|
||||
def __init__(self, cache):
|
||||
self.cache = cache
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
m.submodules.dut = cache = self.cache
|
||||
|
||||
mem_addr = AnyConst(cache.bus_addr.shape())
|
||||
mem_data = AnyConst(cache.bus_rdata.shape())
|
||||
|
||||
with m.If(cache.bus_re & (cache.bus_addr == mem_addr)):
|
||||
m.d.comb += Assume(cache.bus_rdata == mem_data)
|
||||
|
||||
s1_re = AnySeq(1)
|
||||
s1_flush = AnySeq(1)
|
||||
s1_evict = AnySeq(1)
|
||||
s1_select = 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_flush.eq(s1_flush),
|
||||
cache.s2_evict.eq(s1_evict),
|
||||
cache.s2_valid.eq(cache.s1_valid)
|
||||
]
|
||||
|
||||
with m.If((cache.s2_flush & ~cache.s2_flush_ack | cache.s2_re & cache.s2_miss)
|
||||
& cache.s2_valid):
|
||||
m.d.comb += Assume(cache.s1_stall)
|
||||
|
||||
with m.If(Initial()):
|
||||
m.d.comb += Assume(~cache.s2_valid)
|
||||
m.d.comb += Assume(~cache.bus_re)
|
||||
|
||||
# Any hit at `mem_addr` must return `mem_data`.
|
||||
with m.If(cache.s2_re & (cache.s2_addr == mem_addr) & ~cache.s2_miss
|
||||
& cache.s2_valid):
|
||||
m.d.comb += Assert(cache.s2_rdata == mem_data)
|
||||
|
||||
# The next read at any address after a flush must miss.
|
||||
cache_empty = Signal()
|
||||
with m.If(cache.s2_flush & Rose(cache.s2_flush_ack) & cache.s2_valid):
|
||||
m.d.sync += cache_empty.eq(1)
|
||||
with m.If(cache.bus_re & cache.bus_valid & cache.bus_last & ~cache.bus_error):
|
||||
m.d.sync += cache_empty.eq(0)
|
||||
with m.If(cache.s2_re & cache.s2_valid & cache_empty):
|
||||
m.d.comb += Assert(cache.s2_miss)
|
||||
|
||||
# The next read at a refilled address must hit.
|
||||
line_valid = Signal()
|
||||
refill_addr = Record.like(cache.s2_addr)
|
||||
with m.If(cache.bus_re & cache.bus_valid & cache.bus_last & ~cache.bus_error):
|
||||
m.d.sync += line_valid.eq(1)
|
||||
m.d.sync += refill_addr.eq(cache.bus_addr)
|
||||
with m.If((cache.s2_flush | cache.s2_evict) & cache.s2_valid):
|
||||
m.d.sync += line_valid.eq(0)
|
||||
with m.If(cache.s2_re & cache.s2_valid & line_valid
|
||||
& (cache.s2_addr.line == refill_addr.line)
|
||||
& (cache.s2_addr.tag == refill_addr.tag)):
|
||||
m.d.comb += Assert(~cache.s2_miss)
|
||||
|
||||
# The next read at an evicted address must miss.
|
||||
line_empty = Signal()
|
||||
evict_addr = Record.like(cache.s2_addr)
|
||||
with m.If(cache.s2_evict & cache.s2_valid):
|
||||
m.d.sync += line_empty.eq(1)
|
||||
m.d.sync += evict_addr.eq(cache.s2_addr)
|
||||
with m.If(cache.bus_re & cache.bus_valid & cache.bus_last
|
||||
& (cache.bus_addr.line == evict_addr.line)):
|
||||
m.d.sync += line_empty.eq(0)
|
||||
with m.If(cache.s2_re & cache.s2_valid & line_empty
|
||||
& (cache.s2_addr.line == evict_addr.line)
|
||||
& (cache.s2_addr.tag == evict_addr.tag)):
|
||||
m.d.comb += Assert(cache.s2_miss)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
# FIXME: FHDLTestCase is internal to nMigen, we shouldn't use it.
|
||||
class L1CacheTestCase(FHDLTestCase):
|
||||
def check(self, cache):
|
||||
self.assertFormal(L1CacheSpec(cache), mode="bmc", depth=12)
|
||||
|
||||
def test_direct_mapped(self):
|
||||
self.check(L1Cache(nways=1, nlines=2, nwords=4, base=0, limit=64))
|
||||
|
||||
def test_2_ways(self):
|
||||
self.check(L1Cache(nways=2, nlines=2, nwords=4, base=0, limit=64))
|
|
@ -0,0 +1,164 @@
|
|||
import unittest
|
||||
|
||||
from nmigen import *
|
||||
from nmigen.back.pysim 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(unittest.TestCase):
|
||||
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)
|
|
@ -0,0 +1,162 @@
|
|||
import unittest
|
||||
|
||||
from nmigen import *
|
||||
from nmigen.back.pysim 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(unittest.TestCase):
|
||||
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)
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
from .top import *
|
|
@ -0,0 +1,212 @@
|
|||
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):
|
||||
def __init__(self, debugrf):
|
||||
self.dcsr_we = Signal()
|
||||
self.dcsr_w = Record((fname, shape) for (fname, shape, mode) in dcsr_layout)
|
||||
self.dcsr_r = Record.like(self.dcsr_w)
|
||||
self.dpc_we = Signal()
|
||||
self.dpc_w = Signal(32)
|
||||
|
||||
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.haltsum0 = debugrf.reg_port(DebugReg.HALTSUM0)
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
with m.If(self.data0.update):
|
||||
m.d.sync += self.data0.w.eq(self.data0.r)
|
||||
|
||||
m.d.comb += [
|
||||
self.dmstatus.w.version.eq(Version.V013),
|
||||
self.dmstatus.w.authenticated.eq(1),
|
||||
self.resumereq.eq(self.dmcontrol.w.resumereq),
|
||||
self.haltsum0.w[0].eq(self.dmstatus.w.allhalted),
|
||||
]
|
||||
|
||||
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)
|
||||
m.d.comb += self.dmstatus.w.anyrunning.eq(1)
|
||||
with m.If(~halt_pe.n):
|
||||
m.d.sync += self.halt.eq(1)
|
||||
m.d.comb += [
|
||||
self.dcsr_we.eq(1),
|
||||
self.dcsr_w.eq(self.dcsr_r),
|
||||
self.dcsr_w.cause.eq(Array(cause_map)[halt_pe.o]),
|
||||
self.dcsr_w.stepie.eq(1)
|
||||
]
|
||||
m.d.comb += self.dpc_we.eq(1)
|
||||
with m.If(halt_pe.o == HaltCause.EBREAK):
|
||||
m.d.comb += self.dpc_w.eq(self.m_pc)
|
||||
with m.Elif(self.m_exception & self.m_valid):
|
||||
m.d.comb += self.dpc_w.eq(self.mtvec_r_base << 2)
|
||||
with m.Elif(self.m_mret & self.m_valid):
|
||||
m.d.comb += self.dpc_w.eq(self.mepc_r_base << 2)
|
||||
with m.Elif(self.m_branch_taken & self.m_valid):
|
||||
m.d.comb += self.dpc_w.eq(self.m_branch_target)
|
||||
with m.Else():
|
||||
m.d.comb += self.dpc_w.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.d.sync += self.dmstatus.w.anyhalted.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.dmstatus.w.anyresumeack.eq(1),
|
||||
self.halt.eq(0),
|
||||
self.dmstatus.w.allhalted.eq(0),
|
||||
self.dmstatus.w.anyhalted.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.matches("0000------------")):
|
||||
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.matches("00010000000-----")):
|
||||
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
|
|
@ -0,0 +1,144 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
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
|
||||
HALTSUM1 = 0x13
|
||||
ABSTRACTCS = 0x16
|
||||
COMMAND = 0x17
|
||||
PROGBUF0 = 0x20
|
||||
HALTSUM2 = 0x34
|
||||
HALTSUM3 = 0x35
|
||||
SBCS = 0x38
|
||||
SBADDRESS0 = 0x39
|
||||
SBDATA0 = 0x3c
|
||||
HALTSUM0 = 0x40
|
||||
|
||||
|
||||
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.R, 0),
|
||||
("hartsello", 10, RegMode.R, 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)
|
||||
]
|
|
@ -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", 7),
|
||||
]
|
|
@ -0,0 +1,95 @@
|
|||
from nmigen import *
|
||||
from nmigen.hdl.rec import *
|
||||
|
||||
from .dmi import *
|
||||
|
||||
|
||||
__all__ = ["DebugRegisterFile"]
|
||||
|
||||
|
||||
class DmiOp:
|
||||
NOP = 0
|
||||
READ = 1
|
||||
WRITE = 2
|
||||
|
||||
|
||||
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,
|
||||
DebugReg.HALTSUM0: flat_layout,
|
||||
DebugReg.HALTSUM1: flat_layout,
|
||||
}
|
||||
|
||||
|
||||
class DebugRegisterFile(Elaboratable):
|
||||
def __init__(self, dmi):
|
||||
self.dmi = dmi
|
||||
self.ports = dict()
|
||||
|
||||
def reg_port(self, addr, name=None, src_loc_at=0):
|
||||
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)],
|
||||
name=name, src_loc_at=1 + src_loc_at)
|
||||
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)
|
||||
|
||||
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
|
|
@ -0,0 +1,136 @@
|
|||
from nmigen import *
|
||||
from nmigen.hdl.rec import *
|
||||
|
||||
|
||||
from ...csr import *
|
||||
from ...isa import *
|
||||
from ...wishbone import wishbone_layout
|
||||
from .controller 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)
|
||||
|
||||
self.dcsr = CSR(0x7b0, dcsr_layout)
|
||||
self.dpc = CSR(0x7b1, flat_layout)
|
||||
|
||||
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(0x71) # (abits=7, version=1) TODO
|
||||
]
|
||||
|
||||
self.dcsr.r.xdebugver.reset = 4 # Use debug spec v0.13
|
||||
with m.If(self.dcsr.we):
|
||||
m.d.sync += self.dcsr.r.eq(self.dcsr.w)
|
||||
with m.If(controller.dcsr_we):
|
||||
m.d.sync += self.dcsr.r.eq(controller.dcsr_w)
|
||||
|
||||
with m.If(controller.dpc_we):
|
||||
m.d.sync += self.dpc.r.eq(controller.dpc_w)
|
||||
|
||||
m.d.comb += [
|
||||
controller.dcsr_r.eq(self.dcsr.r),
|
||||
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(self.dcsr.r.step),
|
||||
self.dcsr_ebreakm.eq(self.dcsr.r.ebreakm),
|
||||
self.dpc_value.eq(self.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
|
|
@ -0,0 +1,126 @@
|
|||
from functools import reduce
|
||||
from operator import or_
|
||||
|
||||
from nmigen import *
|
||||
from nmigen.hdl.rec import *
|
||||
|
||||
from ...wishbone import wishbone_layout
|
||||
from .dmi import *
|
||||
|
||||
|
||||
__all__ = ["BusError", "AccessSize", "DebugWishboneMaster"]
|
||||
|
||||
|
||||
class BusError:
|
||||
NONE = 0
|
||||
TIMEOUT = 1
|
||||
BAD_ADDRESS = 2
|
||||
MISALIGNED = 3
|
||||
BAD_SIZE = 4
|
||||
OTHER = 7
|
||||
|
||||
|
||||
class AccessSize:
|
||||
BYTE = 0
|
||||
HALF = 1
|
||||
WORD = 2
|
||||
|
||||
|
||||
class DebugWishboneMaster(Elaboratable):
|
||||
def __init__(self, debugrf):
|
||||
self.bus = Record(wishbone_layout)
|
||||
|
||||
self.dbus_busy = Signal()
|
||||
|
||||
self.sbcs = debugrf.reg_port(DebugReg.SBCS)
|
||||
self.sbaddress0 = debugrf.reg_port(DebugReg.SBADDRESS0)
|
||||
self.sbdata0 = debugrf.reg_port(DebugReg.SBDATA0)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
addr = self.sbaddress0.w.value
|
||||
size = self.sbcs.r.sbaccess
|
||||
|
||||
width = Signal(6)
|
||||
m.d.comb += width.eq((1<<size)*8)
|
||||
|
||||
sbbusyerror = self.sbcs.w.sbbusyerror
|
||||
sberror = self.sbcs.w.sberror
|
||||
m.d.comb += self.dbus_busy.eq(self.sbcs.w.sbbusy)
|
||||
|
||||
m.d.comb += [
|
||||
self.sbcs.w.sbaccess8.eq(1),
|
||||
self.sbcs.w.sbaccess16.eq(1),
|
||||
self.sbcs.w.sbaccess32.eq(1),
|
||||
self.sbcs.w.sbasize.eq(32),
|
||||
self.sbcs.w.sbversion.eq(1)
|
||||
]
|
||||
|
||||
with m.If(self.sbcs.update):
|
||||
m.d.sync += [
|
||||
self.sbcs.w.sbbusyerror.eq(self.sbcs.r.sbbusyerror),
|
||||
self.sbcs.w.sberror.eq(self.sbcs.r.sberror)
|
||||
]
|
||||
|
||||
we = Signal()
|
||||
re = Signal()
|
||||
|
||||
with m.If(self.sbdata0.update):
|
||||
with m.If(self.sbcs.w.sbbusy):
|
||||
m.d.sync += self.sbcs.w.sbbusyerror.eq(1)
|
||||
with m.Else():
|
||||
m.d.sync += we.eq(~sberror.bool())
|
||||
|
||||
with m.If(self.sbdata0.capture):
|
||||
with m.If(self.sbcs.w.sbbusy):
|
||||
m.d.sync += self.sbcs.w.sbbusyerror.eq(1)
|
||||
with m.Else():
|
||||
m.d.sync += re.eq(self.sbcs.r.sbreadondata & ~sberror.bool())
|
||||
|
||||
with m.If(self.sbaddress0.update):
|
||||
with m.If(self.sbcs.w.sbbusy):
|
||||
m.d.sync += self.sbcs.w.sbbusyerror.eq(1)
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
re.eq(self.sbcs.r.sbreadonaddr & ~sberror.bool()),
|
||||
self.sbaddress0.w.value.eq(self.sbaddress0.r.value)
|
||||
]
|
||||
|
||||
with m.FSM():
|
||||
with m.State("IDLE"):
|
||||
with m.If(we | re):
|
||||
m.d.sync += we.eq(0), re.eq(0)
|
||||
with m.If(size > AccessSize.WORD):
|
||||
m.d.sync += sberror.eq(BusError.BAD_SIZE)
|
||||
with m.Elif((addr & (1<<size)-1) != 0):
|
||||
m.d.sync += sberror.eq(BusError.MISALIGNED)
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
self.bus.cyc.eq(1),
|
||||
self.bus.stb.eq(1),
|
||||
self.bus.adr.eq(addr[2:]),
|
||||
self.bus.we.eq(we),
|
||||
self.bus.sel.eq((1<<(1<<size))-1 << addr[:2]),
|
||||
self.bus.dat_w.eq((self.sbdata0.r & (1<<width)-1) << addr[:2]*8)
|
||||
]
|
||||
m.next = "BUSY"
|
||||
|
||||
with m.State("BUSY"):
|
||||
m.d.comb += self.sbcs.w.sbbusy.eq(1)
|
||||
with m.If(self.bus.ack | self.bus.err):
|
||||
m.d.sync += [
|
||||
self.bus.cyc.eq(0),
|
||||
self.bus.stb.eq(0),
|
||||
self.bus.we.eq(0),
|
||||
]
|
||||
with m.If(self.bus.err):
|
||||
m.d.sync += sberror.eq(BusError.OTHER)
|
||||
with m.Else():
|
||||
with m.If(~self.bus.we):
|
||||
m.d.sync += self.sbdata0.w.eq((self.bus.dat_r >> addr[:2]*8) & (1<<width)-1)
|
||||
with m.If(self.sbcs.r.sbautoincrement):
|
||||
m.d.sync += addr.eq(addr + (1<<size))
|
||||
m.next = "IDLE"
|
||||
|
||||
return m
|
|
@ -0,0 +1,261 @@
|
|||
from functools import reduce
|
||||
from itertools import starmap
|
||||
from operator import or_
|
||||
|
||||
from nmigen import *
|
||||
|
||||
from ..isa import Opcode, Funct3, Funct7, Funct12
|
||||
|
||||
|
||||
__all__ = ["InstructionDecoder"]
|
||||
|
||||
|
||||
class Type:
|
||||
R = 0
|
||||
I = 1
|
||||
S = 2
|
||||
B = 3
|
||||
U = 4
|
||||
J = 5
|
||||
|
||||
|
||||
class InstructionDecoder(Elaboratable):
|
||||
def __init__(self, with_muldiv):
|
||||
self.with_muldiv = with_muldiv
|
||||
|
||||
self.instruction = Signal(32)
|
||||
|
||||
self.rd = Signal(5)
|
||||
self.rd_we = Signal()
|
||||
self.rs1 = Signal(5)
|
||||
self.rs1_re = Signal()
|
||||
self.rs2 = Signal(5)
|
||||
self.rs2_re = Signal()
|
||||
self.immediate = Signal((32, True))
|
||||
self.bypass_x = Signal()
|
||||
self.bypass_m = Signal()
|
||||
self.load = Signal()
|
||||
self.store = Signal()
|
||||
self.fence_i = Signal()
|
||||
self.adder = Signal()
|
||||
self.adder_sub = Signal()
|
||||
self.logic = Signal()
|
||||
self.multiply = Signal()
|
||||
self.divide = Signal()
|
||||
self.shift = Signal()
|
||||
self.direction = Signal()
|
||||
self.sext = Signal()
|
||||
self.lui = Signal()
|
||||
self.auipc = Signal()
|
||||
self.jump = Signal()
|
||||
self.branch = Signal()
|
||||
self.compare = Signal()
|
||||
self.csr = Signal()
|
||||
self.csr_we = Signal()
|
||||
self.privileged = Signal()
|
||||
self.ecall = Signal()
|
||||
self.ebreak = Signal()
|
||||
self.mret = Signal()
|
||||
self.funct3 = Signal(3)
|
||||
self.illegal = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
opcode = Signal(5)
|
||||
funct3 = Signal(3)
|
||||
funct7 = Signal(7)
|
||||
funct12 = Signal(12)
|
||||
|
||||
iimm12 = Signal((12, True))
|
||||
simm12 = Signal((12, True))
|
||||
bimm12 = Signal((13, True))
|
||||
uimm20 = Signal(20)
|
||||
jimm20 = Signal((21, True))
|
||||
|
||||
insn = self.instruction
|
||||
fmt = Signal(range(Type.J + 1))
|
||||
|
||||
m.d.comb += [
|
||||
opcode.eq(insn[2:7]),
|
||||
funct3.eq(insn[12:15]),
|
||||
funct7.eq(insn[25:32]),
|
||||
funct12.eq(insn[20:32]),
|
||||
|
||||
iimm12.eq(insn[20:32]),
|
||||
simm12.eq(Cat(insn[7:12], insn[25:32])),
|
||||
bimm12.eq(Cat(0, insn[8:12], insn[25:31], insn[7], insn[31])),
|
||||
uimm20.eq(insn[12:32]),
|
||||
jimm20.eq(Cat(0, insn[21:31], insn[20], insn[12:20], insn[31])),
|
||||
]
|
||||
|
||||
with m.Switch(opcode):
|
||||
with m.Case(Opcode.LUI):
|
||||
m.d.comb += fmt.eq(Type.U)
|
||||
with m.Case(Opcode.AUIPC):
|
||||
m.d.comb += fmt.eq(Type.U)
|
||||
with m.Case(Opcode.JAL):
|
||||
m.d.comb += fmt.eq(Type.J)
|
||||
with m.Case(Opcode.JALR):
|
||||
m.d.comb += fmt.eq(Type.I)
|
||||
with m.Case(Opcode.BRANCH):
|
||||
m.d.comb += fmt.eq(Type.B)
|
||||
with m.Case(Opcode.LOAD):
|
||||
m.d.comb += fmt.eq(Type.I)
|
||||
with m.Case(Opcode.STORE):
|
||||
m.d.comb += fmt.eq(Type.S)
|
||||
with m.Case(Opcode.OP_IMM_32):
|
||||
m.d.comb += fmt.eq(Type.I)
|
||||
with m.Case(Opcode.OP_32):
|
||||
m.d.comb += fmt.eq(Type.R)
|
||||
with m.Case(Opcode.MISC_MEM):
|
||||
m.d.comb += fmt.eq(Type.I)
|
||||
with m.Case(Opcode.SYSTEM):
|
||||
m.d.comb += fmt.eq(Type.I)
|
||||
|
||||
with m.Switch(fmt):
|
||||
with m.Case(Type.I):
|
||||
m.d.comb += self.immediate.eq(iimm12)
|
||||
with m.Case(Type.S):
|
||||
m.d.comb += self.immediate.eq(simm12)
|
||||
with m.Case(Type.B):
|
||||
m.d.comb += self.immediate.eq(bimm12)
|
||||
with m.Case(Type.U):
|
||||
m.d.comb += self.immediate.eq(uimm20 << 12)
|
||||
with m.Case(Type.J):
|
||||
m.d.comb += self.immediate.eq(jimm20)
|
||||
|
||||
m.d.comb += [
|
||||
self.rd.eq(insn[7:12]),
|
||||
self.rs1.eq(insn[15:20]),
|
||||
self.rs2.eq(insn[20:25]),
|
||||
|
||||
self.rd_we.eq(reduce(or_, (fmt == T for T in (Type.R, Type.I, Type.U, Type.J)))),
|
||||
self.rs1_re.eq(reduce(or_, (fmt == T for T in (Type.R, Type.I, Type.S, Type.B)))),
|
||||
self.rs2_re.eq(reduce(or_, (fmt == T for T in (Type.R, Type.S, Type.B)))),
|
||||
|
||||
self.funct3.eq(funct3)
|
||||
]
|
||||
|
||||
def matcher(encodings):
|
||||
return reduce(or_, starmap(
|
||||
lambda opc, f3=None, f7=None, f12=None:
|
||||
(opcode == opc if opc is not None else 1) \
|
||||
& (funct3 == f3 if f3 is not None else 1) \
|
||||
& (funct7 == f7 if f7 is not None else 1) \
|
||||
& (funct12 == f12 if f12 is not None else 1),
|
||||
encodings))
|
||||
|
||||
m.d.comb += [
|
||||
self.compare.eq(matcher([
|
||||
(Opcode.OP_IMM_32, Funct3.SLT, None), # slti
|
||||
(Opcode.OP_IMM_32, Funct3.SLTU, None), # sltiu
|
||||
(Opcode.OP_32, Funct3.SLT, 0), # slt
|
||||
(Opcode.OP_32, Funct3.SLTU, 0) # sltu
|
||||
])),
|
||||
self.branch.eq(matcher([
|
||||
(Opcode.BRANCH, Funct3.BEQ, None), # beq
|
||||
(Opcode.BRANCH, Funct3.BNE, None), # bne
|
||||
(Opcode.BRANCH, Funct3.BLT, None), # blt
|
||||
(Opcode.BRANCH, Funct3.BGE, None), # bge
|
||||
(Opcode.BRANCH, Funct3.BLTU, None), # bltu
|
||||
(Opcode.BRANCH, Funct3.BGEU, None) # bgeu
|
||||
])),
|
||||
|
||||
self.adder.eq(matcher([
|
||||
(Opcode.OP_IMM_32, Funct3.ADD, None), # addi
|
||||
(Opcode.OP_32, Funct3.ADD, Funct7.ADD), # add
|
||||
(Opcode.OP_32, Funct3.ADD, Funct7.SUB) # sub
|
||||
])),
|
||||
self.adder_sub.eq(self.rs2_re & (funct7 == Funct7.SUB)),
|
||||
|
||||
self.logic.eq(matcher([
|
||||
(Opcode.OP_IMM_32, Funct3.XOR, None), # xori
|
||||
(Opcode.OP_IMM_32, Funct3.OR, None), # ori
|
||||
(Opcode.OP_IMM_32, Funct3.AND, None), # andi
|
||||
(Opcode.OP_32, Funct3.XOR, 0), # xor
|
||||
(Opcode.OP_32, Funct3.OR, 0), # or
|
||||
(Opcode.OP_32, Funct3.AND, 0) # and
|
||||
])),
|
||||
]
|
||||
|
||||
if self.with_muldiv:
|
||||
m.d.comb += [
|
||||
self.multiply.eq(matcher([
|
||||
(Opcode.OP_32, Funct3.MUL, Funct7.MULDIV), # mul
|
||||
(Opcode.OP_32, Funct3.MULH, Funct7.MULDIV), # mulh
|
||||
(Opcode.OP_32, Funct3.MULHSU, Funct7.MULDIV), # mulhsu
|
||||
(Opcode.OP_32, Funct3.MULHU, Funct7.MULDIV), # mulhu
|
||||
])),
|
||||
|
||||
self.divide.eq(matcher([
|
||||
(Opcode.OP_32, Funct3.DIV, Funct7.MULDIV), # div
|
||||
(Opcode.OP_32, Funct3.DIVU, Funct7.MULDIV), # divu
|
||||
(Opcode.OP_32, Funct3.REM, Funct7.MULDIV), # rem
|
||||
(Opcode.OP_32, Funct3.REMU, Funct7.MULDIV) # remu
|
||||
])),
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
self.shift.eq(matcher([
|
||||
(Opcode.OP_IMM_32, Funct3.SLL, 0), # slli
|
||||
(Opcode.OP_IMM_32, Funct3.SR, Funct7.SRL), # srli
|
||||
(Opcode.OP_IMM_32, Funct3.SR, Funct7.SRA), # srai
|
||||
(Opcode.OP_32, Funct3.SLL, 0), # sll
|
||||
(Opcode.OP_32, Funct3.SR, Funct7.SRL), # srl
|
||||
(Opcode.OP_32, Funct3.SR, Funct7.SRA) # sra
|
||||
])),
|
||||
self.direction.eq(funct3 == Funct3.SR),
|
||||
self.sext.eq(funct7 == Funct7.SRA),
|
||||
|
||||
self.lui.eq(opcode == Opcode.LUI),
|
||||
self.auipc.eq(opcode == Opcode.AUIPC),
|
||||
|
||||
self.jump.eq(matcher([
|
||||
(Opcode.JAL, None), # jal
|
||||
(Opcode.JALR, 0) # jalr
|
||||
])),
|
||||
|
||||
self.load.eq(matcher([
|
||||
(Opcode.LOAD, Funct3.B), # lb
|
||||
(Opcode.LOAD, Funct3.BU), # lbu
|
||||
(Opcode.LOAD, Funct3.H), # lh
|
||||
(Opcode.LOAD, Funct3.HU), # lhu
|
||||
(Opcode.LOAD, Funct3.W) # lw
|
||||
])),
|
||||
self.store.eq(matcher([
|
||||
(Opcode.STORE, Funct3.B), # sb
|
||||
(Opcode.STORE, Funct3.H), # sh
|
||||
(Opcode.STORE, Funct3.W) # sw
|
||||
])),
|
||||
|
||||
self.fence_i.eq(matcher([
|
||||
(Opcode.MISC_MEM, Funct3.FENCEI) # fence.i
|
||||
])),
|
||||
|
||||
self.csr.eq(matcher([
|
||||
(Opcode.SYSTEM, Funct3.CSRRW), # csrrw
|
||||
(Opcode.SYSTEM, Funct3.CSRRS), # csrrs
|
||||
(Opcode.SYSTEM, Funct3.CSRRC), # csrrc
|
||||
(Opcode.SYSTEM, Funct3.CSRRWI), # csrrwi
|
||||
(Opcode.SYSTEM, Funct3.CSRRSI), # csrrsi
|
||||
(Opcode.SYSTEM, Funct3.CSRRCI) # csrrci
|
||||
])),
|
||||
self.csr_we.eq(~funct3[1] | (self.rs1 != 0)),
|
||||
|
||||
self.privileged.eq((opcode == Opcode.SYSTEM) & (funct3 == Funct3.PRIV)),
|
||||
self.ecall.eq(self.privileged & (funct12 == Funct12.ECALL)),
|
||||
self.ebreak.eq(self.privileged & (funct12 == Funct12.EBREAK)),
|
||||
self.mret.eq(self.privileged & (funct12 == Funct12.MRET)),
|
||||
|
||||
self.bypass_x.eq(self.adder | self.logic | self.lui | self.auipc | self.csr),
|
||||
self.bypass_m.eq(self.compare | self.divide | self.shift),
|
||||
|
||||
self.illegal.eq((self.instruction[:2] != 0b11) | ~reduce(or_, (
|
||||
self.compare, self.branch, self.adder, self.logic, self.multiply, self.divide, self.shift,
|
||||
self.lui, self.auipc, self.jump, self.load, self.store,
|
||||
self.csr, self.ecall, self.ebreak, self.mret, self.fence_i,
|
||||
)))
|
||||
]
|
||||
|
||||
return m
|
|
@ -0,0 +1,144 @@
|
|||
from nmigen import *
|
||||
|
||||
from ..isa import Funct3
|
||||
|
||||
|
||||
__all__ = ["DividerInterface", "Divider", "DummyDivider"]
|
||||
|
||||
|
||||
class DividerInterface:
|
||||
def __init__(self):
|
||||
self.x_op = Signal(3)
|
||||
self.x_src1 = Signal(32)
|
||||
self.x_src2 = Signal(32)
|
||||
self.x_valid = Signal()
|
||||
self.x_stall = Signal()
|
||||
|
||||
self.m_result = Signal(32)
|
||||
self.m_busy = Signal()
|
||||
|
||||
|
||||
class Divider(DividerInterface, Elaboratable):
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
x_enable = Signal()
|
||||
x_modulus = Signal()
|
||||
x_signed = Signal()
|
||||
|
||||
with m.Switch(self.x_op):
|
||||
with m.Case(Funct3.DIV):
|
||||
m.d.comb += x_enable.eq(1), x_signed.eq(1)
|
||||
with m.Case(Funct3.DIVU):
|
||||
m.d.comb += x_enable.eq(1)
|
||||
with m.Case(Funct3.REM):
|
||||
m.d.comb += x_enable.eq(1), x_modulus.eq(1), x_signed.eq(1)
|
||||
with m.Case(Funct3.REMU):
|
||||
m.d.comb += x_enable.eq(1), x_modulus.eq(1)
|
||||
|
||||
x_negative = Signal()
|
||||
with m.If(x_modulus):
|
||||
m.d.comb += x_negative.eq(x_signed & self.x_src1[31])
|
||||
with m.Else():
|
||||
m.d.comb += x_negative.eq(x_signed & (self.x_src1[31] ^ self.x_src2[31]))
|
||||
|
||||
x_dividend = Signal(32)
|
||||
x_divisor = Signal(32)
|
||||
m.d.comb += [
|
||||
x_dividend.eq(Mux(x_signed & self.x_src1[31], -self.x_src1, self.x_src1)),
|
||||
x_divisor.eq(Mux(x_signed & self.x_src2[31], -self.x_src2, self.x_src2))
|
||||
]
|
||||
|
||||
m_modulus = Signal()
|
||||
m_negative = Signal()
|
||||
|
||||
timer = Signal(range(33), reset=32)
|
||||
quotient = Signal(32)
|
||||
divisor = Signal(32)
|
||||
remainder = Signal(32)
|
||||
difference = Signal(33)
|
||||
|
||||
with m.FSM() as fsm:
|
||||
with m.State("IDLE"):
|
||||
with m.If(x_enable & self.x_valid & ~self.x_stall):
|
||||
m.d.sync += [
|
||||
m_modulus.eq(x_modulus),
|
||||
m_negative.eq(x_negative)
|
||||
]
|
||||
with m.If(x_divisor == 0):
|
||||
# Division by zero
|
||||
m.d.sync += [
|
||||
quotient.eq(-1),
|
||||
remainder.eq(self.x_src1)
|
||||
]
|
||||
with m.Elif(x_signed & (self.x_src1 == -2**31) & (self.x_src2 == -1)):
|
||||
# Signed overflow
|
||||
m.d.sync += [
|
||||
quotient.eq(self.x_src1),
|
||||
remainder.eq(0)
|
||||
]
|
||||
with m.Elif(x_dividend == 0):
|
||||
m.d.sync += [
|
||||
quotient.eq(0),
|
||||
remainder.eq(0)
|
||||
]
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
quotient.eq(x_dividend),
|
||||
remainder.eq(0),
|
||||
divisor.eq(x_divisor),
|
||||
timer.eq(timer.reset)
|
||||
]
|
||||
m.next = "DIVIDE"
|
||||
|
||||
with m.State("DIVIDE"):
|
||||
m.d.comb += self.m_busy.eq(1)
|
||||
with m.If(timer != 0):
|
||||
m.d.sync += timer.eq(timer - 1)
|
||||
m.d.comb += difference.eq(Cat(quotient[31], remainder) - divisor)
|
||||
with m.If(difference[32]):
|
||||
m.d.sync += [
|
||||
remainder.eq(Cat(quotient[31], remainder)),
|
||||
quotient.eq(Cat(0, quotient))
|
||||
]
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
remainder.eq(difference),
|
||||
quotient.eq(Cat(1, quotient))
|
||||
]
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
quotient.eq(Mux(m_negative, -quotient, quotient)),
|
||||
remainder.eq(Mux(m_negative, -remainder, remainder))
|
||||
]
|
||||
m.next = "IDLE"
|
||||
|
||||
m.d.comb += self.m_result.eq(Mux(m_modulus, remainder, quotient))
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class DummyDivider(DividerInterface, Elaboratable):
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
x_result = Signal.like(self.m_result)
|
||||
|
||||
with m.Switch(self.x_op):
|
||||
# As per the RVFI specification (§ "Alternative Arithmetic Operations").
|
||||
# https://github.com/SymbioticEDA/riscv-formal/blob/master/docs/rvfi.md
|
||||
with m.Case(Funct3.DIV):
|
||||
m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x7f8529ec))
|
||||
with m.Case(Funct3.DIVU):
|
||||
m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x10e8fd70))
|
||||
with m.Case(Funct3.REM):
|
||||
m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x8da68fa5))
|
||||
with m.Case(Funct3.REMU):
|
||||
m.d.comb += x_result.eq((self.x_src1 - self.x_src2) ^ C(0x3138d0e1))
|
||||
|
||||
with m.If(~self.x_stall):
|
||||
m.d.sync += self.m_result.eq(x_result)
|
||||
|
||||
m.d.comb += self.m_busy.eq(C(0))
|
||||
|
||||
return m
|
|
@ -0,0 +1,121 @@
|
|||
from nmigen import *
|
||||
from nmigen.lib.coding import PriorityEncoder
|
||||
|
||||
from ..csr import *
|
||||
from ..isa import *
|
||||
|
||||
|
||||
__all__ = ["ExceptionUnit"]
|
||||
|
||||
|
||||
class ExceptionUnit(Elaboratable, AutoCSR):
|
||||
def __init__(self):
|
||||
self.mstatus = CSR(0x300, mstatus_layout)
|
||||
self.misa = CSR(0x301, misa_layout) # FIXME move elsewhere
|
||||
self.mie = CSR(0x304, mie_layout)
|
||||
self.mtvec = CSR(0x305, mtvec_layout)
|
||||
self.mscratch = CSR(0x340, flat_layout) # FIXME move elsewhere
|
||||
self.mepc = CSR(0x341, mepc_layout)
|
||||
self.mcause = CSR(0x342, mcause_layout)
|
||||
self.mtval = CSR(0x343, flat_layout)
|
||||
self.mip = CSR(0x344, mip_layout)
|
||||
self.irq_mask = CSR(0x330, flat_layout)
|
||||
self.irq_pending = CSR(0x360, flat_layout)
|
||||
|
||||
self.external_interrupt = Signal(32)
|
||||
self.timer_interrupt = Signal()
|
||||
self.software_interrupt = Signal()
|
||||
|
||||
self.m_fetch_misaligned = Signal()
|
||||
self.m_fetch_error = Signal()
|
||||
self.m_fetch_badaddr = Signal(30)
|
||||
self.m_load_misaligned = Signal()
|
||||
self.m_load_error = Signal()
|
||||
self.m_store_misaligned = Signal()
|
||||
self.m_store_error = Signal()
|
||||
self.m_loadstore_badaddr = Signal(30)
|
||||
self.m_branch_target = Signal(32)
|
||||
self.m_illegal = Signal()
|
||||
self.m_ebreak = Signal()
|
||||
self.m_ecall = Signal()
|
||||
self.m_pc = Signal(32)
|
||||
self.m_instruction = Signal(32)
|
||||
self.m_result = Signal(32)
|
||||
self.m_mret = Signal()
|
||||
self.m_stall = Signal()
|
||||
self.m_valid = Signal()
|
||||
|
||||
self.m_raise = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
for csr in self.iter_csrs():
|
||||
with m.If(csr.we):
|
||||
m.d.sync += csr.r.eq(csr.w)
|
||||
|
||||
trap_pe = m.submodules.trap_pe = PriorityEncoder(16)
|
||||
m.d.comb += [
|
||||
trap_pe.i[Cause.FETCH_MISALIGNED ].eq(self.m_fetch_misaligned),
|
||||
trap_pe.i[Cause.FETCH_ACCESS_FAULT ].eq(self.m_fetch_error),
|
||||
trap_pe.i[Cause.ILLEGAL_INSTRUCTION].eq(self.m_illegal),
|
||||
trap_pe.i[Cause.BREAKPOINT ].eq(self.m_ebreak),
|
||||
trap_pe.i[Cause.LOAD_MISALIGNED ].eq(self.m_load_misaligned),
|
||||
trap_pe.i[Cause.LOAD_ACCESS_FAULT ].eq(self.m_load_error),
|
||||
trap_pe.i[Cause.STORE_MISALIGNED ].eq(self.m_store_misaligned),
|
||||
trap_pe.i[Cause.STORE_ACCESS_FAULT ].eq(self.m_store_error),
|
||||
trap_pe.i[Cause.ECALL_FROM_M ].eq(self.m_ecall)
|
||||
]
|
||||
|
||||
m.d.sync += [
|
||||
self.irq_pending.r.eq(self.external_interrupt & self.irq_mask.r),
|
||||
self.mip.r.msip.eq(self.software_interrupt),
|
||||
self.mip.r.mtip.eq(self.timer_interrupt),
|
||||
self.mip.r.meip.eq(self.irq_pending.r.bool())
|
||||
]
|
||||
|
||||
interrupt_pe = m.submodules.interrupt_pe = PriorityEncoder(16)
|
||||
m.d.comb += [
|
||||
interrupt_pe.i[Cause.M_SOFTWARE_INTERRUPT].eq(self.mip.r.msip & self.mie.r.msie),
|
||||
interrupt_pe.i[Cause.M_TIMER_INTERRUPT ].eq(self.mip.r.mtip & self.mie.r.mtie),
|
||||
interrupt_pe.i[Cause.M_EXTERNAL_INTERRUPT].eq(self.mip.r.meip & self.mie.r.meie)
|
||||
]
|
||||
|
||||
m.d.comb += self.m_raise.eq(~trap_pe.n | ~interrupt_pe.n & self.mstatus.r.mie)
|
||||
|
||||
with m.If(self.m_valid & ~self.m_stall):
|
||||
with m.If(self.m_raise):
|
||||
m.d.sync += [
|
||||
self.mstatus.r.mpie.eq(self.mstatus.r.mie),
|
||||
self.mstatus.r.mie.eq(0),
|
||||
self.mepc.r.base.eq(self.m_pc[2:])
|
||||
]
|
||||
with m.If(~trap_pe.n):
|
||||
m.d.sync += [
|
||||
self.mcause.r.ecode.eq(trap_pe.o),
|
||||
self.mcause.r.interrupt.eq(0)
|
||||
]
|
||||
with m.Switch(trap_pe.o):
|
||||
with m.Case(Cause.FETCH_MISALIGNED):
|
||||
m.d.sync += self.mtval.r.eq(self.m_branch_target)
|
||||
with m.Case(Cause.FETCH_ACCESS_FAULT):
|
||||
m.d.sync += self.mtval.r.eq(self.m_fetch_badaddr << 2)
|
||||
with m.Case(Cause.ILLEGAL_INSTRUCTION):
|
||||
m.d.sync += self.mtval.r.eq(self.m_instruction)
|
||||
with m.Case(Cause.BREAKPOINT):
|
||||
m.d.sync += self.mtval.r.eq(self.m_pc)
|
||||
with m.Case(Cause.LOAD_MISALIGNED, Cause.STORE_MISALIGNED):
|
||||
m.d.sync += self.mtval.r.eq(self.m_result)
|
||||
with m.Case(Cause.LOAD_ACCESS_FAULT, Cause.STORE_ACCESS_FAULT):
|
||||
m.d.sync += self.mtval.r.eq(self.m_loadstore_badaddr << 2)
|
||||
with m.Case():
|
||||
m.d.sync += self.mtval.r.eq(0)
|
||||
with m.Else():
|
||||
m.d.sync += [
|
||||
self.mcause.r.ecode.eq(interrupt_pe.o),
|
||||
self.mcause.r.interrupt.eq(1)
|
||||
]
|
||||
with m.Elif(self.m_mret):
|
||||
m.d.sync += self.mstatus.r.mie.eq(self.mstatus.r.mpie)
|
||||
|
||||
return m
|
|
@ -0,0 +1,231 @@
|
|||
from nmigen import *
|
||||
from nmigen.utils import log2_int
|
||||
|
||||
from ..cache import *
|
||||
from ..wishbone import *
|
||||
|
||||
|
||||
__all__ = ["PCSelector", "FetchUnitInterface", "BareFetchUnit", "CachedFetchUnit"]
|
||||
|
||||
|
||||
class PCSelector(Elaboratable):
|
||||
def __init__(self):
|
||||
self.f_pc = Signal(32)
|
||||
self.d_pc = Signal(32)
|
||||
self.d_branch_predict_taken = Signal()
|
||||
self.d_branch_target = Signal(32)
|
||||
self.d_valid = Signal()
|
||||
self.x_pc = Signal(32)
|
||||
self.x_fence_i = Signal()
|
||||
self.x_valid = Signal()
|
||||
self.m_branch_predict_taken = Signal()
|
||||
self.m_branch_taken = Signal()
|
||||
self.m_branch_target = Signal(32)
|
||||
self.m_exception = Signal()
|
||||
self.m_mret = Signal()
|
||||
self.m_valid = Signal()
|
||||
self.mtvec_r_base = Signal(30)
|
||||
self.mepc_r_base = Signal(30)
|
||||
|
||||
self.a_pc = Signal(32)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
m_sel = Signal(reset=1)
|
||||
m_a_pc = Signal(32)
|
||||
|
||||
with m.If(self.m_exception):
|
||||
m.d.comb += m_a_pc[2:].eq(self.mtvec_r_base)
|
||||
with m.Elif(self.m_mret):
|
||||
m.d.comb += m_a_pc[2:].eq(self.mepc_r_base)
|
||||
with m.Elif(self.m_branch_predict_taken & ~self.m_branch_taken):
|
||||
m.d.comb += m_a_pc[2:].eq(self.x_pc[2:])
|
||||
with m.Elif(~self.m_branch_predict_taken & self.m_branch_taken):
|
||||
m.d.comb += m_a_pc[2:].eq(self.m_branch_target[2:]),
|
||||
with m.Else():
|
||||
m.d.comb += m_sel.eq(0)
|
||||
|
||||
with m.If(m_sel & self.m_valid):
|
||||
m.d.comb += self.a_pc[2:].eq(m_a_pc[2:])
|
||||
with m.Elif(self.x_fence_i & self.x_valid):
|
||||
m.d.comb += self.a_pc[2:].eq(self.d_pc[2:])
|
||||
with m.Elif(self.d_branch_predict_taken & self.d_valid):
|
||||
m.d.comb += self.a_pc[2:].eq(self.d_branch_target[2:]),
|
||||
with m.Else():
|
||||
m.d.comb += self.a_pc[2:].eq(self.f_pc[2:] + 1)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class FetchUnitInterface:
|
||||
def __init__(self):
|
||||
self.ibus = Record(wishbone_layout)
|
||||
|
||||
self.a_pc = Signal(32)
|
||||
self.a_stall = Signal()
|
||||
self.a_valid = Signal()
|
||||
self.f_stall = Signal()
|
||||
self.f_valid = Signal()
|
||||
|
||||
self.a_busy = Signal()
|
||||
self.f_busy = Signal()
|
||||
self.f_instruction = Signal(32, reset=0x00000013) # nop (addi x0, x0, 0)
|
||||
self.f_fetch_error = Signal()
|
||||
self.f_badaddr = Signal(30)
|
||||
|
||||
|
||||
class BareFetchUnit(FetchUnitInterface, Elaboratable):
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
ibus_rdata = Signal.like(self.ibus.dat_r)
|
||||
with m.If(self.ibus.cyc):
|
||||
with m.If(self.ibus.ack | self.ibus.err | ~self.f_valid):
|
||||
m.d.sync += [
|
||||
self.ibus.cyc.eq(0),
|
||||
self.ibus.stb.eq(0),
|
||||
ibus_rdata.eq(self.ibus.dat_r)
|
||||
]
|
||||
with m.Elif(self.a_valid & ~self.a_stall):
|
||||
m.d.sync += [
|
||||
self.ibus.adr.eq(self.a_pc[2:]),
|
||||
self.ibus.cyc.eq(1),
|
||||
self.ibus.stb.eq(1)
|
||||
]
|
||||
m.d.comb += self.ibus.sel.eq(0b1111)
|
||||
|
||||
with m.If(self.ibus.cyc & self.ibus.err):
|
||||
m.d.sync += [
|
||||
self.f_fetch_error.eq(1),
|
||||
self.f_badaddr.eq(self.ibus.adr)
|
||||
]
|
||||
with m.Elif(~self.f_stall):
|
||||
m.d.sync += self.f_fetch_error.eq(0)
|
||||
|
||||
m.d.comb += self.a_busy.eq(self.ibus.cyc)
|
||||
|
||||
with m.If(self.f_fetch_error):
|
||||
m.d.comb += self.f_busy.eq(0)
|
||||
with m.Else():
|
||||
m.d.comb += [
|
||||
self.f_busy.eq(self.ibus.cyc),
|
||||
self.f_instruction.eq(ibus_rdata)
|
||||
]
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class CachedFetchUnit(FetchUnitInterface, Elaboratable):
|
||||
def __init__(self, *icache_args):
|
||||
super().__init__()
|
||||
|
||||
self.icache_args = icache_args
|
||||
|
||||
self.f_pc = Signal(32)
|
||||
self.a_flush = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
icache = m.submodules.icache = L1Cache(*self.icache_args)
|
||||
|
||||
a_icache_select = Signal()
|
||||
|
||||
# Test whether the target address is inside the L1 cache region. We use bit masks in order
|
||||
# to avoid carry chains from arithmetic comparisons. This restricts the region boundaries
|
||||
# to powers of 2.
|
||||
with m.Switch(self.a_pc[2:]):
|
||||
def addr_below(limit):
|
||||
assert limit in range(1, 2**30 + 1)
|
||||
range_bits = log2_int(limit)
|
||||
const_bits = 30 - range_bits
|
||||
return "{}{}".format("0" * const_bits, "-" * range_bits)
|
||||
|
||||
if icache.base >= 4:
|
||||
with m.Case(addr_below(icache.base >> 2)):
|
||||
m.d.comb += a_icache_select.eq(0)
|
||||
with m.Case(addr_below(icache.limit >> 2)):
|
||||
m.d.comb += a_icache_select.eq(1)
|
||||
with m.Default():
|
||||
m.d.comb += a_icache_select.eq(0)
|
||||
|
||||
f_icache_select = Signal()
|
||||
f_flush = Signal()
|
||||
|
||||
with m.If(~self.a_stall):
|
||||
m.d.sync += [
|
||||
f_icache_select.eq(a_icache_select),
|
||||
f_flush.eq(self.a_flush),
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
icache.s1_addr.eq(self.a_pc[2:]),
|
||||
icache.s1_stall.eq(self.a_stall),
|
||||
icache.s1_valid.eq(self.a_valid),
|
||||
icache.s2_addr.eq(self.f_pc[2:]),
|
||||
icache.s2_re.eq(f_icache_select),
|
||||
icache.s2_evict.eq(Const(0)),
|
||||
icache.s2_flush.eq(f_flush),
|
||||
icache.s2_valid.eq(self.f_valid),
|
||||
]
|
||||
|
||||
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.sel.eq(0b1111),
|
||||
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:])
|
||||
]
|
||||
m.d.comb += bare_port.sel.eq(0b1111)
|
||||
|
||||
m.d.comb += self.a_busy.eq(bare_port.cyc)
|
||||
|
||||
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(f_flush):
|
||||
m.d.comb += self.f_busy.eq(~icache.s2_flush_ack)
|
||||
with m.Elif(self.f_fetch_error):
|
||||
m.d.comb += self.f_busy.eq(0)
|
||||
with m.Elif(f_icache_select):
|
||||
m.d.comb += [
|
||||
self.f_busy.eq(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
|
|
@ -0,0 +1,294 @@
|
|||
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.m_load = Signal()
|
||||
self.m_store = Signal()
|
||||
self.m_flush = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
dcache = m.submodules.dcache = L1Cache(*self.dcache_args)
|
||||
|
||||
x_dcache_select = Signal()
|
||||
|
||||
# Test whether the target address is inside the L1 cache region. We use bit masks in order
|
||||
# to avoid carry chains from arithmetic comparisons. This restricts the region boundaries
|
||||
# to powers of 2.
|
||||
with m.Switch(self.x_addr[2:]):
|
||||
def addr_below(limit):
|
||||
assert limit in range(1, 2**30 + 1)
|
||||
range_bits = log2_int(limit)
|
||||
const_bits = 30 - range_bits
|
||||
return "{}{}".format("0" * const_bits, "-" * range_bits)
|
||||
|
||||
if dcache.base >= 4:
|
||||
with m.Case(addr_below(dcache.base >> 2)):
|
||||
m.d.comb += x_dcache_select.eq(0)
|
||||
with m.Case(addr_below(dcache.limit >> 2)):
|
||||
m.d.comb += x_dcache_select.eq(1)
|
||||
with m.Default():
|
||||
m.d.comb += x_dcache_select.eq(0)
|
||||
|
||||
m_dcache_select = Signal()
|
||||
m_addr = Signal.like(self.x_addr)
|
||||
|
||||
with m.If(~self.x_stall):
|
||||
m.d.sync += [
|
||||
m_dcache_select.eq(x_dcache_select),
|
||||
m_addr.eq(self.x_addr),
|
||||
]
|
||||
|
||||
m.d.comb += [
|
||||
dcache.s1_addr.eq(self.x_addr[2:]),
|
||||
dcache.s1_stall.eq(self.x_stall),
|
||||
dcache.s1_valid.eq(self.x_valid),
|
||||
dcache.s2_addr.eq(m_addr[2:]),
|
||||
dcache.s2_re.eq(self.m_load & m_dcache_select),
|
||||
dcache.s2_evict.eq(self.m_store & m_dcache_select),
|
||||
dcache.s2_flush.eq(self.m_flush),
|
||||
dcache.s2_valid.eq(self.m_valid),
|
||||
]
|
||||
|
||||
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)
|
||||
m.d.comb += [
|
||||
wrbuf_port.cyc.eq(wrbuf.r_rdy),
|
||||
wrbuf_port.we.eq(Const(1)),
|
||||
]
|
||||
with m.If(wrbuf_port.stb):
|
||||
with m.If(wrbuf_port.ack | wrbuf_port.err):
|
||||
m.d.sync += wrbuf_port.stb.eq(0)
|
||||
m.d.comb += wrbuf.r_en.eq(1)
|
||||
with m.Elif(wrbuf.r_rdy):
|
||||
m.d.sync += [
|
||||
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)
|
||||
]
|
||||
|
||||
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.sel.eq(0b1111),
|
||||
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_flush):
|
||||
m.d.comb += self.m_busy.eq(~dcache.s2_flush_ack)
|
||||
with m.If(self.m_load_error | self.m_store_error):
|
||||
m.d.comb += self.m_busy.eq(0)
|
||||
with m.Elif(m_dcache_select):
|
||||
m.d.comb += [
|
||||
self.m_busy.eq(self.m_load & 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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,38 @@
|
|||
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)
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
d_fetch_misaligned = Signal()
|
||||
m.d.comb += [
|
||||
d_fetch_misaligned.eq(self.d_pc[:2].bool() | self.d_offset[:2].bool()),
|
||||
self.d_branch_target.eq(self.d_pc + self.d_offset),
|
||||
]
|
||||
|
||||
with m.If(d_fetch_misaligned):
|
||||
m.d.comb += self.d_branch_taken.eq(0)
|
||||
with m.Elif(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)
|
||||
|
||||
return m
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,117 @@
|
|||
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):
|
||||
if not isinstance(nb_triggers, int):
|
||||
raise TypeError("Number of triggers must be an int, not {!r}"
|
||||
.format(nb_triggers))
|
||||
if nb_triggers == 0 or nb_triggers & nb_triggers - 1:
|
||||
raise ValueError("Number of triggers must be a power of 2, not {!r}"
|
||||
.format(nb_triggers))
|
||||
self.nb_triggers = nb_triggers
|
||||
|
||||
self.tselect = CSR(0x7a0, flat_layout)
|
||||
self.tdata1 = CSR(0x7a1, tdata1_layout)
|
||||
self.tdata2 = CSR(0x7a2, flat_layout)
|
||||
|
||||
self.x_pc = Signal(32)
|
||||
self.x_valid = Signal()
|
||||
|
||||
self.haltreq = Signal()
|
||||
self.x_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 - 1)):
|
||||
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.x_trap.eq(1)
|
||||
|
||||
return m
|
|
@ -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
|
|
@ -0,0 +1,19 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
setup(
|
||||
name="minerva",
|
||||
version="0.1",
|
||||
description="A 32-bit RISC-V soft processor",
|
||||
author="Jean-François Nguyen",
|
||||
author_email="jf@lambdaconcept.com",
|
||||
license="BSD",
|
||||
python_requires="~=3.6",
|
||||
install_requires=["nmigen>=0.1rc1"],
|
||||
extras_require={ "debug": ["jtagtap"] },
|
||||
packages=find_packages(),
|
||||
project_urls={
|
||||
"Source Code": "https://github.com/lambdaconcept/minerva",
|
||||
"Bug Tracker": "https://github.com/lambdaconcept/minerva/issues"
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue