RISC-V CPU with FPGA RISC-V CPU with FPGA

RISC-V CPU with FPGA

This writeup is shorter than usual as the project is rather standard for a computer architecture module. However, I am still including it here as it demonstrates my understanding of CPU microarchitecture.

If there are any clarification questions, feel free to reach out!

TLDR Summary

Designed a 5-stage pipelined RISC-V CPU with support for the RV32IM instruction set. Enhanced CPU performance with features like hazard detection and forwarding, 2-bit branch prediction, and vectored interrupt handling. Written in Verilog and implemented on a Nexys4 FPGA board.

This project was built up over the course of 8 weeks for the module CG3207: Computer Architecture. This module went in depth into the design of a CPU, covering many key topics like its microarchitecture, advanced pipelining techniques, memory management, exception handling, and so on.

The final CPU design was the result of progressive lab assignments that culminated in a 5-stage pipelined RISC-V CPU supporting the RV32IM instruction set, with enhancements beyond these basic requirements being open-ended. The CPU was written in Verilog and implemented on a Nexys4 DDR FPGA board.

Overall Datapath

The diagram below shows the datapath for the 5-stage pipelined RISC-V CPU implemented (multiply/divide unit excluded). It supports the whole RV32IM instruction set with a fully functional hazard unit capable of resolving all data and control hazards.

CPU microarchitecture

Vectored Interrupt Handling

A simplified implementation of the typical RISC-V vectored interrupt handling mechanism was included in the design.

The user can write a trap handler vector table in their assembly program, then load the base address of this into the mtvec register. Then, the interrupt enable bit in the mstatus register must be set high, as well as the specific interrupt requests to enable in the mie register. To simplify things, exceptions and interrupts were handled in the same register, although proper RISC-V implementation would require separate handling.

For the exception handlers, which trigger during bad memory accesses, an LED is turned on and hangs the program in an infinite loop. For the interrupt handlers, the program added one to the seven segment display value and returns to the main program.

trap_handler.asm
la t0, trap_vector_base
csrw mtvec, t0 # set base address of vector table to mtvec
csrr t0, mstatus
ori t0, t0, 0x8 # set MIE bit (bit 3)
csrw mstatus, t0 # enable interrupts globally
li t0, 0x27 # 0b00100111 only enable DMEM & IROM exception, timer1 & pb0 interrupt
csrw mie, t0 # group exceptions and interrupts into mie register for simplified implementation
trap_vector_base: # these are the exceptions/interrupts implemented
j DMEM_bad_exception # mcause == 0
j IROM_bad_exception # mcause == 1
j timer1_handler # mcause == 2
j timer2_handler # mcause == 3
j timer3_handler # mcause == 4
j pb0_handler # mcause == 5
j pb1_handler # mcause == 6
j pb2_handler # mcause == 7
DMEM_bad_exception:
li s0, MMIO_BASE
addi s1, s0, LED_OFF
li s2, 0x1
sw s2, (s1) # activate LED via MMIO to indicate bad memory access
j DMEM_bad_exception # hang program here on bad memory address
timer1_handler:
li s0, MMIO_BASE
addi s1, s0, SEVENSEG_OFF
lw t0, (s1)
addi t0, t0, 1
sw t0, (s1) # add 1 to seven segment display whenever timer1 interrupt occurs
mret # return from interrupt

On the hardware end, interrupts trigger rising edges on the IRQ wire vector. This is compared to the mstatus and mie registers, which determine if the interrupt should be taken or not.

To handle multiple simultaneous interrupts, priority is given to the interrupt with lowest significance in the IRQ vector (i.e. exceptions, timer, then pushbutton) The highest priority interrupt’s value is written into the mcause register.

If the interrupt is taken, the current PC is saved into the mepc register, and the PC is set to the address in mtvec + 4 * mcause, which runs the interrupt handler. If an mret command is executed, the PC is restored from the mepc register, and the interrupt is cleared.


← Back to projects