Skip to content

§ 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_count once.
  • 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 sets InstructionLimit and instruction_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:

ADD x5=13 pc=0x40->0x44 halted=0 trapped=0
EBREAK x5=13 pc=0x44->0x44 halted=1 trapped=0
attempts=2

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