§ 4.1.4. Executable RV32I Reference¶
Reference index · Instruction catalogue · Benchmarking and verification
The instruction entries specify the required effects in prose and pseudocode. This page documents the existing C++ model that computes those effects: RV32IInstructionOracle.
Its input is the current registers, PC, and memory. Its output is the next state and a record of the instruction's effects. It runs without a clock, circuit hierarchy, or visualizer. Later core implementations can be checked against it without adopting its software organization.
Components¶
These are ordinary C++ types, not connected hardware components.
| Component / type | Description |
|---|---|
RV32IState |
Registers, PC, execution status, and attempted-instruction count |
RV32IFunctionalMemory |
Byte storage with program loading and little-endian reads/writes |
RV32IDecoder |
Instruction recognition and field/immediate extraction |
RV32IControl |
Internal software selection of instruction effects; it is not a hardware wiring specification |
RV32IInstructionOracle |
Executes one instruction or a bounded sequence |
RV32IInstructionTrace |
Record of the instruction, operands, memory access, destination write, next PC, and status |
State and inputs¶
| Object / field | Type | Initial value or role |
|---|---|---|
state.pc |
uint32_t |
Defaults to zero; byte address of the next instruction to attempt |
state.x |
std::array<uint32_t, 32> |
Defaults to zero; use readRegister() and writeRegister() to preserve the x0 rule |
state.halted, state.trapped |
bool |
Both default to false |
state.trap_cause |
RV32IExecutionTrapCause |
Defaults to None; project exception enum |
state.instruction_count |
uint64_t |
Defaults to zero; counts attempts, including terminal instructions and faults |
instruction_memory |
RV32IFunctionalMemory |
Supplies instruction bytes at PC; preload before execution |
data_memory |
RV32IFunctionalMemory |
Supplies load bytes and receives successful stores |
Functional memory defaults to 256 KiB of zero bytes; its constructor accepts a different byte count. loadWords(address, words) places instruction words in little-endian order. loadBytes(), readBytes(), and the readU8/16/32() / writeU8/16/32() methods provide host-side access.
Host memory methods check ranges; the instruction engine additionally applies the ISA/environment alignment and exception rules. A host loading error is a C++ exception, not a simulated instruction trap. See machine conventions for address wrapping, memory policy, and trap meanings.
Execution interface¶
| Call | Inputs | Result and side effects |
|---|---|---|
step(state, imem, dmem) |
Mutable state and separate instruction/data memories | Attempts one instruction; updates state and successful data stores; returns one trace |
step(state, memory) |
Mutable state and one memory | Same operation with unified instruction/data storage |
run(state, memory, max_instructions) |
Mutable state, unified memory, and an attempt limit | Repeats steps until halt, trap, or limit; returns final state, traces, and a limit-reached flag |
All three calls are static methods of RV32IInstructionOracle. The three-argument step() is used for the separate-memory examples. The current run() helper has only the unified-memory form; it also mutates the supplied state and memory.
One-step contract¶
For an active state, step() fetches and decodes the word at PC, reads the old operands, computes the instruction's effects, and applies the permitted updates. One call means one instruction attempt, not one hardware cycle.
- A successful instruction updates only its specified destination, memory bytes, and PC.
- A fault records its cause without changing the faulting instruction's destination, data memory, or PC.
- EBREAK uses the project's halt convention; ECALL produces an environment-call trap.
- Every active attempt increments
instruction_countonce. - Calling
step()after halt/trap does not execute another instruction or increment the count. The returned trace reports the stopped state, not a newly fetched instruction. - Exhausting
run()'s limit while still active setsInstructionLimitandinstruction_limit_reached=true. This host stop adds no synthetic instruction trace.
The returned RV32IInstructionRunResult.final_state is a snapshot. For a limit stop, it includes the stop even though the last instruction trace describes the preceding attempt.
Returned trace¶
| Field | Meaning |
|---|---|
instruction_index |
Attempt count before this call; zero-based for a fresh state |
pc_before, raw_instruction, decoded |
Instruction identity and decoded fields, when fetch succeeds |
rs1_value, rs2_value, immediate |
Old operand values and decoded immediate; use only operands meaningful for this instruction |
writeback |
Whether a destination write was requested, its register/value, and whether x0 discarded it |
memory |
Access kind, address, width, load/store value, and fault flag; a faulting access is not a completed store |
pc_after, halted, trapped, trap_cause |
Next PC and execution outcome |
The trace also contains internal decoded controls and intermediate arithmetic values for diagnostics. They are not additional architectural state or requirements on the later core's internal wiring. No trace field specifies cycle count or propagation delay.
Running the reference¶
This complete example starts at PC 0x40, executes add x5, x6, x7 with source values 10 and 3, and then executes EBREAK. It uses separate memories and requires no simulator setup.
#include "rv32i/RV32IInstructionOracle.hpp"
#include <iomanip>
#include <iostream>
int main() {
rv32i::RV32IState state;
rv32i::RV32IFunctionalMemory imem, dmem;
state.pc = 0x40;
state.writeRegister(6, 10);
state.writeRegister(7, 3);
imem.loadWords(0x40, {
0x007302B3U, // add x5, x6, x7
0x00100073U, // ebreak
});
for (unsigned attempt = 0; attempt < 2; ++attempt) {
const auto trace = rv32i::RV32IInstructionOracle::step(state, imem, dmem);
std::cout << rv32i::toString(trace.decoded.instruction)
<< " x5=" << std::dec << state.readRegister(5)
<< " pc=0x" << std::hex << trace.pc_before
<< "->0x" << trace.pc_after
<< " halted=" << state.halted
<< " trapped=" << state.trapped << '\n';
}
std::cout << "attempts=" << std::dec << state.instruction_count << '\n';
}
Expected output:
Save the example as rv32i-reference-example.cpp. From the repository root, after configuring the native build:
cmake --build build --target SimulatorLib
c++ -std=c++20 -Iinclude rv32i-reference-example.cpp build/libSimulatorLib.a -o build/rv32i-reference-example
./build/rv32i-reference-example
RV32IReferenceDocumentationTest compiles the exact example from this page against the built library and checks its output.
Complete behavioral implementation¶
The listings are included directly from repository files when the site builds. They contain the instruction-level implementation and its state, decoding, control-selection, and functional-memory definitions. They are not a second implementation maintained only for documentation.
The source retains an additional simulator-integration overload; standalone callers use the step() and run() forms above. Normal repository headers and the built library supply the remaining dependencies. Clocked core adapters and their interfaces belong to the later core documentation.
State, instruction fields, and execution trace
include/rv32i/RV32IState.hpp
#pragma once
#include <array>
#include <cstdint>
#include <string_view>
namespace rv32i {
enum class RV32IExecutionTrapCause {
None,
IllegalInstruction,
EnvironmentCall,
InstructionAddressMisaligned,
InstructionAccessFault,
LoadAddressMisaligned,
LoadAccessFault,
StoreAddressMisaligned,
StoreAccessFault,
InstructionLimit,
};
struct RV32IState {
uint32_t pc = 0;
std::array<uint32_t, 32> x{};
bool halted = false;
bool trapped = false;
RV32IExecutionTrapCause trap_cause = RV32IExecutionTrapCause::None;
uint64_t instruction_count = 0;
uint32_t readRegister(uint8_t index) const;
void writeRegister(uint8_t index, uint32_t value);
void forceX0();
};
std::string_view toString(RV32IExecutionTrapCause cause);
}
include/rv32i/RV32IInstruction.hpp
#pragma once
#include <cstdint>
#include <string_view>
namespace rv32i {
enum class RV32IFormat {
R,
I,
S,
B,
U,
J,
Invalid,
};
enum class RV32IInstruction {
ADD,
SUB,
SLL,
SLT,
SLTU,
XOR,
SRL,
SRA,
OR,
AND,
ADDI,
SLTI,
SLTIU,
XORI,
ORI,
ANDI,
SLLI,
SRLI,
SRAI,
LB,
LH,
LW,
LBU,
LHU,
SB,
SH,
SW,
BEQ,
BNE,
BLT,
BGE,
BLTU,
BGEU,
JAL,
JALR,
LUI,
AUIPC,
FENCE,
ECALL,
EBREAK,
INVALID,
};
enum class RV32IDecodeStatus {
Legal,
InvalidInstructionLength,
UnknownOpcode,
UnsupportedFunct3,
UnsupportedFunct7,
UnsupportedSystem,
UnsupportedExtension,
};
struct RV32IDecodedInstruction {
uint32_t raw = 0;
RV32IInstruction instruction = RV32IInstruction::INVALID;
RV32IFormat format = RV32IFormat::Invalid;
RV32IDecodeStatus status = RV32IDecodeStatus::UnknownOpcode;
bool legal = false;
uint8_t opcode = 0;
uint8_t rd = 0;
uint8_t funct3 = 0;
uint8_t rs1 = 0;
uint8_t rs2 = 0;
uint8_t funct7 = 0;
uint16_t imm12 = 0;
uint8_t shamt = 0;
int32_t immediate = 0;
};
std::string_view toString(RV32IFormat format);
std::string_view toString(RV32IInstruction instruction);
std::string_view toString(RV32IDecodeStatus status);
}
include/rv32i/RV32IInstructionTrace.hpp
#pragma once
#include "rv32i/RV32IControl.hpp"
#include "rv32i/RV32IState.hpp"
#include <cstdint>
namespace rv32i {
enum class RV32IMemoryAccessKind {
None,
Read,
Write,
};
struct RV32IMemoryTrace {
RV32IMemoryAccessKind kind = RV32IMemoryAccessKind::None;
RV32IMemorySize size = RV32IMemorySize::None;
bool sign_extend = false;
uint32_t address = 0;
uint32_t write_data = 0;
uint32_t read_data = 0;
bool fault = false;
};
struct RV32IWritebackTrace {
bool enabled = false;
uint8_t rd = 0;
uint32_t value = 0;
bool ignored_x0 = false;
};
struct RV32IInstructionTrace {
uint64_t instruction_index = 0;
uint32_t pc_before = 0;
uint32_t raw_instruction = 0;
RV32IDecodedInstruction decoded{};
RV32IControlSignals control{};
uint8_t rs1 = 0;
uint8_t rs2 = 0;
uint8_t rd = 0;
uint32_t rs1_value = 0;
uint32_t rs2_value = 0;
int32_t immediate = 0;
uint32_t alu_a = 0;
uint32_t alu_b = 0;
uint32_t alu_result = 0;
RV32IMemoryTrace memory{};
RV32IWritebackTrace writeback{};
bool branch_taken = false;
uint32_t pc_after = 0;
bool halted = false;
bool trapped = false;
RV32IExecutionTrapCause trap_cause = RV32IExecutionTrapCause::None;
};
std::string_view toString(RV32IMemoryAccessKind kind);
}
Complete decoder and immediate extraction
include/rv32i/RV32IDecoder.hpp
#pragma once
#include "rv32i/RV32IInstruction.hpp"
#include <cstdint>
#include <string>
namespace rv32i {
class RV32IDecoder {
public:
static RV32IDecodedInstruction decode(uint32_t raw);
static uint8_t opcode(uint32_t raw);
static uint8_t rd(uint32_t raw);
static uint8_t funct3(uint32_t raw);
static uint8_t rs1(uint32_t raw);
static uint8_t rs2(uint32_t raw);
static uint8_t funct7(uint32_t raw);
static uint16_t imm12(uint32_t raw);
static uint8_t shamt(uint32_t raw);
static int32_t signExtend(uint32_t value, unsigned bit_count);
static int32_t immediateI(uint32_t raw);
static int32_t immediateS(uint32_t raw);
static int32_t immediateB(uint32_t raw);
static int32_t immediateU(uint32_t raw);
static int32_t immediateJ(uint32_t raw);
static std::string disassemble(uint32_t raw, uint32_t pc = 0);
};
}
src/rv32i/RV32IDecoder.cpp
#include "rv32i/RV32IDecoder.hpp"
#include <iomanip>
#include <sstream>
#include <string>
#include <string_view>
namespace rv32i {
namespace {
constexpr uint8_t OPCODE_LOAD = 0x03;
constexpr uint8_t OPCODE_MISC_MEM = 0x0F;
constexpr uint8_t OPCODE_OP_IMM = 0x13;
constexpr uint8_t OPCODE_AUIPC = 0x17;
constexpr uint8_t OPCODE_STORE = 0x23;
constexpr uint8_t OPCODE_OP = 0x33;
constexpr uint8_t OPCODE_LUI = 0x37;
constexpr uint8_t OPCODE_BRANCH = 0x63;
constexpr uint8_t OPCODE_JALR = 0x67;
constexpr uint8_t OPCODE_JAL = 0x6F;
constexpr uint8_t OPCODE_SYSTEM = 0x73;
constexpr uint8_t OPCODE_OP_IMM_32 = 0x1B;
constexpr uint8_t OPCODE_OP_32 = 0x3B;
uint32_t bits(uint32_t value, unsigned high, unsigned low) {
const uint32_t width = high - low + 1;
return (value >> low) & ((uint32_t{1} << width) - 1);
}
RV32IDecodedInstruction baseDecode(uint32_t raw) {
RV32IDecodedInstruction decoded;
decoded.raw = raw;
decoded.opcode = RV32IDecoder::opcode(raw);
decoded.rd = RV32IDecoder::rd(raw);
decoded.funct3 = RV32IDecoder::funct3(raw);
decoded.rs1 = RV32IDecoder::rs1(raw);
decoded.rs2 = RV32IDecoder::rs2(raw);
decoded.funct7 = RV32IDecoder::funct7(raw);
decoded.imm12 = RV32IDecoder::imm12(raw);
decoded.shamt = RV32IDecoder::shamt(raw);
return decoded;
}
RV32IDecodedInstruction legal(uint32_t raw, RV32IInstruction instruction, RV32IFormat format, int32_t immediate = 0) {
auto decoded = baseDecode(raw);
decoded.instruction = instruction;
decoded.format = format;
decoded.status = RV32IDecodeStatus::Legal;
decoded.legal = true;
decoded.immediate = immediate;
return decoded;
}
RV32IDecodedInstruction invalid(uint32_t raw, RV32IDecodeStatus status) {
auto decoded = baseDecode(raw);
decoded.status = status;
return decoded;
}
RV32IDecodedInstruction decodeOp(uint32_t raw) {
switch (RV32IDecoder::funct3(raw)) {
case 0x0:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::ADD, RV32IFormat::R);
if (RV32IDecoder::funct7(raw) == 0x20) return legal(raw, RV32IInstruction::SUB, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x1:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::SLL, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x2:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::SLT, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x3:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::SLTU, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x4:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::XOR, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x5:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::SRL, RV32IFormat::R);
if (RV32IDecoder::funct7(raw) == 0x20) return legal(raw, RV32IInstruction::SRA, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x6:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::OR, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x7:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::AND, RV32IFormat::R);
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
default:
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct3);
}
}
RV32IDecodedInstruction decodeOpImm(uint32_t raw) {
switch (RV32IDecoder::funct3(raw)) {
case 0x0: return legal(raw, RV32IInstruction::ADDI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x2: return legal(raw, RV32IInstruction::SLTI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x3: return legal(raw, RV32IInstruction::SLTIU, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x4: return legal(raw, RV32IInstruction::XORI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x6: return legal(raw, RV32IInstruction::ORI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x7: return legal(raw, RV32IInstruction::ANDI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x1:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::SLLI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
case 0x5:
if (RV32IDecoder::funct7(raw) == 0x00) return legal(raw, RV32IInstruction::SRLI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
if (RV32IDecoder::funct7(raw) == 0x20) return legal(raw, RV32IInstruction::SRAI, RV32IFormat::I, RV32IDecoder::immediateI(raw));
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct7);
default:
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct3);
}
}
RV32IDecodedInstruction decodeLoad(uint32_t raw) {
switch (RV32IDecoder::funct3(raw)) {
case 0x0: return legal(raw, RV32IInstruction::LB, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x1: return legal(raw, RV32IInstruction::LH, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x2: return legal(raw, RV32IInstruction::LW, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x4: return legal(raw, RV32IInstruction::LBU, RV32IFormat::I, RV32IDecoder::immediateI(raw));
case 0x5: return legal(raw, RV32IInstruction::LHU, RV32IFormat::I, RV32IDecoder::immediateI(raw));
default: return invalid(raw, RV32IDecodeStatus::UnsupportedFunct3);
}
}
RV32IDecodedInstruction decodeStore(uint32_t raw) {
switch (RV32IDecoder::funct3(raw)) {
case 0x0: return legal(raw, RV32IInstruction::SB, RV32IFormat::S, RV32IDecoder::immediateS(raw));
case 0x1: return legal(raw, RV32IInstruction::SH, RV32IFormat::S, RV32IDecoder::immediateS(raw));
case 0x2: return legal(raw, RV32IInstruction::SW, RV32IFormat::S, RV32IDecoder::immediateS(raw));
default: return invalid(raw, RV32IDecodeStatus::UnsupportedFunct3);
}
}
RV32IDecodedInstruction decodeBranch(uint32_t raw) {
switch (RV32IDecoder::funct3(raw)) {
case 0x0: return legal(raw, RV32IInstruction::BEQ, RV32IFormat::B, RV32IDecoder::immediateB(raw));
case 0x1: return legal(raw, RV32IInstruction::BNE, RV32IFormat::B, RV32IDecoder::immediateB(raw));
case 0x4: return legal(raw, RV32IInstruction::BLT, RV32IFormat::B, RV32IDecoder::immediateB(raw));
case 0x5: return legal(raw, RV32IInstruction::BGE, RV32IFormat::B, RV32IDecoder::immediateB(raw));
case 0x6: return legal(raw, RV32IInstruction::BLTU, RV32IFormat::B, RV32IDecoder::immediateB(raw));
case 0x7: return legal(raw, RV32IInstruction::BGEU, RV32IFormat::B, RV32IDecoder::immediateB(raw));
default: return invalid(raw, RV32IDecodeStatus::UnsupportedFunct3);
}
}
RV32IDecodedInstruction decodeSystem(uint32_t raw) {
if (RV32IDecoder::funct3(raw) != 0x0) {
return invalid(raw, RV32IDecodeStatus::UnsupportedExtension);
}
if (raw == 0x00000073U) {
return legal(raw, RV32IInstruction::ECALL, RV32IFormat::I, 0);
}
if (raw == 0x00100073U) {
return legal(raw, RV32IInstruction::EBREAK, RV32IFormat::I, 1);
}
return invalid(raw, RV32IDecodeStatus::UnsupportedSystem);
}
std::string reg(uint8_t index) {
return "x" + std::to_string(index);
}
std::string lower(std::string_view value) {
std::string result(value);
for (auto& ch : result) {
if (ch >= 'A' && ch <= 'Z') {
ch = static_cast<char>(ch - 'A' + 'a');
}
}
return result;
}
std::string hex32(uint32_t value) {
std::ostringstream out;
out << "0x" << std::hex << std::setfill('0') << std::setw(8) << value;
return out.str();
}
std::string imm(int32_t value) {
return std::to_string(value);
}
}
RV32IDecodedInstruction RV32IDecoder::decode(uint32_t raw) {
if ((raw & 0x3U) != 0x3U) {
return invalid(raw, RV32IDecodeStatus::InvalidInstructionLength);
}
switch (opcode(raw)) {
case OPCODE_LUI: return legal(raw, RV32IInstruction::LUI, RV32IFormat::U, immediateU(raw));
case OPCODE_AUIPC: return legal(raw, RV32IInstruction::AUIPC, RV32IFormat::U, immediateU(raw));
case OPCODE_JAL: return legal(raw, RV32IInstruction::JAL, RV32IFormat::J, immediateJ(raw));
case OPCODE_JALR:
if (funct3(raw) == 0x0) return legal(raw, RV32IInstruction::JALR, RV32IFormat::I, immediateI(raw));
return invalid(raw, RV32IDecodeStatus::UnsupportedFunct3);
case OPCODE_BRANCH: return decodeBranch(raw);
case OPCODE_LOAD: return decodeLoad(raw);
case OPCODE_STORE: return decodeStore(raw);
case OPCODE_OP_IMM: return decodeOpImm(raw);
case OPCODE_OP: return decodeOp(raw);
case OPCODE_MISC_MEM:
if (funct3(raw) == 0x0) return legal(raw, RV32IInstruction::FENCE, RV32IFormat::I, immediateI(raw));
return invalid(raw, RV32IDecodeStatus::UnsupportedExtension);
case OPCODE_SYSTEM: return decodeSystem(raw);
case OPCODE_OP_IMM_32:
case OPCODE_OP_32:
return invalid(raw, RV32IDecodeStatus::UnsupportedExtension);
default:
return invalid(raw, RV32IDecodeStatus::UnknownOpcode);
}
}
uint8_t RV32IDecoder::opcode(uint32_t raw) {
return static_cast<uint8_t>(raw & 0x7FU);
}
uint8_t RV32IDecoder::rd(uint32_t raw) {
return static_cast<uint8_t>(bits(raw, 11, 7));
}
uint8_t RV32IDecoder::funct3(uint32_t raw) {
return static_cast<uint8_t>(bits(raw, 14, 12));
}
uint8_t RV32IDecoder::rs1(uint32_t raw) {
return static_cast<uint8_t>(bits(raw, 19, 15));
}
uint8_t RV32IDecoder::rs2(uint32_t raw) {
return static_cast<uint8_t>(bits(raw, 24, 20));
}
uint8_t RV32IDecoder::funct7(uint32_t raw) {
return static_cast<uint8_t>(bits(raw, 31, 25));
}
uint16_t RV32IDecoder::imm12(uint32_t raw) {
return static_cast<uint16_t>(bits(raw, 31, 20));
}
uint8_t RV32IDecoder::shamt(uint32_t raw) {
return static_cast<uint8_t>(bits(raw, 24, 20));
}
int32_t RV32IDecoder::signExtend(uint32_t value, unsigned bit_count) {
if (bit_count == 0 || bit_count >= 32) {
return static_cast<int32_t>(value);
}
const uint32_t sign_bit = uint32_t{1} << (bit_count - 1);
const uint32_t mask = (uint32_t{1} << bit_count) - 1;
value &= mask;
if ((value & sign_bit) == 0) {
return static_cast<int32_t>(value);
}
return static_cast<int32_t>(value | ~mask);
}
int32_t RV32IDecoder::immediateI(uint32_t raw) {
return signExtend(bits(raw, 31, 20), 12);
}
int32_t RV32IDecoder::immediateS(uint32_t raw) {
const uint32_t value = (bits(raw, 31, 25) << 5) | bits(raw, 11, 7);
return signExtend(value, 12);
}
int32_t RV32IDecoder::immediateB(uint32_t raw) {
const uint32_t value = (bits(raw, 31, 31) << 12)
| (bits(raw, 7, 7) << 11)
| (bits(raw, 30, 25) << 5)
| (bits(raw, 11, 8) << 1);
return signExtend(value, 13);
}
int32_t RV32IDecoder::immediateU(uint32_t raw) {
return static_cast<int32_t>(raw & 0xFFFFF000U);
}
int32_t RV32IDecoder::immediateJ(uint32_t raw) {
const uint32_t value = (bits(raw, 31, 31) << 20)
| (bits(raw, 19, 12) << 12)
| (bits(raw, 20, 20) << 11)
| (bits(raw, 30, 21) << 1);
return signExtend(value, 21);
}
std::string RV32IDecoder::disassemble(uint32_t raw, uint32_t pc) {
const auto decoded = decode(raw);
if (!decoded.legal) {
return "invalid " + std::string(toString(decoded.status));
}
const auto op = lower(toString(decoded.instruction));
switch (decoded.instruction) {
case RV32IInstruction::ADD:
case RV32IInstruction::SUB:
case RV32IInstruction::SLL:
case RV32IInstruction::SLT:
case RV32IInstruction::SLTU:
case RV32IInstruction::XOR:
case RV32IInstruction::SRL:
case RV32IInstruction::SRA:
case RV32IInstruction::OR:
case RV32IInstruction::AND:
return op + " " + reg(decoded.rd) + ", " + reg(decoded.rs1) + ", " + reg(decoded.rs2);
case RV32IInstruction::SLLI:
case RV32IInstruction::SRLI:
case RV32IInstruction::SRAI:
return op + " " + reg(decoded.rd) + ", " + reg(decoded.rs1) + ", " + std::to_string(decoded.shamt);
case RV32IInstruction::ADDI:
case RV32IInstruction::SLTI:
case RV32IInstruction::SLTIU:
case RV32IInstruction::XORI:
case RV32IInstruction::ORI:
case RV32IInstruction::ANDI:
return op + " " + reg(decoded.rd) + ", " + reg(decoded.rs1) + ", " + imm(decoded.immediate);
case RV32IInstruction::LB:
case RV32IInstruction::LH:
case RV32IInstruction::LW:
case RV32IInstruction::LBU:
case RV32IInstruction::LHU:
return op + " " + reg(decoded.rd) + ", " + imm(decoded.immediate) + "(" + reg(decoded.rs1) + ")";
case RV32IInstruction::SB:
case RV32IInstruction::SH:
case RV32IInstruction::SW:
return op + " " + reg(decoded.rs2) + ", " + imm(decoded.immediate) + "(" + reg(decoded.rs1) + ")";
case RV32IInstruction::BEQ:
case RV32IInstruction::BNE:
case RV32IInstruction::BLT:
case RV32IInstruction::BGE:
case RV32IInstruction::BLTU:
case RV32IInstruction::BGEU:
return op + " " + reg(decoded.rs1) + ", " + reg(decoded.rs2) + ", "
+ hex32(pc + static_cast<uint32_t>(decoded.immediate));
case RV32IInstruction::JAL:
return op + " " + reg(decoded.rd) + ", " + hex32(pc + static_cast<uint32_t>(decoded.immediate));
case RV32IInstruction::JALR:
return op + " " + reg(decoded.rd) + ", " + imm(decoded.immediate) + "(" + reg(decoded.rs1) + ")";
case RV32IInstruction::LUI:
case RV32IInstruction::AUIPC:
return op + " " + reg(decoded.rd) + ", " + hex32(static_cast<uint32_t>(decoded.immediate));
case RV32IInstruction::FENCE:
return "fence";
case RV32IInstruction::ECALL:
return "ecall";
case RV32IInstruction::EBREAK:
return "ebreak";
case RV32IInstruction::INVALID:
break;
}
return "invalid";
}
std::string_view toString(RV32IFormat format) {
switch (format) {
case RV32IFormat::R: return "R";
case RV32IFormat::I: return "I";
case RV32IFormat::S: return "S";
case RV32IFormat::B: return "B";
case RV32IFormat::U: return "U";
case RV32IFormat::J: return "J";
case RV32IFormat::Invalid: return "Invalid";
}
return "Invalid";
}
std::string_view toString(RV32IInstruction instruction) {
switch (instruction) {
case RV32IInstruction::ADD: return "ADD";
case RV32IInstruction::SUB: return "SUB";
case RV32IInstruction::SLL: return "SLL";
case RV32IInstruction::SLT: return "SLT";
case RV32IInstruction::SLTU: return "SLTU";
case RV32IInstruction::XOR: return "XOR";
case RV32IInstruction::SRL: return "SRL";
case RV32IInstruction::SRA: return "SRA";
case RV32IInstruction::OR: return "OR";
case RV32IInstruction::AND: return "AND";
case RV32IInstruction::ADDI: return "ADDI";
case RV32IInstruction::SLTI: return "SLTI";
case RV32IInstruction::SLTIU: return "SLTIU";
case RV32IInstruction::XORI: return "XORI";
case RV32IInstruction::ORI: return "ORI";
case RV32IInstruction::ANDI: return "ANDI";
case RV32IInstruction::SLLI: return "SLLI";
case RV32IInstruction::SRLI: return "SRLI";
case RV32IInstruction::SRAI: return "SRAI";
case RV32IInstruction::LB: return "LB";
case RV32IInstruction::LH: return "LH";
case RV32IInstruction::LW: return "LW";
case RV32IInstruction::LBU: return "LBU";
case RV32IInstruction::LHU: return "LHU";
case RV32IInstruction::SB: return "SB";
case RV32IInstruction::SH: return "SH";
case RV32IInstruction::SW: return "SW";
case RV32IInstruction::BEQ: return "BEQ";
case RV32IInstruction::BNE: return "BNE";
case RV32IInstruction::BLT: return "BLT";
case RV32IInstruction::BGE: return "BGE";
case RV32IInstruction::BLTU: return "BLTU";
case RV32IInstruction::BGEU: return "BGEU";
case RV32IInstruction::JAL: return "JAL";
case RV32IInstruction::JALR: return "JALR";
case RV32IInstruction::LUI: return "LUI";
case RV32IInstruction::AUIPC: return "AUIPC";
case RV32IInstruction::FENCE: return "FENCE";
case RV32IInstruction::ECALL: return "ECALL";
case RV32IInstruction::EBREAK: return "EBREAK";
case RV32IInstruction::INVALID: return "INVALID";
}
return "INVALID";
}
std::string_view toString(RV32IDecodeStatus status) {
switch (status) {
case RV32IDecodeStatus::Legal: return "Legal";
case RV32IDecodeStatus::InvalidInstructionLength: return "InvalidInstructionLength";
case RV32IDecodeStatus::UnknownOpcode: return "UnknownOpcode";
case RV32IDecodeStatus::UnsupportedFunct3: return "UnsupportedFunct3";
case RV32IDecodeStatus::UnsupportedFunct7: return "UnsupportedFunct7";
case RV32IDecodeStatus::UnsupportedSystem: return "UnsupportedSystem";
case RV32IDecodeStatus::UnsupportedExtension: return "UnsupportedExtension";
}
return "UnknownOpcode";
}
}
Complete decoded-control mapping
include/rv32i/RV32IControl.hpp
#pragma once
#include "rv32i/RV32IDecoder.hpp"
#include <cstdint>
#include <string_view>
namespace rv32i {
enum class RV32IALUSourceA {
RS1,
PC,
Zero,
};
enum class RV32IALUSourceB {
RS2,
Immediate,
Zero,
};
enum class RV32IWritebackSource {
None,
ALU,
Memory,
PCPlus4,
};
enum class RV32IMemorySize {
None,
Byte,
Halfword,
Word,
};
enum class RV32IBranchType {
None,
BEQ,
BNE,
BLT,
BGE,
BLTU,
BGEU,
};
enum class RV32IJumpType {
None,
JAL,
JALR,
};
enum class RV32ITrapCause {
None,
IllegalInstruction,
EnvironmentCall,
};
struct RV32IControlSignals {
bool legal = false;
uint8_t alu_op = 0;
RV32IALUSourceA alu_a = RV32IALUSourceA::Zero;
RV32IALUSourceB alu_b = RV32IALUSourceB::Zero;
bool reg_write = false;
RV32IWritebackSource writeback = RV32IWritebackSource::None;
bool mem_read = false;
bool mem_write = false;
RV32IMemorySize mem_size = RV32IMemorySize::None;
bool load_sign_extend = false;
RV32IBranchType branch = RV32IBranchType::None;
RV32IJumpType jump = RV32IJumpType::None;
bool uses_rs1 = false;
bool uses_rs2 = false;
bool uses_rd = false;
bool uses_immediate = false;
bool halt = false;
bool trap = false;
RV32ITrapCause trap_cause = RV32ITrapCause::None;
};
class RV32IControl {
public:
static RV32IControlSignals fromDecoded(const RV32IDecodedInstruction& decoded);
static RV32IControlSignals fromRaw(uint32_t raw);
};
std::string_view toString(RV32IALUSourceA source);
std::string_view toString(RV32IALUSourceB source);
std::string_view toString(RV32IWritebackSource source);
std::string_view toString(RV32IMemorySize size);
std::string_view toString(RV32IBranchType branch);
std::string_view toString(RV32IJumpType jump);
std::string_view toString(RV32ITrapCause cause);
}
src/rv32i/RV32IControl.cpp
#include "rv32i/RV32IControl.hpp"
#include "modules/composite/ALU32.hpp"
namespace rv32i {
namespace {
RV32IControlSignals baseLegal(uint8_t alu_op,
RV32IALUSourceA alu_a,
RV32IALUSourceB alu_b) {
RV32IControlSignals control;
control.legal = true;
control.alu_op = alu_op;
control.alu_a = alu_a;
control.alu_b = alu_b;
return control;
}
RV32IControlSignals trapControl(RV32ITrapCause cause) {
RV32IControlSignals control;
control.alu_op = ALU32Op::ZERO;
control.trap = true;
control.trap_cause = cause;
return control;
}
RV32IControlSignals regAlu(uint8_t alu_op,
RV32IALUSourceA alu_a,
RV32IALUSourceB alu_b,
bool uses_rs1,
bool uses_rs2,
bool uses_immediate) {
auto control = baseLegal(alu_op, alu_a, alu_b);
control.reg_write = true;
control.writeback = RV32IWritebackSource::ALU;
control.uses_rs1 = uses_rs1;
control.uses_rs2 = uses_rs2;
control.uses_rd = true;
control.uses_immediate = uses_immediate;
return control;
}
RV32IControlSignals loadControl(RV32IMemorySize size, bool sign_extend) {
auto control = baseLegal(ALU32Op::ADD, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate);
control.reg_write = true;
control.writeback = RV32IWritebackSource::Memory;
control.mem_read = true;
control.mem_size = size;
control.load_sign_extend = sign_extend;
control.uses_rs1 = true;
control.uses_rd = true;
control.uses_immediate = true;
return control;
}
RV32IControlSignals storeControl(RV32IMemorySize size) {
auto control = baseLegal(ALU32Op::ADD, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate);
control.mem_write = true;
control.mem_size = size;
control.uses_rs1 = true;
control.uses_rs2 = true;
control.uses_immediate = true;
return control;
}
RV32IControlSignals branchControl(RV32IBranchType branch) {
auto control = baseLegal(ALU32Op::SUB, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2);
control.branch = branch;
control.uses_rs1 = true;
control.uses_rs2 = true;
control.uses_immediate = true;
return control;
}
RV32IControlSignals jumpControl(RV32IJumpType jump,
RV32IALUSourceA alu_a,
RV32IALUSourceB alu_b,
bool uses_rs1) {
auto control = baseLegal(ALU32Op::ADD, alu_a, alu_b);
control.reg_write = true;
control.writeback = RV32IWritebackSource::PCPlus4;
control.jump = jump;
control.uses_rs1 = uses_rs1;
control.uses_rd = true;
control.uses_immediate = true;
return control;
}
}
RV32IControlSignals RV32IControl::fromDecoded(const RV32IDecodedInstruction& decoded) {
if (!decoded.legal) {
return trapControl(RV32ITrapCause::IllegalInstruction);
}
switch (decoded.instruction) {
case RV32IInstruction::ADD: return regAlu(ALU32Op::ADD, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::SUB: return regAlu(ALU32Op::SUB, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::SLL: return regAlu(ALU32Op::SLL, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::SLT: return regAlu(ALU32Op::SLT, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::SLTU: return regAlu(ALU32Op::SLTU, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::XOR: return regAlu(ALU32Op::XOR, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::SRL: return regAlu(ALU32Op::SRL, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::SRA: return regAlu(ALU32Op::SRA, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::OR: return regAlu(ALU32Op::OR, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::AND: return regAlu(ALU32Op::AND, RV32IALUSourceA::RS1, RV32IALUSourceB::RS2, true, true, false);
case RV32IInstruction::ADDI: return regAlu(ALU32Op::ADD, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::SLTI: return regAlu(ALU32Op::SLT, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::SLTIU: return regAlu(ALU32Op::SLTU, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::XORI: return regAlu(ALU32Op::XOR, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::ORI: return regAlu(ALU32Op::OR, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::ANDI: return regAlu(ALU32Op::AND, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::SLLI: return regAlu(ALU32Op::SLL, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::SRLI: return regAlu(ALU32Op::SRL, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::SRAI: return regAlu(ALU32Op::SRA, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true, false, true);
case RV32IInstruction::LB: return loadControl(RV32IMemorySize::Byte, true);
case RV32IInstruction::LH: return loadControl(RV32IMemorySize::Halfword, true);
case RV32IInstruction::LW: return loadControl(RV32IMemorySize::Word, false);
case RV32IInstruction::LBU: return loadControl(RV32IMemorySize::Byte, false);
case RV32IInstruction::LHU: return loadControl(RV32IMemorySize::Halfword, false);
case RV32IInstruction::SB: return storeControl(RV32IMemorySize::Byte);
case RV32IInstruction::SH: return storeControl(RV32IMemorySize::Halfword);
case RV32IInstruction::SW: return storeControl(RV32IMemorySize::Word);
case RV32IInstruction::BEQ: return branchControl(RV32IBranchType::BEQ);
case RV32IInstruction::BNE: return branchControl(RV32IBranchType::BNE);
case RV32IInstruction::BLT: return branchControl(RV32IBranchType::BLT);
case RV32IInstruction::BGE: return branchControl(RV32IBranchType::BGE);
case RV32IInstruction::BLTU: return branchControl(RV32IBranchType::BLTU);
case RV32IInstruction::BGEU: return branchControl(RV32IBranchType::BGEU);
case RV32IInstruction::JAL: return jumpControl(RV32IJumpType::JAL, RV32IALUSourceA::PC, RV32IALUSourceB::Immediate, false);
case RV32IInstruction::JALR: return jumpControl(RV32IJumpType::JALR, RV32IALUSourceA::RS1, RV32IALUSourceB::Immediate, true);
case RV32IInstruction::LUI: return regAlu(ALU32Op::PASS_B, RV32IALUSourceA::Zero, RV32IALUSourceB::Immediate, false, false, true);
case RV32IInstruction::AUIPC: return regAlu(ALU32Op::ADD, RV32IALUSourceA::PC, RV32IALUSourceB::Immediate, false, false, true);
case RV32IInstruction::FENCE:
return baseLegal(ALU32Op::ZERO, RV32IALUSourceA::Zero, RV32IALUSourceB::Zero);
case RV32IInstruction::ECALL: {
auto control = trapControl(RV32ITrapCause::EnvironmentCall);
control.legal = true;
return control;
}
case RV32IInstruction::EBREAK: {
auto control = baseLegal(ALU32Op::ZERO, RV32IALUSourceA::Zero, RV32IALUSourceB::Zero);
control.halt = true;
return control;
}
case RV32IInstruction::INVALID:
return trapControl(RV32ITrapCause::IllegalInstruction);
}
return trapControl(RV32ITrapCause::IllegalInstruction);
}
RV32IControlSignals RV32IControl::fromRaw(uint32_t raw) {
return fromDecoded(RV32IDecoder::decode(raw));
}
std::string_view toString(RV32IALUSourceA source) {
switch (source) {
case RV32IALUSourceA::RS1: return "RS1";
case RV32IALUSourceA::PC: return "PC";
case RV32IALUSourceA::Zero: return "Zero";
}
return "Zero";
}
std::string_view toString(RV32IALUSourceB source) {
switch (source) {
case RV32IALUSourceB::RS2: return "RS2";
case RV32IALUSourceB::Immediate: return "Immediate";
case RV32IALUSourceB::Zero: return "Zero";
}
return "Zero";
}
std::string_view toString(RV32IWritebackSource source) {
switch (source) {
case RV32IWritebackSource::None: return "None";
case RV32IWritebackSource::ALU: return "ALU";
case RV32IWritebackSource::Memory: return "Memory";
case RV32IWritebackSource::PCPlus4: return "PCPlus4";
}
return "None";
}
std::string_view toString(RV32IMemorySize size) {
switch (size) {
case RV32IMemorySize::None: return "None";
case RV32IMemorySize::Byte: return "Byte";
case RV32IMemorySize::Halfword: return "Halfword";
case RV32IMemorySize::Word: return "Word";
}
return "None";
}
std::string_view toString(RV32IBranchType branch) {
switch (branch) {
case RV32IBranchType::None: return "None";
case RV32IBranchType::BEQ: return "BEQ";
case RV32IBranchType::BNE: return "BNE";
case RV32IBranchType::BLT: return "BLT";
case RV32IBranchType::BGE: return "BGE";
case RV32IBranchType::BLTU: return "BLTU";
case RV32IBranchType::BGEU: return "BGEU";
}
return "None";
}
std::string_view toString(RV32IJumpType jump) {
switch (jump) {
case RV32IJumpType::None: return "None";
case RV32IJumpType::JAL: return "JAL";
case RV32IJumpType::JALR: return "JALR";
}
return "None";
}
std::string_view toString(RV32ITrapCause cause) {
switch (cause) {
case RV32ITrapCause::None: return "None";
case RV32ITrapCause::IllegalInstruction: return "IllegalInstruction";
case RV32ITrapCause::EnvironmentCall: return "EnvironmentCall";
}
return "None";
}
}
Complete functional memory
include/rv32i/RV32IFunctionalMemory.hpp
#pragma once
#include "rv32i/RV32IProgram.hpp"
#include <cstddef>
#include <cstdint>
#include <vector>
namespace rv32i {
class RV32IFunctionalMemory {
public:
static constexpr size_t DefaultCapacityBytes = 64 * 1024 * 4;
explicit RV32IFunctionalMemory(size_t byte_count = DefaultCapacityBytes);
size_t sizeBytes() const { return bytes.size(); }
bool canAccess(uint32_t address, size_t count) const;
void clear();
void loadProgram(const RV32IProgram& program, uint32_t base_address = 0);
void loadBytes(uint32_t base_address, const std::vector<uint8_t>& data);
void loadWords(uint32_t base_address, const std::vector<uint32_t>& words);
std::vector<uint8_t> readBytes(uint32_t base_address, size_t count) const;
uint8_t readU8(uint32_t address) const;
uint16_t readU16(uint32_t address) const;
uint32_t readU32(uint32_t address) const;
void writeU8(uint32_t address, uint8_t value);
void writeU16(uint32_t address, uint16_t value);
void writeU32(uint32_t address, uint32_t value);
private:
std::vector<uint8_t> bytes;
void requireRange(uint32_t address, size_t count) const;
};
}
src/rv32i/RV32IFunctionalMemory.cpp
#include "rv32i/RV32IFunctionalMemory.hpp"
#include <algorithm>
#include <stdexcept>
namespace rv32i {
RV32IFunctionalMemory::RV32IFunctionalMemory(size_t byte_count)
: bytes(byte_count, 0) {}
bool RV32IFunctionalMemory::canAccess(uint32_t address, size_t count) const {
return count <= bytes.size() && address <= bytes.size() - count;
}
void RV32IFunctionalMemory::requireRange(uint32_t address, size_t count) const {
if (!canAccess(address, count)) {
throw std::out_of_range("RV32IFunctionalMemory address range is outside memory capacity");
}
}
void RV32IFunctionalMemory::clear() {
std::fill(bytes.begin(), bytes.end(), 0);
}
void RV32IFunctionalMemory::loadProgram(const RV32IProgram& program, uint32_t base_address) {
loadBytes(base_address, program.bytes());
}
void RV32IFunctionalMemory::loadBytes(uint32_t base_address, const std::vector<uint8_t>& data) {
requireRange(base_address, data.size());
std::copy(data.begin(), data.end(), bytes.begin() + static_cast<size_t>(base_address));
}
void RV32IFunctionalMemory::loadWords(uint32_t base_address, const std::vector<uint32_t>& words) {
if ((base_address & 0x3U) != 0) {
throw std::invalid_argument("RV32IFunctionalMemory word load address must be 4-byte aligned");
}
std::vector<uint8_t> data;
data.reserve(words.size() * 4);
for (const auto word : words) {
data.push_back(static_cast<uint8_t>(word & 0xFFU));
data.push_back(static_cast<uint8_t>((word >> 8) & 0xFFU));
data.push_back(static_cast<uint8_t>((word >> 16) & 0xFFU));
data.push_back(static_cast<uint8_t>((word >> 24) & 0xFFU));
}
loadBytes(base_address, data);
}
std::vector<uint8_t> RV32IFunctionalMemory::readBytes(uint32_t base_address, size_t count) const {
requireRange(base_address, count);
return std::vector<uint8_t>(
bytes.begin() + static_cast<size_t>(base_address),
bytes.begin() + static_cast<size_t>(base_address) + count);
}
uint8_t RV32IFunctionalMemory::readU8(uint32_t address) const {
requireRange(address, 1);
return bytes[static_cast<size_t>(address)];
}
uint16_t RV32IFunctionalMemory::readU16(uint32_t address) const {
requireRange(address, 2);
return static_cast<uint16_t>(bytes[static_cast<size_t>(address)])
| static_cast<uint16_t>(static_cast<uint16_t>(bytes[static_cast<size_t>(address) + 1]) << 8);
}
uint32_t RV32IFunctionalMemory::readU32(uint32_t address) const {
requireRange(address, 4);
return static_cast<uint32_t>(bytes[static_cast<size_t>(address)])
| (static_cast<uint32_t>(bytes[static_cast<size_t>(address) + 1]) << 8)
| (static_cast<uint32_t>(bytes[static_cast<size_t>(address) + 2]) << 16)
| (static_cast<uint32_t>(bytes[static_cast<size_t>(address) + 3]) << 24);
}
void RV32IFunctionalMemory::writeU8(uint32_t address, uint8_t value) {
requireRange(address, 1);
bytes[static_cast<size_t>(address)] = value;
}
void RV32IFunctionalMemory::writeU16(uint32_t address, uint16_t value) {
requireRange(address, 2);
bytes[static_cast<size_t>(address)] = static_cast<uint8_t>(value & 0xFFU);
bytes[static_cast<size_t>(address) + 1] = static_cast<uint8_t>((value >> 8) & 0xFFU);
}
void RV32IFunctionalMemory::writeU32(uint32_t address, uint32_t value) {
requireRange(address, 4);
bytes[static_cast<size_t>(address)] = static_cast<uint8_t>(value & 0xFFU);
bytes[static_cast<size_t>(address) + 1] = static_cast<uint8_t>((value >> 8) & 0xFFU);
bytes[static_cast<size_t>(address) + 2] = static_cast<uint8_t>((value >> 16) & 0xFFU);
bytes[static_cast<size_t>(address) + 3] = static_cast<uint8_t>((value >> 24) & 0xFFU);
}
}
Complete instruction-step engine and bounded runner
include/rv32i/RV32IInstructionOracle.hpp
#pragma once
#include "rv32i/RV32IFunctionalMemory.hpp"
#include "rv32i/RV32IState.hpp"
#include "rv32i/RV32IInstructionTrace.hpp"
#include <cstddef>
#include <vector>
class MemoryStorage;
namespace rv32i {
struct RV32IInstructionRunResult {
RV32IState final_state{};
std::vector<RV32IInstructionTrace> trace;
bool instruction_limit_reached = false;
};
class RV32IInstructionOracle {
public:
static RV32IInstructionTrace step(RV32IState& state, RV32IFunctionalMemory& memory);
static RV32IInstructionTrace step(RV32IState& state,
RV32IFunctionalMemory& instruction_memory,
RV32IFunctionalMemory& data_memory);
static RV32IInstructionTrace stepWithoutDataMemoryWrite(RV32IState& state,
MemoryStorage& instruction_memory,
MemoryStorage& data_memory);
static RV32IInstructionRunResult run(RV32IState& state, RV32IFunctionalMemory& memory, size_t max_instructions);
};
}
src/rv32i/RV32IInstructionOracle.cpp
#include "rv32i/RV32IInstructionOracle.hpp"
#include "modules/composite/ALU32.hpp"
#include "modules/memory/MemoryStorage.hpp"
#include "rv32i/RV32IDecoder.hpp"
#include <cstdint>
namespace rv32i {
namespace {
uint32_t addWrap(uint32_t a, uint32_t b) {
return a + b;
}
uint32_t asUInt(int32_t value) {
return static_cast<uint32_t>(value);
}
int32_t asInt(uint32_t value) {
return static_cast<int32_t>(value);
}
uint32_t signExtend(uint32_t value, unsigned bits) {
return static_cast<uint32_t>(RV32IDecoder::signExtend(value, bits));
}
size_t memorySizeBytes(RV32IMemorySize size) {
switch (size) {
case RV32IMemorySize::Byte: return 1;
case RV32IMemorySize::Halfword: return 2;
case RV32IMemorySize::Word: return 4;
case RV32IMemorySize::None: return 0;
}
return 0;
}
bool isMisaligned(uint32_t address, RV32IMemorySize size) {
switch (size) {
case RV32IMemorySize::Byte: return false;
case RV32IMemorySize::Halfword: return (address & 0x1U) != 0;
case RV32IMemorySize::Word: return (address & 0x3U) != 0;
case RV32IMemorySize::None: return true;
}
return true;
}
template<typename MemoryT>
RV32IExecutionTrapCause loadTrapCause(uint32_t address, RV32IMemorySize size, const MemoryT& memory) {
if (isMisaligned(address, size)) {
return RV32IExecutionTrapCause::LoadAddressMisaligned;
}
if (!memory.canAccess(address, memorySizeBytes(size))) {
return RV32IExecutionTrapCause::LoadAccessFault;
}
return RV32IExecutionTrapCause::None;
}
template<typename MemoryT>
RV32IExecutionTrapCause storeTrapCause(uint32_t address, RV32IMemorySize size, const MemoryT& memory) {
if (isMisaligned(address, size)) {
return RV32IExecutionTrapCause::StoreAddressMisaligned;
}
if (!memory.canAccess(address, memorySizeBytes(size))) {
return RV32IExecutionTrapCause::StoreAccessFault;
}
return RV32IExecutionTrapCause::None;
}
void trap(RV32IState& state, RV32IInstructionTrace& trace, RV32IExecutionTrapCause cause) {
state.trapped = true;
state.trap_cause = cause;
trace.trapped = true;
trace.trap_cause = cause;
trace.pc_after = state.pc;
}
RV32IExecutionTrapCause mapControlTrap(RV32ITrapCause cause) {
switch (cause) {
case RV32ITrapCause::None: return RV32IExecutionTrapCause::None;
case RV32ITrapCause::IllegalInstruction: return RV32IExecutionTrapCause::IllegalInstruction;
case RV32ITrapCause::EnvironmentCall: return RV32IExecutionTrapCause::EnvironmentCall;
}
return RV32IExecutionTrapCause::IllegalInstruction;
}
uint32_t selectAluA(const RV32IControlSignals& control, const RV32IInstructionTrace& trace) {
switch (control.alu_a) {
case RV32IALUSourceA::RS1: return trace.rs1_value;
case RV32IALUSourceA::PC: return trace.pc_before;
case RV32IALUSourceA::Zero: return 0;
}
return 0;
}
uint32_t selectAluB(const RV32IControlSignals& control, const RV32IInstructionTrace& trace) {
switch (control.alu_b) {
case RV32IALUSourceB::RS2: return trace.rs2_value;
case RV32IALUSourceB::Immediate: return asUInt(trace.immediate);
case RV32IALUSourceB::Zero: return 0;
}
return 0;
}
uint32_t evaluateAlu(uint8_t op, uint32_t a, uint32_t b) {
const auto shamt = b & 0x1FU;
switch (op) {
case ALU32Op::ADD: return addWrap(a, b);
case ALU32Op::SUB: return a - b;
case ALU32Op::AND: return a & b;
case ALU32Op::OR: return a | b;
case ALU32Op::XOR: return a ^ b;
case ALU32Op::SLL: return a << shamt;
case ALU32Op::SRL: return a >> shamt;
case ALU32Op::SRA: return static_cast<uint32_t>(asInt(a) >> shamt);
case ALU32Op::SLT: return asInt(a) < asInt(b) ? 1U : 0U;
case ALU32Op::SLTU: return a < b ? 1U : 0U;
case ALU32Op::PASS_A: return a;
case ALU32Op::PASS_B: return b;
case ALU32Op::ZERO: return 0;
default: return 0;
}
}
bool branchTaken(RV32IBranchType branch, uint32_t rs1, uint32_t rs2) {
switch (branch) {
case RV32IBranchType::None: return false;
case RV32IBranchType::BEQ: return rs1 == rs2;
case RV32IBranchType::BNE: return rs1 != rs2;
case RV32IBranchType::BLT: return asInt(rs1) < asInt(rs2);
case RV32IBranchType::BGE: return asInt(rs1) >= asInt(rs2);
case RV32IBranchType::BLTU: return rs1 < rs2;
case RV32IBranchType::BGEU: return rs1 >= rs2;
}
return false;
}
template<typename MemoryT>
uint32_t readMemoryValue(MemoryT& memory, RV32IMemorySize size, bool sign_extend, uint32_t address) {
switch (size) {
case RV32IMemorySize::Byte: {
const auto value = memory.readU8(address);
return sign_extend ? signExtend(value, 8) : value;
}
case RV32IMemorySize::Halfword: {
const auto value = memory.readU16(address);
return sign_extend ? signExtend(value, 16) : value;
}
case RV32IMemorySize::Word:
return memory.readU32(address);
case RV32IMemorySize::None:
return 0;
}
return 0;
}
template<typename MemoryT>
void writeMemoryValue(MemoryT& memory, RV32IMemorySize size, uint32_t address, uint32_t value) {
switch (size) {
case RV32IMemorySize::Byte:
memory.writeU8(address, static_cast<uint8_t>(value & 0xFFU));
break;
case RV32IMemorySize::Halfword:
memory.writeU16(address, static_cast<uint16_t>(value & 0xFFFFU));
break;
case RV32IMemorySize::Word:
memory.writeU32(address, value);
break;
case RV32IMemorySize::None:
break;
}
}
uint32_t writebackValue(const RV32IControlSignals& control,
const RV32IInstructionTrace& trace,
uint32_t memory_value) {
switch (control.writeback) {
case RV32IWritebackSource::None: return 0;
case RV32IWritebackSource::ALU: return trace.alu_result;
case RV32IWritebackSource::Memory: return memory_value;
case RV32IWritebackSource::PCPlus4: return trace.pc_before + 4;
}
return 0;
}
class NonMutatingMemoryStorage {
public:
explicit NonMutatingMemoryStorage(MemoryStorage& memory)
: memory_(memory) {}
bool canAccess(uint32_t address, size_t count) const {
return memory_.canAccess(address, count);
}
uint8_t readU8(uint32_t address) const { return uint8_t(read(address, 1)); }
uint16_t readU16(uint32_t address) const { return uint16_t(read(address, 2)); }
uint32_t readU32(uint32_t address) const { return read(address, 4); }
void writeU8(uint32_t address, uint8_t value) {
(void)address;
(void)value;
}
void writeU16(uint32_t address, uint16_t value) {
(void)address;
(void)value;
}
void writeU32(uint32_t address, uint32_t value) {
(void)address;
(void)value;
}
private:
MemoryStorage& memory_;
uint32_t read(uint32_t address, size_t bytes) const {
const auto data = memory_.readBytes(address, bytes);
uint32_t value = 0;
for (size_t i = 0; i < bytes; ++i) value |= uint32_t(data[i]) << (8 * i);
return value;
}
};
template<typename InstructionMemoryT, typename DataMemoryT>
RV32IInstructionTrace stepImpl(RV32IState& state,
InstructionMemoryT& instruction_memory,
DataMemoryT& data_memory) {
state.forceX0();
RV32IInstructionTrace trace;
trace.instruction_index = state.instruction_count;
trace.pc_before = state.pc;
trace.pc_after = state.pc;
trace.halted = state.halted;
trace.trapped = state.trapped;
trace.trap_cause = state.trap_cause;
if (state.halted || state.trapped) {
return trace;
}
if ((state.pc & 0x3U) != 0) {
trap(state, trace, RV32IExecutionTrapCause::InstructionAddressMisaligned);
++state.instruction_count;
return trace;
}
if (!instruction_memory.canAccess(state.pc, 4)) {
trap(state, trace, RV32IExecutionTrapCause::InstructionAccessFault);
++state.instruction_count;
return trace;
}
trace.raw_instruction = instruction_memory.readU32(state.pc);
trace.decoded = RV32IDecoder::decode(trace.raw_instruction);
trace.control = RV32IControl::fromDecoded(trace.decoded);
trace.rs1 = trace.decoded.rs1;
trace.rs2 = trace.decoded.rs2;
trace.rd = trace.decoded.rd;
trace.rs1_value = state.readRegister(trace.rs1);
trace.rs2_value = state.readRegister(trace.rs2);
trace.immediate = trace.decoded.immediate;
trace.alu_a = selectAluA(trace.control, trace);
trace.alu_b = selectAluB(trace.control, trace);
trace.alu_result = evaluateAlu(trace.control.alu_op, trace.alu_a, trace.alu_b);
if (trace.control.trap) {
trap(state, trace, mapControlTrap(trace.control.trap_cause));
++state.instruction_count;
return trace;
}
if (trace.control.halt) {
state.halted = true;
trace.halted = true;
trace.pc_after = state.pc;
++state.instruction_count;
return trace;
}
uint32_t memory_value = 0;
if (trace.control.mem_read) {
trace.memory.kind = RV32IMemoryAccessKind::Read;
trace.memory.size = trace.control.mem_size;
trace.memory.sign_extend = trace.control.load_sign_extend;
trace.memory.address = trace.alu_result;
const auto cause = loadTrapCause(trace.memory.address, trace.memory.size, data_memory);
if (cause != RV32IExecutionTrapCause::None) {
trace.memory.fault = true;
trap(state, trace, cause);
++state.instruction_count;
return trace;
}
memory_value = readMemoryValue(data_memory, trace.memory.size, trace.memory.sign_extend, trace.memory.address);
trace.memory.read_data = memory_value;
}
if (trace.control.mem_write) {
trace.memory.kind = RV32IMemoryAccessKind::Write;
trace.memory.size = trace.control.mem_size;
trace.memory.address = trace.alu_result;
trace.memory.write_data = trace.rs2_value;
const auto cause = storeTrapCause(trace.memory.address, trace.memory.size, data_memory);
if (cause != RV32IExecutionTrapCause::None) {
trace.memory.fault = true;
trap(state, trace, cause);
++state.instruction_count;
return trace;
}
writeMemoryValue(data_memory, trace.memory.size, trace.memory.address, trace.rs2_value);
}
uint32_t next_pc = state.pc + 4;
bool control_transfer_taken = false;
if (trace.control.branch != RV32IBranchType::None) {
trace.branch_taken = branchTaken(trace.control.branch, trace.rs1_value, trace.rs2_value);
if (trace.branch_taken) {
next_pc = state.pc + asUInt(trace.immediate);
control_transfer_taken = true;
}
} else if (trace.control.jump == RV32IJumpType::JAL) {
next_pc = state.pc + asUInt(trace.immediate);
control_transfer_taken = true;
} else if (trace.control.jump == RV32IJumpType::JALR) {
next_pc = (trace.rs1_value + asUInt(trace.immediate)) & ~uint32_t{1};
control_transfer_taken = true;
}
// RV32I has IALIGN=32. A taken branch or jump to a non-word-aligned
// target raises the exception on the control-flow instruction itself.
// In particular, JAL/JALR must not write their link register on fault.
if (control_transfer_taken && (next_pc & 0x3U) != 0) {
trap(state, trace, RV32IExecutionTrapCause::InstructionAddressMisaligned);
++state.instruction_count;
return trace;
}
if (trace.control.reg_write) {
trace.writeback.enabled = true;
trace.writeback.rd = trace.rd;
trace.writeback.value = writebackValue(trace.control, trace, memory_value);
trace.writeback.ignored_x0 = trace.rd == 0;
state.writeRegister(trace.rd, trace.writeback.value);
}
state.pc = next_pc;
state.forceX0();
trace.pc_after = state.pc;
trace.halted = state.halted;
trace.trapped = state.trapped;
trace.trap_cause = state.trap_cause;
++state.instruction_count;
return trace;
}
}
uint32_t RV32IState::readRegister(uint8_t index) const {
return index == 0 ? 0 : x[index & 0x1FU];
}
void RV32IState::writeRegister(uint8_t index, uint32_t value) {
if ((index & 0x1FU) != 0) {
x[index & 0x1FU] = value;
}
forceX0();
}
void RV32IState::forceX0() {
x[0] = 0;
}
RV32IInstructionTrace RV32IInstructionOracle::step(RV32IState& state, RV32IFunctionalMemory& memory) {
return step(state, memory, memory);
}
RV32IInstructionTrace RV32IInstructionOracle::step(RV32IState& state,
RV32IFunctionalMemory& instruction_memory,
RV32IFunctionalMemory& data_memory) {
return stepImpl(state, instruction_memory, data_memory);
}
RV32IInstructionTrace RV32IInstructionOracle::stepWithoutDataMemoryWrite(
RV32IState& state,
MemoryStorage& instruction_memory,
MemoryStorage& data_memory
) {
NonMutatingMemoryStorage non_mutating_data_memory(data_memory);
NonMutatingMemoryStorage instructions(instruction_memory);
return stepImpl(state, instructions, non_mutating_data_memory);
}
RV32IInstructionRunResult RV32IInstructionOracle::run(RV32IState& state, RV32IFunctionalMemory& memory, size_t max_instructions) {
RV32IInstructionRunResult result;
while (!state.halted && !state.trapped && result.trace.size() < max_instructions) {
result.trace.push_back(step(state, memory));
}
if (!state.halted && !state.trapped && result.trace.size() >= max_instructions) {
state.trapped = true;
state.trap_cause = RV32IExecutionTrapCause::InstructionLimit;
result.instruction_limit_reached = true;
}
state.forceX0();
result.final_state = state;
return result;
}
std::string_view toString(RV32IExecutionTrapCause cause) {
switch (cause) {
case RV32IExecutionTrapCause::None: return "None";
case RV32IExecutionTrapCause::IllegalInstruction: return "IllegalInstruction";
case RV32IExecutionTrapCause::EnvironmentCall: return "EnvironmentCall";
case RV32IExecutionTrapCause::InstructionAddressMisaligned: return "InstructionAddressMisaligned";
case RV32IExecutionTrapCause::InstructionAccessFault: return "InstructionAccessFault";
case RV32IExecutionTrapCause::LoadAddressMisaligned: return "LoadAddressMisaligned";
case RV32IExecutionTrapCause::LoadAccessFault: return "LoadAccessFault";
case RV32IExecutionTrapCause::StoreAddressMisaligned: return "StoreAddressMisaligned";
case RV32IExecutionTrapCause::StoreAccessFault: return "StoreAccessFault";
case RV32IExecutionTrapCause::InstructionLimit: return "InstructionLimit";
}
return "None";
}
std::string_view toString(RV32IMemoryAccessKind kind) {
switch (kind) {
case RV32IMemoryAccessKind::None: return "None";
case RV32IMemoryAccessKind::Read: return "Read";
case RV32IMemoryAccessKind::Write: return "Write";
}
return "None";
}
}
Using it as an answer sheet¶
For a later core, initialize the same register/PC values and program/data images. After each completed instruction attempt, compare the architectural registers, next PC, successful memory effects, and halt/trap outcome with the reference. Compare at instruction boundaries, not at matching simulator timestamps.
The reference is a test oracle, not an infallible specification. Check discrepancies against the instruction rules and explicit expected examples. A behavioral adapter that calls this same oracle is not an independent implementation; agreement between the two alone does not validate the ISA.
Fixed programs supplies full expected results and embedded runs. This page defines the executable behavior; it does not choose the datapath, control wiring, or timing of the core that will implement it.
One-step examples for all 40 instructions¶
Each row is an independent attempt at P=0x00000040, with x6=A, x7=B, every other register zero, and separate instruction/data memory. The instruction word is loaded at P. Initially R32(0x100)=0x80FF7F01, so bytes at 0x100–0x103 are 01 7F FF 80; all other data bytes are zero.
The columns give the resulting x5, next PC, word at 0x100, and terminal status. All arithmetic/register-destination examples use rd=x5; stores and branches have no destination. A/B remain unchanged. Values are hexadecimal words. Run means neither halted nor trapped; Halt means the project's EBREAK stop; EnvironmentCall is a trap. These explicit expectations are executed by RV32IReferenceDocumentationTest, not inferred by copying the oracle output.
| Instruction | Word | A | B | Expected x5 | Expected PC | Expected memory[0x100] | Status |
|---|---|---|---|---|---|---|---|
ADD |
0x007302B3 |
0x7FFFFFFF |
0x00000001 |
0x80000000 |
0x00000044 |
0x80FF7F01 |
Run |
SUB |
0x407302B3 |
0x00000000 |
0x00000001 |
0xFFFFFFFF |
0x00000044 |
0x80FF7F01 |
Run |
SLL |
0x007312B3 |
0x00000001 |
0x00000021 |
0x00000002 |
0x00000044 |
0x80FF7F01 |
Run |
SLT |
0x007322B3 |
0xFFFFFFFF |
0x00000001 |
0x00000001 |
0x00000044 |
0x80FF7F01 |
Run |
SLTU |
0x007332B3 |
0xFFFFFFFF |
0x00000001 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
XOR |
0x007342B3 |
0x000000AA |
0x0000000F |
0x000000A5 |
0x00000044 |
0x80FF7F01 |
Run |
SRL |
0x007352B3 |
0x80000000 |
0x00000001 |
0x40000000 |
0x00000044 |
0x80FF7F01 |
Run |
SRA |
0x407352B3 |
0x80000000 |
0x00000001 |
0xC0000000 |
0x00000044 |
0x80FF7F01 |
Run |
OR |
0x007362B3 |
0x000000A0 |
0x0000000F |
0x000000AF |
0x00000044 |
0x80FF7F01 |
Run |
AND |
0x007372B3 |
0x000000AA |
0x0000000F |
0x0000000A |
0x00000044 |
0x80FF7F01 |
Run |
ADDI |
0xFFF30293 |
0x00000000 |
0x00000000 |
0xFFFFFFFF |
0x00000044 |
0x80FF7F01 |
Run |
SLTI |
0x00032293 |
0xFFFFFFFF |
0x00000000 |
0x00000001 |
0x00000044 |
0x80FF7F01 |
Run |
SLTIU |
0xFFF33293 |
0xFFFFFFFE |
0x00000000 |
0x00000001 |
0x00000044 |
0x80FF7F01 |
Run |
XORI |
0xFFF34293 |
0x12345678 |
0x00000000 |
0xEDCBA987 |
0x00000044 |
0x80FF7F01 |
Run |
ORI |
0x80036293 |
0x00000001 |
0x00000000 |
0xFFFFF801 |
0x00000044 |
0x80FF7F01 |
Run |
ANDI |
0x7FF37293 |
0x12345678 |
0x00000000 |
0x00000678 |
0x00000044 |
0x80FF7F01 |
Run |
SLLI |
0x01F31293 |
0x00000001 |
0x00000000 |
0x80000000 |
0x00000044 |
0x80FF7F01 |
Run |
SRLI |
0x01F35293 |
0x80000000 |
0x00000000 |
0x00000001 |
0x00000044 |
0x80FF7F01 |
Run |
SRAI |
0x41F35293 |
0x80000000 |
0x00000000 |
0xFFFFFFFF |
0x00000044 |
0x80FF7F01 |
Run |
LB |
0x00230283 |
0x00000100 |
0x00000000 |
0xFFFFFFFF |
0x00000044 |
0x80FF7F01 |
Run |
LH |
0x00231283 |
0x00000100 |
0x00000000 |
0xFFFF80FF |
0x00000044 |
0x80FF7F01 |
Run |
LW |
0x00032283 |
0x00000100 |
0x00000000 |
0x80FF7F01 |
0x00000044 |
0x80FF7F01 |
Run |
LBU |
0x00234283 |
0x00000100 |
0x00000000 |
0x000000FF |
0x00000044 |
0x80FF7F01 |
Run |
LHU |
0x00235283 |
0x00000100 |
0x00000000 |
0x000080FF |
0x00000044 |
0x80FF7F01 |
Run |
SB |
0x007300A3 |
0x00000100 |
0x000000AB |
0x00000000 |
0x00000044 |
0x80FFAB01 |
Run |
SH |
0x00731123 |
0x00000100 |
0x00001234 |
0x00000000 |
0x00000044 |
0x12347F01 |
Run |
SW |
0x00732023 |
0x00000100 |
0x12345678 |
0x00000000 |
0x00000044 |
0x12345678 |
Run |
BEQ |
0x00730463 |
0x00000005 |
0x00000005 |
0x00000000 |
0x00000048 |
0x80FF7F01 |
Run |
BNE |
0x00731463 |
0x00000005 |
0x00000005 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
BLT |
0x00734463 |
0xFFFFFFFF |
0x00000001 |
0x00000000 |
0x00000048 |
0x80FF7F01 |
Run |
BGE |
0x00735463 |
0xFFFFFFFF |
0x00000001 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
BLTU |
0x00736463 |
0xFFFFFFFF |
0x00000001 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
BGEU |
0x00737463 |
0xFFFFFFFF |
0x00000001 |
0x00000000 |
0x00000048 |
0x80FF7F01 |
Run |
JAL |
0x008002EF |
0x00000000 |
0x00000000 |
0x00000044 |
0x00000048 |
0x80FF7F01 |
Run |
JALR |
0x003302E7 |
0x00000046 |
0x00000000 |
0x00000044 |
0x00000048 |
0x80FF7F01 |
Run |
LUI |
0x123452B7 |
0x00000000 |
0x00000000 |
0x12345000 |
0x00000044 |
0x80FF7F01 |
Run |
AUIPC |
0x12345297 |
0x00000000 |
0x00000000 |
0x12345040 |
0x00000044 |
0x80FF7F01 |
Run |
FENCE |
0x0FF0000F |
0x00000000 |
0x00000000 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
ECALL |
0x00000073 |
0x00000000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
EnvironmentCall |
EBREAK |
0x00100073 |
0x00000000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
Halt |
The table is vertically and horizontally scrollable. These one-step semantic examples have no simulator timestamps: step() itself is not clocked. Each program page provides its own timestamped checkpoint table.
Boundary and exception answer sheet¶
These rows use the same independent initial state as the 40 examples above. Trap names replace Run/Halt in the status column. In particular, the last JALR row succeeds now; its next fetch would fail. A reserved-field FENCE is accepted, while an all-zero instruction is not.
| Instruction | Word | A | B | Expected x5 | Expected PC | Expected memory[0x100] | Status |
|---|---|---|---|---|---|---|---|
JAL |
0x002002EF |
0x00000000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
InstructionAddressMisaligned |
JALR |
0x000302E7 |
0x00000046 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
InstructionAddressMisaligned |
BEQ |
0x00730163 |
0x00000001 |
0x00000001 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
InstructionAddressMisaligned |
BNE |
0x00731163 |
0x00000001 |
0x00000001 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
LW |
0x00032283 |
0x00000101 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
LoadAddressMisaligned |
LW |
0x00032283 |
0x00040000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
LoadAccessFault |
SW |
0x00732023 |
0x00000102 |
0x11223344 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
StoreAddressMisaligned |
SW |
0x00732023 |
0x00040000 |
0x11223344 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
StoreAccessFault |
LB |
0x00030283 |
0x00040000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
LoadAccessFault |
ADDI |
0x00130013 |
0x00000005 |
0x00000000 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
LW |
0x00032003 |
0x00040000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
LoadAccessFault |
JALR |
0x000302E7 |
0x00040000 |
0x00000000 |
0x00000044 |
0x00040000 |
0x80FF7F01 |
Run |
FENCE |
0xFFF3028F |
0x00000001 |
0x00000002 |
0x00000000 |
0x00000044 |
0x80FF7F01 |
Run |
INVALID |
0x00000000 |
0x00000000 |
0x00000000 |
0x00000000 |
0x00000040 |
0x80FF7F01 |
IllegalInstruction |
Additional arithmetic examples¶
These preserved examples use independent initial register values x6=10, x7=3 and destination x5; PC starts at 0x40. Numbers without 0x are decimal. Each successful result advances PC to 0x44 and changes no memory. These examples are checked along with the per-instruction entries.
Ten register examples and nine immediate examples
| Instruction | Value written to rd |
Example and result |
|---|---|---|
ADD rd, rs1, rs2 |
First source plus second source | add x5, x6, x7 → x5=13 |
SUB rd, rs1, rs2 |
First source minus second source | sub x5, x6, x7 → x5=7 |
SLL rd, rs1, rs2 |
First source shifted left; zeros enter the low bits | sll x5, x6, x7 → x5=80 |
SLT rd, rs1, rs2 |
1 if first source is less than second, signed; otherwise 0 |
slt x5, x6, x7 → x5=0 |
SLTU rd, rs1, rs2 |
Same less-than test, unsigned | sltu x5, x6, x7 → x5=0 |
XOR rd, rs1, rs2 |
Bitwise exclusive OR of the sources | xor x5, x6, x7 → x5=9 |
SRL rd, rs1, rs2 |
First source shifted right; zeros enter the high bits | srl x5, x6, x7 → x5=1 |
SRA rd, rs1, rs2 |
First source shifted right; copies of its sign bit enter the high bits | sra x5, x6, x7 → x5=1 |
OR rd, rs1, rs2 |
Bitwise OR of the sources | or x5, x6, x7 → x5=11 |
AND rd, rs1, rs2 |
Bitwise AND of the sources | and x5, x6, x7 → x5=2 |
| Instruction | Value written to rd |
Example and result |
|---|---|---|
ADDI rd, rs1, imm12 |
Source plus immediate | addi x5, x6, -1 → x5=9 |
SLTI rd, rs1, imm12 |
1 if source is less than immediate, signed; otherwise 0 |
slti x5, x6, 11 → x5=1 |
SLTIU rd, rs1, imm12 |
Same less-than test, unsigned after sign extension | sltiu x5, x6, -1 → x5=1 |
XORI rd, rs1, imm12 |
Bitwise exclusive OR with the extended immediate | xori x5, x6, -1 → x5=0xFFFFFFF5 |
ORI rd, rs1, imm12 |
Bitwise OR with the extended immediate | ori x5, x6, 5 → x5=15 |
ANDI rd, rs1, imm12 |
Bitwise AND with the extended immediate | andi x5, x6, 7 → x5=2 |
SLLI rd, rs1, shamt |
Source shifted left by shamt; zeros enter low bits |
slli x5, x6, 3 → x5=80 |
SRLI rd, rs1, shamt |
Source shifted right by shamt; zeros enter high bits |
srli x5, x6, 1 → x5=5 |
SRAI rd, rs1, shamt |
Source shifted right by shamt; sign-bit copies enter high bits |
srai x5, x6, 1 → x5=5 |