Specification

This part summarises the specification for the design, including the instructions that needs supporting, the CSR registers, and the interrupts/exceptions. Throughout the specification, the RISC-V specification is referenced in the following format:

  • v1_c9 means RISC-V volume 1 (unprivileged ISA), chapter 9

  • v1_t9.1 means RISC-V volume 1 (unprivileged ISA), table 9.1

  • v2_s2.3 means RISC-V volume 2 (privileged architecture), section 2.3

All references to volume 1 refer to version 20191213 (document riscv-spec-20191213.pdf), and references to volume 2 refer to version 20211203 (document riscv-privileged-20211203.pdf). Both documents are available from the official specification page.

Other non-ISA specifications are also used. References to the Platform-Level Interrupt Controller (PLIC) Specification refer to version 1.0.0, 3/2023 (document riscv-plic-1.0.0.pdf, available here). References use a format like:

  • plic_f1 means RISC-V PLIC specification, figure 1

CPU State

The registers required by the RISC-V implementation are summarised in this section. The register file contains 32 registers which are each 32 bits wide, called x0-x31. The first register x0 is writable, but always returns zero when read. The program counter is 32 bits wide, and is initialised to the reset vector.

Control and Status Registers

A certain minimal set of control and status registers (CSRs) are required by the RISC-V privileged specification. Each is listed in the format "`CSR-address CSR-name`: description" below.

The following informational registers are required to be present, but can be read-only zero:

  • 0xf11 mvendorid: read-only; returns 0 to indicate not implemented.

  • 0xf12 marchid: read-only; returns 0 to indicate not implemented.

  • 0xf13 mimpid: read-only; returns 0 to indicate not implemented.

  • 0xf14 mhartid: read-only; single hart system, returns 0 to indicate hart 0.

  • 0xf15 mconfigptr: read-only zero, configuration platform-specification defined

  • 0x301 misa: read/write; single legal value 0 always returned (WARL), meaning architecture is determined by non-standard means (it is rv32im_zicsr implementing M-mode only).

The status of the CPU is stored in the following status registers:

  • 0x300 mstatus: read/write, containing both WPRI and WARL fields. The bit fields which are non-zero are as follows (assumes only M-mode):

    • bit 3: MIE (interrupt enable), read/write

    • bit 7: MPIE (previous value of interrupt enable), read/write (?)

    • bits [12:11]: MPP (previous privilege mode), WARL always 0b11 (?)

  • 0x310 mstatush: read/write, upper 32-bit of status; all fields are read-only zero (only little-endian memory is supported).

In addition to the global bit in the status register, interrupts are controlled and monitored by these registers:

  • 0x304 mie: read/write interrupt-enable register. To enable an interrupt in M-mode, both mstatus.MIE and the bit in mie must be set. Bits corresponding to interrupts that cannot occur must be read-only zero.

  • 0x344 mip: 32-bit read/write interrupt-pending register. Since all fields are read-only, writes to this CSR are no-op. The following bits are defined:

    • bit 3: machine software interrupt pending (read-only)

    • bit 7: machine timer interrupt pending (read-only)

    • bit 11: machine-level external interrupt pending (read-only)

When a trap occurs (either due to an interrupt or a exception), the following registers determine where control flow is transferred to and where it returns to after the trap:

  • 0x305 mtvec: read-only, trap handler vector table base address

    • bits [1:0]: 1 (vectored mode)

    • bits [31:2]: trap vector table base address (4-byte aligned)

  • 0x341 mepc: 32-bit, read/write register, stores the return-address from trap handler. WARL, valid values are allowed physical addresses (4-byte aligned and fit within physical memory address width).

Information about the trap is obtained from these registers:

  • 0x342 mcause: 32-bit, read/write, stores exception code and bit indicating whether trap is interrupt. Exception code is WLRL.

  • 0x343 mtval: read-only zero

In addition, software may use this register in any way while processing traps:

  • 0x340 mscratch: 32-bit read/write register for use by trap handlers

The following privileged-mode registers hold performance monitoring information:

  • 0xb00 mcycle: low 32 bits of read/write 64-bit register incrementing at a constant rate

  • 0xb80 mcycleh: high 32 bits of read/write, 64-bit register containing number of clock cycles executed by the processor.

  • 0xb02 minstret: low 32 bits of read/write, 64-bit register containing number of instructions retired by the processor.

  • 0xb82 minstreth: high 64 bits of read/write, 64-bit register containing number of instructions retired by the processor.

