A clean, educational implementation of the RV32I base integer instruction set with machine-mode CSR support and trap handling. The design features a multi-cycle, non-pipelined architecture optimized for clarity, verifiability, and comprehensive testing.
- Architecture Overview
- RTL Module Hierarchy
- Control Flow and FSM
- Datapath Organization
- Test Infrastructure
- Build System
- Synthesis
- Key Interfaces
- Adding New Tests
- Performance Characteristics
- File Structure
- Architecture Type: Multi-cycle, non-pipelined processor
- Execution Model: FSM-based control with 7-12 cycles per instruction
- ISA Support: RV32I (32-bit RISC-V Integer Base)
- Design Goals: Educational clarity, formal verification readiness, comprehensive testing
- Non-Pipelined: Single instruction executes completely before next begins
- Multi-Cycle: Instructions broken into atomic FSM states (FETCH, DECODE, EXECUTE, etc.)
- Harvard-Style Memory Access: Single memory interface with time-multiplexed instruction/data access
- Shared Databus Architecture: Central 32-bit bus connects all major components
- PC Reset Address: 0x1000 (matching typical embedded systems)
RV32I Base Instructions:
- Arithmetic:
ADD,SUB,ADDI - Logical:
AND,OR,XOR,ANDI,ORI,XORI - Shifts:
SLL,SRL,SRA,SLLI,SRLI,SRAI - Comparison:
SLT,SLTU,SLTI,SLTIU - Load Upper:
LUI,AUIPC - Control Flow:
JAL,JALR,BEQ,BNE,BLT,BGE,BLTU,BGEU - Memory Access:
LW,LH,LB,LHU,LBU,SW,SH,SB - Memory Ordering:
FENCE,FENCE.I(architectural NOPs in single-core, no-cache design) - CSR Operations:
CSRRW,CSRRS,CSRRC,CSRRWI,CSRRSI,CSRRCI - Trap Instructions:
ECALL,EBREAK,MRET
CSR Registers:
- User-mode (read-only):
cycle,cycleh,time,timeh,instret,instreth - Machine-mode (read-write):
mtvec,mepc,mcause,mtval
Location: /rtl/core_top.sv
Purpose: Complete processor datapath and control integration
Major Components:
-
Program Registers (4 instances):
IR(Instruction Register) - stores current instructionPC(Program Counter) - initialized to 0x1000MAR(Memory Address Register) - holds memory access addressMDR(Memory Data Register) - buffers memory data
-
Register File (
regfile.sv):- 32 x 32-bit general-purpose registers
- Dual-read, single-write ports
- x0 hardwired to zero
-
Control FSM (
control.sv):- Central state machine
- Generates all control signals
-
ALU (
alu/alu.sv):- Arithmetic and logic operations
- Branch condition evaluation
-
Decoder (
control/decoder.sv):- Instruction field extraction
- Immediate generation
-
Byte Lane (
byte_lane.sv):- Sub-word memory access handling
- Byte enable generation
-
CSR File (
csr_file.sv):- Control and status registers
- Performance counters
-
CSR ALU (
csr_alu.sv):- Atomic read-modify-write logic
Memory Interface:
input [31:0] mem_rdata // Memory read data
input logic mem_resp // Memory response (ready)
output [31:0] mem_wdata // Memory write data
output [31:0] mem_addr // Memory address (word-aligned)
output logic mem_read // Memory read request
output logic mem_write // Memory write request
output [3:0] mem_be // Byte enablesLocation: /rtl/control.sv
FSM States (36 states total):
Instruction Fetch (4+ cycles):
FETCH_0: MAR <- PC, initiate read
FETCH_1: Wait for memory
FETCH_2: MDR <- memory
FETCH_3: IR <- MDR
Decode:
DECODE: Dispatch to execution state
Execution States:
REG_REG, REG_IMM: ALU operations
BRANCH_0, BRANCH_T: Branch evaluation
JAL_0, JAL_1: Jump and link
JALR_0, JALR_1: Jump register
LD_0..LD_4: Load (5 cycles)
ST_0..ST_3: Store (4 cycles)
LUI_0, AUIPC_0: Upper immediate
CSR_0, CSR_1: CSR access
FENCE_0: FENCE/FENCE.I (architectural NOP)
TRAP_ENTRY_0..4: Trap handling
MRET_0: Machine return
PC_INC: Increment PC
ERROR_*: Error states
Control Signal Generation:
- Load enables:
load_pc,load_ir,load_mar,load_mdr,load_reg - Memory:
mem_read,mem_write - Multiplexer selects:
databus_mux_sel,rs1_mux_sel,rs2_mux_sel,mdr_mux_sel - ALU operation:
alu_op - Memory access:
mem_size,load_unsigned - CSR and trap signals
Location: /rtl/alu/alu.sv
Operations:
ALU_ADD,ALU_SUBALU_AND,ALU_OR,ALU_XORALU_SLL,ALU_SRL,ALU_SRA(shift operations)ALU_SLT,ALU_SLTU(comparison with result 0/1)ALU_PASS_RS1,ALU_PASS_RS2(forLUI, etc.)
Branch Status Register (BSR):
output [2:0] bsr
bsr[2]: beq (a == b)
bsr[1]: blt (a < b signed)
bsr[0]: bltu (a < b unsigned)Location: /rtl/regfile.sv
Architecture:
- 32 x 32-bit registers
- Port A: Read port 1 (rs1)
- Port B: Read port 2 (rs2)
- Port C: Write port (rd)
- Combinational read, synchronous write
- x0 always reads as 0, writes ignored
Location: /rtl/byte_lane.sv
Load Path (memory → register):
- Extracts byte/halfword based on address[1:0]
- Sign-extends or zero-extends based on
load_unsigned - Supports
LB,LH,LW,LBU,LHU
Store Path (register → memory):
- Replicates byte/halfword to correct lanes
- Generates byte enables (mem_be)
- Supports
SB,SH,SW
Location: /rtl/csr_file.sv
User-Mode CSRs (read-only):
0xC00: cycle[31:0]0xC01: time[31:0]0xC02: instret[31:0]0xC80: cycleh[63:32]0xC81: timeh[63:32]0xC82: instreth[63:32]
Machine-Mode CSRs (read-write):
0x305: mtvec (trap vector, default 0x100)0x341: mepc (exception PC)0x342: mcause (trap cause)0x343: mtval (trap value)
Counter Logic:
cycleincrements every clockinstretincrements onload_pc(instruction completion)timemirrorscycle
Location: /rtl/csr_alu.sv
Operations:
CSRRW/CSRRWI: New value = rs1/zimmCSRRS/CSRRSI: New value = old | rs1/zimmCSRRC/CSRRCI: New value = old & ~rs1/zimm
Write Suppression:
- RW variants: Always write
- RS/RC variants: Suppress if rs1=x0 or zimm=0
Example 1: ADD rd, rs1, rs2 (R-type)
FETCH_0 (1 cycle): MAR <- PC, mem_read=1
FETCH_1 (N cycles): Wait for mem_resp
FETCH_2 (1 cycle): MDR <- mem_rdata
FETCH_3 (1 cycle): IR <- MDR
DECODE (1 cycle): Decode opcode, dispatch to REG_REG
REG_REG (1 cycle): RD <- ALU(RS1, RS2), goto PC_INC
PC_INC (1 cycle): PC <- PC + 4, goto FETCH_0
Total: ~7-10 cycles (depends on memory delay)
Example 2: LW rd, offset(rs1) (I-type load)
FETCH sequence (4+ cycles)
DECODE (1 cycle)
LD_0 (1 cycle): MAR <- RS1 + IMM
LD_1 (1 cycle): mem_read=1
LD_2 (N cycles): Wait for mem_resp
LD_3 (1 cycle): MDR <- mem_rdata
LD_4 (1 cycle): RD <- MDR (via byte_lane)
PC_INC (1 cycle)
Total: ~11-15 cycles
Example 3: BEQ rs1, rs2, offset (B-type)
FETCH sequence (4+ cycles)
DECODE (1 cycle)
BRANCH_0 (1 cycle): Evaluate rs1 == rs2
If true: BRANCH_T -> PC <- PC + IMM, goto FETCH
If false: PC_INC -> PC <- PC + 4, goto FETCH
Total: ~7-10 cycles
ECALL/EBREAK:
TRAP_ENTRY_0: mepc <- PC (save return address)
TRAP_ENTRY_1: mcause <- 11 (ECALL) or 3 (EBREAK)
TRAP_ENTRY_2: mtval <- 0
TRAP_ENTRY_3: Read mtvec CSR
TRAP_ENTRY_4: PC <- mtvec (jump to handler)
→ FETCH_0
MRET (Machine Return):
MRET_0: PC <- mepc (return from trap)
→ FETCH_0
Sources:
DATABUS_PC = 0: PC output
DATABUS_ALU = 1: ALU result
DATABUS_MDR = 2: MDR output
DATABUS_MAR = 3: MAR output (unused)
DATABUS_CSR = 4: CSR read data
Destinations:
- PC input (when
load_pc=1) - IR input (when
load_ir=1) - Register file write port (when
load_reg=1)
RS1 Multiplexer:
RS1_OUT = 0: rs1 register value
RS1_PC = 1: PC value (for AUIPC, branches)
RS1_2 = 2: Constant 2
RS1_4 = 3: Constant 4 (for PC+4 in JAL/JALR)
RS2 Multiplexer:
RS2_OUT = 0: rs2 register value
RS2_SEL = 1: Unused
RS2_IMM = 2: Immediate value
RS2_PC = 3: PC value
Address Alignment:
mem_addr = {mar_out[31:2], 2'b00} // Word-alignedWrite Data:
mem_wdata = store_data_aligned // From byte_laneByte Enables:
- Generated by byte_lane module
- Controls which bytes are written
- Verilator: RTL → C++ compilation and simulation
- Boost.Test: C++ testing framework
- CMake: Build system integration
- Docker: RISC-V toolchain containerization
- Module-Level Tests: Individual RTL module verification
- System-Level Tests: Complete programs on full core
Location: /simulation/include/test_runner.h
Key Methods:
TestRunner(const std::string &test_name, bool enable_trace);
bool load_program(const std::string &hex_file);
TestResult run(uint32_t max_cycles);
void reset();
void clock_cycle();
uint32_t get_pc() const;
uint32_t get_result() const;Clock Cycle Sequence:
- Rising edge:
- Memory eval BEFORE DUT (critical for edge detection)
- DUT eval with rising clock
- Trace dump
- Falling edge:
- Memory eval
- DUT eval with falling clock
- Trace dump
Location: /simulation/memory_model.h
Features:
- Configurable size (default 1MB)
- Configurable delay (default 4 cycles, matching hardware)
- FSM-based delay modeling
- Little-endian byte ordering
- Byte enable support
- Magic address region (0xDEAD0000-0xDEADFFFF) for test communication
FSM States:
IDLE: Waiting for request
WAIT_READ: Counting delay cycles for read
WAIT_WRITE: Counting delay cycles for write
DONE_READ: Asserting mem_resp with data
DONE_WRITE: Asserting mem_resp
Magic Address Protocol:
constexpr uint32_t MAGIC_RESULT_ADDR = 0xDEAD0000;
constexpr uint32_t MAGIC_PASS_VALUE = 0x00000001;
constexpr uint32_t MAGIC_FAIL_VALUE = 0xFFFFFFFF;Test Program Pattern:
# Perform test computation
...
# Write result to magic address
lui a7, 0xDEAD0 # Load magic address upper bits
sw a6, 0(a7) # Store computation result
# Indicate test pass
li a0, 1 # MAGIC_PASS_VALUE
sw a0, 0(a7) # Write pass indicator
# Infinite loop
LOOP:
j LOOPLocation: /simulation/tests/system_tests.cpp
Test Cases:
test_add_program: Basic arithmetictest_subtract_program: Subtractiontest_gcd_program: GCD algorithmtest_fibonacci_program: Fibonacci sequencetest_bitops_program: Bitwise operationstest_multiply_program: Software multiplicationtest_strlen_program: String lengthtest_memcpy_program: Memory copytest_bubble_sort_program: Bubble sort algorithmtest_factorial_program: Factorial computationtest_prime_program: Prime number testtest_byte_load_simple_program: Byte load verificationtest_halfword_program: Halfword access verificationtest_fence_basic_program: Basic FENCE instruction executiontest_fence_i_program: FENCE.I instruction executiontest_fence_ordering_program: Memory ordering verification
Boost.Test Integration:
BOOST_AUTO_TEST_SUITE(SystemLevelTests)
BOOST_AUTO_TEST_CASE(test_add_program) {
TestRunner runner("add", false);
BOOST_REQUIRE_MESSAGE(runner.load_program(get_test_program_path("add")),
"Failed to load add.ini");
TestResult result = runner.run(10000);
BOOST_CHECK_EQUAL(result, TestResult::PASS);
BOOST_CHECK_LT(runner.get_cycle_count(), 1000);
}
BOOST_AUTO_TEST_SUITE_END()Location: /simulation/CMakeLists.txt
Build Targets:
-
verilated_core (library):
- Verilates core_top.sv and dependencies
- Includes memory_model.cpp, test_utils.cpp, test_runner.cpp
- Generates Vcore_top C++ model
-
riscv_tests (executable):
- Links verilated_core
- Includes system_tests.cpp, csr_system_tests.cpp
- Integrated with CTest
-
Module test libraries:
- verilated_alu, verilated_decoder, etc.
- Each module separately verilated for unit testing
-
module_tests (executable):
- Links all module test libraries
- Includes all module/*.cpp test files
Verilator Configuration:
verilate(verilated_core COVERAGE TRACE
PREFIX Vcore_top
INCLUDE_DIRS ${RTL_ROOT}
VERILATOR_ARGS -f ./input.vc -O0 -x-assign 0
SOURCES ${RTL_SRC}
)Key Flags:
COVERAGE: Enable coverage analysisTRACE: Enable VCD waveform generation-O0: Disable optimization (for debugging)-x-assign 0: Initialize unknown values to 0
Script: /scripts/compile_tests.sh
Process:
- Build Docker image with RISC-V toolchain
- For each test directory:
- Run
compile.shinside Docker container - Generate .elf, .dump, and .ini files
- Run
Individual Test Compilation:
riscv64-unknown-elf-gcc \
-march=rv32i \
-mabi=ilp32 \
-nostdlib \
-ffreestanding \
-Tmemory_map.ld \
-o test.elf test.s
riscv64-unknown-elf-objdump -d test.elf > test.dump
hexdump -v -e '/1 "%02X "' test.elf > test.iniToolchain:
- Compiler: riscv64-unknown-elf-gcc
- Target: RV32I (32-bit integer)
- ABI: ilp32 (32-bit int, long, pointer)
- Flags: -nostdlib, -ffreestanding (bare-metal)
Location: /test/*/memory_map.ld
ENTRY(__start)
MEMORY {
rom (rx): ORIGIN = 0x00001000, LENGTH = 16M
ram (rw): ORIGIN = 0x10000000, LENGTH = 32M
}
SECTIONS {
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.bss : { *(.bss) } > ram
}Memory Map:
- Code starts at 0x1000 (matches PC reset value)
- ROM: 16MB at 0x1000 (instructions)
- RAM: 32MB at 0x10000000 (data)
# Compile test programs
cd /path/to/potato/riscv
./scripts/compile_tests.sh
# Build simulation
cd simulation/build
cmake ..
make
# Run all tests
ctest
# Run specific test suite
./riscv_tests --run_test=SystemLevelTests
./module_tests --run_test=ALUTests
# Run with verbose output
./riscv_tests --log_level=allThe design can be synthesized to AIGER format for formal verification and model checking.
cd synthesis
make allThis runs the complete synthesis flow (~2-3 minutes):
- Synthesis - Convert RTL to AIGER using Yosys
- Validation - Check AIGER file structure and statistics
- Equivalence Checking - Formal proof of RTL ≡ gate-level
- Reports - Generate synthesis summary
synthesis/build/output/
├── core_top.aag # ASCII AIGER with symbols (50-200 MB)
├── core_top.aig # Binary AIGER compressed (5-20 MB)
├── core_top_synth.v # Gate-level Verilog netlist
└── *.map # Symbol mappings
- Formal Verification - Model checking, property proving
- Equivalence Checking - Proving transformations preserve functionality
- Simulation - Using formal verification tools (ABC, nuXmv)
- Circuit Analysis - Structure analysis, optimization studies
See synthesis/README.md for detailed documentation, build targets, and integration guide.
Signals:
// From Core
output [31:0] mem_addr // Word-aligned address
output [31:0] mem_wdata // Write data
output logic mem_read // Read request
output logic mem_write // Write request
output [3:0] mem_be // Byte enables
// To Core
input [31:0] mem_rdata // Read data
input logic mem_resp // Response (transaction complete)Protocol:
Read:
- Core: Assert
mem_read, setmem_addr - Core: Wait in state until
mem_resp=1 - Memory: Return data on
mem_rdata, assertmem_resp - Core: Capture data, proceed
Write:
- Core: Assert
mem_write, setmem_addr,mem_wdata,mem_be - Core: Wait in state until
mem_resp=1 - Memory: Write data with byte enables, assert
mem_resp - Core: Proceed
Timing:
- Configurable delay (default 4 cycles)
mem_respasserted for 1 cycle in DONE_READ/DONE_WRITE states
Between control.sv and csr_file.sv:
// Control → CSR File
output logic [11:0] csr_addr // CSR address (from trap_csr_addr mux)
output [31:0] csr_wdata // Write data (from trap_csr_wdata mux)
output logic csr_we // Write enable (from trap_csr_we)
output logic csr_access // Access signal (high during CSR ops)
output logic instret_inc // Increment instret counter
// CSR File → Control
input [31:0] csr_rdata // Read data
input logic csr_valid // Address validCSR Address Multiplexing (in core_top.sv):
- Normal CSR ops: Use imm[11:0] from instruction
- Trap entry: Hardcoded mtvec (0x305), mepc (0x341), mcause (0x342), mtval (0x343)
- MRET: Hardcoded mepc (0x341)
Signals:
output logic trap_entry // High during trap sequence
output logic load_pc_from_csr // Load PC from CSR (mtvec/mepc)
output logic load_mepc // Write PC to mepc
output logic load_mcause // Write mcause
output logic load_mtval // Write mtval
output [31:0] mcause_val // Mcause value to writeStep 1: Create test program directory
cd /path/to/potato/riscv/test
mkdir my_test
cd my_testStep 2: Write assembly/C code
# my_test.s
.text
.global __start
__start:
# Your test logic here
li a0, 42
# Signal test completion
lui a7, 0xDEAD0
li a0, 1 # MAGIC_PASS_VALUE
sw a0, 0(a7)
LOOP:
j LOOPStep 3: Create compile.sh
#!/bin/bash
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib \
-ffreestanding -Tmemory_map.ld -o my_test.elf my_test.s
riscv64-unknown-elf-objdump -d my_test.elf > my_test.dump
hexdump -v -e '/1 "%02X "' my_test.elf > my_test.iniStep 4: Copy memory_map.ld from existing test
Step 5: Compile test program
cd /path/to/potato/riscv
./scripts/compile_tests.shStep 6: Add test case to system_tests.cpp
BOOST_AUTO_TEST_CASE(test_my_test_program) {
TestRunner runner("my_test", false);
std::string ini_file = get_test_program_path("my_test");
BOOST_REQUIRE_MESSAGE(runner.load_program(ini_file),
"Failed to load my_test.ini");
TestResult result = runner.run(10000); // Max cycles
BOOST_CHECK_EQUAL(result, TestResult::PASS);
BOOST_CHECK_LT(runner.get_cycle_count(), 1000); // Expected cycles
}Step 7: Rebuild and run tests
cd simulation/build
cmake ..
make
ctestStep 1: Create test file
// tests/module/my_module_test.cpp
#include <boost/test/unit_test.hpp>
#include "Vmy_module.h"
BOOST_AUTO_TEST_SUITE(MyModuleTests)
BOOST_AUTO_TEST_CASE(test_basic_operation) {
Vmy_module* dut = new Vmy_module();
dut->input_signal = 0x12345678;
dut->eval();
BOOST_CHECK_EQUAL(dut->output_signal, 0xExpected);
delete dut;
}
BOOST_AUTO_TEST_SUITE_END()Step 2: Add to CMakeLists.txt
# Add Verilated module library
add_library(verilated_my_module STATIC)
verilate(verilated_my_module COVERAGE TRACE
PREFIX Vmy_module
INCLUDE_DIRS ${RTL_ROOT}
VERILATOR_ARGS -O0
SOURCES ${RTL_ROOT}/my_module.sv ${RTL_ROOT}/datatypes.sv
)
# Add to module_tests
target_link_libraries(module_tests verilated_my_module)
add_executable(module_tests tests/module/my_module_test.cpp)Step 3: Rebuild and run
cd simulation/build
cmake ..
make module_tests
./module_testsEnable VCD Tracing:
TestRunner runner("my_test", true); // true = enable trace- Generates
trace/my_test.vcd - View with GTKWave:
gtkwave trace/my_test.vcd
Enable Verbose Logging:
./riscv_tests --log_level=allInspect Memory State:
runner.get_memory().dump_memory(0x1000, 0x1100);
uint32_t value = runner.get_memory().backdoor_read_word(0x1234);Typical Values (with 4-cycle memory delay):
- ALU instructions (ADD, XOR, etc.): ~7 cycles
- Load word (LW): ~11 cycles
- Store word (SW): ~10 cycles
- Branches (taken): ~7 cycles
- JAL/JALR: ~8 cycles
- CSR instructions: ~8 cycles
Memory Delay Impact:
- Each memory access adds configured delay
- Instruction fetch: Always requires memory read
- Data loads/stores: Add additional memory accesses
From actual test runs:
- add: <1,000 cycles
- fibonacci: <5,000 cycles
- gcd: <50,000 cycles
- bubble_sort: <20,000 cycles
- prime: <70,000 cycles
rtl/
├── core_top.sv # Top-level integration
├── control.sv # Control FSM
├── datatypes.sv # Type definitions
├── regfile.sv # Register file
├── program_register.sv # PC, IR, MAR, MDR
├── byte_lane.sv # Sub-word memory access
├── csr_file.sv # CSR registers
├── csr_alu.sv # CSR read-modify-write
├── mux4.sv, mux8.sv, mux2.sv # Multiplexers
├── alu/
│ └── alu.sv # Arithmetic logic unit
└── control/
├── decoder.sv # Instruction decoder
└── imm_gen.sv # Immediate generator
simulation/
├── CMakeLists.txt # Build configuration
├── memory_model.cpp/.h # C++ memory model
├── test_runner.cpp/.h # Test execution framework
├── test_utils.cpp/.h # Utility functions
├── include/ # Header files
└── tests/
├── test_main.cpp # Boost.Test main
├── system_tests.cpp # System-level tests
├── csr_system_tests.cpp # CSR-specific tests
└── module/ # Module-level tests
├── alu_test.cpp
├── decoder_test.cpp
├── imm_gen_test.cpp
├── byte_lane_test.cpp
├── csr_file_test.cpp
└── csr_alu_test.cpp
test/
├── add/
│ ├── add.s # Assembly source
│ ├── compile.sh # Compilation script
│ ├── memory_map.ld # Linker script
│ ├── add.elf # Compiled binary
│ ├── add.dump # Disassembly
│ └── add.ini # Hex format for loading
├── gcd/
├── fibonacci/
├── bubble_sort/
└── ... (multiple test programs)
Choice: Multi-cycle, non-pipelined
Rationale:
- Educational clarity: Easy to understand state-by-state execution
- Simplified verification: No pipeline hazards, forwarding, or stalls
- Formal methods friendly: Simpler invariants and proofs
- Hardware resource efficiency: Fewer duplicate functional units
Trade-off: Lower throughput (1 instruction per 7-12 cycles vs. ideal 1 per cycle)
Choice: Central databus connecting PC, IR, register file
Rationale:
- Reduced wiring complexity
- Clear data flow visualization
- Simplified control logic
- Educational value: Classic CPU architecture pattern
Trade-off: Limits concurrent operations (acceptable for multi-cycle design)
Choice: Explicit state machine with named states
Rationale:
- Highly readable and maintainable
- Easy to add new instructions or states
- Natural fit for multi-cycle execution
- Debugging: State names visible in waveforms
Alternative: Microcode would be more compact but less transparent
- No pipeline: Lower performance than pipelined designs
- No M extension: No hardware multiply/divide (software only)
- No interrupts: Only synchronous traps (ECALL/EBREAK)
- No privilege modes: Machine mode only, no user/supervisor
- Simple CSRs: Minimal CSR set, no performance monitoring counters beyond basics
Note on FENCE instructions: FENCE and FENCE.I are implemented as architectural NOPs. This is correct behavior for a single-core, non-pipelined, no-cache design where all memory operations are strictly ordered by the FSM.
