Philosophy of the QCPU 2 architecture
← Home
created: 2026-08-16
last change: 2026-09-19
In 2020, the QCPU 1 microprocessor model was created in Minecraft. It is featured in the Open Redstone organisation. It was a stepping stone to QCPU 2, which is being made on silicon, for power-efficient microcontrollers. It aims to be an extensible open standard for advanced microcontrollers and alike systems with native support for privilege modes and virtual memory.

The QCPU 2 microprocessor is a 16 bit RISC architecture with fixed-length instructions, inspired by RISC-V as well as few features of the Motorola 68000 design philosophy. It features a classic five stage pipeline. Each instruction strictly uses the ALU and/or memory system once to reduce complexity and to run on deterministic, 16 bit hardware. Structurally, the CPU consists of:

  • 8 Data Registers (DRs) (including zero register)
  • 8 Address Registers (ARs) (including zero register)
  • Control & Status Registers (CSRs), including:
    • System information (e.g. hart)
    • System control (e.g. features, clock time base)
    • Flag register
    • Interrupt control
    • Virtual memory control
  • Branch Target Buffer (BTB)
  • Translation Look-Aside Buffer (TLB)
  • Arithmetic Logic Unit (ALU)
  • Barrel Shifter (BS)
  • Address Register comparators (i.e. over-/underflow exceptions, stack boundaries)
  • Asynchronous memory instructions (i.e. scoreboarding)
  • Atomic memory instructions
  • Timer

This blog post aims to explain the architecture and the rationale behind certain design decisions. The QCPU 2 architecture is documented on its own web page. Please note that the QCPU 2 architecture is still being researched, designed, and developed, and may still change over time.

Programming model

Fundamentally, the programming model of the QCPU 2 architecture consists of three typed register domains:

  • Data Registers
  • Address Registers
  • Machine-State Registers (CSRs)

Data Registers are fully general-purpose, except for the return pointer. Address Registers are partially special-purpose (i.e. the instruction pointer) and contain reserved purposes as well (i.e. the frame pointer). Data can be moved freely between these two register domains, but they support different operations and adopt different behaviours. Control & Status Registers cannot be addressed in normal operation and are limited to kernel mode.

Domain Register Identifier
Data Register r0 zr (zero)
Data Register/Async r1 rp (return ptr)
Data Register/Async r2 d1
Data Register/Async r3 d2
Data Register/Async r4 d3
Data Register/Async r5 d4
Data Register/Async r6 d5
Data Register/Async r7 d6
Address Register r8 zr (zero)
Address Register r9 ip (instruction ptr)
Address Register r10 sp (stack ptr)
Address Register r11 fp (frame ptr)
Address Register r12 a1
Address Register r13 a2
Address Register r14 a3
Address Register r15 a4

QCPU 2 deliberately divides the computational data domain with the computational address domain to provide a couple of benefits. Firstly, a second set of general-purpose registers increases the total system registers whilst keeping the operand bit size the same for operations on DRs. For a subset of operations, ARs may be used. Data can be transferred freely between DRs and ARs. Secondly, with the introduction of ARs, the CPU contextualises the address domain. Special-purpose registers of the CPU are collapsed into the ARs where primitive address arithmetic of the ISA can be used rather than special control flow introduced into the ISA. QCPU 2 has a striking absence of these special instructions. For example, for the instruction pointer ip, a relative jump is performed with an add-immediate addi ip, imm instruction on the instruction pointer. Once addresses have their own register domain, architectural features may differ from operations on DRs and include special semantics. For instance, operations which overflow ARs may emit hardware exceptions, which is not always desirable for DRs due to the lack of informational context by the CPU in traditional programming models.

ARs are particularly useful within subroutines where memory needs to be accessed:

        
function:
            movew -(sp), d1           ; predecrement word stride 2
            movew -(sp), d2           ; predecrement word stride 2
            move fp, sp               ; pseudoinstruction for `add fp, zr, sp`
            addi sp, -16              ; allocate 16 bytes on stack
       
            move sp, fp               ; pseudoinstruction for `add sp, zr, fp`
            movew d2, (sp)+           ; postincrement word stride 2
            movew d1, (sp)+           ; postincrement word stride 2
            ret                       ; pseudoinstruction for `add ip, zr, rp`
        
        

