SCELBAL runs on DINO
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.

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:
| 8008 | DINO | What the translator does |
|---|---|---|
| carry after subtract means borrow | the 74F382 gives NOT-borrow | tracks 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-connect | keeps the last result in a RAM cell and tests bit 7 |
| rotate right | no shifter | a 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:
- SCELBAL needs
SCRtyped after a cold load or LIST loops forever. Same on the original, it’s how it was. - SCELBAL’s error handler and
ENDjump back to the main loop from inside subroutine calls and never return from them. On an 8008 that’s fine, its 7-deep stack just wraps. On DINO the stack is RAM and it grew: SP went from 0x83F7 to 0x83E7 over 12 errors and 3 RUNs. A few hundred errors in, it would have run into BASIC’s own variables. The fix is resetting the stack pointer at the top of the main loop. - Typing a program in too fast drops characters. SCELBAL takes a few milliseconds to store a line and the monitor’s input buffer is 16 bytes, so pasting a program loses letters. Typing it by hand is fine.
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.
| Step | Code size | Free in the ROM |
|---|---|---|
| straight translation | 19.8K | doesn’t fit |
| big macros moved into shared subroutines | 14.7K | |
| laid out in ROM with the monitor | 162 bytes | |
| dropping stores to register cells nobody reads | 1,190 bytes | |
| after adding UDF, PEEK and POKE | about 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
- POKE’s read-back on the machine.
POKE 61400,PEEK(16384)went in without an error,PRINT PEEK(61400)hasn’t been run yet. - The other 7% of SCELBAL, mostly error branches, has only been translated, never executed in a test.
- No SAVE or LOAD, and pasting a program drops characters. A host script that types a
.basfile one character at a time, waiting for each echo, would fix the second one. - The UDF off-by-one.
- Microcode that would shrink and speed this up next time: a jump-if-zero (SCELBAL has 141 of those and each costs two jumps now), a sign branch, and increment-in-memory.