Seventy becomes seventy one: a machine that can wait

HALT is terminal - stepCPU returns at once when the Halt Flag is up, so a
halted machine does not execute, service devices, or take an interrupt -
and that has to stay true, because every test ends with a halt and "halted"
is how a program says it has finished. The consequence was that SplitBit
had no way to wait at all. Every wait was a spin, and a spin is bus
traffic: 11.5% of Type over a 14K file on a disk of ten thousand cycles,
after read-ahead had already hidden three quarters of the latency.

WAIT is 0xFE, one byte, no operands, sitting under HALT where the
instruction that almost stops the machine belongs. Three decisions in it:

- A line already standing means there is nothing to wait for, so WAIT does
  nothing. That is what makes test-then-wait race-free.
- Any line ends the wait, masked or not, so a program can sleep on a device
  it has no handler for and read its status afterwards. Masking says who
  answers a request, not whether it happened.
- A line that wakes the CPU without being dispatched is taken down by the
  WAIT. Left standing it would be found by the next WAIT, which would
  return at once - the program would spin exactly as before while looking
  as though it slept.

Waiting is NOT a Status bit, and that is the trap avoided rather than a
gap: Status rides into the interrupt frame and comes back out, so a machine
interrupted mid-wait would return from its handler still waiting, and wait
again for what it had already been given. An internal field instead.

Idle cycles are counted apart from bus cycles and the halt line says so
when there are any, which is what makes the difference observable at all -
with the line-clearing removed the total moves by ONE cycle, 20,100 against
20,099, and only the idle half changes, halving to 9,976. A test on
totals could never have seen it. Tests/terminal.sh asks that question,
being the file for things a recorded output cannot see, and fails with the
clear removed while "both reads finished" still passes.

Three collisions, all found by building it:

- 0xFE was the assembler's "not an instruction" sentinel. getOpcode now
  answers a negative NOT_AN_OPCODE, which is outside the range of every
  possible answer instead of inside the unused part of it.
- 0xFE was also what faultTest and faultResumeTest executed to provoke a
  fault. They now use 0xFD and say why, because they did not fail when it
  became an instruction - they HUNG, having started sleeping instead.
- Keys.asm has had a label called "wait" for a year, and mnemonics are
  matched uppercased. What that reported was "Branch without label" at the
  BRQ thirty lines away. The assembler now refuses a label that is already
  an instruction, at the label, by name; every instruction added takes a
  word out of the space of label names, so this will happen again.
