Files
SplitBit-Emulator/Programs/CosmOS/Assembler/table.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

114 lines
3.2 KiB
NASM

; The instruction set, as the assembler needs to see it.
;
; A SECOND COPY, and it is worth saying why rather than hoping nobody notices. The monitor
; has one of these in cosmos.asm, and the assembler cannot use it: the monitor's copy lives
; in the system's data at an address that moves every time CosmOS is rebuilt, and there is
; no linker to reach it by name. So the assembler carries its own 448 bytes. That is the
; cost of having no libraries, paid where it is cheapest to pay.
;
; Both copies are generated by Tests/instructiontable.py from the C assembler's own list,
; and Tests/docs.sh checks both against it. Neither can drift without the suite saying so.
;
; Seven bytes an entry: the opcode, the shape, and four characters of name with the zero
; the assembler puts after a string. Every mnemonic is four characters or fewer, so a name
; padded to four is an exact match rather than a prefix.
;
; It goes in the DATA Segment, because the assembler has to read it and an instruction can
; only read Data Memory. A table in Program Memory could not be reached by the program
; holding it, except through the memory controller.
#Data
; How many bytes an instruction of each shape runs to, the opcode included. The assembler
; does not use this to size a token - the operand that follows is a token of its own and
; carries its own length - but it is what says an instruction is well formed.
AsmShapeLength:
0d1 0d3 0d2 0d2 0d3 0d4 0d3
; How many Data Pointer selectors an instruction of each shape names. This is what the
; assembler needs: a selector is part of the mnemonic rather than a token after it, so it
; is the one thing about an instruction's length that is not settled by the opcode alone.
;
; 0 no operand 4 a selector and a byte
; 1 an address 5 a selector and an address, which is SETD
; 2 a byte 6 two selectors, which is LDD and STD
; 3 a selector
AsmShapeSelectors:
0d0 0d0 0d0 0d1 0d1 0d1 0d2
AsmInstructionCount:
0d71
AsmInstructions:
0x00 0d0 "ADD "
0x01 0d0 "SUB "
0x02 0d0 "AND "
0x03 0d0 "OR "
0x04 0d0 "XOR "
0x05 0d0 "NOTA"
0x06 0d0 "NOTB"
0x07 0d0 "SHL "
0x08 0d0 "SHR "
0x10 0d1 "BRI "
0x11 0d1 "BRQ "
0x12 0d1 "BRA "
0x13 0d1 "BRB "
0x14 0d1 "BRC "
0x15 0d3 "BRD "
0x1A 0d1 "BNQ "
0x1B 0d1 "BNA "
0x1C 0d1 "BNB "
0x1D 0d1 "BNC "
0x16 0d1 "RCAL"
0x17 0d1 "CALL"
0x18 0d2 "SWI "
0x19 0d0 "RETI"
0x1E 0d0 "RRET"
0x1F 0d0 "RET "
0x20 0d0 "RSTA"
0x21 0d0 "RSTB"
0x22 0d0 "INCA"
0x23 0d0 "INCB"
0x24 0d0 "DECA"
0x25 0d0 "DECB"
0x26 0d2 "INIA"
0x27 0d2 "INIB"
0x28 0d0 "CCF "
0x29 0d0 "MVQA"
0x2A 0d0 "MVQB"
0x2B 0d0 "SIF "
0x2C 0d0 "CIF "
0x30 0d0 "PSHQ"
0x31 0d0 "PSHA"
0x32 0d0 "PSHB"
0x33 0d3 "PSHD"
0x34 0d0 "POPA"
0x35 0d0 "POPB"
0x36 0d3 "POPD"
0x40 0d3 "INCD"
0x41 0d3 "DECD"
0x42 0d3 "LDA "
0x43 0d3 "LDB "
0x44 0d3 "STQ "
0x45 0d3 "STA "
0x46 0d3 "STB "
0x47 0d5 "SETD"
0x48 0d4 "DPUP"
0x49 0d4 "DPDN"
0x4A 0d6 "LDD "
0x4B 0d6 "STD "
0x4C 0d3 "MVSD"
0x4D 0d3 "MVDS"
0x4E 0d3 "DPUA"
0x4F 0d3 "DPDA"
0x50 0d3 "DPUW"
0x51 0d3 "DPDW"
0xD0 0d2 "OUTQ"
0xD1 0d2 "OUTA"
0xD2 0d2 "OUTB"
0xE0 0d2 "INA "
0xE1 0d2 "INB "
0xF0 0d0 "NOP "
0xFE 0d0 "WAIT"
0xFF 0d0 "HALT"