SC32 Microarchitecture Specification
SC32 is a single-core 32-bit CPU architecture designed for deterministic low-level control, explicit hardware configuration, optional cache bypassing, and direct hardware access through a register-mapped programming model.
The architecture supports:
- single-core execution
- `int32` and `fp32`
- optional L1 cache disable
- L1 cache reuse as software-managed SRAM
- variable-length instructions
- little-endian memory layout
- direct I/O register access
- dedicated arithmetic hardware for expensive operations
- programmer-visible timing for asynchronous single-cycle math units
- optional auxiliary coprocessors for stack-oriented math and programmable I/O
- coprocessor running and interrupt-visible flags
This document is the current draft ISA and hardware programming model.
---
1. Design Goals
SC32 is intended for:
- bare-metal systems
- deterministic embedded workloads
- hardware-near programming
- systems where cache may need to be disabled
- designs where software controls timing explicitly
Core design principles:
- simple single-core execution
- no privilege separation
- direct hardware visibility
- low-overhead register-based control of peripherals
- explicit control over cache vs SRAM behavior
- allow programmer-managed timing for fast arithmetic units
---
2. Core Properties
- Single core
- 32-bit architecture
- Variable-length instruction encoding
- Little-endian
- No instruction alignment requirement
- `int32` integer arithmetic
- `fp32` floating point arithmetic
- Root-only execution model
- Interrupts jump to dedicated addresses
- L1 cache line alignment matches external RAM/cache line behavior
- Optional auxiliary coprocessors are controlled through normal flags and I/O registers
---
3. Numeric Model
3.1 Integer representation
All integer arithmetic uses 32-bit two's complement.
Examples:
- `0x7F = 127`
- `0x80 = -128`
- `0xFF = -1`
By extension:
- `0x7FFFFFFF = 2147483647`
- `0x80000000 = -2147483648`
- `0xFFFFFFFF = -1`
3.2 Floating point
Floating point operations use `fp32`.
Recommended interpretation:
- IEEE-754 single precision
A future revision can tighten exact FP exception and NaN handling.
---
4. Register Map
Architectural register space is divided into 3 ranges:
- `0-63`: general-purpose registers
- `64-127`: math and special compute registers
- `128-255`: I/O and control registers
---
5. General-Purpose Registers
5.1 Registers `0-63`
These are normal read/write registers used for:
- integers
- floating point values
- addresses
- temporaries
- arguments
- return values
Suggested convention:
- `r0-r47`: general use
- `r48-r55`: argument registers
- `r56-r59`: return value registers
- `r60`: stack pointer
- `r61`: frame pointer
- `r62`: link register
- `r63`: temporary/system use
This convention is software-defined and not mandatory.
---
6. I/O and Control Registers
6.1 Registers `128-255`
These registers are used for hardware and system control.
Typical functions include:
- GPIO
- I2C
- SPI
- UART
- PCI
- PCIe
- I2S
- interrupt mask
- interrupt reason
- interrupt destination address
- PLL multipliers
- RAM timing/delay control
- GPIO pull-up/down
- GPIO matrix configuration
- DDR/parallel RAM/PSRAM configuration
- cache and memory controller configuration
- coprocessor control/status
- coprocessor program/data ports
- PIO GPIO data/output-enable/pull/high-z routing
These registers are directly accessible through normal register move instructions.
6.2 Suggested coprocessor control space
Suggested control/state fields within the I/O register range include:
- coprocessor run/stop bits
- coprocessor interrupt-enable bits
- coprocessor interrupt-pending bits
- coprocessor fault/status words
- program counters and stack/FILO pointers for each coprocessor
- GPIO matrix selectors for PIO data, output-enable, pull-up, pull-down, and high-z paths
Exact register numbers remain open.
---
7. Math Register Bank
7.1 Purpose
Math registers are reserved for expensive, hardware-assisted, or multi-result arithmetic operations.
Trivial binary operations and integer addition are not mapped into the math register bank. They are handled by explicit ISA instructions.
Math registers are readable and writable through normal register move instructions, except output-only registers, which are read-only.
7.2 Writeability rule
- operand/source math registers: writable
- result/output math registers: read-only
- reading a result register returns the currently stored hardware output
- writing an output register is invalid and should either:
- do nothing, or
- raise an illegal instruction fault
Recommended behavior:
- illegal instruction fault
---
8. Math Register Map
The revised math register map removes integer add and simple bitwise ops from hardware register mapping.
8.1 Integer multiplication
- `64 = 65 * 66`
- asynchronous single-cycle multiply result
- `67 = 68 * 69`
- pipelined multiply result
8.2 Integer division
- `70 = quotient`, `71 = remainder`, from `72 / 73`
- asynchronous single-cycle divide
- `74 = quotient`, `75 = remainder`, from `76 / 77`
- pipelined divide
8.3 Floating point operations
Recommended use of remaining space:
- `78 = 79 + 80`
- fp add, asynchronous single-cycle
- `81 = 82 + 83`
- fp add, pipelined
- `84 = 85 * 86`
- fp multiply, asynchronous single-cycle
- `87 = 88 * 89`
- fp multiply, pipelined
- `90 = 91 / 92`
- fp divide, asynchronous single-cycle
- `93 = 94 / 95`
- fp divide, pipelined
- `96 = sqrt(97)`
- fp sqrt, asynchronous or multi-cycle
- `98 = sqrt(99)`
- fp sqrt, pipelined
8.4 Status and future extension
Reserve:
- `100-111`: math status / readiness / overflow / underflow / invalid / div-by-zero / pipeline ownership
- `112-127`: future extensions
Possible future additions:
- fused multiply-add
- integer multiply-high
- integer modulo
- fp compare
- fp convert
- vector or packed subword ops
---
9. Asynchronous Single-Cycle Math Semantics
9.1 Behavior
Some math operations are marked "single-cycle", but they are physically asynchronous from the programmer's point of view.
That means:
- the operation begins when operand registers are updated
- the output register may still hold the old value for some time
- at higher clock speeds, software must wait long enough for the hardware result to settle
- no automatic stall or interlock is required
9.2 Read semantics
Reading the result register before the operation settles returns the previous value.
This is intentional.
9.3 Programmer responsibility
The programmer is responsible for waiting the required number of cycles before reading a valid result from asynchronous single-cycle math registers.
This can be done with:
- `nop`
- unrelated instructions
- counter polling, if hardware exposes such state
- explicit delay routines
9.4 Pipelined unit behavior
Pipelined math units also return the previously completed value until the new result reaches the output stage.
They do not automatically stall the CPU.
---
10. Arithmetic Instructions
Since trivial binary ops and integer addition are no longer register-mapped, they are defined as normal ISA instructions.
10.1 Integer add
add rd, ra, rb
Semantics:
rd = ra + rb
10.2 Integer subtract
sub rd, ra, rb
Semantics:
rd = ra - rb
10.3 Bitwise not
not rd, ra
Semantics:
rd = ~ra
10.4 Bitwise or
or rd, ra, rb
Semantics:
rd = ra | rb
10.5 Bitwise and
and rd, ra, rb
Semantics:
rd = ra & rb
10.6 Bitwise xor
xor rd, ra, rb
Semantics:
rd = ra ^ rb
10.7 Rotate right
rotr rd, ra, rb
Semantics:
rd = rotate_right(ra, rb[4:0])
10.8 Rotate left
rotl rd, ra, rb
Semantics:
rd = rotate_left(ra, rb[4:0])
10.9 Logical shift left
shl rd, ra, rb
Semantics:
rd = ra << rb[4:0]
10.10 Arithmetic shift right
sar rd, ra, rb
Semantics:
rd = ra >>> rb[4:0]
10.11 Logical shift right
Recommended addition:
shr rd, ra, rb
Semantics:
rd = logical_right_shift(ra, rb[4:0])
10.12 Immediate forms
Recommended immediate variants:
addi rd, ra, imm
subi rd, ra, imm
andi rd, ra, imm
ori rd, ra, imm
xori rd, ra, imm
shli rd, ra, imm
shri rd, ra, imm
sari rd, ra, imm
rotli rd, ra, imm
rotri rd, ra, imm
---
11. Core Instruction Set
11.1 `nop`
No operation.
nop
---
11.2 `movr rd, rs`
Move one register to another.
movr rd, rs
Semantics:
rd = rs
Works for:
- general registers
- writable math operand registers
- I/O/control registers
Reading from math result registers and I/O registers is allowed.
Writing to math result registers is invalid.
---
11.3 `movd rd, rs`
Move two adjacent registers at once.
movd rd, rs
Semantics:
rd = rs
rd + 1 = rs + 1
Recommended constraint:
- `rd` and `rs` must be even-numbered registers
---
11.4 `movc rd, lc`
Read L1 SRAM entry into a register.
Valid only when L1 cache mode is disabled.
movc rd, lc
Semantics:
rd = L1[lc]
---
11.5 `movc lc, rs`
Write register value into L1 SRAM entry.
Valid only when L1 cache mode is disabled.
movc lc, rs
Semantics:
L1[lc] = rs
---
11.6 `rdc counter_num, rd`
Read hardware counter.
rdc counter_num, rd
Semantics:
rd = COUNTER[counter_num]
---
11.7 `rdf src, rd`
Read math flag or control flag.
rdf src, rd
Semantics:
rd = FLAG[src]
Suggested readable fields:
- math overflow
- math underflow
- divide-by-zero
- invalid op
- pipeline ready
- math pipeline ownership
- cache mode
- interrupt pending state
- coprocessor running state
- coprocessor interrupt pending state
- PIO FILO empty/full state
---
11.8 `stf flagnum, rs`
11.9 `stf flagnum, imm`
Set system/configuration flags.
stf flagnum, rs
stf flagnum, imm
Semantics:
FLAG[flagnum] = value
Use cases include:
- cache enable
- PLL multiplier
- RAM delay tuning
- GPIO pull mode
- GPIO matrix routing
- PSRAM/DDR setup
- interrupt enable/mask
- coprocessor start/halt
- coprocessor interrupt enable/ack
- coprocessor GPIO matrix selection
---
11.10 `mov rd, [ra]`
Load from memory.
mov rd, [ra]
Semantics:
rd = MEM32[ra]
No alignment is required.
---
11.11 `mov [ra], rs`
Store to memory.
mov [ra], rs
Semantics:
MEM32[ra] = rs
No alignment is required.
---
11.12 `jmp rs`
Indirect jump.
jmp rs
Semantics:
PC = rs
---
11.13 `call rs`
Indirect call.
call rs
Semantics:
r62 = PC_next
PC = rs
This spec uses `r62` as the link register.
---
11.14 `rtn`
Return from subroutine.
rtn
Semantics:
PC = r62
---
11.15 `push ra, rs`
Push/store with address register.
Current recommended addressed form:
push ra, rs
Semantics:
MEM32[ra] = rs
ra = ra + 4
This is an address-postincrement store, not an implicit stack-only instruction.
If stack semantics are desired, use `r60` as the stack pointer.
Example:
push r60, r5
---
11.16 `pop ra, rd`
Pop/load with address register.
pop ra, rd
Semantics:
rd = MEM32[ra]
ra = ra + 4
Example:
pop r60, r5
---
12. Suggested Missing Core Instructions
To make the ISA complete, these should exist.
12.1 Immediate move
movi rd, imm
12.2 Compare and test
cmp ra, rb
test ra, rb
12.3 Conditional branches
Because interrupts have dedicated addresses and the ISA is variable length, normal branches should still be present.
Suggested:
beq rs
bne rs
blt rs
bgt rs
ble rs
bge rs
Or relative forms:
beq imm
bne imm
Relative branch forms are strongly recommended for compact code.
12.4 Multiply/divide direct instructions
Even though heavy operations exist in math registers, direct instructions may still be useful:
mul rd, ra, rb
div rd, ra, rb
mod rd, ra, rb
These may map internally onto the same math hardware.
---
13. Cache Model
13.1 L1 modes
The L1 can operate in two modes.
Cache mode
- normal caching enabled
SRAM mode
- caching disabled
- L1 becomes software-managed SRAM
13.2 Control
L1 mode is controlled through flags.
13.3 SRAM access
When cache mode is disabled, `movc` can access the L1 contents directly.
13.4 Alignment relationship
Instructions themselves do not require alignment.
Cache organization is aligned to the external RAM/cache line structure.
---
14. Counters
Counters are accessed with `rdc`.
Recommended counters:
- cycle counter
- instruction retired counter
- load count
- store count
- branch count
- interrupt count
- cache hit count
- cache miss count
- stall count
- math issue count
- math completion count
- coprocessor instruction count
- coprocessor interrupt count
- PIO GPIO event count
- math arbitration wait count
---
15. Interrupt Model
15.1 Privilege model
There is only one privilege level:
- root / level 0 only
No user mode.
15.2 Interrupt entry
Interrupts use dedicated handler addresses.
This means each interrupt source, or each interrupt class, jumps to a predefined address rather than using a software-looked-up vector table.
15.3 Suggested interrupt behavior
On interrupt:
1. current PC is saved
2. control transfers to the interrupt's dedicated address
3. interrupt handler executes
4. handler returns with a dedicated interrupt return instruction
15.4 Recommended addition
Add:
iret
Semantics:
PC = saved_interrupt_return_pc
restore interrupt state
Using `rtn` for interrupts is possible but less clean than a distinct `iret`.
15.5 Coprocessor interrupts
Optional coprocessors may raise interrupt sources visible to the main CPU.
Recommended model:
- each coprocessor has its own running flag
- each coprocessor has its own interrupt-enable flag
- each coprocessor has its own interrupt-pending flag
- completion, explicit coprocessor `irq`, or coprocessor fault may set interrupt-pending
- host software clears or acknowledges coprocessor interrupts through normal flags or I/O registers
As with other interrupts in SC32, each enabled coprocessor interrupt should map to a dedicated handler address.
---
16. Memory Model
16.1 Endianness
SC32 is little-endian.
16.2 Access alignment
- instruction fetch: no alignment required
- data load/store: no alignment required
16.3 Memory access granularity
Current base form is 32-bit:
- `MEM32[address]`
A future revision should add byte and halfword loads/stores:
movb rd, [ra]
movh rd, [ra]
movb [ra], rs
movh [ra], rs
---
17. Flag Domains
Flags/configuration registers may cover:
- L1 cache enable
- PLL multipliers
- RAM port selection
- RAM delays
- GPIO pull-up/down
- GPIO matrix routing
- DDR configuration
- parallel RAM configuration
- PSRAM configuration
- memory size declaration
- interrupt enable/mask
- peripheral clock enable
- coprocessor running state
- coprocessor start/halt request
- coprocessor interrupt enable/pending/ack
- math pipeline ownership/availability for coprocessor issue
- PIO GPIO matrix routing for data/output-enable/pull-up/pull-down/high-z
---
18. Register Access Rules
18.1 General registers
- readable
- writable
18.2 Math operand registers
- readable
- writable
18.3 Math result registers
- readable
- not writable
18.4 I/O registers
- readable if implemented
- writable if implemented
Normal register move instructions can access general, math, and I/O registers.
---
19. Example Usage
19.1 Normal integer add
add r3, r1, r2
Meaning:
r3 = r1 + r2
19.2 Bitwise logic
and r4, r5, r6
xor r7, r8, r9
not r10, r11
19.3 Asynchronous multiply through math registers
movr 65, 1
movr 66, 2
nop
nop
movr 3, 64
Meaning:
- write multiplier operands
- wait for result to settle
- read multiply result from register `64`
If the wait is too short, reading `64` may return the previous multiply result.
19.4 Division with quotient and remainder
movr 72, 10
movr 73, 11
nop
nop
nop
movr 12, 70
movr 13, 71
19.5 Memory access
mov 5, [10]
mov [11], 5
Meaning:
- `r5 = MEM32[r10]`
- `MEM32[r11] = r5`
19.6 L1 as SRAM
stf L1cache_enable, 0
movc 0, 4
movc 5, 0
Meaning:
- disable caching
- write `r4` into L1 SRAM entry `0`
- read L1 SRAM entry `0` into `r5`
---
20. Revised Register Summary
20.1 General-purpose registers
- `0-63`: general-purpose
20.2 Math registers
Integer
- `64`: int mul single result
- `65-66`: int mul single operands
- `67`: int mul pipeline result
- `68-69`: int mul pipeline operands
- `70`: int div single quotient
- `71`: int div single remainder
- `72-73`: int div single operands
- `74`: int div pipeline quotient
- `75`: int div pipeline remainder
- `76-77`: int div pipeline operands
Floating point
- `78`: fp add single result
- `79-80`: fp add single operands
- `81`: fp add pipeline result
- `82-83`: fp add pipeline operands
- `84`: fp mul single result
- `85-86`: fp mul single operands
- `87`: fp mul pipeline result
- `88-89`: fp mul pipeline operands
- `90`: fp div single result
- `91-92`: fp div single operands
- `93`: fp div pipeline result
- `94-95`: fp div pipeline operands
- `96`: fp sqrt single result
- `97`: fp sqrt single operand
- `98`: fp sqrt pipeline result
- `99`: fp sqrt pipeline operand
Status / reserved
- `100-111`: math status / flags
- `112-127`: reserved
20.3 I/O registers
- `128-255`: I/O, interrupts, clocks, memory config, peripheral config, coprocessor control/status, GPIO matrix
---
21. Architecture Decisions So Far
These items are now decided:
1. Instruction encoding is variable length
2. Endianness is little-endian
3. No alignment required for instructions
4. Interrupts use dedicated addresses
5. Only root/level-0 execution exists
6. I/O registers are accessible through normal register moves
7. Math registers are accessible through normal register moves, except output registers are not writable
8. Reading a not-yet-settled math result returns the old value; software must delay explicitly
9. Optional coprocessors are controlled through normal flags and I/O registers
10. Each coprocessor exposes at least running and interrupt-pending state to software
---
22. Remaining Open Questions
Still to define:
1. exact binary encoding format
2. exact branch encoding style
3. exact immediate widths
4. illegal-write behavior for read-only result registers
5. exact counter list
6. exact interrupt save/restore state
7. cache line size
8. L1 SRAM addressing granularity
9. FP exception semantics
10. reset state and boot address
11. DMA and cache coherence rules
12. whether direct `mul/div` instructions exist architecturally or are assembler aliases for math-register use
13. exact coprocessor register allocation and reset values
14. exact RPN math coprocessor opcode table and stack bounds behavior
15. exact PIO opcode map, register count, and local interrupt frame format
16. exact arbitration timing when CPU and math coprocessor contend for pipelined math units
17. whether coprocessor program/data areas are always in normal memory or may use dedicated local RAM
---
23. Status
This is an active draft specification.
Next recommended steps:
1. define instruction encoding
2. finalize branch and compare instructions
3. define `iret`
4. define byte/halfword memory ops
5. define exact interrupt register layout
6. define cache control registers
7. define math status flags in detail
8. write an assembler syntax guide
9. define coprocessor control register layout
10. define the RPN math coprocessor opcode families and stack fault behavior
11. define the PIO opcode map and GPIO matrix bit planes
12. define CPU-priority rules for shared pipelined math units
---
24. Coprocessors
SC32 may include small auxiliary coprocessors that are controlled through the same flag and I/O register model as the main CPU.
24.1 Shared host-visible model
Recommended common model for any coprocessor:
- a `running` status bit
- a `start` or `run-enable` control bit
- an `interrupt-enable` bit
- an `interrupt-pending` bit
- an optional `fault` bit
- a small set of visible state registers such as program counter and stack pointer
Recommended host interaction rules:
- when `running = 1`, host software must treat execution-visible coprocessor state as owned by the coprocessor
- when `running = 0`, host software may inspect and modify coprocessor state through flags or I/O registers
- host software may only push or pop coprocessor side FIFOs/FILOs when the corresponding `running` flag is clear
- CPU writes take priority over coprocessor start or issue requests if both target the same shared resource in the same cycle
Exact signal timing is implementation-defined for now, but software-visible behavior should follow these rules.
24.2 RPN math coprocessor
This optional coprocessor is a small bytecode interpreter intended for chained scalar math such as polynomial evaluation, transcendental approximation, and other stack-oriented helper routines.
Recommended visible state:
- `PC_cp`: bytecode program counter
- `SP_cp`: stack pointer for 32-bit stack entries
- `CP_math_running`
- `CP_math_irq_enable`
- `CP_math_irq_pending`
- `CP_math_fault`
- optional stack/program limit registers
Execution model:
- the coprocessor fetches one opcode byte at a time from `PC_cp`
- some opcodes are 1 byte wide
- some opcodes include one or two inline 32-bit little-endian immediates
- stack entries are 32-bit and may represent `int32` or `fp32` depending on the opcode
- opcodes may pop values from the stack, push results, or both
- the opcode space is 256 commands wide
Suggested opcode classes include:
- `pushStack.i32 imm32`
- `pushStack.f32 imm32`
- `clone`
- `swap`
- `swapclone`
- `add.i32 imm, imm`
- `add.f32 imm, imm`
- `add.i32 stack, imm`
- `add.f32 stack, imm`
- `add.i32 stack, stack`
- `add.f32 stack, stack`
- `conv.i32_to_f32` for stack or immediate forms
- `conv.f32_to_i32` for stack or immediate forms
- `nop`
- `halt`
- `irq`
This unit is intended for routines such as `log`, `sin`, polynomial approximation, and other multi-step math helpers that are awkward to express as one-off core instructions.
24.2.1 Shared math pipeline usage
The RPN math coprocessor reuses the pipelined math units instead of duplicating large arithmetic hardware.
Recommended arbitration rule:
- the main CPU has priority on writes to pipelined math operand registers
- the coprocessor may issue only when the selected pipeline is ready and the main CPU is not writing that pipeline's operand registers
- if contention occurs, the coprocessor stalls and retries without corrupting CPU-visible math state
- ownership/ready state should be exposed through the math status/flag space
This applies to the pipelined integer and floating-point units described in Section 8.
24.2.2 Interrupt and fault behavior
The RPN math coprocessor should be able to set `CP_math_irq_pending` on:
- normal program completion, if enabled
- explicit `irq` opcode
- illegal opcode
- stack underflow or overflow
- shared math fault or unrecoverable conversion fault
24.3 PIO coprocessor
This optional coprocessor is a small deterministic GPIO sequencer intended for bit-banging, waveform generation, input sampling, and protocol glue logic.
Recommended visible state:
- `PC_pio`: PIO instruction program counter
- `FILO_pio_ptr`: pointer/top index for the PIO FILO
- a small local register file, recommended as 2-4 data registers
- `CP_pio_running`
- `CP_pio_irq_enable`
- `CP_pio_irq_pending`
- `CP_pio_filo_empty`
- `CP_pio_filo_full`
Execution model:
- each PIO instruction is 1 byte wide
- the opcode set is intentionally limited
- larger constants should be staged through local registers or host-visible control registers rather than variable-length instructions
- recommended operations include GPIO read/write, GPIO output-enable control, wait/delay, simple branch-on-pin or branch-on-flag, FILO push/pop, `irq`, and `halt`
24.3.1 GPIO matrix integration
The GPIO matrix should be able to route the following bit planes to or from the PIO coprocessor:
- GPIO input data
- GPIO output data
- GPIO output-enable
- GPIO pull-up control
- GPIO pull-down control
- GPIO high-z control
This allows the PIO to control not only pin values but also pin direction and biasing.
24.3.2 FILO ownership and interrupt data
The PIO FILO is bidirectional between the host CPU and the PIO coprocessor.
Recommended ownership rules:
- the host core may push or pop FILO data only when `CP_pio_running = 0`
- while `CP_pio_running = 1`, FILO ownership belongs to the PIO hardware and its interrupt/event logic
- on a PIO-local interrupt or selected GPIO event, hardware may push event metadata or return state into the FILO before branching to a local PIO handler
- the exact FILO interrupt frame format remains open
The PIO coprocessor may set `CP_pio_irq_pending` on completion, explicit `irq`, FILO overflow/underflow, or selected GPIO events.
24.4 Suggested named control fields
Exact register numbers are still open, but the following named fields are recommended:
- `CP_math_PC`
- `CP_math_SP`
- `CP_math_CTRL`
- `CP_math_STATUS`
- `CP_pio_PC`
- `CP_pio_FILO_PTR`
- `CP_pio_CTRL`
- `CP_pio_STATUS`
- `PIO_GPIO_IN_SEL`
- `PIO_GPIO_OUT_SEL`
- `PIO_GPIO_OE_SEL`
- `PIO_GPIO_PULLUP_SEL`
- `PIO_GPIO_PULLDOWN_SEL`
- `PIO_GPIO_HIZ_SEL`
---
25. License
Draft specification, license TBD.