Cycle-exact T-states, then SCELBAL, then booting straight into it

/ Intel 8008 VHDL / from intel-8008-vhdl 8008 b8008 scelbal basic cycle-exact tiny-os ecp5

Cycle-exact

The ISA table in docs/isa.json always said which T-slots each machine cycle skips. The RTL ran every cycle a fat five states. Now cycles end where the table says: fetch cycles of multi-cycle instructions end at T3 (except LMr, which does its register write at T4), middle cycles of 3-cycle instructions end at T3, a not-taken conditional RET is 3 states, and not-taken conditional jumps and calls drop their last two states, 11 down to 9. The new cycle_done path goes T3 or T4 back to T1 mid-instruction without the interrupt check, again per Figure 2.

The first cut skidded. Fetch-cycle decisions have to evaluate in the second half of T3, because at T3 entry the instruction register still holds the previous opcode and the decoder flags are stale. A new regression runs one instruction per timing class, counts simulated states between fetch markers, and diffs against the table.

CheckResult
Timing classes cycle-exact27/27
Regression suite28/28
Interrupt suite10/10
Bitstream timing at 25 MHz93 MHz clk / 114 MHz

Pi visibly prints faster on the thin cycles.

SCELBAL from RAM

RAM grew to 12 KB, 0x1000 to 0x3FFF, to fit a 7.3 KB interpreter under the monitor. The ledger for Jim Loos’s build came to six changes: the variable-page EQUs, the program-space bounds, one ORG, four deleted init lines, the two USART I/O shims (only A and B touched, output preserves A with the MSB restored, per SCELBAL’s documented contract), and CRLF in the banner. I added an auto-SCR at cold entry so G 2000 would land in initialized BASIC, then reverted it the same evening. Jim’s manual SCR stays.

First SCELBAL run, RAM-resident, loaded through the monitor and started with G 2000. FOR/NEXT with PRINT I+1.
First SCELBAL run, RAM-resident, loaded through the monitor and started with G 2000. FOR/NEXT with PRINT I+1.

Loading it exposed weak spots in my send_hex script, which now fails loudly on silently dropped records, counts the EOF record’s OK correctly, and refuses to send unless it sees the monitor prompt first.

The tiny OS

Loading BASIC through a monitor every boot is a workflow. Booting into it feels like a computer. New build, new memory map: RAM 4 KB at 0x0000, 12 KB ROM at 0x1000 holding the monitor and SCELBAL, and three bytes of block-RAM initialization at address zero, JMP 1800h. The memory map became generics on b8008_top and the ROM address widened to 14 bits so both builds are the same top.

MON lives in an unused zero-filled slack region of SCELBAL’s keyword lookup table, every original offset preserved, and jumps to the monitor. G 1FB6 warm-enters BASIC’s executive without re-initializing, so the program survives the trip. The boot vector was verified in the synthesized netlist: ram[0..2] is 44 00 18.

Relocating my own monitor exposed a hardcoded page-low-byte in its command buffer, the same class of bug as calc’s FININP the day before.

The full ceremony passed in a testbench over the real RTL with a real UART: power-on to banner with nothing typed, SCR and a program and RUN, MON to the monitor prompt, W and D alive there, G 1FB6 and LIST intact. Then on the board the same evening.

Boot-to-BASIC build on the ECP5. SCELBAL from ROM, a FOR/NEXT loop, MON dropping to the monitor to dump the tokenized program at 0100, and G 1FB6 back into BASIC with the program still there.
Boot-to-BASIC build on the ECP5. SCELBAL from ROM, a FOR/NEXT loop, MON dropping to the monitor to dump the tokenized program at 0100, and G 1FB6 back into BASIC with the program still there.

Next

Prove the thing beyond my own tests. The self-test passing means the CPU agrees with my reading of the datasheet, which is circular. The period software helped. Formal is the next step.