These registers are also accessible via the following shadows (intended for use by unprivileged mode; however, there is only M-mode here, so there is no distinction between privilege levels):

  • 0bc00 cycle: read-only shadow of mcycle

  • 0xc80 cycleh: read-only shadow of mcycleh

  • 0xc02 instret: read-only shadow of minstret

  • 0xc82 instreth: read-only shadow of minstreth

The 64-bit mtime register is memory-mapped (it is not a CSR); however, it does have a read-only shadow as a CSR:

  • 0xc01 time: read-only shadow of lower 32 bits of memory mapped 64-bit mtime

  • 0xc81 timeh: read-only shadow of upper 32 bits of memory mapped 64-bit mtime

Finally, the following required performance monitoring counters are all implemented as read-only zero

  • (0xb00 + n) mhpmcountern: read-only zero (n ranges from 3 to 32)

  • (0xb80 + n) mhpmcounternh: read-only zero (n ranges from 3 to 32)

  • (0x320 + n) mhpmevent: read-only zero (n ranges from 3 to 32)

  • (0xc00 + n) hpmcountern: read-only zero (n ranges from 3 to 32)

  • (0xc80 + n) hpmcounternh: read-only zero (n ranges from 3 to 32)

Traps

The standard privileged architecture requires support for exceptions and interrupts.

  • At the start of each instruction cycle, a trap is triggered transferring control to an interrupt vector, if both (v2_s3.1.9):

    • Interrupts are globally enabled (MIE bit set in mstatus)

    • An interrupt enable bit (mie) and corresponding interrupt pending bit (mip) are both set

  • While an instruction is in execution, if it raises an exception, a trap is triggered transferring control to the exception vector.

The process of triggering a trap takes one cycle, and is the same for both an exception and an interrupt:

  • The mepc CSR is set to the current program counter (instruction interrupted or raising the exception) (v2_s3.1.14)

  • The program counter is set to a trap vector. For exceptions, this is the base address in mtvec (v2_s3.1.7). For interrupts, it is base + 4*cause, where cause is from the exception code column in v2_t3.6. (This design uses vectored interrupts.)

  • The mcause CSR is set to the exception code in v2_t3.6. The interrupt bit is set for an interrupt (v2_s3.1.15).

  • The MIE bit in mstatus is copied to MPIE, and the MIE bit is set to 0 (v2_s3.1.6.1)

Interrupts

The basic interrupt mechanism follows the Core Local Interruptor (CLINT) specification, based on SiFive chips. The CLINT memory map begins at address 0x1000_0000. The meaning of the memory-mapped I/O registers in this region are as follows:

  • msip: write 1 to this bit to request a software interrupt, and write 0 to clear it. The value of this bit is reflected in the read-only MSIP field of the mip CSR. The register is initialised to zero.

  • mtimecmp: this is the 64-bit timer compare register as described in the RISC-V privileged ISA specification. A timer interrupt becomes pending exactly when mtime >= mtimecmp. It is initialised to zero.

  • mtime: this is the 64-bit real time register, which increments at a constant rate with wall clock time. It is initialised to zero.

The external interrupt bit meip in mip (the interrupt-pending register) comes directly from an external interrupt controller which follows the PLIC specification (plic_f1). There is only a single hart in this system, which only implements M-mode, therefore there is only one signal line from the PLIC to the hart (the M-mode external interrupt signal; see plic_s1.3).

Exceptions

When an instruction is executed, it may synchronously raise an exception (meaning the exception is associated with the instruction being executed, and raising the exception will deterministically trigger a trap on the next instruction). The following subset of exceptions defined in v2_t3.6 is implemented in the design:

Table 1. Supported exceptions
Exception code Exception Caused by

0

Instruction address misaligned

Program counter not four-byte-aligned on an unconditional jump or branch taken (v2_s2.2). Exception is raised on the jump/branch instruction itself.

1

Instruction access fault

Program counter does not fall within the instruction memory address region. Exception raised on the instruction with invalid program counter.

2

Illegal instruction

The fetched instruction is not supported by this implementation (i.e. is not RV32I, Zicsr, mret or wfi).

3

Breakpoint

The ebreak instruction was executed

5

Load access fault

a load instruction was executed with an address which is not in main memory or an I/O region

7

Store access fault

a store instruction was executed with an address which is not in main memory or an I/O region, or the address is read-only

11

(M-mode) Environment call

The ecall instruction was executed

In this implementation, load/store instructions do not have alignment requirements, so the load/store address misaligned exceptions are not required.

