diff --git a/doc/run.md b/doc/run.md index 42a9db9..f718f9f 100644 --- a/doc/run.md +++ b/doc/run.md @@ -60,13 +60,12 @@ file build/$PROJ_NAME.elf # make sure it exists #### Dependencies -For `Ubuntu 22.04` and newer: +* yosys +* nextpnr-ice40 +* fpga-icestorm -```sh -sudo apt-get install yosys nextpnr-ice40 fpga-icestorm -``` - -For different distros follow the instructions in project references. +In theory `yosys` and `nextpnr` are available to install as `apt-get install` from Ubuntu 22.04 and newer, however it ships a very old version. +I recommend compiling it from sources instead. Using old `yosys` may cause too much resources being used and `mtkcpu` won't synthesize at all! #### Bitstream generation @@ -104,4 +103,4 @@ Info: ICESTORM_SPRAM: 0/ 4 0% If you run the command above with additional `--program` param, it will program your board after build succeeded. -And this is it, your board is blinking happily! \ No newline at end of file +And this is it, your board is blinking happily! diff --git a/mtkcpu/cli/top.py b/mtkcpu/cli/top.py index 5a9d763..1616ec3 100755 --- a/mtkcpu/cli/top.py +++ b/mtkcpu/cli/top.py @@ -129,7 +129,9 @@ def generate_bsp(): dev_mode=False, with_debug=True, pc_reset_value=0xdeadbeef, + with_virtual_memory=False, ) + cpu = get_board_cpu(elf_path=None, cpu_config=cpu_config) platform = get_platform() dummy_elaborate(cpu, platform) diff --git a/mtkcpu/cpu/cpu.py b/mtkcpu/cpu/cpu.py index 5f9279d..0b1c685 100755 --- a/mtkcpu/cpu/cpu.py +++ b/mtkcpu/cpu/cpu.py @@ -6,7 +6,7 @@ from amaranth import Mux, Cat, Signal, Const, Record, Elaboratable, Module, Memory, signed from amaranth.hdl.rec import Layout -from mtkcpu.units.csr import CsrUnit, match_csr +from mtkcpu.units.csr.csr import CsrUnit, match_csr from mtkcpu.units.exception import ExceptionUnit from mtkcpu.utils.common import EBRMemConfig from mtkcpu.units.adder import AdderUnit, match_adder_unit @@ -150,6 +150,7 @@ def __init__( self.running_state = CpuRunningState() self.running_state_interface = CpuRunningStateExternalInterface() + self.running_state_interface._MustUse__used = True def elaborate(self, platform): @@ -262,8 +263,8 @@ def prev(sig: Signal) -> Signal: single_step_is_active = Signal() just_resumed = self.just_resumed = Signal() just_halted = self.just_halted = Signal() - dcsr = self.csr_unit.reg_by_addr(CSRIndex.DCSR).rec.r - dpc = self.csr_unit.reg_by_addr(CSRIndex.DPC).rec.r + dcsr = self.csr_unit.dcsr + dpc = self.csr_unit.dpc with m.If(just_resumed): sync += single_step_is_active.eq(dcsr.step) diff --git a/mtkcpu/cpu/priv_isa.py b/mtkcpu/cpu/priv_isa.py index 18fa190..80b5316 100644 --- a/mtkcpu/cpu/priv_isa.py +++ b/mtkcpu/cpu/priv_isa.py @@ -92,135 +92,6 @@ class IrqCause(IntEnum): S_EXTERNAL_INTERRUPT = 9 M_EXTERNAL_INTERRUPT = 11 - -# CSR layouts - -flat_layout = [ - ("value", 32), -] - - -class MisaExtensionBit(IntEnum): - INTEGER_BASE_ISA = 1 << 8 - MULDIV = 1 << 12 - -class MisaRXL(IntEnum): - RV32 = 1 - RV64 = 2 - -class MtvecModeBits(IntEnum): - DIRECT = 0 # All exceptions set pc to BASE. - VECTORED = 1 # Asynchronous interrupts set pc to BASE+4×cause. - -misa_layout = [ - ("extensions", 26), - ("zero", 4), - ("mxl", 2), -] - -mstatus_layout = [ - ("uie", 1), # User Interrupt Enable - ("sie", 1), # Supervisor Interrupt Enable - ("zero0", 1), - ("mie", 1), # Machine Interrupt Enable - ("upie", 1), # User Previous Interrupt Enable - ("spie", 1), # Supervisor Previous Interrupt Enable - ("zero1", 1), - ("mpie", 1), # Machine Previous Interrupt Enable - ("spp", 1), # Supervisor Previous Privilege - ("zero2", 2), - ("mpp", 2), # Machine Previous Privilege - ("fs", 2), # FPU Status - ("xs", 2), # user-mode eXtensions Status - ("mprv", 1), # Modify PRiVilege - ("sum", 1), # Supervisor User Memory access - ("mxr", 1), # Make eXecutable Readable - ("tvm", 1), # Trap Virtual Memory - ("tw", 1), # Timeout Wait - ("tsr", 1), # Trap SRET - ("zero3", 8), - ("sd", 1), # State Dirty (set if XS or FS are set to dirty) -] - -mtvec_layout = [ - ("mode", 2), - ("base", 30), -] - -mip_layout = [ - ("usip", 1), - ("ssip", 1), - ("zero0", 1), - ("msip", 1), - ("utip", 1), - ("stip", 1), - ("zero1", 1), - ("mtip", 1), - ("ueip", 1), - ("seip", 1), - ("zero2", 1), - ("meip", 1), - ("zero3", 20), -] - - -mie_layout = [ - ("usie", 1), - ("ssie", 1), - ("zero0", 1), - ("msie", 1), - ("utie", 1), - ("stie", 1), - ("zero1", 1), - ("mtie", 1), - ("ueie", 1), - ("seie", 1), - ("zero2", 1), - ("meie", 1), - ("zero3", 20), -] - - -mcause_layout = [ - ("ecode", 31), - ("interrupt", 1), -] - - -dcsr_layout = [ - ("prv", 2), - ("step", 1), - ("nmip", 1), - ("mprven", 1), - ("v", 1), - ("cause", 3), - ("stoptime", 1), - ("stopcount", 1), - ("stepie", 1), - ("ebreaku", 1), - ("ebreaks", 1), - ("zero1", 1), - ("ebreakm", 1), - ("ebreakvu", 1), - ("ebreakvs", 1), - ("zero2", 10), - ("debugver", 4), -] - - -tdata1_layout = [ - ("data", 27), - ("dmode", 1), - ("type", 4), -] - - -satp_layout = [ - ("ppn", 22), - ("asid", 9), - ("mode", 1), -] - pte_layout = [ ("v", 1), ("r", 1), @@ -239,4 +110,25 @@ class MtvecModeBits(IntEnum): ("page_offset", 12), ("vpn0", 10), ("vpn1", 10), -] \ No newline at end of file +] + +from amaranth.lib import data +from amaranth import unsigned + +class PTE_Layout(data.Struct): + v : unsigned(1) + r : unsigned(1) + w : unsigned(1) + x : unsigned(1) + u : unsigned(1) + g : unsigned(1) + a : unsigned(1) + d : unsigned(1) + rsw : unsigned(2) + ppn0 : unsigned(10) + ppn1 : unsigned(12) + +class Virt_Addr_Layout(data.Struct): + page_offset : unsigned(12) + vpn0 : unsigned(10) + vpn1 : unsigned(10) \ No newline at end of file diff --git a/mtkcpu/tests/test_address_translation.py b/mtkcpu/tests/test_address_translation.py index ad61dd5..01f3b9d 100644 --- a/mtkcpu/tests/test_address_translation.py +++ b/mtkcpu/tests/test_address_translation.py @@ -3,9 +3,9 @@ from mtkcpu.utils.tests.memory import MemoryContents from mtkcpu.utils.tests.registers import RegistryContents from mtkcpu.utils.tests.utils import (MemTestCase, MemTestSourceType, mem_test) +from mtkcpu.units.csr.types import MSTATUS_Layout, SATP_Layout -from mtkcpu.cpu.priv_isa import PrivModeBits, pte_layout, satp_layout -from mtkcpu.units.csr import RegisterResetValue +from amaranth import Signal # page tables phys. addresses must be aligned to 4K == 0x1000 bytes root_pt_offset = 0x2000 @@ -14,22 +14,15 @@ root_pt_addr = MEM_START_ADDR + root_pt_offset leaf_pt_addr = MEM_START_ADDR + leaf_pt_offset -def get_flat_value_generator(layout): - return lambda fields: RegisterResetValue.calc_reset_value(fields, layout) - -def get_field_values_generator(layout): - return lambda value: RegisterResetValue.value_to_fields(value, layout) - -satp_get_flat_value = get_flat_value_generator(satp_layout) -satp_get_field_values = get_field_values_generator(satp_layout) - -pte_get_flat_value = get_flat_value_generator(pte_layout) -pte_get_fields_values = get_field_values_generator(pte_layout) - -satp_value = satp_get_flat_value({ +satp_value = SATP_Layout.const({ "mode": 1, # enable address translation in user mode "ppn": root_pt_addr >> 12, -}) +}).value + +pte_const = lambda fields: PTE_Layout.const(fields).value + +# https://github.com/amaranth-lang/amaranth/issues/786 +mpp_offset_in_MSTATUS = MSTATUS_Layout(Signal(32))._View__layout._fields["mpp"].offset virt_addr_high = 0x111 virt_addr_low = 0x222 @@ -66,7 +59,7 @@ def get_field_values_generator(layout): // set 'previous priv.' mstatus's field to user mode li x4, {PrivModeBits.USER} - slli x4, x4, {get_layout_field_offset(mstatus_layout, 'mpp')} + slli x4, x4, {mpp_offset_in_MSTATUS} csrw mstatus, x4 // set machine mode trap la x4, mmode_trap @@ -89,13 +82,13 @@ def get_field_values_generator(layout): # when usermode starts, it's expected to perform address translation # and jump to MEM_START_ADDR + 0x1000 + offset (address of symbol 'usermode'). mem_init=MemoryContents(memory={ - root_page_phys_addr: pte_get_flat_value({ + root_page_phys_addr: pte_const({ "v": 1, # {ppn0, ppn1} is a pointer to the next level pte "ppn1": hi_pn(leaf_pt_addr >> 12), "ppn0": lo_pn(leaf_pt_addr >> 12), }), - leaf_page_phys_addr: pte_get_flat_value({ + leaf_page_phys_addr: pte_const({ "v": 1, "r": 1, "w": 1, diff --git a/mtkcpu/tests/test_csr.py b/mtkcpu/tests/test_csr.py index 4f4e18d..296a513 100644 --- a/mtkcpu/tests/test_csr.py +++ b/mtkcpu/tests/test_csr.py @@ -4,7 +4,7 @@ from mtkcpu.utils.tests.registers import RegistryContents from mtkcpu.utils.tests.utils import (MemTestCase, MemTestSourceType, mem_test) -from mtkcpu.units.csr_handlers import MISA +from mtkcpu.units.csr.csr_handlers import MISA # some registers (e.g. mcause) are tested in test_exception.py file. CSR_TESTS = [ @@ -53,7 +53,7 @@ csrr x3, misa """, out_reg=3, - out_val=MISA().reset_value, + out_val=MISA.const(), timeout=5, mem_init=MemoryContents.empty(), reg_init=RegistryContents.fill(), @@ -68,7 +68,7 @@ csrr x2, misa """, out_reg=2, - out_val=MISA().reset_value, + out_val=MISA.const(), timeout=5, mem_init=MemoryContents.empty(), reg_init=RegistryContents.fill(), @@ -269,7 +269,7 @@ # ), MemTestCase( - name="basic csrrc", + name="basic csrrs - misa", source_type=MemTestSourceType.RAW, source=f""" start: diff --git a/mtkcpu/tests/test_debug_unit.py b/mtkcpu/tests/test_debug_unit.py index 5bc191e..b9ec01a 100755 --- a/mtkcpu/tests/test_debug_unit.py +++ b/mtkcpu/tests/test_debug_unit.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from typing import Sequence from dataclasses import dataclass from amaranth.sim import Simulator, Settle @@ -15,7 +16,7 @@ from mtkcpu.units.debug.dmi_handlers import DMI_HANDLERS_MAP from mtkcpu.units.debug.impl_config import PROGBUFSIZE, PROGBUF_MMIO_ADDR from mtkcpu.units.debug.impl_config import DATASIZE -from mtkcpu.units.csr_handlers import DCSR +from mtkcpu.units.csr.csr_handlers import DCSR logging = get_color_logging_object() @@ -526,7 +527,7 @@ def main_process(): new_pc = pc // 2 + 0x1000 assert new_pc != pc - yield dmi_monitor.cpu.csr_unit.reg_by_addr(CSRIndex.DPC).rec.r.eq(new_pc) + yield dmi_monitor.cpu.csr_unit.dpc.eq(new_pc) yield from DMCONTROL_setup_basic_fields(dmi_monitor=dmi_monitor, dmi_op=DMIOp.WRITE) yield dmi_monitor.cur_DMCONTROL.haltreq.eq(0) @@ -656,7 +657,7 @@ def mepc(): yield Passive() while True: pc = yield cpu.pc - mepc = yield cpu.csr_unit.mepc.value + mepc = yield cpu.csr_unit.mepc.as_value() instr = yield cpu.instr print("mepc", hex(mepc), "pc", hex(pc), "instr", hex(instr)) yield @@ -687,7 +688,7 @@ def main_process(): yield from few_ticks() # TODO: hardcoded 'cause' field offset. - expected_dcsr_reset_value = DCSR()._reset_value.value | (DCSR_DM_Entry_Cause.HALTREQ << 6) + expected_dcsr_reset_value = DCSR.const() | (DCSR_DM_Entry_Cause.HALTREQ << 6) DCSR_ins = instructions.RiscvCsrRegister("dcsr", num=0x7b0) diff --git a/mtkcpu/tests/test_priv_modes.py b/mtkcpu/tests/test_priv_modes.py index 64616fe..76d15de 100644 --- a/mtkcpu/tests/test_priv_modes.py +++ b/mtkcpu/tests/test_priv_modes.py @@ -3,10 +3,14 @@ from mtkcpu.utils.tests.memory import MemoryContents from mtkcpu.utils.tests.registers import RegistryContents from mtkcpu.utils.tests.utils import (MemTestCase, MemTestSourceType, mem_test) +from mtkcpu.units.csr.csr_handlers import MISA +from mtkcpu.units.csr.types import MSTATUS_Layout +from mtkcpu.cpu.priv_isa import PrivModeBits -from mtkcpu.units.csr_handlers import MISA +from amaranth import Signal -from mtkcpu.cpu.priv_isa import PrivModeBits +# https://github.com/amaranth-lang/amaranth/issues/786 +mpp_offset_in_MSTATUS = MSTATUS_Layout(Signal(32))._View__layout._fields["mpp"].offset PRIV_TESTS = [ MemTestCase( @@ -19,7 +23,7 @@ csrw mepc, x5 // set 'previous priv.' mstatus's field to user mode li x4, {PrivModeBits.USER} - slli x4, x4, {get_layout_field_offset(mstatus_layout, 'mpp')} + slli x4, x4, {mpp_offset_in_MSTATUS} csrw mstatus, x4 // set machine mode trap la x4, mmode_trap @@ -35,7 +39,7 @@ csrr x3, mstatus """, out_reg=3, - out_val=lambda x : x & (1 << get_layout_field_offset(mstatus_layout, 'mpp')) == PrivModeBits.USER, + out_val=lambda x : x & (1 << mpp_offset_in_MSTATUS) == PrivModeBits.USER, timeout=150, mem_init=MemoryContents.empty(), reg_init=RegistryContents.fill(), @@ -51,7 +55,7 @@ csrw mepc, x5 // set 'previous priv.' mstatus's field to user mode li x4, {PrivModeBits.USER} - slli x4, x4, {get_layout_field_offset(mstatus_layout, 'mpp')} + slli x4, x4, {mpp_offset_in_MSTATUS} csrw mstatus, x4 // set machine mode trap la x4, mmode_trap diff --git a/mtkcpu/units/csr.py b/mtkcpu/units/csr/csr.py similarity index 59% rename from mtkcpu/units/csr.py rename to mtkcpu/units/csr/csr.py index 18cfae6..26ed0f5 100644 --- a/mtkcpu/units/csr.py +++ b/mtkcpu/units/csr/csr.py @@ -1,11 +1,11 @@ -from typing import Callable, Dict, List, Union +from typing import Sequence from amaranth import Signal, Elaboratable, Module from mtkcpu.utils.common import matcher from mtkcpu.cpu.isa import Funct3, InstrType from mtkcpu.cpu.priv_isa import * -from mtkcpu.units.csr_handlers import * +from mtkcpu.units.csr.csr_handlers import * def _is(x, ys): @@ -13,62 +13,45 @@ def _is(x, ys): from operator import or_ return reduce(or_, [x == y for y in ys]) -# RegisterCSR handlers do communicate with CsrUnit via signals defined here. -class ControllerInterface(): - def __init__(self): - self.handler_done = Signal() - class CsrUnit(Elaboratable): - def enabled_csr_regs(self, controller : ControllerInterface): - regs : set[RegisterCSR] = { - MISA(), - MTVEC(), - MTVAL(), - MEPC(), - DPC(), - MSCRATCH(), - MHARTID(), - MCAUSE(), - MTIME(), - MTIMECMP(), - MSTATUS(), - MIE(), - MIP(), - DCSR(), + @staticmethod + def enabled_csr_regs(with_virtual_memory: bool) -> Sequence[type]: + regs = { + MISA, + MTVEC, + MTVAL, + MEPC, + DPC, + MSCRATCH, + MHARTID, + MCAUSE, + MTIME, + MTIMECMP, + MSTATUS, + MIE, + MIP, + DCSR, } - - if self.with_virtual_memory: - regs.add(SATP()) - - def sanity_check(): - for r in regs: - r.associate_with_csr_unit(controller, self) - for r in regs: - assert len(r.rec.r) == 32 - sanity_check() + if with_virtual_memory: + regs.add(SATP) return regs - def reg_by_addr(self, addr : Union[CSRIndex, CSRNonStandardIndex]) -> RegisterCSR: - all = [x for x in self.csr_regs if x.csr_idx == addr] - if not all: - raise ValueError(f"CSR with address {hex(addr)} not defined!") - return all[0] + def reg_by_addr(self, addr : CSRNonStandardIndex | CSRIndex) -> CSR_Write_Handler: + matches = [x for x in self.csr_regs if x.addr == addr] + if len(matches) != 1: + raise ValueError(f"Expected to find a single CSR with address {hex(addr)}, got {matches} instead!") + return matches[0] # simplify direct access, e.g. csr_unit.mtvec - def __getattr__(self, name): - for reg in self.csr_regs: - if reg.name == name: - if isinstance(reg, WriteOnlyRegisterCSR): - return reg.rec.w - elif isinstance(reg, ReadOnlyRegisterCSR): - return reg.rec.r - elif isinstance(reg, RegisterCSR): - return reg.rec.r - else: - assert False - raise ValueError(f"CSRUnit: Not found register named {name}") - + def __getattr__(self, name: str) -> data.View: + def get_name(handler: CSR_Write_Handler) -> str: + return handler.__class__.__name__ + matches = [x for x in self.csr_regs if get_name(x).lower() == name.lower()] + if len(matches) != 1: + raise ValueError(f"Expected to find a single CSR named {name}, got {matches} instead!") + match = matches[0] + return data.View(match.layout, match.my_reg_latch) def __init__(self, in_machine_mode : Signal, @@ -87,22 +70,19 @@ def __init__(self, self.in_debug_mode = in_debug_mode self.with_virtual_memory = with_virtual_memory - # Debug - self.ONREAD = Signal() - self.ONWRITE = Signal() - self.ONSET = Signal() - self.ONCLEAR = Signal() - # Output signals. self.rd_val = Signal(32) self.vld = Signal() self.illegal_insn = Signal() - - self.controller = ControllerInterface() - self.csr_regs = self.enabled_csr_regs(self.controller) + self.csr_regs = [ + reg_constructor(my_reg_latch=Signal(32, reset=reg_constructor.const())) + for reg_constructor in + __class__.enabled_csr_regs(with_virtual_memory=with_virtual_memory) + ] def elaborate(self, platform): m = self.m = Module() + m.submodules += self.csr_regs sync = m.d.sync comb = m.d.comb @@ -111,7 +91,7 @@ def elaborate(self, platform): func3_latch = Signal.like(self.func3) csr_idx_latch = Signal.like(self.csr_idx) - with m.FSM() as fsm: + with m.FSM(): with m.State("IDLE"): with m.If(self.en): with m.If(~self.in_machine_mode): @@ -119,7 +99,7 @@ def elaborate(self, platform): with m.Else(): with m.Switch(self.csr_idx): for reg in self.csr_regs: - with m.Case(reg.csr_idx): + with m.Case(reg.addr): sync += [ rd_latch.eq(self.rd), rs1_latch.eq(self.rs1), @@ -130,7 +110,7 @@ def elaborate(self, platform): # Debug Specs 1.0, 4.10: # 'These registers are only accessible from Debug Mode.' - if reg.csr_idx in range(0x7b0, 0x7b4): + if reg.addr in range(0x7b0, 0x7b4): with m.If(~self.in_debug_mode): # TODO: slippery code here - needs to be reverified. m.d.comb += self.illegal_insn.eq(1) @@ -142,21 +122,18 @@ def elaborate(self, platform): # NOTE from doc: # If rd=x0, then the instruction shall not read the CSR and shall not # cause any of the side effects that might occur on a CSR read. - # however, we don't support read side-effects, so we can save one mux here. + # however, we don't support read side-effects, so we skip that check (and save on resources). with m.Switch(csr_idx_latch): for reg in self.csr_regs: - with m.Case(reg.csr_idx): - register = reg.rec.w if isinstance(reg, WriteOnlyRegisterCSR) else reg.rec.r - m.d.sync += self.rd_val.eq(register) + with m.Case(reg.addr): + m.d.sync += self.rd_val.eq(reg.my_reg_latch) + dst = Signal(32) + with m.Switch(csr_idx_latch): for reg in self.csr_regs: - with m.Case(reg.csr_idx): - if isinstance(reg, ReadOnlyRegisterCSR): - continue - src = reg.rec.w if isinstance(reg, WriteOnlyRegisterCSR) else reg.rec.r - dst = reg.rec.w # always exists - # TODO it needs to use rec.r + with m.Case(reg.addr): + src = reg.my_reg_latch with m.If(_is(func3_latch, [Funct3.CSRRS, Funct3.CSRRSI])): m.d.sync += dst.eq(src | self.rs1val) with m.Elif(_is(func3_latch, [Funct3.CSRRC, Funct3.CSRRCI])): @@ -167,26 +144,20 @@ def elaborate(self, platform): with m.State("REG_SPECIFIC"): with m.Switch(csr_idx_latch): for reg in self.csr_regs: - with m.Case(reg.csr_idx): + with m.Case(reg.addr): # NOTE from doc: # For both CSRRS and CSRRC, if rs1=x0, then the instruction will not write # to the CSR at all, and so shall not cause any of the side effects # that might otherwise occur on a CSR write, - need_wait = Signal() - with m.If(need_wait): - with m.If(self.controller.handler_done): - m.next = "FINISH" - with m.Else(): - # covers all non-waiting paths. + with m.If(_is(func3_latch, [Funct3.CSRRS, Funct3.CSRRC]) & (rs1_latch == 0)): m.next = "FINISH" - - with m.If(_is(func3_latch, [Funct3.CSRRS, Funct3.CSRRC])): - with m.If(rs1_latch != 0): - reg.handle_write() - comb += need_wait.eq(1) with m.Else(): - reg.handle_write() - comb += need_wait.eq(1) + comb += [ + reg.active.eq(1), + reg.write_value.eq(dst) + ] + with m.If(reg.write_finished): + m.next = "FINISH" with m.State("FINISH"): m.next = "IDLE" comb += [ diff --git a/mtkcpu/units/csr/csr_handlers.py b/mtkcpu/units/csr/csr_handlers.py new file mode 100644 index 0000000..5aca502 --- /dev/null +++ b/mtkcpu/units/csr/csr_handlers.py @@ -0,0 +1,205 @@ +from amaranth import Signal, Module, Elaboratable + +from mtkcpu.units.csr.types import * +from mtkcpu.utils.common import CODE_START_ADDR +from mtkcpu.cpu.priv_isa import CSRIndex, CSRNonStandardIndex +from amaranth.lib import data + +from abc import ABC, abstractmethod +from typing import Optional + +class CSR_Write_Handler(ABC, Elaboratable): + + # 'layout' field is to be overwritten by subclasses, with more detailed ones. + layout=data.StructLayout({"_value": unsigned(32)}) + + # default 'flat' layout makes sense, but default register address could cause pain. + @property + @abstractmethod + def addr(self) -> int: + pass + + def __init__(self, my_reg_latch: Optional[Signal] = None): + # -- Input signals + # + # Needs to be deasserted in cycle following 'controller.cmd_finished' asserted. + self.active = Signal() + self.write_value = Signal(32) + self.write_finished = Signal() + + if my_reg_latch is not None: + self.my_reg_latch = my_reg_latch + assert my_reg_latch.width == 32 + + def latch_whole_value_with_no_side_effect(self): + m = Module() + + with m.If(self.active): + m.d.sync += self.my_reg_latch.eq(self.write_value) + m.d.comb += self.write_finished.eq(1) + + return m + + @classmethod + def const(cls) -> int: + return cls.layout.const(cls.reset()).value + + @staticmethod + def reset() -> dict[str, int]: + return {} + + def latch_partial_value_with_no_side_effect(self, fields: list[str]): + m = Module() + + lhs = data.View(self.layout, self.my_reg_latch) + rhs = data.View(self.layout, self.write_value) + + with m.If(self.active): + for x in fields: + m.d.sync += getattr(lhs, x).eq(getattr(rhs, x)) + m.d.comb += self.write_finished.eq(1) + + return m + + def no_action_at_all(self): + return self.latch_partial_value_with_no_side_effect(fields=[]) + + def elaborate(self, _): + raise NotImplementedError() + +class MISA(CSR_Write_Handler): + layout = MISA_Layout + addr = CSRIndex.MISA + + def elaborate(self, _): + return self.no_action_at_all() + + def reset(): + return { + "mxl": MisaRXL.RV32, + "extensions": MisaExtensionBit.INTEGER_BASE_ISA, + } + +class MTVEC(CSR_Write_Handler): + layout = MTVEC_Layout + addr = CSRIndex.MTVEC + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + + def reset(): + return { + "mode": MtvecModeBits.DIRECT, + "base": (CODE_START_ADDR + 0x20) >> 2 + } + + +class MTVAL(CSR_Write_Handler): + layout = MTVEC_Layout + addr = CSRIndex.MTVAL + + def elaborate(self, _): + return self.no_action_at_all() + +class MEPC(CSR_Write_Handler): + addr = CSRIndex.MEPC + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + + +class DPC(CSR_Write_Handler): + addr = CSRIndex.DPC + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + + +class DCSR(CSR_Write_Handler): + layout = DCSR_Layout + addr = CSRIndex.DCSR + + def reset() -> dict[str, int]: + return { + # For valid (prv, v) combination, refer to Debug Specs 1.0, table 4.6. + "prv": 3, + "v": 0, + # From Debug Specs 1.0: + # 4 - Debug support exists as it is described in this document. + "debugver": 4, + } + def elaborate(self, _): + return self.latch_partial_value_with_no_side_effect(fields=["step", "ebreakm"]) + + +class MSCRATCH(CSR_Write_Handler): + addr = CSRIndex.MSCRATCH + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + + +class MHARTID(CSR_Write_Handler): + addr = CSRIndex.MHARTID + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + + +class MCAUSE(CSR_Write_Handler): + addr = CSRIndex.MCAUSE + layout = MCAUSE_Layout + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + + +class MSTATUS(CSR_Write_Handler): + addr = CSRIndex.MSTATUS + layout = MSTATUS_Layout + + # TODO dangerous (doesn't implement WARL) - change it + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + +class MIE(CSR_Write_Handler): + addr = CSRIndex.MIE + layout = MIE_Layout + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + +class MIP(CSR_Write_Handler): + addr = CSRIndex.MIE + layout = MIP_Layout + + # TODO + # For now it's fully readonly - doesn't support software interrupts, + # normally triggered via write to {m|s|u}sip field. + def elaborate(self, _): + return Module() + +class SATP(CSR_Write_Handler): + addr = CSRIndex.SATP + layout = SATP_Layout + + def elaborate(self, _): + return self.latch_partial_value_with_no_side_effect(fields=["ppn", "mode"]) + + +class MTIME(CSR_Write_Handler): + addr = CSRNonStandardIndex.MTIME + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() + +class MTIMECMP(CSR_Write_Handler): + addr = CSRNonStandardIndex.MTIMECMP + + # TODO - for now i commented out some timer IRQ functionality + # # From https://forums.sifive.com/t/how-to-clear-interrupt-in-interrupt-handler/2781: + # # The timer interrupt for example is cleared with writing a new value to the mtimecmp register (which must be higher than the current timer value). + # m.d.sync += [ + # self.csr_unit.mip.mtip.eq(0) + # ] + + def elaborate(self, _): + return self.latch_whole_value_with_no_side_effect() diff --git a/mtkcpu/units/csr/types.py b/mtkcpu/units/csr/types.py new file mode 100644 index 0000000..05686b5 --- /dev/null +++ b/mtkcpu/units/csr/types.py @@ -0,0 +1,113 @@ +from dataclasses import dataclass +from enum import IntEnum + +from amaranth.lib import data, enum +from amaranth import unsigned + + +class MISA_Layout(data.Struct): + extensions: unsigned(26) + zero: unsigned(4) + mxl: unsigned(2) + +class MSTATUS_Layout(data.Struct): + uie: unsigned(1) # User Interrupt Enable + sie: unsigned(1) # Supervisor Interrupt Enable + zero0: unsigned(1) + mie: unsigned(1) # Machine Interrupt Enable + upie: unsigned(1) # User Previous Interrupt Enable + spie: unsigned(1) # Supervisor Previous Interrupt Enable + zero1: unsigned(1) + mpie: unsigned(1) # Machine Previous Interrupt Enable + spp: unsigned(1) # Supervisor Previous Privilege + zero2: unsigned(2) + mpp: unsigned(2) # Machine Previous Privilege + fs: unsigned(2) # FPU Status + xs: unsigned(2) # user-mode eXtensions Status + mprv: unsigned(1) # Modify PRiVilege + sum: unsigned(1) # Supervisor User Memory access + mxr: unsigned(1) # Make eXecutable Readable + tvm: unsigned(1) # Trap Virtual Memory + tw: unsigned(1) # Timeout Wait + tsr: unsigned(1) # Trap SRET + zero3: unsigned(8) + sd: unsigned(1) # State Dirty (set if XS or FS are set to dirty) + +class MTVEC_Layout(data.Struct): + mode: unsigned(2) + base: unsigned(30) + +class MIP_Layout(data.Struct): + usip: unsigned(1) + ssip: unsigned(1) + zero0: unsigned(1) + msip: unsigned(1) + utip: unsigned(1) + stip: unsigned(1) + zero1: unsigned(1) + mtip: unsigned(1) + ueip: unsigned(1) + seip: unsigned(1) + zero2: unsigned(1) + meip: unsigned(1) + zero3: unsigned(20) + +class MIE_Layout(data.Struct): + usie: unsigned(1) + ssie: unsigned(1) + zero0: unsigned(1) + msie: unsigned(1) + utie: unsigned(1) + stie: unsigned(1) + zero1: unsigned(1) + mtie: unsigned(1) + ueie: unsigned(1) + seie: unsigned(1) + zero2: unsigned(1) + meie: unsigned(1) + zero3: unsigned(20) + +class MCAUSE_Layout(data.Struct): + ecode: unsigned(31) + interrupt: unsigned(1) + +class DCSR_Layout(data.Struct): + prv: unsigned(2) + step: unsigned(1) + nmip: unsigned(1) + mprven: unsigned(1) + v: unsigned(1) + cause: unsigned(3) + stoptime: unsigned(1) + stopcount: unsigned(1) + stepie: unsigned(1) + ebreaku: unsigned(1) + ebreaks: unsigned(1) + zero1: unsigned(1) + ebreakm: unsigned(1) + ebreakvu: unsigned(1) + ebreakvs: unsigned(1) + zero2: unsigned(10) + debugver: unsigned(4) + +class SATP_Layout(data.Struct): + ppn: unsigned(22) + asid: unsigned(9) + mode: unsigned(1) + + +flat_layout = [ + ("value", 32), +] + +class MisaExtensionBit(IntEnum): + INTEGER_BASE_ISA = 1 << 8 + MULDIV = 1 << 12 + +class MisaRXL(IntEnum): + RV32 = 1 + RV64 = 2 + +class MtvecModeBits(IntEnum): + DIRECT = 0 # All exceptions set pc to BASE. + VECTORED = 1 # Asynchronous interrupts set pc to BASE+4×cause. diff --git a/mtkcpu/units/csr_handlers.py b/mtkcpu/units/csr_handlers.py deleted file mode 100644 index ba04cf7..0000000 --- a/mtkcpu/units/csr_handlers.py +++ /dev/null @@ -1,323 +0,0 @@ -from typing import Dict - -from amaranth import Signal, Module -from amaranth.hdl.rec import Record - -from mtkcpu.cpu.priv_isa import * -from mtkcpu.utils.common import CODE_START_ADDR - -class RegisterResetValue: - def __init__(self, layout) -> None: - self.layout = layout - self.sanity_check() - - def field_values(self) -> Dict[str, int]: - raise NotImplementedError("ReadOnlyRegValue must implement 'field_values(self)' method!") - - def __len__(self): - return sum([x[1] for x in self.layout]) - - def set_reset(self, rec : Record): - fields : Dict[str, int] = self.field_values() - for name, sig in rec.fields.items(): - if isinstance(sig, Signal): - reset_val = fields.get(name, None) - if reset_val is not None: - sig.reset = reset_val - elif isinstance(sig, Record): - assert False - self.set_reset(sig) - else: - assert False - - @staticmethod - def calc_reset_value(fields: Dict[str, int], layout: List[Tuple[str, int]]) -> int: - res, off = 0, 0 - for name, width, *_ in layout: - res |= (fields.get(name, 0) & ((1 << width) - 1)) << off - off += width - return res - - @staticmethod - def value_to_fields(value: int, layout: List[Tuple[str, int]]) -> Dict[str, int]: - res = {} - aux_val = value - for name, width, *_ in layout: - res[name] = aux_val & ((1 << width) - 1) - aux_val >>= width - return res - - @property - def value(self) -> int: - return __class__.calc_reset_value(fields=self.field_values(), layout=self.layout) - - @property - def layout_field_names(self): - return [name for name, *_ in self.layout] - - def sanity_check(self): - for name in self.field_values().keys(): - if name not in self.layout_field_names: - raise ValueError(f"name {name} does not match known from layout {self.layout}") - -class RegisterCSR(): - def __init__(self, csr_idx, layout, reset_value_t): - self.name = self.__class__.__name__.lower() - self._reset_value : RegisterResetValue = reset_value_t(layout) - bits = len(self._reset_value) - assert bits == 32 - self.rec = Record(__class__.reg_make_rw(layout)) - self._reset_value.set_reset(self.rec.r) - self._reset_value.set_reset(self.rec.w) - self.csr_idx = csr_idx - self.controller = None - self.csr_unit = None - - @staticmethod - def reg_make_rw(layout): - f = lambda x : x[:2] - return [ - ("r", list(map(f, layout))), - ("w", list(map(f, layout))), - ] - - @property - def reset_value(self): - return self._reset_value.value - - def associate_with_csr_unit(self, controller : "ControllerInterface", csr_unit : "CsrUnit"): - self.controller = controller - self.csr_unit = csr_unit - - def get_m(self) -> Module: - csr_unit = self.csr_unit - if csr_unit is None: - raise ValueError("RegisterCSR: self.csr_unit not set during elaboration! Did you call super().__init__()?") - return self.csr_unit.m - - def handler_notify_comb(self): - m = self.get_m() - m.d.comb += self.controller.handler_done.eq(1) - - # because of WARL sometimes we cannot copy whole record - # TODO, should we overload it in ReadOnly/WriteOnly CSR registers to raise? Otherwise 'hasattr' will raise anyway. - def copy_specific_fields_only(self, fields : List[str]): - m = self.get_m() - for f in fields: - dst = getattr(self.rec.r, f) - src = getattr(self.rec.w, f) - m.d.sync += dst.eq(src) - m.d.comb += self.controller.handler_done.eq(1) - - def handle_write(self): - raise NotImplementedError("RegisterCSR must implement 'handle_write(self)' method!") - - -class ReadOnlyRegisterCSR(RegisterCSR): - # user doesn't have way to change it's read value. - def handle_write(self): - self.handler_notify_comb() - -class WriteOnlyRegisterCSR(RegisterCSR): - # only user is able to affect it's read value. - pass - -class MISA(ReadOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return { - "mxl": MisaRXL.RV32, - "extensions": MisaExtensionBit.INTEGER_BASE_ISA, - } - def __init__(self): - super().__init__(CSRIndex.MISA, misa_layout, __class__.RegValueLocal) - -class MTVEC(WriteOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return { - "mode": MtvecModeBits.DIRECT, - "base": (CODE_START_ADDR + 0x20) >> 2 - } - def __init__(self): - super().__init__(CSRIndex.MTVEC, mtvec_layout, __class__.RegValueLocal) - - def handle_write(self): - self.handler_notify_comb() - -class MTVAL(ReadOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MTVAL, flat_layout, __class__.RegValueLocal) - -class MEPC(RegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MEPC, flat_layout, __class__.RegValueLocal) - - def handle_write(self): - m = self.get_m() - m.d.sync += [ - self.rec.r.eq(self.rec.w) - ] - self.handler_notify_comb() - -class DPC(RegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.DPC, flat_layout, __class__.RegValueLocal) - - def handle_write(self): - m = self.get_m() - m.d.sync += [ - self.rec.r.eq(self.rec.w) - ] - self.handler_notify_comb() - - -# dcsr_layout = [ -# ("prv", 2, CSRAccess.RW), -# ("step", 1, CSRAccess.RW), -# ("nmip", 1, CSRAccess.RO), -# ("mprven", 1, CSRAccess.RW), -# ("v", 1, CSRAccess.RW), -# ("cause", 3, CSRAccess.RO), -# ("stoptime", 1, CSRAccess.RW), -# ("stopcount", 1, CSRAccess.RW), -# ("stepie", 1, CSRAccess.RW), -# ("ebreaku", 1, CSRAccess.RW), -# ("ebreaks", 1, CSRAccess.RW), -# ("zero1", 1, CSRAccess.RO) -# ("ebreakm", 1, CSRAccess.RW), -# ("ebreakvu", 1, CSRAccess.RW), -# ("ebreakvs", 1, CSRAccess.RW), -# ("zero2", 10, CSRAccess.RO), -# ("debugver", 4, CSRAccess.RO), -# ] - -class DCSR(RegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return { - # For valid (prv, v) combination, refer to Debug Specs 1.0, table 4.6. - "prv": 3, - "v": 0, - - # From Debug Specs 1.0: - # 4 - Debug support exists as it is described in this document. - "debugver": 4, - } - def __init__(self): - super().__init__(CSRIndex.DCSR, dcsr_layout, __class__.RegValueLocal) - - def handle_write(self): - m = self.get_m() - - for x in ["step", "ebreakm"]: - m.d.sync += [ - getattr(self.rec.r, x).eq(getattr(self.rec.w, x)) - ] - self.handler_notify_comb() - - -class MSCRATCH(WriteOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MSCRATCH, flat_layout, __class__.RegValueLocal) - - def handle_write(self): - self.handler_notify_comb() - -class MHARTID(ReadOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MHARTID, flat_layout, __class__.RegValueLocal) - -class MCAUSE(ReadOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MCAUSE, mcause_layout, __class__.RegValueLocal) - -class MTIME(ReadOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRNonStandardIndex.MTIME, flat_layout, __class__.RegValueLocal) - - -class MTIMECMP(WriteOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRNonStandardIndex.MTIMECMP, flat_layout, __class__.RegValueLocal) - - def handle_write(self): - m = self.get_m() - # From https://forums.sifive.com/t/how-to-clear-interrupt-in-interrupt-handler/2781: - # The timer interrupt for example is cleared with writing a new value to the mtimecmp register (which must be higher than the current timer value). - m.d.sync += [ - self.csr_unit.mip.mtip.eq(0) - ] - self.handler_notify_comb() - - -class MSTATUS(RegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MSTATUS, mstatus_layout, __class__.RegValueLocal) - - def handle_write(self): - m = self.get_m() - m.d.sync += [ - self.rec.r.eq(self.rec.w) # TODO dangerous (doesn't implement WARL) - change it - ] - self.handler_notify_comb() - -class MIE(WriteOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MIE, mie_layout, __class__.RegValueLocal) - - def handle_write(self): - m = self.get_m() - # TODO - self.handler_notify_comb() - -# TODO -# For now it's fully readonly - doesn't support software interrupts, -# normally triggered via write to {m|s|u}sip field. -class MIP(ReadOnlyRegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.MIP, mip_layout, __class__.RegValueLocal) - - -class SATP(RegisterCSR): - class RegValueLocal(RegisterResetValue): - def field_values(self): - return {} - def __init__(self): - super().__init__(CSRIndex.SATP, satp_layout, __class__.RegValueLocal) - - def handle_write(self): - self.copy_specific_fields_only(["ppn", "mode"]) diff --git a/mtkcpu/units/debug/dmi_handlers.py b/mtkcpu/units/debug/dmi_handlers.py index 112e2dc..b82d6e3 100644 --- a/mtkcpu/units/debug/dmi_handlers.py +++ b/mtkcpu/units/debug/dmi_handlers.py @@ -223,7 +223,7 @@ def elaborate(self, _): from mtkcpu.cpu.priv_isa import CSRIndex cpu : MtkCpu = self.debug_unit.cpu real_dpc = Signal(32) - dpc = cpu.csr_unit.reg_by_addr(CSRIndex.DPC).rec.r + dpc = cpu.csr_unit.dpc with m.FSM(): with m.State("SANITY_CHECK"): with m.If(~self.debug_unit.cpu.running_state.halted): diff --git a/mtkcpu/units/debug/types.py b/mtkcpu/units/debug/types.py index bd00de3..69cc583 100644 --- a/mtkcpu/units/debug/types.py +++ b/mtkcpu/units/debug/types.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Annotated, Sequence, Tuple, List, Type from enum import IntEnum from amaranth.lib import data, enum diff --git a/mtkcpu/units/exception.py b/mtkcpu/units/exception.py index 51a7e31..65e5597 100644 --- a/mtkcpu/units/exception.py +++ b/mtkcpu/units/exception.py @@ -5,7 +5,7 @@ from amaranth.lib.coding import PriorityEncoder from mtkcpu.cpu.priv_isa import * -from mtkcpu.units.csr import CsrUnit +from mtkcpu.units.csr.csr import CsrUnit class ExceptionUnit(Elaboratable): def __init__(self, current_priv_mode: Signal, csr_unit : CsrUnit): diff --git a/mtkcpu/units/loadstore.py b/mtkcpu/units/loadstore.py index dfb5c76..a437523 100644 --- a/mtkcpu/units/loadstore.py +++ b/mtkcpu/units/loadstore.py @@ -4,7 +4,7 @@ from amaranth import Cat, Signal, Const, Elaboratable, Module, signed, Mux from amaranth.hdl.rec import Record, DIR_FANOUT, DIR_FANIN from mtkcpu.cpu.priv_isa import PrivModeBits, pte_layout, virt_addr_layout -from mtkcpu.units.csr import CsrUnit +from mtkcpu.units.csr.csr import CsrUnit from mtkcpu.units.exception import ExceptionUnit from mtkcpu.utils.common import matcher, EBRMemConfig from mtkcpu.cpu.isa import Funct3, InstrType diff --git a/mtkcpu/utils/tests/dmi_utils.py b/mtkcpu/utils/tests/dmi_utils.py index 57167df..91616c8 100644 --- a/mtkcpu/utils/tests/dmi_utils.py +++ b/mtkcpu/utils/tests/dmi_utils.py @@ -9,6 +9,8 @@ from mtkcpu.cpu.cpu import MtkCpu from mtkcpu.units.debug.types import DMI_reg_kinds from mtkcpu.utils.tests.sim_tests import get_state_name +from mtkcpu.cpu.isa import Funct3 +from mtkcpu.units.csr.csr_handlers import DCSR, DPC logging = get_color_logging_object() @@ -573,10 +575,9 @@ def aux(): return aux def monitor_writes_to_dcsr(dmi_monitor: DMI_Monitor): - from mtkcpu.cpu.isa import Funct3 - from mtkcpu.units.csr_handlers import DCSR, DPC - dcsr_addr = DCSR().csr_idx - dpc_addr = DPC().csr_idx + + dcsr_addr = DCSR().addr + dpc_addr = DPC().addr def aux(): yield Passive() diff --git a/mtkcpu/utils/tests/utils.py b/mtkcpu/utils/tests/utils.py index 467fb04..63fec55 100644 --- a/mtkcpu/utils/tests/utils.py +++ b/mtkcpu/utils/tests/utils.py @@ -19,7 +19,7 @@ from mtkcpu.asm.asm_dump import dump_asm from mtkcpu.cpu.cpu import MtkCpu from mtkcpu.global_config import Config -from mtkcpu.units.csr import CsrUnit +from mtkcpu.units.csr.csr import CsrUnit from mtkcpu.units.exception import ExceptionUnit from mtkcpu.utils.common import CODE_START_ADDR, MEM_START_ADDR, EBRMemConfig, read_elf from mtkcpu.utils.decorators import parametrized, rename @@ -144,8 +144,8 @@ def reg_test( # csr_unit.mtvec.base, # csr_unit.mtvec.mode, # *csr_unit.mepc.fields.values(), - *csr_unit.mcause.fields.values(), - *csr_unit.satp.fields.values(), + # *csr_unit.mcause.members.values(), + # *csr_unit.satp.fields.values(), # *csr_unit.mie.fields.values(), # *csr_unit.mstatus.fields.values(), # *csr_unit.mtime.fields.values(),