RISC-V Registers
A RISC-V processor holds three sets of registers: the 32 general purpose ones every instruction works on, the 32 floating point registers the extension adds, and the control and status registers that report on the machine itself. The registers panel of the editor shows each of the three as its own tab.
General purpose registers
RISC-V has 32 general purpose registers, and with one exception the hardware treats them all alike: x0 reads as zero whatever is written to it, and the other 31 are plain 32 bit words, or 64 bit on the RV64 target. Everything else is convention, written down in the ABI and followed by every program that means to call another one.
Each register has a number and a name, and the assembler accepts both: addi t0, zero, 1 and addi x5, x0, 1 assemble to the same instruction. The names are what the panel shows and what a program should use, because the name says what the register is for. The convention divides them into registers a called function may destroy, the temporaries and the arguments, and registers it has to give back unchanged, the saved ones.
zero (x0)
Always reads as 0, and writing to it is silently ignored. It is not a wasted register: having a guaranteed zero is what lets one instruction do the work of many, so mv a0, a1 is really add a0, zero, a1, beqz t0, label is beq t0, zero, label, and a store of a constant zero needs no register loaded first.
ra (x1)
Return address. jal and jalr write the address of the following instruction here before jumping, and a function returns by executing ret, which is jalr zero, ra, 0. A function that calls another one has to save ra on the stack first, because the inner call overwrites it.
sp (x2)
Stack pointer, pointing at the lowest used word of the stack. A function makes room by subtracting from it on entry and gives the room back by adding the same amount before returning. The ABI expects it to stay aligned to 16 bytes at every call.
gp (x3)
Global pointer. It is set up once before the program starts and never changed, so that a global variable near it can be reached in a single instruction with a signed 12 bit offset instead of the two an arbitrary address needs.
tp (x4)
Thread pointer, the base of the storage private to the running thread. A program here has one thread and no use for it, but the ABI reserves it, so nothing else should be kept in it.
t0 - t2 (x5 - x7)
Temporaries. A called function may overwrite them freely, so a caller that still needs a value after the call has to save it, or keep it in a saved register instead.
s0, also written fp (x8)
The first saved register, and by convention the frame pointer: the fixed handle on the current stack frame for functions whose stack pointer moves while they run. The assembler accepts both spellings for the same register.
s1 (x9)
A saved register. A function that uses it must put back the value it found, which is what makes it the right place for anything that has to survive a call.
a0 - a1 (x10 - x11)
The first two arguments of a call, and the registers a result comes back in: a0 carries a single return value and a1 the second half of a pair. The environment calls that this simulator implements also take their argument in a0 and leave their result there.
a2 - a7 (x12 - x17)
Six more argument registers; anything beyond the eighth argument is passed on the stack. a7 has a second job here: it holds the number of the environment call ecall is about to make.
s2 - s11 (x18 - x27)
Ten more saved registers, preserved across calls by whoever uses them. A loop whose body calls a function keeps its counter in one of these.
t3 - t6 (x28 - x31)
Four more temporaries, with the same rule as t0 to t2: free to use, gone after a call.
Floating point registers
The floating point extension adds 32 registers of its own, f0 to f31, with the same kind of ABI names as the general ones. They are a separate file: no arithmetic instruction reads one of each, and a value crosses over only through an explicit instruction. fcvt.s.w ft0, t0 converts the integer in a general register into a single precision number, fcvt.w.s t0, ft0 converts back, and fmv.x.w copies the raw bits of a floating point register into an integer one while fmv.w.x copies them back, neither of them converting anything. The older spellings fmv.x.s and fmv.s.x assemble to the same two instructions. A comparison is the other place the two files meet: feq.s, flt.s and fle.s test two floating point registers and write 1 or 0 into a general register, so a floating point comparison reaches a branch through bnez or beqz rather than through a flag of its own.
Every register here is 64 bits wide, on both the 32 and the 64 bit targets, because that is what a double needs. A single precision value is stored NaN-boxed: it occupies the low 32 bits and the upper 32 are all ones, a pattern that reads as a NaN if a double instruction looks at it. That is deliberate, and it is why the panel shows NaN in the single format for a register holding a genuine double: it is the same protection, running the other way. fadd.s adds singles and fadd.d doubles, flw and fsw load and store a single, fld and fsd a double, and fcvt.d.s widens a single into a double when the two have to meet.
ft0 - ft7 (f0 - f7)
Floating point temporaries, destroyed by a call like the integer ones. The first eight registers of the file, which is why an example that needs one register usually reaches for ft0.
fs0 - fs1 (f8 - f9)
The first two floating point saved registers, preserved across calls by the function that uses them.
fa0 - fa7 (f10 - f17)
The floating point arguments of a call, and in fa0 the floating point return value. They sit beside the integer argument registers rather than counting against them, so a function taking an integer and a double is passed a0 and fa0.
fs2 - fs11 (f18 - f27)
Ten more floating point saved registers. Note that the ABI names run out of order against the numbers: fs2 is f18, well after fa7. The reason is the compressed encoding, the 16 bit form of the instruction set that this simulator does not assemble but that the ABI was written around: an instruction that short has only three bits for a register, which reaches f8 to f15 and no further, so the ABI put the registers a program uses most there, fs0, fs1 and fa0 to fa5, and the saved registers that were left over landed at f18 and above.
ft8 - ft11 (f28 - f31)
Four more floating point temporaries, closing the file. They follow the same rule as ft0 to ft7, and as t3 to t6 on the integer side: free to use, and gone after a call.
Control and status registers
The control and status registers are the machine talking about itself: how the floating point unit is rounding, why an exception happened, how many instructions have run. They are not addressed like the other registers. csrr t0, fcsr reads one into a general register and csrw t0, fcsr writes it back: the general register comes first in both of them and the control register second. Each is a short form of one of the csrr* instructions, which read the old value and write a new one in a single step: csrr is csrrs with zero as the value to set, so nothing is written, and csrw is csrrw with zero as the destination, so the old value is thrown away. The forms that take a constant instead of a register, csrwi and csrsi, are the exception to the order and name the control register first, as in csrsi ustatus, 1.
This simulator implements the seventeen registers below, the user level subset. On the 32 bit target a counter that needs 64 bits is read as two registers, the name for the low half and the name ending in h for the high half; on RV64 the plain name holds all of it.
ustatus (0x000)
The user status register. Only two bits of it are writable here: the one that enables user level interrupts, and the one that remembers whether they were enabled before the current handler was entered, so that uret can put things back as it found them. Entering a handler copies the first into the second and clears the first, which is why a handler is not interrupted by the thing it is handling.
fflags (0x001)
The five floating point exception flags: invalid operation, divide by zero, overflow, underflow and inexact. Arithmetic sets them and nothing clears them, so they say what has happened since the program started, or since it last wrote a zero here. This is not a separate register but the low five bits of fcsr under their own name.
frm (0x002)
The rounding mode the floating point instructions use when they are not given one: round to nearest with ties to even by default, with truncation and the two directed roundings selectable. Like fflags, it is a window onto fcsr, bits 5 to 7.
fcsr (0x003)
The floating point control and status register, which is the two registers above in one place: fflags is its low five bits and frm the three above them. Writing it writes both, which is how a program resets the flags and chooses a rounding mode in a single instruction.
uie (0x004)
Which user level interrupts are enabled, one bit per source. An interrupt is delivered only when its bit is set here and interrupts are enabled in ustatus.
utvec (0x005)
The address of the user level trap handler: where the machine jumps when an exception or an enabled interrupt happens. A program that means to handle its own traps writes the address of its handler here and also sets the enable bit of ustatus, with csrsi ustatus, 1, because this simulator only enters the handler when that bit is set. A trap taken without both of them stops the program with the usual error message.
uscratch (0x040)
A word the handler may use as it likes. The classic use is to give the handler a stack pointer of its own, since the trap arrives with the program halfway through something and no register free to borrow.
uepc (0x041)
The address of the instruction the trap interrupted. uret returns to it, so a handler that has fixed the cause returns unchanged, and a handler that means to skip the offending instruction adds 4 to this register first.
ucause (0x042)
Why the trap happened: a code such as an illegal instruction or a misaligned address, with the top bit set when the cause was an interrupt rather than an exception. A handler serving several causes reads this first.
utval (0x043)
The detail that goes with the cause: the address a load or store failed on, or the instruction that could not be decoded. Meaningless for causes that carry no such value.
uip (0x044)
Which interrupts are pending, in the same bit positions as uie. A source can be pending and not enabled, in which case nothing happens until the program enables it.
cycle, time, instret (0xC00 - 0xC02)
The three counters, read only: elapsed cycles, elapsed time, and the number of instructions retired. A program times a piece of itself by reading one of them before and after and subtracting.
cycleh, timeh, instreth (0xC80 - 0xC82)
The high halves of the three counters, for the 32 bit target where a 64 bit count does not fit in one register. Reading the pair safely means reading the high half, then the low, then the high again, and starting over if it changed in between.