RISCV ISA
RISCV ISA
Open source. Has 32 int and 32 FP registers. Also has XLEN variable,
which is either 32 or 64 for 32-bit and 64-bit processors respectively.
There are 13 callee saved registers. 12 of them are explicitly called
saved registers, and the first one of them (s0) is the frame pointer
(equivalent to base pointer in AMD64). The 13 one is the stack register.
| Register | ABI Name | Description | Saver |
|---|---|---|---|
| x0 | zero | Zero always | |
| x1 | ra | Return address | caller |
| x2 | sp | Stack Pointer | callee |
| x3 | gp | Global Pointer | |
| x4 | tp | Thread Pointer | |
| x5-x7 | t0-t2 | Temps | caller |
| x8 | s0 /fp | saved / frame pointer (base pointer effectively) | callee |
| x9 | s1 | saved register | callee |
| x10-x11 | a0-a1 | Fn args/ return values | callee |
| x12-x18 | a2-a7 | Fn args | caller |
| x18-x27 | s2-s11 | Saved registers | callee |
| x27-x31 | t3-t6 | Temporaries | caller |
Maximum memory depends on the size of the address bus from the load store unit to the memory.
The caller saved registers have to be explicitly saved by the caller function in a stack frame before calling the other function. On the other hand, callee saved functions are guaranteed to stay the same across function calls and there is no need for functions to save them.
RISCV Instructions
The ones listed below are called R-type or register-type instructions.
add rd, rs1, rs2: add the contents ofrs1andrs2and store it inrd. Signed addition. Also has the unsigned version. Similarly, sub, and, or, xor also exist.mul/mulh rd, rs1, rs2: multiplies and stores the lower/upper 32 bits inrd.div/rem rd, rs1, rs2: stores the quotient/remainder.sll rd, rs1, rs2: Left shift the number inrs1by the value inrs2and store inrd.srl rd, rs1, rs2: Right shift the number inrs1by the value inrs2and store inrd. Zero extends.sra rd, rs1, rs2: Right shift the number inrs1by the value inrs2and store inrd. Sign extends.
All of these also have an immediate version where the last argument is a hardcoded literal that is 12 bits.
These are called I-type or immediate-type instructions.
l(b/h/w) rd, imm(rs1): loads a byte/half-word/word tord=(dest. register) from =*(rs1 + imm)l(b/h/w)u rd, imm(rs1): loads a byte/half-word/word tord=(dest. register) from =*(rs1 + imm). This one zero-extends on the left.
These are also I-type instructions.
The below one instruction is S-type instruction (for obvious reasons).
s(b/h/w) rd, imm(rs1): stores a byte/half-word/word to*(rs1 + imm)from the contents ofrd.
The below instruction is called B-type or branch-type instruction.
blt/bltu r1, r2, label/offset: ifr1 < r2(signed or unsigned), jump to label or symbol. Can jump to atmost 4 KiB. (13 bits with the lsb always 0, the rest are used for determining values).
The above instructions have greater than, greater than equal to variants as well. Also immediate variants of all.
Function call instructions
All of these can jump to at most 1 MiB as they take an immediate value of 20 bits apart from the lsb that's always 0.
The below type is called a J-type instruction.
jal rd, imm: Jump topc + imm, and storepc + 4in registerrd.
The below one is an I-type and NOT J-type.
jalr rd, rs1, imm: Jump tors1 + imm, storingpc + 4in the registerrd. Used for function pointers.
Both of the ones below are aliases.
j label: alias tojal zero, label, discards the return addressret rs1: return to the address inrs1, if no argument is specified usera(return address register/x1). Alias tojalr zero, rs1, 0
For function calls we also need a stack-based execution. x2 register
is the stack pointer that points to the top of the stack. Frame pointer
s0 saves the base of the stack. These mark the stack frame in the stack.
Always load and save values relative to the frame pointer.
Registers a0 to a7 are used to pass function arguments from one
function to another. These are 8 parameters. If we have more, then you
need to use the stack.
Control and status registers
csrrw rd, csr, rs: atomic swap valuescsrrs rd, csr, rs: atomic copy tordand set the bits that are set inrs.csrrc rd, csr, rs: atomic copy tordand reset the bits that are reset inrs.
Atomic means that interrupts won't stop the entire set of operations
that is going on. If one of the operands is x0/zero, then that copying
doesn't happen.
Executing instructions
If we are using an operating system:
- Proxy Kernel is handling syscalls, mapping memory, program counter according to memory map, etc.
If we are without an operating system:
- Manually write the
.textsection to the flash memory. - Load the
.datasection to the RAM. - Set/reset the program counter to the required memory. It resets to a fixed value called as reset vector.
- Address out of range is your skill issue.
- Instruction fetched by the instruction pointer. 32 bits in width.
- Then it is sent to control ROM. This does not have microcodes and are instead hardcoded using combinational circuits.
Register Bank
Program Counter
This keeps track of the next instruction to be executed. Usually incremented by 4 unless branches. It resets to a fixed value called as reset vector.
Instructions and data are fetched every rising edge of the clock and that is when the program counter is also incremented by 4. The instruction fetched is later stored in the instruction register.
The instruction register sends it to the control unit which later sends signals to whatever is responsible.
It also gets reset at interrupts to an interrupt vector and begins to consume instructions from there.
ALU
Performs arithmetic (no shit). Has the gen module before it that generates the immediate instruction from the opcode, regardless of whether it was an I-type or not.
After this, there is a 2:1 mux that has a select line coming from the Control Unit that decides whether or not the immediate values has to be selected or not, the other option being 0.