That being said, all ARs have access to the memory addressing modes as with the stack pointer sp. To do a simple memory move, two increment instructions are saved:

        
mem_move:
            movew -(sp), d1

.loop:      move d1, (a1)+            ; move (a1) to d1
            move (a2)+, d1            ; move d1 to (a2)
            dec d2                    ; pseudoinstruction for `addi d2, -1`
            brh nz, .loop             ; loop if not zero

            movew d1, (sp)+
            ret
        
        

The zero register is a small but powerful feature in the architecture. Firstly, it enables simple primitive operations to be done for multiple motives due to a statically-known source operand. For example, register-to-register move operations are pseudoinstructions for addition with zero. Similarly, the set-one-if-negative sneg pseudoinstruction is composed of the set-one-if-less-than slt instruction with zero. Secondly, results of operations may be discarded by writing into the zero register, having no architectural effect other than the flag register or other side effects, such as cache behaviour. This is most evident in the prefetch data prfd pseudoinstruction, which is composed of a memory move into the zero register.

Generally, QCPU 2's design philosophy prioritises simple and reusable primitives rather than specialised instructions. This has been discussed regarding the instruction pointer. However, this can also be seen with the stack pointer sp. The ISA does not implement push, pop or peek instructions, as the CPU itself mostly disregards the stack as an architectural feature (except for the CSR-configurable low/high boundaries of the stack pointer AR). Instead, memory move primitives are used. These move instructions contain various addressing modes such as predecrement (push), postincrement (pop), and static offsets (peek). This allows these instructions to be reused for generic memory operations and arrays as well.

CSRs and MMIO in SMP

An undiscussed register domain are the Machine-State Registers. These CSRs reflect the internal state and configuration of the CPU. The flag register, for example, can be read from and written to as a CSR for use in interrupt context switches. Additionally, features of the CPU can be enabled or disabled. These features are:

  • Kernel protection active
  • Branch Target Buffer active
  • Asynchronous memory active (as opposed to fully synchronous)
  • Interrupts

Features such as the BTB consume more energy, and it may be preferred to substitute it for a loss in performance. As a last example, the clock-time-base ctb CSR allows the CPU to determine its own clock speed.

In a Symmetric Multiprocessing (SMP) system, each CPU has its own CSRs. Each CPU is configured individually through these CSRs. Memory is used for CPUs to communicate with each other. In the same manner, access to the same devices in an SMP system is done through Memory-Mapped I/O (MMIO), which uses the same memory move primitives as seen before. The ISA adds instructions such as fence and atomic exchange xch to allow synchronisation between these CPUs.

To synchronise CPUs with a lock, a mutex can be implemented using xch:

        
            lua a1, .mutex_ptr'u
            lli d1, 1
.try_again: xch d1, .mutex_ptr'l(a1)  ; atomically swap 1 into mutex
            test d1                   ; if 0 is returned, the mutex is now ours
            brh nz, .try_again

            ; critical section...

            xch zr, .mutex_ptr'l(a0)  ; unlock mutex
        
        

For interrupts, they can be configured on both a local and global level. Individual CPUs can enable or disable the reception of certain interrupts. The I/O-mapped global interrupter routes platform-level interrupts to available CPUs. However, local interrupts such as the CPU timer, address faults, and memory faults are not managed by the global interrupter. These are instead enabled and configured by CSRs, such as the CPU timer:

        
            lli d1, 16
            csrw itb, d1              ; interrupt time base = default / 16
            lli d1, 0b0000_0001
            csrw ien, d1              ; enable timer interrupt
        
        

Privilege levels

Perhaps the most distinct architectural feature of the QCPU 2 microprocessor is its approach on privilege levels.

The CPU does not hold the privilege level as an architectural state. Instead, the location of the instruction pointer determines the effective privilege level. In virtual memory, a kernel region is created above an address watermark. Entering this kernel region from userland emits a hardware exception. It is only possible to enter predefined locations in the kernel region through interrupts and system calls. The effective privilege level is implicitly demoted once the instruction pointer leaves the kernel region.

The discussed design provides one main advantage in safety. Memory ownership determines the effective privilege level of the CPU. This reduces the attack surface of diverted control flow from within kernel mode to unauthorised text sections in userland.