A single-board computer with a 7-bit data bus and 14-bit address bus.
  • Rust 59.1%
  • SystemVerilog 20.9%
  • Assembly 10.2%
  • Verilog 3.9%
  • Makefile 2.4%
  • Other 3.4%
Find a file
Ben Cox c96d8a3501 syn7: the counts this wave moved, in the nine places that still said otherwise
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>
2026-08-28 23:12:56 -04:00
.cargo Add emu7 GUI emulator with VGA display and UART0 serial I/O 2026-03-20 01:30:04 -04:00
.claude use American spellings in comments and documentation 2026-08-07 09:21:17 -04:00
.forgejo/workflows syn7: --syn7-trace, and the replay that makes M5e affordable 2026-08-16 18:30:57 -04:00
.vscode Switch builds, packaging, and tooling to PR7 as default output format 2026-04-12 23:04:49 -04:00
android docs, fdroid: the Forgejo instance is the only place the code lives 2026-08-04 11:49:28 -04:00
assets Switch builds, packaging, and tooling to PR7 as default output format 2026-04-12 23:04:49 -04:00
docs syn7: the counts this wave moved, in the nine places that still said otherwise 2026-08-28 23:12:56 -04:00
editors/vscode-sbc7 l7_lsp: surface /// doc comments and complete block members 2026-07-26 23:44:37 -04:00
keys Replace all non-ASCII characters with plain ASCII equivalents 2026-04-05 07:59:49 -04:00
mister syn7: M5d-c closed -- what 111 blocks cost, measured 2026-08-28 19:12:38 -04:00
pkg build: add cpal and hound behind the desktop feature 2026-08-12 17:31:43 -04:00
rtl syn7: the counts this wave moved, in the nine places that still said otherwise 2026-08-28 23:12:56 -04:00
scripts fix s7fs-fatcache-desync bug 2026-08-15 00:05:18 -04:00
src syn7: the counts this wave moved, in the nine places that still said otherwise 2026-08-28 23:12:56 -04:00
tools build: guard rom.s's PRINT_RTC literal against silent drift 2026-08-11 20:25:08 -04:00
web Switch builds, packaging, and tooling to PR7 as default output format 2026-04-12 23:04:49 -04:00
.editorconfig Add fmt7 assembly formatter, apply canonical formatting, and add conventions doc 2026-03-23 18:17:53 -04:00
.envrc Add clap CLI parsing, release automation, and Docker packaging 2026-03-22 16:36:18 -04:00
.gitignore syn7: the fourteen tables, in a form Quartus can read 2026-08-16 18:21:30 -04:00
.markdownlint.jsonc Add fmt7 assembly formatter, apply canonical formatting, and add conventions doc 2026-03-23 18:17:53 -04:00
CLAUDE.md separate the test fixtures from the examples 2026-08-07 14:41:38 -04:00
INSTALL.md packaging: ship what the tools now are, not what they were in 1.3.7 2026-08-07 17:54:35 -04:00
LICENSE Apply CC0-1.0 license, add INSTALL.md, update permissions 2026-03-23 20:29:23 -04:00
Makefile syn7: syn7_bank -- sixteen channels of working copy, transposed 2026-08-27 20:19:26 -04:00
README.md use American spellings in comments and documentation 2026-08-07 09:21:17 -04:00
RELEASE_NOTES.md use American spellings in comments and documentation 2026-08-07 09:21:17 -04:00

SBC7

SBC7 Logo

Most computers went from 4 to 8 bits and kept growing. SBC7 stopped off to smell the clover and decided 7 felt lucky.

SBC Screenshot

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.