- Rust 59.1%
- SystemVerilog 20.9%
- Assembly 10.2%
- Verilog 3.9%
- Makefile 2.4%
- Other 3.4%
The fix wave's whole subject was comments that went stale under a later task, and it left nine of its own. Adding `cutoff-shadows` took the set to 56 / 11,380 at N_ENGINE=3 and 51 / 9,780 at N_ENGINE=1; the M5d-c spec's headline claim, two docs/index.md rows, docs/sound.md's M5 row, the M5 spec's M5d row and four places in syn7_tb.sv all still quoted 55 / 11,120 and 50 / 9,520. docs/index.md:339's "by task 10 to fifty-five and 11,120 samples" stays as it is: that row narrates the set's growth task by task, and the clause is the task-10 figure. The current total follows it in a clause of its own, naming what the new scenario proves. The watchdog is re-divided rather than asserted. Its comment counts the four previous times the figure was re-checked against a widened set; this is the fifth, and the arithmetic is redone from the run rather than carried over -- 2049 silent samples plus 11,380 engine samples is about 280 ms, and `$finish` lands at 281.6 ms at N_ENGINE=3 and 248.1 at N_ENGINE=1, against 276.3 and 242.8 before. 470 over 281.6 is 1.67x, so this is the first widening that did not need the figure raised with it, which is now what the comment says instead of a 1.7x the sample count had quietly falsified. Two cosmetic fixes in the test the wave added. The `assert!` messages lost their line continuations and were emitting fourteen-space runs mid-sentence. And `DRAIN_WINDOW_CLOCKS` is transcribed from `rtl/syn7.sv`'s `drain_window` with nothing in Rust able to notice if that line moves -- exactly the shape being closed here -- so it now says so, quoting the line; while `assert_eq!(worst, 30)` followed by `worst <= 31` said nothing twice, and is now `assert_eq!(worst, DRAIN_WINDOW_CLOCKS - 1)`, which says the thing that matters: the twelve-post budget buys exactly one clock of headroom. Documentation and message edits only, so the counts must not move, and do not: rtl-test 56 / 11,380 at N_ENGINE=3 and 51 / 9,780 with 5 skipped at N_ENGINE=1; emu7 774 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .cargo | ||
| .claude | ||
| .forgejo/workflows | ||
| .vscode | ||
| android | ||
| assets | ||
| docs | ||
| editors/vscode-sbc7 | ||
| keys | ||
| mister | ||
| pkg | ||
| rtl | ||
| scripts | ||
| src | ||
| tools | ||
| web | ||
| .editorconfig | ||
| .envrc | ||
| .gitignore | ||
| .markdownlint.jsonc | ||
| CLAUDE.md | ||
| INSTALL.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| RELEASE_NOTES.md | ||
SBC7
Most computers went from 4 to 8 bits and kept growing. SBC7 stopped off to smell the clover and decided 7 felt lucky.
Every data path in this machine -- the bus, the registers, the instruction
words, the memory cells -- is exactly 7 bits wide. Not because it's practical
(it mostly isn't), but because interesting constraints produce interesting
designs. The address bus is 14 bits (two 7-bit halves, notated &HH:LL),
giving the CPU a 16 KB logical address space. Bank switching extends this to
80 KB of physical memory (64 KB RAM + 16 KB ROM) by paging 2 KB banks into
eight independently controlled slots.
The ISA fills all 128 possible opcodes with zero waste: 14 general registers arranged as 7 named pairs (AB, CD, EF, GH, PC, FL, SP), an accumulator-centric ALU with 8 operations x 4 source modes, a 14-bit pair ALU, conditional branches (relative and absolute), stack operations, block copy (LDIR), and a VGA text display. It's a complete little computer -- just one bit short of conventional.
Documentation
See the docs directory.
Collaboration
See here for details. tl;dr: This is a hobby project that I host here. Ask me if you want an account, and you can file issues and PRs like anyone else — or just email me a patch.
Block Diagram
flowchart TD
clk([clk])
rst_n([rst_n])
io(["I/O bus\nio_addr . io_req\nio_we . io_din . io_dout"])
subgraph top["sbc7_top.sv"]
cpu["cpu.sv\n14-bit addr . 7-bit data . I/O bus"]
bankregs["bank_regs.sv\nI/O ports 0-7 . 8 x 7-bit"]
ram["ram.sv\nPort A: CPU . Port B: VGA\n32 banks x 2 KB (64 KB)"]
rom["rom.sv\n8 banks x 2 KB (16 KB)"]
vga["vga.sv\n640x480 @ 60 Hz . 64x24 chars"]
end
hsync([hsync])
vsync([vsync])
pixel([pixel])
clk & rst_n --> cpu
cpu <-->|io_*| bankregs
bankregs -->|"bank[0:7]"| ram & rom
cpu <-->|"addr / data / we"| ram
cpu -->|addr| rom
ram -->|"vram (port B)"| vga
rom -->|"char data (port B)"| vga
vga --> hsync & vsync & pixel
cpu <-->|io_*| io
Memory Map
The 14-bit logical address space is divided into eight 2 KB slots. Bank registers on I/O ports 0-7 select which physical bank appears in each slot. Default mapping (after reset) reproduces a flat layout:
| Range | Slot | Contents |
|---|---|---|
| &00:00-&00:3F | 0 | RST/interrupt vectors (slot 0 is fixed to RAM bank 0) |
| &00:40-&00:7F | 0 | ROM working variables |
| &01:00-&5F:7E | 0-5 | General-purpose RAM; default program load address |
| &5F:7F | 5 | Initial stack pointer (SP grows downward) |
| &60:00-&63:7F | 6 | Memory-mapped I/O (MMIO bypass -- always accessible) |
| &64:00-&6F:7F | 6 | Screen RAM (1536 bytes, 64x24 characters; VGA display bank set by port 37) |
| &70:00-&7F:7F | 7 | ROM (2 KB, reset vector at &70:00) |
Physical memory: 64 KB RAM (32 banks) + 16 KB ROM (8 banks) = 80 KB total. See docs/banking.md for the full specification.
Toolkit
asm7 is a two-pass assembler. Its default output format is H7X, a
checksummed ASCII object format designed for the SBC7; flat binary output is
available via --bin.
disasm7 is a disassembler that auto-detects H7X or binary input and produces annotated assembly listings with hex offsets.
emu7 is a GUI emulator with VGA character display, UART0 wired to
stdin/stdout, and disk, RTC, gamepad and timer emulation. Pass --no-gui for
terminal-only operation.
l7 compiles Lucky 7, the machine's own systems language -- a small
statically typed language that knows about 7-bit cells and bank switching, so
paging a bank in is a with block rather than a hand-written OUT. The command
shell, the edit text editor and the standard library are all written in it.
See the language spec.
roll is Lucky 7's build and dependency tool -- cargo for dice, the
language's unit of compilation. It reads a .dice manifest, resolves
dependencies, and drives l7, link7 and emu7 to build, run and test.
link7 links OB7 relocatable objects into a loadable PR7 program, packing dice into physical banks and dropping code nothing reaches.
s7fs creates and manages S7FS disk images from the host -- format, ls,
put, get, mkdir, rm, and a consistency check.
od7 dumps the structure of an S7B file (.pr7 or .ob7): chunks, symbols
and relocations.
TinyFORTH is a minimal Forth interpreter written in SBC7 assembly, inspired
by jonesforth. It uses 14-bit cells (two 7-bit halves) and provides 30+ built-in
words covering stack manipulation, arithmetic, memory access, and I/O. Run it
with make forth.
TinyBASIC is a line-numbered integer BASIC interpreter inspired by Li-Chen
Wang's Palo Alto Tiny BASIC (1976). Run it with make basic.
TinyLisp is a minimal Lisp interpreter with tagged 14-bit values, a
mark-and-sweep garbage collector, and a built-in standard library. Run it with
make lisp.
fmt7 is an opinionated assembly formatter for SBC7 source files.
lsp7 is a language server providing editor support (completions, diagnostics, hover) for SBC7 assembly.
dap7 is a Debug Adapter Protocol server for step-debugging SBC7 programs.
For the full story -- ISA reference, assembler manual, ROM walkthrough,
language interpreter guides, and more -- see the docs/
directory.
Building
SBC7 uses make as its build tool.
| Command | What it does |
|---|---|
make |
Build ROM image (default) |
make run |
Build and launch the emulator |
make forth |
Build and run TinyFORTH |
make basic |
Build and run TinyBASIC |
make lisp |
Build and run TinyLisp |
make example NAME=typewriter |
Assemble an example program |
make run-example NAME=typewriter |
Build and run an example in the emulator |
Process
SBC7 is a collaboration between a human designer and Claude Code. The ISA was shaped by cross-analysis of the 6502 and Z80 instruction sets, with the goal of maximizing code density within the 7-bit constraint -- squeezing every last microjoule of utility out of 128 opcodes. The working method is straightforward: agree on a design decision in plain language, plan the changes, implement and commit. All decisions -- memory map layout, mnemonic naming, notation conventions -- are recorded in commit history and the documentation, because future-you deserves to know what past-you was thinking.
At boot, a custom glyph at position $20 in the character ROM greets you on
screen -- the Lucky 7 glyph, a stylized 7 that serves as the machine's quiet
little signature.
Font
The SBC7 character ROM is available as a webfont in
tools/sbc7.woff2 (TTF also available). It's a monospace
bitmap font covering printable ASCII, generated from the VGA character ROM
glyphs.
Acknowledgments
The MiSTer FPGA core was built with the help of Alan Steremberg's MiSTer tutorials.