Memory Map

The memory map for the physical address space is as follows (all addresses are in hexadecimal):

Table 2. Memory regions
Memory Region Address range Size Physical Memory Attributes Notes

Instruction ROM

0000_0000 - 1000_0000

1024

Instruction fetch only

I/O

1000_0000 - 2000_0000

Sum of special register sizes

Read/write (load/store)

See Special registers in I/O memory below

Main Memory

2000_0000 - 2000_0400

1024

Read/write (load/store)

The addresses in the I/O region are vacant by default (load/store accesses generate an illegal instruction exception). The accessible addresses are given below:

Table 3. Special registers in I/O memory
Address Register name Register size Attributes

1000_0000

msip

4

Read/write, but only bit 0 is modified

1000_4000

mtimecmp

8

Read/write (v2_s3.2.1)

1000_bff8

mtime

8

Read/write (v2_s3.2.1)

Certain addresses in the instruction memory region have the following defined purposes:

Table 4. Special vectors in instruction memory
Start Address Memory-mapped Register Size

0000_0000

Reset vector (initial PC)

4

0000_0004

NMI vector

4

0000_0008

Exception vector (mtvec)

4

0000_0014

Software interrupt vector

4

0000_0024

Timer interrupt vector

4

0000_0034

External interrupt vector

4

Instructions

The required instructions are the 32-bit RV32I base integer ISA, the Zicsr extension for CSR manipulation, and the privileged-architecture instructions mret and wfi. Each category of instructions is defined below.

Unless otherwise specified, all programs are assumed to increment the program counter by four bytes.

Many instructions involve building an immediate from instruction fields. The clearest diagram of how to build immediates for most instruction formats is v1_f2.4. Most immediates are sign-extended based on bit 31 of the instruction. The CSR instructions are an exception, which use the rs2 field as an unsigned immediate (not sign extended). Normally, the (resulting 32-bit) immediate is used as an operand to an addition, or is written directly to a register.

Whenever calculations below involve rs1, rs2, or rd, the calculation involves the value in the indexed register, not the register index itself.

Privileged Instructions

The following instructions are required for code executing in machine mode (the only privilege level in this design) (v2_s3.3): ecall, ebreak, mret, and wfi.

The ecall and ebreak instructions are required by the unprivileged specification RV32I (v1_s2.8). Since the unprivileged instructions are also executing in M-mode in this design, these ecall and ebreak instructions do the same thing as the M-mode versions.

The instructions ecall and ebreak raise the exceptions shown in Supported exceptions, and take no further action.

The instruction mret is executed by software to return from a trap. It performs the following steps:

  • Copies the MPIE bit to MIE in mstatus

  • Sets the MPIE to 1

  • Sets the program counter to mepc (v2_s3.3.2)

The wfi is implemented as a NOP (v2_s3.3.3)

Upper Immediate (RV32I)

Upper immediate instructions lui and auipc use an immediate imm, where imm[31:12] is stored in the U-type instruction, and imm[11:0] is 0. Then:

  • lui stores imm in the register rd

  • auipc stores imm + pc in the register rd

No exceptions are raised.

Register-Register (RV32I)

The instructions add, sub, sll, slt sltu, xor, srl, sra, or, and and, all execute in the same way:

  • Perform an operation between the two registers rs1 and rs2

  • Store the result to the register rd

No exceptions are raised.

Register-Immediate (RV32I)

The instructions addi, slli, slti sltiu, xori, srli, srai, ori, and andi, all execute in the same way:

  • Perform an operation between register rs1 and the sign-extended immediate { 20{instr[31]} , instr[31:20] }

  • Store the result to the register rd

No exceptions are raised.

Unconditional Jump (RV32I)

The jal and jalr instructions store pc + 4 in the register rd, and unconditionally update the program counter. For both instructions, a signed 32-bit immediate imm is encoded in the instruction. (The encoding is different for jal and jalr.) The new program counter is calculated as follows:

  • For jal, pc = pc + imm

  • For jalr, pc = 0xffff_fffe & (rs1 + imm)

If the new pc is not four-byte aligned, an instruction address misaligned exception is raised (and the program counter is not updated).

Conditional Branch (RV32I)

The conditional branch instructions beq, bne, blt, bge, bltu, and bgeu, check a condition between registers rs1 and rs2, and update the program counter if the condition is satisfied.

The new program counter is pc = pc + imm, where imm is a signed 32-bit immediate encoded in the instruction. If the new pc is not four-byte aligned, an instruction misaligned exception is raised (and the program counter is not updated).

