SCELBAL runs on DINO

/ DINO / updated 2026-10-06 / from dino-homebrew dino basic scelbal 8008 translator rom

DINO runs BASIC now. It’s SCELBAL, Mark Arnold and Nat Wadsworth’s floating-point BASIC for the Intel 8008 from 1974, and it lives in the ROM socket next to the monitor. You type G 0A00 at the > prompt, get READY, and it’s an interpreter with six-digit floats, arrays, GOSUB and the usual functions.

First session on the machine. A two-line HELLO loop, Ctrl-C back to the monitor, G 0a00 into BASIC again, LIST, then PRINT 2/3. The 0.6666667 matches the original running on an 8008 emulator digit for digit.
First session on the machine. A two-line HELLO loop, Ctrl-C back to the monitor, G 0a00 into BASIC again, LIST, then PRINT 2/3. The 0.6666667 matches the original running on an 8008 emulator digit for digit.

The source is the “Fast” version from the SCELBAL page on willegal.net (sc1fast.asm, with MGA’s patches). I didn’t port it by hand. A Python script translates every 8008 instruction into DINO instructions, and the original runs alongside in an 8008 emulator as the answer key.

How a 7-register CPU fits on a 3-register one

The 8008 has A, B, C, D, E, H and L. DINO has A, B and C, and C gets eaten by every RET. So 8008 A stays in DINO’s A, and B through L become bytes in RAM. H:L is the 8008’s memory pointer, and DINO already has a memory-indirect load (LDAM) that follows a pointer stored in RAM, so LAM turns into one 3-byte instruction.

Three things didn’t map straight across:

8008DINOWhat the translator does
carry after subtract means borrowthe 74F382 gives NOT-borrowtracks which way the carry points at every branch and picks the jump to match
jump on sign (JTS, 83 of them)no sign branch, the select line for it is a no-connectkeeps the last result in a RAM cell and tests bit 7
rotate rightno shiftera 256-byte table in ROM

The carry thing is the one that would have bitten quietly. The translator does a flow analysis over the whole program to know, at each of the 32 places SCELBAL reads the carry, whether DINO’s carry flag is the same or inverted.

Proving it before burning

Everything got checked in layers on the Mac first. My 8008 assembler had to reproduce the published sc1fast.bin from the same page byte for byte (11,942 bytes). Then each translated instruction ran on DINO’s real microcode in the simulator and on the 8008 emulator from the same random state, compared register by register. Then the whole interpreter: the same keystrokes typed into both, and the DINO transcript had to match the 8008’s exactly. Those sessions cover 93% of SCELBAL’s instructions now.

A few things only showed up at the whole-program level:

If you’re porting 8008 code to something with a real stack: watch for code that jumps out of a subroutine without returning. The 8008 forgives that, nothing with a RAM stack does.

Fitting in 16K

The ROM is a 28C256, which is 32K, but the decode only selects it for 0x0000-0x3FFF. The other half of the chip is never addressed. The I/O window owns 0x4000-0x7FFF. So the monitor and BASIC share 16K.

StepCode sizeFree in the ROM
straight translation19.8Kdoesn’t fit
big macros moved into shared subroutines14.7K
laid out in ROM with the monitor162 bytes
dropping stores to register cells nobody reads1,190 bytes
after adding UDF, PEEK and POKEabout 700 bytes

Most of the bloat is H and L. SCELBAL loads L 578 times and H 225 times, and every one was a store to RAM. When the translator can work out that H and L are both constants at a memory access, it uses the absolute address and the stores usually die.

That optimization had a bug the whole-program test caught: LIST printed program lines as rows of zeros. A second LHI that loaded the value H already held got skipped as redundant, but the analysis still counted it as a write, so it threw away the first one too.

Reaching the hardware from BASIC

SCELBAL has no PEEK or POKE, so I added them as an 8008 patch file. The upstream source stays untouched and the translator applies the patch. The keyword and function tables were full, so the new ones live in a spare page that’s served straight out of ROM.

PRINT PEEK(16384)       reads the DIP switch at 0x4000
 28.0
POKE 61400,PEEK(16384)

PEEK and POKE take real DINO addresses in decimal, so POKE can stomp on BASIC’s own memory if you aim it wrong. RAM from 50176 (0xC400) up is free. The LEDs aren’t on the address bus, only the OUT instruction reaches them, so they go through SCELBAL’s user function: X=UDF(43) lights 42. That’s off by one because UDF(0) was taken for reading the switches before PEEK existed.

It’s slow. PRINT 2/3 takes about 0.3 seconds, SQR(2) about 0.8, and a simple FOR loop runs around 10 passes a second at 1.024 MHz.

Still open