I am using HiFive Rev-B board. I am trying to fire UART0 Rx interrupt using vectored mode. I have changed the mtvec LSB to 0x1 and written the base address of the interrupt vector table as well. Now, whenever I try to send a character from the serial terminal, I do receive the interrupt but PC always points to the base address of vector table and not the corresponding interrupt vector entry (in this case it should have been mtvec.BASE + 0x2C). I have already enabled the external interrupt in MIE and enabled the MIE in mstatus register. I have also enabled the interrupt in PLIC and changed the priority as needed.
Note: This works completely fine if I am using Direct mode instead of vectored mode for interrupt.
Can anyone please help to identify what am I missing here?
I believe you’ve done everything correctly. As you said, the external interrupt should be getting serviced from
BASE+0x2C as the UART0 interrupt is is coming from the PLIC.
The one item to check is that your table is 64-byte aligned, as this is a requirement for vectored interrupt mode.
Thank you for the response. I am using the below trap vector which is one of the examples assembly files I found in SDK. But, it is still not helping.
#if __riscv_xlen == 32
Looks good @SJain you might like the interrupt procedural flow illustration and somewhat related discussion.
It’s not clear to me how synchronous interrupts, those with a positive
mcause value such as the exceptions, are handled in Vectored mode. Could it be that, in Vectored mode, you always see
BASE+0 because you’re getting an
Instruction address misaligned exception – which happens to be
mcause=0, without the msb?
You can see a fairly complete MTVEC Demonstration in the
As @RalphF mentions, you want to make sure that your vectored handler (the
trap_handler in line 77 of the Demonstration) is on an 4-byte boundary for Direct operation, and 64-byte boundary for Vectored operation. See Assembler Directives for description of the GNU Assembler.
Vectored mode lets you bypass the testing and arithmetic of
mcause, but in the case of an external interrupt you still need to do the testing and arithmetic of the PLIC Claim procedure (i.e.,
int_external_m_mode in the Demonstration).
@pds Thank you for the response. Sorry, I’m fairly new to RISCV, this may be a dumb question but is it necessary to use CLIC/CLINT for vectored mode? My understanding was in vectored mode all exception goes to MTVEC.val + 0 and all other asynchronous interrupts go to MTVEC.val + interrupt_id4. Therefore, all external interrupts should cause a jump to MTVEC.val + 0xB4. From there, in the handler, we use PLIC_CLAIM to identify what caused the interrupt and then handle it accordingly. I was in the impression that there is no need of using CLINT and also for HiFive Rev-B, I think CLINT only supports software and timer interrupt.
I checked, and there is no
Instruction address misaligned exception in my code. It always jumps to trap vector when I send a character over UART0, not sure what am I missing here.
Thanks for the clarification @SJain makes sense about exceptions in Vectored mode. In your trap vector, can you check what is the
mcause value whenever you get in there, from characters sent over your UART0? Might give a clue …
A couple thoughts come to mind:
- Are you properly setting all 32 bits of the csr? That is, both the long immediate 20-bit upper part, and the short immediate 12-bit lower part?
lui t0, %hi(mtvec_vec_tab)
addi t0, t0, %lo(mtvec_vec_tab)
andi t0, t0, 0xFFFFFFFC # mtvec.BASE [1:0] 0=direct, 1=vector
ori t0, t0, 0x00000001
csrrw x0, mtvec, t0
- Did you fully implement all sixteen entries of the vector table? (example shown below shortened for brevity and compactness)
.balign 64 ;# 16 words of 4 bytes (32-bit) each
j mtvec_int_sw_u / ..._s / ..._h / ..._m
j mtvec_int_timer_u / ..._s / ..._h / ..._m
j mtvec_int_ext_u / ..._s / ..._h / ..._m
j mtvec_int_resvd_u / ..._s / ..._h / ..._m
- Did you check your assembled
.lst 'ing output of your vector table to make sure no (two byte, 16-bit) compressed instruction forms are being used (perhaps automatically, by the assembler) in the vector table entry jump instructions? All sixteen entries should all be four bytes long. You can do this by specifying
.balign 4 with each end every one of the vector table targets, for example in case of external m-mode (see also int_external_m_mode at line 304 of Demonstration):
.balign 4 ;# IMPORTANT: prevent compressed instruction forms
t0 = CLAIM
calculate plic vec tab index
jalr ra, 0(t0)
CLAIM = t0 ;# complete
@SJain UART0 is used by OCD. Try with UART1.
@pds Thank you so much for your response. It worked for me, there were a couple of issues:
- My vector table was getting overwritten by another source code.
- You highlighted the issue was spot-on. When I looked into .lst file, entries in my vector table were 8-byte spread instead of 4-byte which I then fixed and now I am correctly jumping on the external interrupt handler.
However, when I am returning from my UART0 ISR, I get a hard fault. When I looked into the registers, I found that the fp/s0 is getting corrupted. I am not sure what may be the reason. I am using the “interrupt” attribute for external_interrutp_handler where PLIC identifies the cause of the interrupt and then jumps the control to UART0 handler. I thought the “interrupt” attribute takes care of saving and restoring the values of registers.
Thanks @SJain !
call’s in your
__metal_vector_table look strange to me, shouldn’t they be simple
j(ump) instructions, so each vector table entry doesn’t destroy the return address
riscv_interrupt_handle_machine_ext (referenced by your IRQ_11 table entry) should be declared with the “interrupt” attribute. Look again carefully at the
.lst output of this handler. When the desired PLIC vector table entry is invoked, is it done with a
jalr which uses
ra)? If so, your specific UART code should end with only a
ret, not an
mret, because the
mret is at the end of the
I prefer writing interrupts and handlers in assembly, not C, where you can see everything that is going on and how it is being done.
One more thought, at the very top level of all your program, are you initializing the stack pointer at top of available ram (or elsewhere with enough space)?
lui a1, 0x80004
addi sp, a1, -4 # initialize stack pointer
Thank you for the prompt response. Everything is working now. Yes, I have replaced
call in my vector table with
j. However, the problem was I was using the “interrupt” attribute at wrong place.
Thank you for being patient and responding to all my queries.