If the condition is not satisfied, the program counter is set to pc + 4 as normal.

Load (RV32I)

Load instructions lb, lh, lw, lbu, and lhu, attempt to read a location in memory and write it to rd.

The address for the attempted read is rs1 + imm, where imm is a signed 32-bit immediate stored in the instruction.

The width of the read is given by the instruction. If the address and width means that a byte falls outside the valid memory region for reading (in main memory or I/O), a load access fault exception is raised, and no registers are modified.

If the read is valid, the 1-byte, 2-byte, or 4-byte result is either sign- or zero-extended, and written to rd.

The behaviour of exceptions does not depend on whether rd is x0 (v1_s2.6).

In this design, all alignments are supported (in both loads and stores), and so load/store misalignment exceptions are not raised.

Store (RV32I)

Store instructions sb, sh, and sw, attempt to write the contents of rs2 to a location in memory.

The address for the attempted write is rs1 + imm, where imm is a signed 32-bit immediate stored in the instruction. (The encoding is not the same as for load instructions.)

The width of the write is given by the instruction. If the address and width means that a byte falls outside the valid memory region for write (in main memory or I/O), a store access fault exception is raised, and no registers are modified.

If the write is valid, the 1-byte, 2-byte, or 4-byte value from the low bits of rs2 is written to the address.

Control and Status Register (Zicsr)

The unprivileged specification (v1_ch9) defines the behaviour of the instructions which manipulate CSRs, in the Zicsr ISA extension. The behaviour of the instructions is as follows:

  • csrrw: read the addressed CSR into destination register rd, and then write the source register rs1 to the addressed CSR.

  • csrrwi: read the addressed CSR into destination register rd, and then zero extend the immediate uimm and write it to the addressed CSR.

  • csrrs: read the addressed CSR into destination register rd. Then, only if the source register rs1 is not x0, bitwise-OR the current value of the CSR with rs1, and write the result back to the CSR (i.e. set bits in the CSR where there is a 1 in rs1).

  • csrrsi: read the addressed CSR into destination register rd. Then, only if the immediate uimm is not 0, bitwise-OR the current value of the CSR with uimm (zero-extended to 32 bits), and write the result back to the CSR (i.e. set bits in the CSR where there is a 1 in uimm).

  • csrrc: read the addressed CSR into destination register rd. Then, only if the source register rs1 is not x0, bitwise-AND the current value of the CSR with !rs1, and write the result back to the CSR (i.e. clear bits in the CSR where there is a 1 in rs1).

  • csrrci: read the addressed CSR into destination register rd. Then, only if the immediate uimm is not 0, bitwise-AND the current value of the CSR with !uimm (zero-extended to 32 bits after negation), and write the result back to the CSR (i.e. clear bits in the CSR where there is a 1 in uimm).

Any instructions that write to a CSR:

  • will raise an illegal instruction exception if the CSR is read-only. In this case, the state of registers will be as if the instruction did not occur.

  • will not change the value of an CSR bits that are read-only in otherwise writable registers

  • for WLRL fields in writable CSRs, any value that is written (even an invalid one) will be written anyway, without any checking in hardware.

  • for WARL fields in writable CSRs, any attempt to write an invalid value will cause no change in the CSR field (the old value will be retained).

  • the write will displace any other automatic modification of the CSR by hardware; for example, writing to instret will stop auto-increment of instret on that instruction (v1_s9.1). This is also interpreted as applying to all counters, including mcycle, etc.)

Instructions that read CSRs read the value of the CSR as it was just prior to instruction execution (e.g. the value of instret is taken before incrementing in due to the read instruction itself).

Instructions that attempt to perform an operation on a non-existent CSR raise an illegal instruction exception.

Notes on behaviour

  • The instructions are defined (v1_s9.1) to atomically read and write CSRs. Since there is only one hart in this design, this required is satisfied by a single read/write operation.

  • The csrrw and csrrwi instructions are defined (v1_t9.1) to omit the CSR read if the destination register rd is x0, and not trigger any side effects that would occur on a read. In this design, no CSR has a side effect that occurs on a read, so for simplicity the rw instructions can perform a read irrespective of rd, and attempt the write to rd (which will have no effect if rd is x0).

  • In this design, all CSRs are 32-bits wide, so there is no need to zero-extend them before writing to registers.

  • In this design, writing invalid values to WLRL fields does not raise an illegal instruction exception (v2_s2.3).