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 start
The 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.