Use of external interrupt in vectored mode

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?

Hi Sjain,

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.


Hi Ralph
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.

.section .vector_trap_entry
#if __riscv_xlen == 32
.balign 64,0
.balign 128,0
.option norvc
.global __metal_vector_table
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_software_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_timer_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call riscv_interrupt_handle_machine_ext
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_interrupt_vector_handler
call metal_lc0_interrupt_vector_handler
call metal_lc1_interrupt_vector_handler
call metal_lc2_interrupt_vector_handler
call metal_lc3_interrupt_vector_handler
call metal_lc4_interrupt_vector_handler
call metal_lc5_interrupt_vector_handler
call metal_lc6_interrupt_vector_handler
call metal_lc7_interrupt_vector_handler
call metal_lc8_interrupt_vector_handler
call metal_lc9_interrupt_vector_handler
call metal_lc10_interrupt_vector_handler
call metal_lc11_interrupt_vector_handler
call metal_lc12_interrupt_vector_handler
call metal_lc13_interrupt_vector_handler
call metal_lc14_interrupt_vector_handler
call metal_lc15_interrupt_vector_handler

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 start.s file.

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:

  1. 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
  1. 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
  1. 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
  push ra
  push t0
  calculate plic vec tab index
  jalr ra, 0(t0)
  pop t0
  pop ra
  CLAIM = t0  ;# complete

@SJain UART0 is used by OCD. Try with UART1.