diff --git a/mtkcpu/cpu/cpu.py b/mtkcpu/cpu/cpu.py index d8fd8f6..f8a5678 100755 --- a/mtkcpu/cpu/cpu.py +++ b/mtkcpu/cpu/cpu.py @@ -251,13 +251,12 @@ def elaborate(self, platform): mtime = self.mtime = Signal(32) sync += mtime.eq(mtime + 1) comb += csr_unit.mtime.as_view().eq(mtime) + timer_irq_happened = Signal() - # with m.If(csr_unit.mstatus.mie & csr_unit.mie.mtie): - # with m.If(mtime == csr_unit.mtimecmp): - # # 'halt' signal needs to be cleared when CPU jumps to trap handler. - # sync += [ - # self.halt.eq(1), - # ] + # Timer IRQ delivery. + with m.If(csr_unit.mstatus.as_view().mie & csr_unit.mie.as_view().mtie): + with m.If(mtime == csr_unit.mtimecmp.as_view().as_value()): + sync += timer_irq_happened.eq(1) def prev(sig: Signal) -> Signal: res = Signal() @@ -464,6 +463,13 @@ def fetch_with_new_pc(pc : Signal): # NOTE: 'Elif' is not accidental here - HALTREQ has higher priority than STEP. sync += dcsr.as_view().cause.eq(DCSR_DM_Entry_Cause.STEP) m.next = "HALTED" + with m.Elif(timer_irq_happened): + # TODO from specs: + # "The interrupt remains posted until it clears by writing the MTIMECMP register" + # + # For now we don't implement that part of specs, just clear the bit by default. + sync += timer_irq_happened.eq(0) # clear the bit + trap(IrqCause.M_TIMER_INTERRUPT, interrupt=True) with m.Else(): # maybe next time.. m.next = "FETCH" diff --git a/mtkcpu/tests/test_csr.py b/mtkcpu/tests/test_csr.py index 296a513..3e935d5 100644 --- a/mtkcpu/tests/test_csr.py +++ b/mtkcpu/tests/test_csr.py @@ -283,8 +283,117 @@ ), ] +EXDI = [ + MemTestCase( + name="timer interrupt happens with proper mcause", + source_type=MemTestSourceType.RAW, + source=f""" + start: + la x5, trap + csrw mtvec, x5 + csrr x2, {int(CSRNonStandardIndex.MTIME)} + addi x2, x2, 128 # interupt in ~100 cycles + csrw {int(CSRNonStandardIndex.MTIMECMP)}, x2 + + li x1, 0b10000000 # mie.mtie + csrw mie, x1 + + li x1, 0b1000 # mstatus.mie + csrw mstatus, x1 + loop: + j loop + trap: + csrr x14, mcause + andi x15, x14, 0xff + """, + out_reg=15, + out_val=IrqCause.M_TIMER_INTERRUPT, + timeout=2000, + mem_init=MemoryContents.empty(), + reg_init=RegistryContents.fill(), + ), + + # "The interrupt remains posted until it clears by writing the MTIMECMP register" - privileged specs. + MemTestCase( + name="timer interrupt is cleared only with 'mtimecmp' write", + source_type=MemTestSourceType.RAW, + source=f""" +start: + la x5, trap + csrw mtvec, x5 + csrr x2, {int(CSRNonStandardIndex.MTIME)} + addi x2, x2, 128 # interupt in ~100 cycles + csrw {int(CSRNonStandardIndex.MTIMECMP)}, x2 + + li x1, 0b10000000 # mie.mtie + csrw mie, x1 + + li x1, 0b1000 # mstatus.mie + csrw mstatus, x1 +loop: + j loop + +trap: + + // error codes on too_bad entry: + // 1 - NON-TIMER TRAP + // 2 - UNREACHABLE CODE + // 3 - MRET CLEARS TIMER IRQ (IT SHOULDN'T, DUE TO SPECS.) + + + // filter out non-timer related traps. + csrr x5, mcause + andi x5, x5, 0xff + li x6, {IrqCause.M_TIMER_INTERRUPT} + + li x20, 1 + bne x5, x6, too_bad + + addi x10, x10, 1 + + li x11, 1 + beq x10, x11, 1f + li x11, 2 + beq x10, x11, 2f + li x11, 3 + beq x10, x11, 3f + + add x20, x0, x10 + // li x20, 2 + jump too_bad, x3 // should not be reached + +1: + li x20, 3 + la x5, too_bad // should not be executed, as we haven't yet written to 'mtimecmp'. + csrw mepc, x5 + mret + +2: + csrr x1, {int(CSRNonStandardIndex.MTIME)} + addi x1, x0, 1000 + csrw {int(CSRNonStandardIndex.MTIMECMP)}, x1 + la x5, loop + csrw mepc, x5 + mret + +3: + jump ok, x3 + +too_bad: // exit code in x20 + addi x15, x20, 0 +ok: + addi x15, x0, 100 + """, + out_reg=15, + out_val=100, + timeout=2000, + mem_init=MemoryContents.empty(), + reg_init=RegistryContents.empty(), + ), +] -@mem_test(CSR_TESTS) +# @mem_test(CSR_TESTS) +@mem_test(EXDI) def test_registers(_): pass diff --git a/mtkcpu/units/csr/csr_handlers.py b/mtkcpu/units/csr/csr_handlers.py index 1a16ea1..4ad495b 100644 --- a/mtkcpu/units/csr/csr_handlers.py +++ b/mtkcpu/units/csr/csr_handlers.py @@ -171,7 +171,7 @@ def elaborate(self, _): return self.latch_whole_value_with_no_side_effect() class MIP(CSR_Write_Handler): - addr = CSRIndex.MIE + addr = CSRIndex.MIP layout = MIP_Layout # TODO diff --git a/mtkcpu/units/exception.py b/mtkcpu/units/exception.py index a056d45..35442eb 100644 --- a/mtkcpu/units/exception.py +++ b/mtkcpu/units/exception.py @@ -105,8 +105,8 @@ def elaborate(self, platform): mip.meip.eq(self.external_interrupt) ] m.d.sync += [ - # self.mstatus.r.mpie.eq(self.mstatus.r.mie), - # self.mstatus.r.mie.eq(0), + mstatus.mpie.eq(mstatus.mie), + mstatus.mie.eq(0), mepc.eq(self.m_pc) ] with m.If(~trap_pe.n): diff --git a/mtkcpu/units/mmio/bspgen.py b/mtkcpu/units/mmio/bspgen.py index 55e95f4..6e72598 100644 --- a/mtkcpu/units/mmio/bspgen.py +++ b/mtkcpu/units/mmio/bspgen.py @@ -95,4 +95,13 @@ def gen_bsp_sources(owners : List['BusSlaveOwnerInterface'], addr_schemes : List __class__.__generate_cc(c, s) log(f"generating {__class__.get_h_path(s)}") __class__.__generate_h(c, s) + csr_path = Path(f"{__class__.gen_dir}/csr.h") + + log(f"generating {csr_path}") + csr_text_lines = ["// autogenerated by 'gen_bsp' pass"] + from mtkcpu.cpu.priv_isa import CSRNonStandardIndex + for x in CSRNonStandardIndex: + csr_text_lines.append(f"#define {x.name}_CSR_REG_ADDR {hex(x.value)}") + + csr_path.write_text("\n".join(csr_text_lines)) log("ok, code generation done!") diff --git a/mtkcpu/utils/tests/sim_tests.py b/mtkcpu/utils/tests/sim_tests.py index 9788c9c..962ae15 100644 --- a/mtkcpu/utils/tests/sim_tests.py +++ b/mtkcpu/utils/tests/sim_tests.py @@ -290,7 +290,7 @@ def reg_test(timeout=default_timeout_extra + timeout_cycles, expected_val=expect yield Tick() yield Settle() - for _ in range(timeout): + for i in range(timeout): en = yield cpu.reg_write_port.en if en == 1: addr = yield cpu.reg_write_port.addr @@ -311,9 +311,10 @@ def reg_test(timeout=default_timeout_extra + timeout_cycles, expected_val=expect if check_reg_content and cond: # TODO that mechanism for now allows for only one write to observed register per test, # extend it if neccessary. + pc = yield cpu.pc print( f"== ERROR: Expected data write to reg x{addr} of value {hex(expected_val)}," - f" got value {hex(val)}.. \n== fail test: {name}\n" + f" got value {hex(val)} in cycle={i}, PC={hex(pc)}.. \n== fail test: {name}\n" ) print( f"{format(expected_val, '32b')} vs {format(val, '32b')}" diff --git a/mtkcpu/utils/tests/utils.py b/mtkcpu/utils/tests/utils.py index 626a56c..a7d470c 100644 --- a/mtkcpu/utils/tests/utils.py +++ b/mtkcpu/utils/tests/utils.py @@ -210,6 +210,8 @@ def reg_test( sim.add_sync_process(capture_write_transactions(cpu=cpu, dict_reference=result_mem)) # sim.add_sync_process(print_mem_transactions(cpu=cpu)) sim.add_sync_process(check_addr_translation_errors(cpu=cpu)) + + sim.add_sync_process(monitor_pc_and_main_fsm(cpu=cpu, wait_for_first_haltreq=False, log_fn=print)) sim.add_sync_process( get_sim_register_test( @@ -234,7 +236,7 @@ def reg_test( # *csr_unit.mepc.fields.values(), # *csr_unit.mcause.members.values(), # *csr_unit.satp.fields.values(), - # *csr_unit.mie.fields.values(), + # csr_unit.mie.as_view().mtie, # *csr_unit.mstatus.fields.values(), # *csr_unit.mtime.fields.values(), # *csr_unit.mtimecmp.fields.values(), @@ -247,23 +249,23 @@ def reg_test( # csr_unit.vld, # csr_unit.ONREAD, # csr_unit.ONWRITE, - cpu.arbiter.pe.i, - cpu.arbiter.pe.o, - cpu.arbiter.pe.none, - cpu.arbiter.bus_free_to_latch, - - cpu.arbiter.error_code, - cpu.arbiter.addr_translation_en, - cpu.arbiter.translation_ack, - cpu.arbiter.start_translation, - cpu.arbiter.phys_addr, - cpu.arbiter.root_ppn, - - *cpu.arbiter.pte.fields.values(), - - cpu.arbiter.generic_bus.addr, - cpu.arbiter.generic_bus.read_data, - cpu.arbiter.vpn, + # cpu.arbiter.pe.i, + # cpu.arbiter.pe.o, + # cpu.arbiter.pe.none, + # cpu.arbiter.bus_free_to_latch, + + # cpu.arbiter.error_code, + # cpu.arbiter.addr_translation_en, + # cpu.arbiter.translation_ack, + # cpu.arbiter.start_translation, + # cpu.arbiter.phys_addr, + # cpu.arbiter.root_ppn, + + # *cpu.arbiter.pte.fields.values(), + + # cpu.arbiter.generic_bus.addr, + # cpu.arbiter.generic_bus.read_data, + # cpu.arbiter.vpn, ] # from amaranth.back import verilog @@ -292,7 +294,8 @@ def get_code_mem(case: MemTestCase, mem_size_kb: int) -> MemoryContents: import tempfile with tempfile.NamedTemporaryFile( suffix=".elf", - dir=Path(__file__).parent + dir=Path(__file__).parent, + delete=False ) as tmp_elf: source = f""" .global start