Skip to content

Architecture

Kevin Henderson edited this page Jul 1, 2026 · 5 revisions

Architecture

clearCore is organized into four independent libraries that share no circular dependencies. The core simulation code has zero knowledge of any UI framework.


Module overview

┌─────────────────────────────────────────────────────┐
│  nsc_ui (FTXUI terminal)  │  nsc_qt (Qt6 desktop)  │
└────────────┬──────────────┴──────────┬──────────────┘
             │                         │
             ▼                         ▼
┌─────────────────────────────────────────────────────┐
│                      mips_core                      │
│   IProcessor ◄── SingleCycleCpu / PipelinedCpu     │
│   Decoder · ALU · RegisterFile · Memory            │
└────────────────────────┬────────────────────────────┘
                         │
             ┌───────────┘
             ▼
┌─────────────────────────┐
│        nsc_core         │
│  Number system converter│
└─────────────────────────┘
Library Responsibility
nsc_core Real-time binary/hex/decimal conversion around a uint64_t value
mips_core CPU simulation: decoder, ALU, register file, memory, both CPU backends
nsc_ui FTXUI terminal interface — depends only on public mips_core headers
nsc_qt Qt6 widget layer — SimulatorController bridges CPU state to Qt signals

Rule: nsc_core and mips_core must never include UI headers. This boundary is enforced in CMakeLists.txt through target link dependencies.


Three design pillars

1. Separation of concerns

Each library is independently unit-testable. You can run the decoder, ALU, and both CPU backends without any terminal or window on screen. Adding a new UI (e.g. a web frontend) requires implementing nothing in the core.

2. Pluggable-backend pattern

class IProcessor {
public:
    virtual StepResult step() = 0;
    virtual void       load(std::vector<uint32_t> program) = 0;
    virtual void       reset() = 0;
    virtual PipelineState pipeline_state() const = 0;
    // ...
};

class SingleCycleCpu : public IProcessor { /* ... */ };
class PipelinedCpu   : public IProcessor { /* ... */ };

Both UIs hold an IProcessor*. Switching between single-cycle and pipelined mode at runtime is a pointer swap — neither UI changes. This pattern is inspired by Ripes and DrMIPS.

A backward-compat shim using Cpu = SingleCycleCpu keeps any code that referenced the old name compiling without changes.

3. Observable pipeline state

PipelineState is a plain struct that snapshots all five pipeline register contents, forwarding-path selections, and hazard flags in one place:

struct PipelineState {
    StageSnapshot if_id, id_ex, ex_mem, mem_wb, wb;
    bool          stall;
    bool          forward_ex_mem;
    bool          forward_mem_wb;
    bool          flush;
    // ...
};

Every visualizer — the TUI pipeline strip, the Qt6 Datapath tab, the trace grid — reads from PipelineState. There is no separate "visualization model"; the simulation state is the model.


Control signal derivation

A single derive_control() free function maps an opcode/funct pair to the full set of hardware control signals (RegWrite, MemRead, MemWrite, Branch, Jump, ALUSrc, ALUOp, etc.). Both SingleCycleCpu and PipelinedCpu call it. This prevents the two backends from drifting apart on control signal semantics.


mips_core internals

Component File(s) Role
Decoder include/mips/decoder.h 32-bit word → instruction fields + mnemonic
ALU include/mips/alu.h Arithmetic, logic, shifts, overflow flags
RegisterFile include/mips/register_file.h 32 × uint32_t, $zero hardwired
Memory include/mips/memory.h Byte-addressable, word/half/byte access, alignment checks
SingleCycleCpu src/mips/ Simulates all five stages in one step() call
PipelinedCpu src/mips/ Concurrent pipeline with five pipeline registers
HazardUnit (internal to PipelinedCpu) Load-use stall detection, branch/jump flush
ForwardingUnit (internal to PipelinedCpu) EX/MEM → EX and MEM/WB → EX forwarding paths

Qt6 bridge (nsc_qt)

The Qt6 layer adds a SimulatorController object that:

  1. Runs the CPU in a background QThread
  2. Emits pipelineStateChanged(PipelineState), registersChanged(...), etc. as Qt signals
  3. Qt's event loop automatically marshals those signals to the main (UI) thread

This means widget code never touches the CPU directly and never needs a mutex. See Qt6 GUI for the full breakdown.


Academic foundations

Pattern Source
Pluggable-backend IProcessor Ripes (Petersen, 2021)
Observable PipelineState WebRISC-V (Mariotti & Giorgi, 2022); Arches (Haydel et al., 2025)
Single-cycle datapath design Harris & Harris, Digital Design and Computer Architecture
Pipeline hazard handling Patterson & Hennessy, Computer Organization and Design
TUI-first educational focus Unique to clearCore

Clone this wiki locally