§ 4.1.1. Machine Model and Common Conventions¶
Reference index · Instruction catalogue
This page defines the state and common rules assumed by every instruction entry. Rules marked CircuitSim policy describe this execution environment, not requirements imposed on all RISC-V machines.
Architectural state¶
| State | Width / extent | Contract |
|---|---|---|
x0 |
32 bits | Reads as zero; destination writes are discarded |
x1–x31 |
31 × 32 bits | General-purpose integer registers; interpretation depends on the instruction |
pc |
32 bits | Byte address of the instruction being executed; successful ordinary execution advances by four |
| Memory addresses | 32 bits | Byte addresses; address arithmetic wraps modulo 2^32, but an access must still lie inside the implemented memory |
| Condition-code register | None | No architectural carry, overflow, zero, or sign flags; internal ALU flags are not persistent ISA state |
Register values are bit patterns. Signed operations interpret bit 31 as the two's-complement sign bit; unsigned operations interpret all 32 bits as a nonnegative integer. Both interpretations use the same registers.
Notation and operation helpers¶
Instruction operations use Python-style pseudocode. Each block starts after successful instruction fetch and decoding, with an active machine. It is a binary instruction-level specification, not gate-level timing or a second registered simulator.
| Name | Definition |
|---|---|
x[i] |
Unsigned 32-bit value in register i; x[0] is always zero |
pc |
Address of this instruction until the block updates it |
rd, rs1, rs2 |
Decoded register numbers; only operands listed in the entry are used |
imm |
Decoded immediate: signed I/S/B/J value; for U format the already shifted upper value |
shamt |
Five-bit immediate shift count, 0–31 |
u32(v) |
v & 0xFFFFFFFF, keeping the low 32 bits |
s32(v) |
Interpret u32(v) as signed: subtract 2^32 when bit 31 is set |
sext(v, n) |
Interpret the low n bits as a signed two's-complement value |
write_rd(v) |
Write u32(v) to x[rd] unless rd=0 |
read8/16/32(address) |
Validate alignment and the whole byte range, then read an unsigned little-endian value |
store8/16/32(address, value) |
Validate alignment and the whole byte range, then write the corresponding low bits, least-significant byte first |
check_target(address) |
Raise InstructionAddressMisaligned unless the target is divisible by four; do not check instruction-memory range here |
trap(cause) |
Stop the current operation with the named terminal trap; no later statements execute |
The documentation tests execute these operation blocks against the compiled reference. The complete production C++ implementation is in 4.1.4.
Register and PC updates¶
- Read source operands from the old state before writing a destination. Using the same register as source and destination is allowed.
- All register results and calculated addresses are reduced to 32 bits. Integer arithmetic never traps merely because of signed overflow or unsigned carry.
- Successful ordinary instructions advance PC by four. Branches and jumps select their next PC explicitly; terminal instructions stop without advancing it.
- Only the listed destination, memory bytes, PC, or terminal status changes. There are no persistent arithmetic condition-code flags.
- A destination of
x0discards only the register write. Loads still access memory and jumps still redirect execution. - FENCE and accepted no-effect/HINT encodings have no hidden architectural register effect. Performance hints are not implemented.
CircuitSim execution environment¶
| Property | Current reference environment |
|---|---|
| Instruction width / alignment | 32-bit instructions, four-byte-aligned instruction addresses (IALIGN=32) |
| Byte order | Little-endian for instructions and data |
| Memory organization | Separate instruction and data memories in the visual testbench; a data store does not modify instruction memory |
| Default capacity | Each FF-model instruction/data device has 65,536 words (256 KiB): byte addresses 0x00000000–0x0003FFFF |
| Memory interface | All cores: universal REQ/RSP endpoints. Single-cycle and baseline require zero-wait FF timing; the transaction-capable pipeline also supports delayed technologies. These timing interfaces do not change instruction semantics |
| Reset | Visual program core resets PC and all registers to zero, clears halt/trap state; program/data memory is retained |
| Initial memory | Fixtures explicitly clear memory and load their instruction/data images before running |
| Execution status | Simulator state halted, trapped, and trap_cause; these are not privileged RISC-V CSRs |
| Instruction count | Reference bookkeeping counts attempted instructions, including terminal faults and EBREAK; it is not an implementation of minstret |
| Extensions/platform features | No M, A, F/D, C, vector, Zicsr, Zifencei, privilege modes, interrupts, virtual memory, or resumable trap handlers |
The 32-bit address space is an ISA property; the small address range, Harvard memory organization, initial state, and halt policy are project choices. The standalone oracle permits caller-sized functional memory and either unified or separate instruction/data memories. That does not enlarge the supported address map of the current pin-level reference core.
Data-memory rules¶
For an n-byte access, the address must be divisible by n and the entire byte range must fit inside memory. Byte accesses require no extra alignment. CircuitSim policy: misaligned loads/stores trap; an implementation in another execution environment may support them.
In the default 256 KiB memory, the last word begins at 0x0003FFFC. Address 0x0003FFFF is usable for one byte, not four. Effective-address arithmetic wraps to 32 bits, but a memory access does not wrap around the end of the memory.
Little-endian example: storing word 0x12345678 at a places bytes 78 56 34 12 at a, a+1, a+2, a+3. A store changes only its addressed bytes. A rejected store changes none.
Common exceptions¶
Every instruction is subject to these checks before its operation block:
- Check the current PC's four-byte alignment.
- Check that all four instruction bytes are accessible.
- Fetch and decode the instruction. Rejected encodings produce an illegal-instruction trap.
For data accesses, alignment is checked before range. For a taken branch/jump, target alignment is checked before any link write. An aligned but inaccessible target is different: the transfer completes, and the next instruction fetch faults at that target.
| Project cause | TRAP_CAUSE code |
Trigger |
|---|---|---|
None |
0 | No trap; also used with successful EBREAK halt |
IllegalInstruction |
1 | Decoder rejects the fetched instruction |
EnvironmentCall |
2 | Exact ECALL instruction |
InstructionAddressMisaligned |
3 | Current PC or taken branch/jump target is not four-byte aligned |
InstructionAccessFault |
4 | Current fetch is outside instruction memory or receives a fetch fault |
LoadAddressMisaligned |
5 | Load address violates the selected access alignment |
LoadAccessFault |
6 | Load cannot access its complete byte range or receives a data-memory fault |
StoreAddressMisaligned |
7 | Store address violates the selected access alignment |
StoreAccessFault |
8 | Store cannot access its complete byte range or receives a data-memory fault |
InstructionLimit |
9 | Host oracle run() reaches its requested step limit without halting/trapping; not an instruction-generated exception |
These numbers are project enum encodings, not RISC-V mcause values. No mcause, mepc, or mtvec register exists in this environment. The behavioral system exposes the cause through its state API; the behavioral core and visual root also have a four-bit cause pin.
For an instruction-generated trap, PC remains at the faulting instruction; its destination register and data memory are unchanged; earlier instruction effects remain. The reference sets trapped=true and leaves halted=false. EBREAK instead sets halted=true, with no trap. Further steps are inert until reset or explicit host reinitialization. Reset clears core state but does not erase program/data images.
If multiple conditions apply, the oracle checks current-PC misalignment before fetch range, fetch before decode, and data alignment before data range. For example, a misaligned out-of-range load reports LoadAddressMisaligned. These priorities describe the current environment. External pin responses must obey the memory protocol; arbitrary conflicting fault/data signals are not an ISA-level test.
The reference instruction_count increments once for a terminal instruction attempt as well as for a successful instruction. Calling step() after stopping does not increment it. run(..., max_instructions) adds an InstructionLimit stop if the limit is exhausted while still active; it does not execute a synthetic instruction. Answer-sheet checkpoint numbers therefore must not be confused with privileged retired-instruction counters.
Register names, calls, and the stack¶
The integer ABI assigns conventional names to the same registers:
| Registers | ABI names | Conventional use |
|---|---|---|
x0 |
zero |
Constant zero |
x1 |
ra |
Return address |
x2 |
sp |
Stack pointer |
x3, x4 |
gp, tp |
Global and thread pointers |
x5–x7 |
t0–t2 |
Temporaries |
x8, x9 |
s0/fp, s1 |
Saved registers; optional frame pointer |
x10–x17 |
a0–a7 |
Arguments; a0/a1 also carry return values |
x18–x27 |
s2–s11 |
Saved registers |
x28–x31 |
t3–t6 |
Temporaries |
Only x0 has a fixed value imposed by the ISA. Calling-convention names do not create separate registers. Calls do not automatically push a return address, save other registers, or adjust the stack pointer. Software does that with ordinary instructions.
The stack is a software-managed region of data memory, not a third memory component. Under the standard ABI it grows toward lower addresses and sp remains 16-byte aligned. Reset does not automatically select a stack region.
The linker/startup code is responsible for stack setup. Our Embench linker script reserves 16 KiB after the image data, and startup code loads __stack_top into sp before calling main. This is not necessarily the end of all data memory, and the core does not enforce that stack-region boundary separately. Hand-written programs may use x2 as another general-purpose register if they are not following the ABI.