A 1986 alternate-history CPU architecture, designed in detail and implemented enough to run.
Object RISC is a single-issue 32-bit load-store RISC in the style of its era — MIPS R2000, SPARC v1, the family — with two non-conventional commitments:
- A separate object register file. Sixteen 64-bit object registers hold references — opaque, capability-bearing pointers to objects in a global namespace. Integer registers cannot be dereferenced as objects, and objects cannot be smuggled through integer memory. The capability invariant is enforced statically at the ISA level.
- Crossbar interconnect as a primitive. A
SENDinstruction ships a message across an N-port crossbar to another processor's service object, where firmware dispatches it as a fresh task. Multiprocessor isn't an afterthought; it's in the ISA.
This repository contains the architecture documentation, an assembler, a simulator, a wire-level crossbar daemon, a graphical terminal device, and a validation suite.
All under docs/:
| Volume | File | Subject |
|---|---|---|
| I | docs/OVERVIEW.md |
Architectural pitch and shape |
| II | docs/INSTRUCTION_SET.md |
The ISA |
| III | docs/OBJECT_SYSTEM.md |
References, descriptors, capabilities |
| IV | docs/INTERCONNECT_PROTOCOL.md |
Crossbar wire format |
| V | docs/REFERENCE_IMPLEMENTATION.md |
OR-1000 / OR-XBAR-1 reference designs |
| VI | docs/SYSTEM_FIRMWARE_INTERFACE.md |
Firmware primitive ABI |
| VII | docs/PROGRAMMING_PRACTICE.md |
ABI, idioms, worked example |
docs/CONTRACT.md pins what the architecture
spec leaves to the integrator: the .orx binary format, the
loader's initial task state, and the host-side semantics of the
firmware primitives the simulator implements directly.
docs/HISTORY.md traces the project's evolution
— three passes on Volume I, the Apollo correction, the toolchain's
contract-first dispatch, the validation suite, multi-CPU, the
wire-level crossbar, multi-process, the manual revision, and the
phases of Ouroboros — the OS layer growing on top: privilege
modes, trap delivery, tasks/scheduler, supervisor handlers, timer
interrupts, the C-level task API, the shell becoming a real
supervisor that spawns guests as child tasks on its own CPU,
focus-switchable interactive guests (the editor), and program
arguments + cwd passing.
Combined PDFs:
docs/ObjectRISC.pdf (Computer Modern,
72 pp) and
docs/ObjectRISC-Palatino.pdf
(TeX Gyre Pagella, 79 pp).
The asm / sim / devices / launcher tools are pure Python 3.10+,
standard library only — no build step. The C compiler is a
vendored pcc and needs ./configure && make once (see
tools/cc/arch/orisc/README.md).
| Tool | Purpose |
|---|---|
tools/asm/asmorisc |
Assembler — .s → .orx, or .s → .oro with -r |
tools/ld/orld |
Linker — .oro and .ora inputs → .orx |
tools/ld/oar |
Archiver — bundle .oro files into .ora archives |
tools/sim/simorisc |
Simulator — runs .orx binaries, single or multi-CPU |
tools/sim/oriscbar |
Standalone wire-level crossbar daemon |
tools/devices/oriscterm |
Tk-based terminal device that connects to oriscbar |
tools/devices/linkbootd |
Python-side link-boot server |
tools/devices/hostfsd |
Host-filesystem server (open/read/write/close, optional jail) |
tools/oriscrun |
Launcher: spawns crossbar + devices + CPU processes |
tools/cc |
Vendored pcc with an Object RISC backend (arch/orisc/) |
tools/cc/lib |
C library (liborisc.ora) — console I/O, string/memory, hostfs, terminal, tasks (task.c), .orx loader (orx.c) |
132 tests across fourteen categories — integer, logical, memory, control flow, object registers, object memory, firmware, traps (including privileged-instruction enforcement and supervisor-mode handler delivery), CALL (including EPERM gating), golden programs, multi-CPU (with link boot), loadable modules, receive queues, and tasks (TaskCreate / TaskWait / TaskYield / timer preemption):
python3 tools/sim/tests/validation/runner.pyPlus 12 device/shell integration tests under
tools/devices/tests/ — the shell, hostfsd,
oriscterm, the multi-task and concurrent-children libc demos, and
shell-as-supervisor running guest .orx files end-to-end.
Most of the toolchain is pure Python — no build step. The C
compiler is the exception: a vendored pcc that needs ./configure && make once. Bootstrap script handles it:
tools/cc/build.sh # produces /tmp/pcc-build/cc/{cpp,ccom}/orisc-*Then make builds liborisc, the shell, and every program in
ouroboros/programs/ — and make boot drops you into Ouroboros
in a Tk window:
make # build everything (incremental)
make boot # build + run Ouroboros
make clean # nuke build/After pcc is bootstrapped, examples/cc/run_c.sh and the
tools/devices/tests/ scripts find the binaries automatically.
Override the location with PCC_BUILD=/path/to/build if /tmp
isn't where you want it.
Hello world (assembly, doesn't need pcc):
tools/asm/asmorisc tools/asm/examples/hello.s -o /tmp/hello.orx
tools/sim/simorisc /tmp/hello.orx
# Hello, world!Two CPUs talking over the crossbar (single-process):
tools/sim/simorisc --processors 2 --trace ping-pong.orxHello world on a graphical terminal connected to a real wire-format crossbar in its own process:
examples/run_hello_terminal.shA generic link-boot loader: CPU 0 (master) ships an 8-instruction
module to a CPU running a content-free linkboot.orx, which
discovers the master via an announce SEND, copies the module
across the wire, maps it executable, and JRs in:
examples/linkboot/run.sh
# [xbar] oriscbar READY ...
# [cpu1] Booted!See examples/linkboot/README.md
for the announce/boot protocol, the unrolled-OLW copy strategy,
and the limits.
C compiled by our in-tree pcc port. Build pcc once (cd tools/cc && ./configure --target=orisc-unknown-none && make into a build dir
of your choice — /tmp/pcc-build is the default), then run any
.c through the pipeline:
examples/cc/run_c.sh examples/cc/hello.c # Hello, world!
examples/cc/run_c.sh examples/cc/factab.c # 1!..7! table
examples/cc/run_c.sh examples/cc/primes.c # primes < 50
examples/cc/run_c.sh examples/cc/fizzbuzz.c # 1..20 fizzbuzz
examples/cc/run_c.sh examples/cc/pascal.c # Pascal's triangle
examples/cc/run_c.sh examples/cc/hello_or.c # __or in C
examples/cc/run_c.sh examples/cc/print_or.c # __or + inline asm
examples/cc/run_c.sh examples/cc/inspect.c # OEQ/OISN/OLEN/OTAG/OHOME/OCAP
examples/cc/run_c.sh examples/cc/print_clean.c # __or via calling convention
examples/cc/run_c.sh examples/cc/print_via_or_arg.c # callee takes __or arg
examples/cc/run_c.sh examples/cc/or_callee_inspect.c # OISN/OLEN inside __or callee
examples/cc/run_c.sh examples/cc/strings_demo.c # exercises liborisc string fnsThree interactive C demos that use oriscterm's keyboard, mouse, and graphics capabilities — each opens a Tk window:
examples/cc/run_kbd_echo.sh # echoes every keystroke (codepoint + mods)
examples/cc/run_paint.sh # arrow + letter keys → vector drawing on canvas
examples/cc/run_mouse_paint.sh # mouse: click drops dot, drag draws strokeA demo that reads an actual host file via hostfsd and prints
it to the simulated console:
examples/cc/run_host_cat.sh # cat README.md from inside an emulated CPUBuilt on top of the architecture, phases 24–30 of HISTORY
record the growth of an in-tree OS layer named Ouroboros —
named (after a brief stop at "OROS" = Object RISC OS) for the
snake-eating-its-tail symbolism: the OS runs on the architecture
whose toolchain self-hosts; traps return where they came from
via ERET; and the VM/CMS analogy that motivates the design
(CP = firmware, CMS = Ouroboros) is itself a recursive shape.
What's wired so far:
- Privilege modes (Vol II §13): user / supervisor / firmware,
enforced at decode for
LCTRL/SCTRL/ERET/WAIT/ TLB ops, and atCALLdispatch via per-primitive minimum-mode gating. - Trap delivery (Vol II §14.1): on a trap the CPU populates
EPC/Cause/BadVAddr, saves the current mode intoStatus, switches to firmware, and jumps toVECBASE + offset[cause].ERETreverses it. A supervisor-installable handler primitive (InstallTrapHandler, #0x520) bypasses the firmware-onlyVECBASEfor guest OSes. - Tasks & scheduler (Vol VI §4):
Taskis a first-class swappable execution context with its own register file, PC, privilege state, and address-space mappings.TaskCreate/TaskExit/TaskResume/TaskSuspend/TaskYield/TaskCurrent/TaskWaitare wired; cooperative round-robin scheduler picks the next runnable on every yield/exit. - Per-task address spaces. pcc-generated code that assumes
the flat CONTRACT.md §2 layout (
CODE_VA/DATA_VA/STACK_TOP) Just Works in spawned children — they each get a fresh layout pointing at their own code/data/stack objects. - Timer interrupt path.
STATUS.IE+COMPAREraisesexternal-interrupt(cause 0x01) when the cycle counter crosses the compare value;deliver_trapauto-clearsIEso handlers don't re-fire mid-handler. - C libc API.
tools/cc/lib/task.cexposes a multi-childtask_thandle abstraction over an OREF-backed task table.tools/cc/lib/orx.cloads a.orxfrom disk viahostfsd, sets up code/data/stack objects, andTaskCreates it as a child task — that's how the shell'sruncommand works under Phase 30.
The MVP shell is no longer just an interactive front-end — it's the
supervisor. cmd_run calls orx_run which loads the .orx
itself, allocates code/data/stack objects, and TaskCreates the
guest as a child task on the shell's own CPU. No separate
spawn-server, no spare-CPU pool, no chunked-boot trip across the
crossbar — just a parent task forking a child.
Commands: help / cat / more / ls / cd / pwd / echo /
run / cycles / time / exit. Command history (up/down
arrow recall), backspace with visual undo, --More-- paginator
on more and help, paths resolved against the shell's cwd
(prompt mirrors it: /sub>). The build-date banner is shifted
back 40 years (alternate-history conceit).
Launch:
make boot # build + start Ouroboros in a Tk windowmake builds the shell into build/shell.orx and every program
in ouroboros/programs/ into
build/programs/*.orx. make boot symlinks build/programs/
into the hostfsd jail at /programs/, so they're immediately
runnable from inside the shell:
/> ls /programs
count.orx
dhry.orx
edit.orx
exit42.orx
hello.orx
hello_term.orx
/> run /programs/hello.orx
[exited 0]
/> run /programs/exit42.orx
[exited 42]
/> run /programs/edit.orx hello.c
... (full-screen editor opens; F1 to return focus, ^X to quit)
hello / count / exit42 use print_str / print_int, which
go through firmware ConsoleWrite to host stdout (the terminal
where you launched make boot, not the Tk oriscterm window).
The [exited N] line is what the shell prints back to the Tk
window via term_print after orx_run returns.
To add a new program: drop a .c file in
ouroboros/programs/ and rerun make.
The Makefile picks it up automatically — there's a uniform
%.c → %.orx pattern rule.
The print_str-vs-term_print split is documented in
ouroboros/programs/README.md.
Dhrystone v2.1 — the canonical 1984 benchmark, runs from inside the shell. Reports cycle count + dhry/s + DMIPS at the OR-1000's nominal 16/20 MHz clock rates from Vol I §3 (VAX 11/780 = 1.0 DMIPS reference):
/> run /programs/dhry.orx
Dhrystone Benchmark, Version 2.1 (Object RISC port)
Iterations: 5000
...
Cycles per iteration: 4074
16 MHz: ~3927 dhry/s = ~2.2 DMIPS
20 MHz: ~4909 dhry/s = ~2.7 DMIPS
The hello_or.c and print_or.c variants use the __or
qualifier to control the OR file directly from C:
register __or void *p __asm__("o5") binds a C variable to a
named Object Register slot. Assignments between __or variables
compile to omov; assigning 0 compiles to onull. Combined
with extended inline asm — asm("olw %0, 0(%1)" : "=r"(out) : "r"(or_var)) — you can read or write through OR pointers, invoke
firmware primitives via call #N, and otherwise treat OR slots
as first-class C lvalues.
The __or calling convention is wired both ways: callers pass
__or args in O1..O4 (print_clean.c), and pure-C callees
that take __or parameters can use them inside the body
without explicit register binding (print_via_or_arg.c,
or_callee_inspect.c). OL/OS-via-OR as native pcc patterns
(rather than via inline asm) and __or returns in O1 are
still pending — see
tools/cc/arch/orisc/README.md
for the backend's status and TODO.
The demos collectively exercise: recursion, loops with
conditionals, if/else-if chains, stack-allocated arrays of int and
char, computed array indexing, pointer arithmetic, string literals,
integer arithmetic (add/sub/mul/div/mod/and/or/xor), bitwise
operations, multi-file linkage (programs link against the
liborisc.ora C library for print_str /
print_int / strlen / memcpy / etc.), and external calls.
Each program links against crt0.s (provides .entry _start,
calls main, TaskExits with main's R2 as the exit code) and
console_io.s (a small bridge to firmware ConsoleWrite —
temporary until the C compiler grows the __or qualifier and
OR-file patterns). Both live in tools/cc/arch/orisc/.
Parallel π(N) across K+1 CPUs, results streamed live to the terminal. The coordinator partitions [2..N] into K+1 equal ranges, dispatches work to the workers via SEND with a derived reply cap, computes its own range, and prints each worker's count and elapsed cycles as the reply arrives:
examples/run_parallel_primes.sh # defaults: N=2000, w=3
examples/run_parallel_primes.sh -N 5000 -w 5
examples/run_parallel_primes.sh -N 10000 -w 8
# Parallel pi(10000) across 9 CPUs:
# CPU 0: 186 primes in 41270 cycles
# CPU 1: 145 primes in 59893 cycles
# CPU 2: 139 primes in 71274 cycles
# CPU 3: 133 primes in 79765 cycles
# CPU 4: 129 primes in 86472 cycles
# CPU 5: 127 primes in 93499 cycles
# CPU 6: 126 primes in 99069 cycles
# CPU 7: 122 primes in 103186 cycles
# CPU 8: 122 primes in 108465 cycles
# Total: pi(10000) = 1229w is capped at 10 (the program's worker-dispatch jump table has ten
entries — one per OR slot O6..O15 holding worker service refs at
boot). At larger w the result lines visibly arrive out of order
because the OS schedules each CPU process independently and they
finish at different rates.
Object RISC is a designed and implemented exercise in alternate- history computer architecture, not a production system. It is complete enough to design programs against, write them in assembly, and run them — single-CPU, multi-CPU, distributed across processes, and talking to a graphical device — but it does not pretend to be silicon. The reference implementation lives in Python.
MIT — see LICENSE.
The window manager's OPEN LOOK chrome fonts (Lucida Sans / Lucida Sans
Typewriter and the OPEN LOOK glyph font, baked into ouroboros/wm_fonts.h)
are third-party bitmap assets, © 1989 Sun Microsystems and © 1985, 1986
Bigelow & Holmes, used under the OpenLook CD-ROM's royalty-free license.
See NOTICE for the required attributions.