Content
- Overview
- RISC-V
- Base Integer Design
- Instruction Encoding
- Memory Model
Overview
ISA defines the contract between H/W and S/W. Usually includes:
- Observable state of a processor
- A set of machine instructions
- Semantics of the instruction and processor execution
* ISA
Assembly Code -> |Assembler| -> Machine Instructions
RISC-V
Base Integer Design
- 32 32-bit general-purpose registers
x0 - x31
- ABI names for use in assembler
- Additional Program Counter (PC)
Why 32 registers?
Too less -> not enough for 3-address format (2-address increases instruction counts and lowers frequency) More -> helps with high-performance code (loop unrolling, software pipelining, cache tiling, etc.) 32-bit size of instruction -> makes 32 registers (5 bits) fit better
Instruction Encoding
- Good encoding optimizes:
- Regularity
- Simple hardware decode
- Instruction length
- Extensibility
- 6 general types of instructions
- R-type, I-type, S-type, SB-type, U-type, UJ-type
- Instructions with the same type shares similar encoding
R-Type (Register-Register) Instructions
- Arithmetic: ADD, SUB
- Bit Operations: AND, OR, XOR, SLL, SRL, SRA
- Comparison: SLT, SLTU
2 source registers, 1 dest register.
|func|rs2|rs1|func|rd|opcode|
7 5 5 3 5 7
Number of registers affects number of bits to encode rs/rd, hence affects number of instructions encodable.
I-Type (Register-Immediate) Instructions
- Arithmetic: ADDI (SUBI isn't needed, just put negative number for ADDI)
- Bit Operations: ANDI, ORI, XORI
- Comparison: SLTI, SLTIU
1 source registers, 1 dest register.
|imm |rs1|func|rd|opcode|
12 5 3 5 7
Range for immediate value: [-2^11, 2^11-1]
Number of registers affects number of bits to encode rs/rd, hence affects range of immediate value.
U-Type Instructions
- LUI (Load Upper Immediate)
|imm |rd|opcode|
20 5 7
// Example - building larger immediate
// to load from 0x12345678
lui t0 0x12345
addi t0 0 0x678
lw t1 0(t0)
Memory Operations
* LOAD (I-type)
|imm |rs1 |func |rd |opcode|
12 5 3 5 7
offset base width dest LOAD // width: W/H/B
// example
int y = e + f[3]; // e: a0, f: a1, y: a2
=>
lw t0, 12(a1) # 12=3x4(int size)
ADD a2, a0, t0
* STORE (S-type)
|imm |rs2 |rs1 |func |imm |opcode|
7 5 5 3 5 7
offset src base width offset STORE
Using offset because not enough bits for 32-bit addresses. And mostly we jump to an address not too far away. Split immediate for register specifiers to be aligned, which helps simplifying decoding (which is usually on the critical paths).
Conditional Branch & Unconditional Jump
* Branch (SB-type)
|imm|imm|rs2 |rs1 |func|imm|imm |opcode|
1 6 5 5 3 4 1 7
offset src2 src1 offset BRANCH
// immediate: signed offset in multiples of 2
* Jump-And-Link (UJ-type)
|imm|imm|imm|imm|rd |opcode|
1 10 1 8 5 7
offset dest JAL // dest: return address
# jump to (offset*2 + PC)
|imm |rs1 |funct3|rd |opcode|
12 5 3 5 7
offset base 0 dest JALR // dest: return address
# jump to (offset + rs1)
// immediate: signed offset in multiples of 2
// plain jump: rd=x0
For returning to return address: jalr zero, ra
Two's Complement
- 2's complement of an n-bit number
v
is the value2^n - v
- 1's complement +
- For
{b_n-1, b_n-2, ..., b0}
, 2's complement is-2^(n-1)*b_n-1 + sum_i=0_to_n-2(bi * 2^i)
- Range:
[-2^(n-1), 2^(n-1)-1]
Sign Extension
Fill the additional bits with the most significant bit.
Memory Model
- Byte-addressable: each address corresponds to 1 byte
- Little endian byte addressing scheme: the LSB is at lowest address
- Words aligned at 4-byte boundary; half-words aligned at 2-byte boundary