Files
SplitBit-Emulator/Programs/CosmOS/Apps/Keys.asm
T
Anachronaut c3188ed657 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.
2026-08-26 11:11:25 -04:00

104 lines
2.9 KiB
NASM

; A loaded program that is interrupted by the console rather than asking it for anything.
;
; Until the loadable program format could carry vectors, this could not be written. A
; handler has to be an address in the vector table, and a program that is not the one the
; machine booted from had no way to say what its vectors were, so interrupts belonged to
; boot images and every loaded program had to poll. Snake polls for exactly that reason.
;
; What this shows is the whole path: the assembler writes the vectors into the file, the
; system installs them when the program is run, the console raises its line, the handler
; runs, and the system takes the vectors back out again when the program gives the machine
; back. Nothing in the waiting loop below looks at the console at all.
;
; The vector is named by port, because a device interrupts on the port it is plugged into
; and the console is on port 0x00.
#Include services.asm
#Program
#Base 0x4000
start:
CIF ; Nothing arrives until there is something to catch it.
; Set rather than trusted to be zero. Running a program a second time does not load it
; again, so its Data Segment is exactly as the last run left it - and the last thing the
; last run did was set this.
RSTA
SETD.0 Stopping
STA.0
SETD.0 Banner
CALL printString
CALL newLine
; Key mode and interrupt on input, in one write, since the two control bits are
; independent of each other.
INIA 0x03
OUTA 0x02
SIF
; 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 spin
CALL newLine
RSTA
OUTA 0x02 ; Line mode and no interrupts, the way it was found.
SETD.0 DoneText
CALL printString
CALL newLine
SWI osExit
; Entered because the console had something to say. Never called.
keyHandler:
INA 0x01
INIB 0x01 ; READY: is there a byte, as opposed to the end of input?
AND
BRQ keyNoByte
INA 0x00
INIB 0x71 ; q, which is how this program is stopped.
XOR
BRQ keyStop
OUTA 0x00 ; Nothing echoes in key mode, so the handler does it.
RETI
keyNoByte:
; The end of input raises the line once as well, so a program driven entirely by
; interrupts is told when nothing more is coming instead of waiting for ever.
keyStop:
SETD.3 Stopping
INIA 0x01
STA.3
RETI
#Data
#Base 0x2000
Banner:
"keys, by interrupt. q stops."
DoneText:
"the console has been handed back"
; The only thing the handler and the loop it interrupts have to say to each other.
Stopping:
0x00
#Vectors
Boot start
Device 0x00 keyHandler
#Include console.asm