This commit is contained in:
Anachronaut
2026-08-26 11:11:25 -04:00
parent 6b41354f8f
commit c3188ed657
21 changed files with 311 additions and 23 deletions
+4 -2
View File
@@ -39,14 +39,16 @@ start:
OUTA 0x02
SIF
wait:
; Called spin rather than wait because WAIT is an instruction now, and a label may not
; be one. Which is the joke of it: this loop is exactly what WAIT exists to replace.
spin:
; This loop is the point. It never touches the console, so every character that appears
; below was put there by something that interrupted it.
SETD.3 Stopping
LDA.3
RSTB
OR
BRQ wait
BRQ spin
CALL newLine
RSTA
+2 -1
View File
@@ -37,7 +37,7 @@ AsmShapeSelectors:
0d0 0d0 0d0 0d1 0d1 0d1 0d2
AsmInstructionCount:
0d70
0d71
AsmInstructions:
0x00 0d0 "ADD "
@@ -109,4 +109,5 @@ AsmInstructions:
0xE0 0d2 "INA "
0xE1 0d2 "INB "
0xF0 0d0 "NOP "
0xFE 0d0 "WAIT"
0xFF 0d0 "HALT"
+2 -1
View File
@@ -3753,7 +3753,7 @@ ShapeLength:
0d1 0d3 0d2 0d2 0d3 0d4 0d3
InstructionCount:
0d70
0d71
; ---- The instruction table ----
;
@@ -3830,6 +3830,7 @@ Instructions:
0xE0 0d2 "INA "
0xE1 0d2 "INB "
0xF0 0d0 "NOP "
0xFE 0d0 "WAIT"
0xFF 0d0 "HALT"
DumpBytes:
#Reserve 0d16
+7 -2
View File
@@ -5,7 +5,12 @@
; the same byte again, which is why this handler moves the saved address on by one
; first. MVSD is what lets it reach the frame at all.
;
; 0xFE is not an instruction. Writing it as a literal is the only way past the
; CHOSEN AWAY FROM HALT, because the bytes next to it get used. This said 0xFE for
; a long time, and then 0xFE became WAIT - so the test stopped faulting and started
; SLEEPING. It hung instead of failing, which is the worst way for a test to notice
; that the thing it was testing had moved.
;
; 0xFD is not an instruction. Writing it as a literal is the only way past the
; assembler, which is what makes a program containing one buildable.
;
; Correct output is:
@@ -20,7 +25,7 @@ start:
INIA 0x0A
OUTA 0x00
0xFE ; Not an instruction. The handler steps over this.
0xFD ; Not an instruction. The handler steps over this.
INIA 0d75 ; 'K'. Reached only because the handler moved the address on.
OUTA 0x00
+7 -2
View File
@@ -1,6 +1,11 @@
; Tests that the CPU stops when it meets a byte it cannot decode.
;
; 0xFE is not an instruction. Placing it in the Program Segment as a literal gets
; CHOSEN AWAY FROM HALT, because the bytes next to it get used. This said 0xFE for
; a long time, and then 0xFE became WAIT - so the test stopped faulting and started
; SLEEPING. It hung instead of failing, which is the worst way for a test to notice
; that the thing it was testing had moved.
;
; 0xFD is not an instruction. Placing it in the Program Segment as a literal gets
; it past the assembler, which is the only way to build a program containing one.
;
; The CPU should raise the Fault Flag, halt, and leave the Program Counter pointing
@@ -11,5 +16,5 @@
start:
INIA 0d65 ; Something harmless first, so the fault is not at address zero.
0xFE ; Not an instruction.
0xFD ; Not an instruction.
HALT ; Never reached.
+59
View File
@@ -0,0 +1,59 @@
; waitTest.asm
; WAIT, on a disk slow enough to be worth waiting for.
;
; Two reads, waited for rather than spun on, with INTERRUPTS MASKED and no handler
; installed anywhere. That is the shape this instruction exists for: a program sleeps on a
; device it has no handler for and reads the device's status when it wakes.
;
; THE SECOND READ IS THE TEST. A line that wakes the CPU without being dispatched has to
; be taken down by the WAIT itself; left standing, it is found by the next WAIT, which
; returns at once, and the program spins for the whole of the second read while looking
; exactly as though it slept. Both reads print, so a hang fails here - but a program that
; spun would print the same two characters, and what tells them apart is the emulator's
; count of idle cycles against bus cycles. Tests/terminal.sh is where that is checked,
; because settle() strips cycle counts out of every recorded output including this one.
;
; Written by Anachronaut
#Program
start:
CIF
INIA 0d3
OUTA 0xE3
INIA 0x20
OUTA 0xE2
INIA 0x03
OUTA 0xE8
RSTA
OUTA 0x20
RSTA
OUTA 0x21
INIA 0x01
OUTA 0x22
CALL waitDisk
INIA 0x31 ; "1"
OUTA 0x00
RSTA
OUTA 0x20
INIA 0x01
OUTA 0x21 ; Block 1, a different one.
INIA 0x01
OUTA 0x22
CALL waitDisk
INIA 0x32 ; "2"
OUTA 0x00
INIA 0x0A
OUTA 0x00
HALT
waitDisk:
INA 0x23
INIB 0x01
AND
BRQ waitDone
WAIT
BRI waitDisk
waitDone:
RET
#Vectors
Boot start