This page gives a hands-on example of using interrupts with the PicoRV32.
At the instantiation, the following additional mappings need to be made:
These settings:
Interrupts can be given through signal irq, and the ack’s come at the eoi signal.

The PicoRV32 has a handful of custom instructions. To make the assembly-code (a bit) more readable some custom instructions and register-names have been defined. These can be found in custom_ops.S from the PicoRV32 Github repository.
More info here.
The start.S assembly file that has been used up until now, needs to be extended.
reset_vec:
// no more than 16 bytes here !
// enable all interrupts
picorv32_maskirq_insn(zero, zero)
// jump to start function
j startThe start.S assembly file that has been used up until now, needs to be extended some more.
.balign 16
irq_vec:
/* save registers, by copying through x1 and x2 */
picorv32_setq_insn(q2, x1)
picorv32_setq_insn(q3, x2)
lui x1, %hi(irq_regs)
addi x1, x1, %lo(irq_regs)
picorv32_getq_insn(x2, q0)
sw x2, 0*4(x1)
picorv32_getq_insn(x2, q2)
sw x2, 1*4(x1)
picorv32_getq_insn(x2, q3)
sw x2, 2*4(x1)
sw x3, 3*4(x1)
sw x4, 4*4(x1)
sw x5, 5*4(x1)
sw x6, 6*4(x1)
sw x7, 7*4(x1)
sw x8, 8*4(x1)
sw x9, 9*4(x1)
sw x10, 10*4(x1)
sw x11, 11*4(x1)
sw x12, 12*4(x1)
sw x13, 13*4(x1)
sw x14, 14*4(x1)
sw x15, 15*4(x1)
sw x16, 16*4(x1)
sw x17, 17*4(x1)
sw x18, 18*4(x1)
sw x19, 19*4(x1)
sw x20, 20*4(x1)
sw x21, 21*4(x1)
sw x22, 22*4(x1)
sw x23, 23*4(x1)
sw x24, 24*4(x1)
sw x25, 25*4(x1)
sw x26, 26*4(x1)
sw x27, 27*4(x1)
sw x28, 28*4(x1)
sw x29, 29*4(x1)
sw x30, 30*4(x1)
sw x31, 31*4(x1)
/* call interrupt handler C function */
lui sp, %hi(irq_stack)
addi sp, sp, %lo(irq_stack)
/* arg0 = address of regs */
lui a0, %hi(irq_regs)
addi a0, a0, %lo(irq_regs)
/* arg1 = interrupt type */
picorv32_getq_insn(a1, q1)
/* call to C function */
jal ra, irq
/* restore registers */
/* new irq_regs address returned from C code in a0 */
addi x1, a0, 0
lw x2, 0*4(x1)
picorv32_setq_insn(q0, x2)
lw x2, 1*4(x1)
picorv32_setq_insn(q1, x2)
lw x2, 2*4(x1)
picorv32_setq_insn(q2, x2)
lw x3, 3*4(x1)
lw x4, 4*4(x1)
lw x5, 5*4(x1)
lw x6, 6*4(x1)
lw x7, 7*4(x1)
lw x8, 8*4(x1)
lw x9, 9*4(x1)
lw x10, 10*4(x1)
lw x11, 11*4(x1)
lw x12, 12*4(x1)
lw x13, 13*4(x1)
lw x14, 14*4(x1)
lw x15, 15*4(x1)
lw x16, 16*4(x1)
lw x17, 17*4(x1)
lw x18, 18*4(x1)
lw x19, 19*4(x1)
lw x20, 20*4(x1)
lw x21, 21*4(x1)
lw x22, 22*4(x1)
lw x23, 23*4(x1)
lw x24, 24*4(x1)
lw x25, 25*4(x1)
lw x26, 26*4(x1)
lw x27, 27*4(x1)
lw x28, 28*4(x1)
lw x29, 29*4(x1)
lw x30, 30*4(x1)
lw x31, 31*4(x1)
picorv32_getq_insn(x1, q1)
picorv32_getq_insn(x2, q2)
picorv32_retirq_insn()As a reminder:
First of all, it has to be made sure that the interrupt handler starts at address 0x00000010. This can be done with .balign 16.
Next, the copying of the state has to be done. All the copies need to be made to the interrupt registers . This happens in these steps:
When everything is safely backed up, it’s time to call the actual instructions for handling the interrupt. This is a C-function:
uint32_t *irq(uint32_t *regs, uint32_t irqs)Before a call to this function can be made, the stack pointer (sp) is changed to use the interrupt stack .
The two arguments that are passed to the irq function need to be stored in a0 and a1. These arguments are set, prior to performing the jump-and-link to the actual function.
The restore of the state is done in a similar fashion as the backup. When everything is tidied up, the ‘return-from-interrupt’ function is called: picorv32_retirq_insn().
The actual C-code for the interrupt handler can be downloaded from the PicoRV32 Github repository (irq.c).
irq_regs:
// registers are saved to this memory region during interrupt handling
// the program counter is saved as register 0
.fill 32,4 // stack for the interrupt handler
.fill 128,4
irq_stack:With these modifications to hardware and software, the simulation should be able to show the working, interrupt-able processor.