maikmerten / spu32

Small Processing Unit 32: A compact RV32I CPU written in Verilog
MIT License
64 stars 13 forks source link
fpga ice40 icestorm risc-processor risc-v rv32i system-on-chip verilog

SPU32

This is SPU32 ("Small Processing Unit 32"), a compact RISC-V processor implementing the RV32I instruction set.

A demo-SoC is also included, featuring some peripherals.

The project ist writting in Verilog and is designed to be synthesizable using the open-source Yosys open synthesis suite.

An example SoC:


graph TD;
    CPU[CPU<br>RISC-V, 32 bit] --- WB[Wishbone bus<br>8 or 32 bit, pipelined]
    WB --- BROM[Boot ROM]
    WB --- RAM[RAM]
    WB --- UART[UART<br>115200 baud]
    WB --- SPI[SPI bus]
    SPI --- SDCARD[SD card]
    WB --- TIMER[Timer<br>Interrupt-capable]
    WB --- PRNG[PRNG<br>32 bit predictable<br>random numbers]
    WB --- LEDS[LEDs<br>blinky board LEDs]
    WB --- IR[IR decoder<br>NEC protocol]
    IR --- IRREC[IR receiver<br>38 kHz carrier]
    WB --- VGA[VGA graphics<br>text and bitmaps]        

Building a fitting 32-bit RISC-V toolchain

For Linux, in a nutshell (adapt paths as desired):

mkdir riscv-gcc
cd riscv-gcc
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
./configure --prefix=/opt/riscv32 --with-arch=rv32i --with-abi=ilp32
make -j$(nproc)

CPU

Vectors

Following vectors are used by the CPU and can be configured via parameters when instantiating the CPU module:

Interrupts and exceptions

The CPU supports following types of interrupts and exceptions:

If an interrupt of any type occurs, the CPU will jump to VECTOR_EXCEPTION, where a handling routine should be present.

The mode of interrupt/exception-handling is inspired by the privileged RISC-V specification, but much simplified for sake of implementation compactness. It therefore does not conform to an official specification, but should feel somewhat similar.

Interrupt- and exception-handling is controlled using machine-status registers that can be accessed using the csrrw instruction (the other csr-instructions are not supported in hardware and will raise an illegal-instruction exception, which can be handled in software if so desired). All status-registers have a read-write address (to allow swapping values with normal registers) and read-only address (to allow reading status-registers without changing their contents).

Following machine-status registers (MSRs) are used to control interrupt/exception-handling:

MSR_STATUS

Following information is encoded:

When an interrupt/exception occurs, the value of meie is saved to meie_prev. The meie-flag is set to zero, which ensures that external interrupts are ignored until the current interrupt is handled or the meie-flag is reinstated.

The mret-instruction is used to return from an interrupt/exception. meie is set to the value of meie_prev and execution is resumed at the address stored in MSR_EPC.

On reset, meie is set to zero, which means that external interrupts will be ignored. To enable external interrupts, meie needs to be set to 1.

Software-interrupts are always processed.

MSR_CAUSE

This status-register encodes the cause of of the raised interrupt/exception:

MSR_CAUSE[31] MSR_CAUSE[3-0] type of exception
1 1011 external interrupt
0 0010 invalid instruction
0 0011 ebreak-instruction
0 1011 ecall-instruction

MSR_CAUSE[30] to MSR_CAUSE[4] are reserved and always read as zero.

Note that MSR_CAUSE[31] can be used to easily distinguish external interrupts from software-interrupts. A neat effect of this encoding is that external interrupt and software-interrupts can be distinguished by signed comparison with zero, e.g., by using the bltz (branch if less than zero) instruction.

MSR_EPC

This status-register contains the address of the instruction where the last interrupt/exception occurred. The mret-instruction will jump to the address stored in this status-register.

Please note: In case of software interrupts/exceptions, this status-register will point to the instruction that caused the interrupt/exception. When directly issuing mret, execution will resume at exactly the same instruction, which means that yet another interrupt/exception will be raised immediately. To resume normal program flow, one needs to increment the value of MSR_EPC by 4 (i.e., by the length of one instruction) to resume execution a the next instruction. This can be detected by MSR_CAUSE[31].

External interrupts directly resume execution at the address stored in MSR_EPC, so no increment is needed prior to mret.

MSR_EVECT

This status-register specifies the memory address the CPU will jump to when an interrupt/exception occurs. By default initialized to VECTOR_EXCEPTION, but may be changed to any memory address where an interrupt service routine is located.