FANCO-16 v9 - CPU AND MACHINE REFERENCE ====================================== Tentative working target. v9 owns its runtime and build sources. The guest monitor assembles all existing CPU instructions; see MANUAL.txt for syntax. Implemented differences: - Startup configuration, IRQ descriptors and trap entry occupy page zero. - 0080-00FF is contiguous RAM; ROM object zero starts its bootstrap at 0080. - Optional guest HCF trap entry saves user context independently of its stack. - CPU registers reset to zero: five zero bytes execute SYS LOAD8 0, 0. - All program memory remains writable. - SYS LOAD accepts executable WRITE/JUMP record streams. Image proposal: ASCII FAN followed by byte 09; records until end of image. Control byte: bits 7-6 operation (0 WRITE, 1 JUMP), bits 5-4 reserved zero, bits 3-2 length width, bits 1-0 destination width. Widths: 0 = 4 bits, 1 = 8 bits, 2 = 16 bits; 3 invalid. WRITE stores length minus one, then destination, then literal bytes. Fields pack low nibble first with zero padding to the next byte boundary. JUMP has no length field, requires zero length-width bits, and must be last. Control byte 80 is LOAD: four bytes follow, a little-endian object ID. The server expands LOAD records depth-first in file order, so /v9/programs/ delivers one image of WRITE records and at most one final JUMP: the last JUMP in expanded order. Repeated LOADs repeat their writes. A cycle, a missing object or more than 65,536 payload bytes fails the whole tree. Assembly: .load ID emits a LOAD record at that point of the source. Validate the complete stream before installation. No address wrapping. A final JUMP selects PC; otherwise written PC bytes take effect, or execution continues at its current PC. Successful loading must preserve installed A. Object IDs: bit 0 clear means immutable (these bytes forever, usually derived from the content hash); bit 0 set means mutable (its publisher can replace the bytes, and loads get the newest version). ROM IDs 00-FF are immutable whatever their low bit. Built-in prototypes 0101, 0103 and 0107 are mutable. First-party ranges: 0100-0FFF test apps, 1000-1FFF usable apps (piano 1001). A first-party app ID holds one LOAD record naming an immutable object; each new target is a numbered version, written ID@n from 1 (piano@1 is 1001@1). The server expands the record, so the app ID loads in one request. Automatic IDs come only from the random pool 80000000-FFFFFFFF. Initial text layout: 1024 bytes of character cells and a separate 1024-byte 8x8 ASCII font in the guest image. Future fonts and screen shapes must not be constrained by a permanent 32-column hardware assumption. Machine address map (hex): 0000-000F Eight little-endian word registers A B X Y P R SP PC. 0010-0011 Device configuration; 0012 device status; 0013 RAM. 0014 A/B/X/Y held bits 0-3; 0015 primary/secondary active bits 0-1. 0016-0019 Primary X, Y; secondary X, Y: signed bytes. 001A-001F RAM. 0020-002F Four IRQ descriptors: vector word, rate word. 0030 IRQ enable; 0031 IRQ active. 0032-0033 Trap handler PC; 0034-0035 trap SP; 0036 trap control. 0037 LOAD status: 0 success, 1 failure. 0038-0039 Aggregate LOAD progress: bytes still to write; 003A-003F RAM. 0040-005F Sound aliases. 0060-006D blitter; 0070-0078 graphics ports. 007A-007B WIDTH: pixel row width, reset 256; zero also means 256. 0080-00FF Ordinary RAM, including bootstrap code at 0080. 0100-03FB SIREN operators, mixers, filters, effects and output controls. 0400-041F Key-down bitmap; 0420-043F pressed, 0440-045F released. 0460-047F Reserved keyboard control. 0480-057F sound mixers and noise. 0580-059F SIREN additive stiffness; 05A0-05FF reserved. Machine state above 0600 is listed below. 0680-069F Eight touch slots; 06A0-06B7 audio capture descriptors. Other addresses are usable RAM; the monitor reserves its own areas below. Sound register details are in SOUND.txt. Each bitmap key ID has byte key/8 and bit key%8; writing one clears a pressed or released bit. Working register map (hex): 061C-061F IRQ missed counters. 0620 MIDI status: bit 0 queued, bit 1 overflow, bit 7 connected. Any write pops an event; bit 1 also clears overflow. 0621-0624 Popped status, data 1, data 2, and sample offset 0-79. The queue holds 64 channel messages; system messages are dropped. 0625-0627 MIDI out: status, data 1, data 2. Writing a channel status byte (80-EF) to 0625 sends it with the data bytes (bit 7 ignored) to MIDI outputs named like a connected input. Write the data first. At most 64 messages per host slice; more are dropped. 0032-0033 Guest trap handler PC. 0034-0035 Guest trap handler SP. 0036 Bit 0 enables guest traps; bit 1 marks handler active. Setting bit 2 restores saved context before the next instruction and sets control to 1. Guest traps while active fall back to the host halt report. All CPU faults, including HCF, use this path. 0650-065F Saved A, B, X, Y, P, R, SP, PC. PC points at the faulting opcode. 0660 Saved flags (NZCV). 0661-0662 Saved IRQ active/enable bits. Handler entry disables IRQs. 0037 SYS LOAD status: LOAD8 writes 0 or 1. An asynchronous failure sets 1 and stays set until the guest clears it; A is unchanged. 0664-0665 Trap code (A for HCF, otherwise the existing fault code). 0010-0011 CONFIG: bits 0 keyboard, 1 touch, 2 sound, 3 MIDI, bits 4-7 A/B/X/Y individually, bits 8-9 primary controller mode, bits 10-11 secondary mode. Modes: 0 off, 1 cursor, 2 digital, 3 analog. Setting bit 3 starts the browser's MIDI permission request. Bit 2 requests sound; the runner starts it on the next user gesture (pointer down/up or key) and closes the output when bit 2 clears. 0012 STATUS: bit 0 sound live (guest samples reach the speaker; safe to play now), 1 MIDI enabled, 2 MIDI input connected, 3 MIDI API unavailable, 4 MIDI permission pending, 5 MIDI denied. Written while sound or MIDI is requested. 0014-0019 Controller readouts. The host writes each field only while its device is requested, and only when the value changes; otherwise the bytes are ordinary RAM. X/Y range -127..127, right/down positive. Cursor mode reports one axis at a time, digital mode -127/0/127 per axis (diagonals allowed), analog the full range. Active bit: the stick is touched or its direction is nonzero. Held A/B/X/Y bits are masked by CONFIG bits 4-7. Runner inputs: arrows/Z/X/C/V keys and on-screen buttons (keys 16-23), IJKL for the secondary stick, standard gamepads (axes 0-3, face buttons, d-pad). Without touch requested, a pointer on the canvas makes a floating stick that follows the finger: left half primary, right half secondary (whole canvas if only one is on). Trap entry costs 22 virtual CPU cycles; restore costs 19, including timer accounting. These are guest timing charges, not host speed measurements. Fault codes: 8001 undefined opcode, 8002 invalid operand, 8003 invalid device write, 8004 HLT with no interrupt able to wake it, 8005 invalid RETI. HLT is DEC PC (opcode 1F), accelerated while waiting for an interrupt. SYS is opcode 00. Service 00 is LOAD8: byte object ID, word destination. SYS LOAD8 0, 0 is five zero bytes. ROM slots 00-FF are reserved immutable bundled objects; only slot zero is assigned. Missing slots set LOAD status 1. Object zero is exactly 256 raw bytes in wasm/bootrom.h. Its atomic copy sets SP=FFFE, PC=0080, IRQ enable=15 and A=0101. Code at 0080 calls the online loader, polls progress with SLEEP, then HCF if no entry point took over. CPU advances PC before the copy, so copied PC bytes win. No address wrapping. Copy costs one guest cycle per byte plus ordinary device work; the instruction itself costs five cycles. Services 01/02 select LOAD16/LOAD32, IDs in A/B:A; SYS LOAD aliases LOAD32. Service 05 LOADN is LOAD32 with a limit: R = most payload bytes the object may write (0 means 65,536). A larger object fails before any write. Online loads are asynchronous: the instruction returns at once and the guest keeps running. Objects arrive in syscall order, one at a time, in pieces that never cross a 256-byte page boundary; each piece is written atomically between instructions. Virtual media delivers 40,000 bytes/s (1500 guest cycles per byte at 60 MHz; 64 KB takes about 1.6 s of virtual time). Writes also cost one guest cycle per byte plus device work. A later object's bytes overwrite an earlier object's. A final JUMP sets PC after the object's last piece. An object is refused (failure) when its bytes plus the bytes still to write for already resolved objects would exceed 65,536; the check runs when the object resolves. An image that still contains a LOAD record also fails. Any failure (missing object, invalid image, rejected device write) stops that object, sets 0037 to 1 and continues with the next one; written pieces stay. 0038-0039 counts bytes still to write across all outstanding objects: FFFF while any object is unresolved (and when the sum saturates), 0 when all are done. The host writes it only when the value changes. Service 03 is PRESENT. The runner starts by executing the zero instruction, then supplies the chosen online ID in B:A before the bootstrap's SYS LOAD32. Default mutable ID 0101 is the monitor, 0103 the width demo. Reserved ROM IDs are not server objects. The runner resolves online IDs through /v9/programs/0xXXXXXXXX. Service 04 FIND (tentative) searches public universe metadata without pausing the guest. X = NUL-terminated ASCII query (empty lists all), Y = result buffer, R = capacity in bytes (at least 16), P = first result index. The host snapshots the arguments, sets the buffer status word to 1 and returns at once. On completion it writes the whole page, then the status: +0 status u16 (1 pending, 0 done, 2 error) +2 count u16 +4 total u32 +8 next index u16 (FFFF = end) +10 bytes written u16 +16 records, 64 bytes each: ID u32, size u32, version u8 (0 unknown), kind u8 (0 data, 1 app, 2 library), flags u16 (bit 0 mutable, bit 1 has source), NUL-terminated ASCII name (51 characters max). Writes cost one guest cycle per byte. Registers are unchanged. Display width ------------- The framebuffer is 65,536 palette-index bytes, separate from guest RAM. OUT/IN keep their linear 16-bit byte offsets. WIDTH does not copy, clear or reallocate pixels. Rows contain WIDTH pixels; height is floor(65536/WIDTH). Unused trailing pixels remain stored and become visible with another width. The existing RGB blending state also keeps its linear pixel offsets. BLIT clips against the current dimensions and uses WIDTH as destination stride. Its width times height must not exceed 65,536; its work still costs one virtual CPU cycle per source pixel, even when clipped. X/Y stay signed. The host samples WIDTH between instructions, resizes its canvas and draws existing pixels in the new shape. A word store changes both bytes together. No automatic guest present or IRQ is generated by a width change. Browser canvas dimension limits may prevent displaying extreme widths/heights; the runner reports display errors rather than changing the guest register. Browser runner -------------- runner.ts owns the VM clock, SYS LOAD resolution, display, audio transport and input. Pages mount it with a root element and optional initial program ID. It boots the ordinary SYS LOAD bootstrap, then runs 400-sample quanta at 48 kHz (120 quanta/s), with a maximum 100 ms catch-up after a delay. It handles intermediate SYS PRESENT calls, draws the latest frame once per browser animation callback, and acknowledges IRQ3 only after that draw. Its measured draw count is canvas submissions, not physical display scans. The machine runs from page load. Browsers start audio only inside a user gesture, so sound waits for a tap or key while CONFIG bit 2 is set; on iOS it then plays through Silent mode. Clearing bit 2 or a reset closes the audio output, releasing the system's playing state. No page sound control. Audio processing stays in SIREN; the runner transports its samples while CONFIG bit 2 is set. Text input is lowercase ASCII, with Backspace 8 and Enter 13. Direction IDs are 16 left, 17 right, 18 up, 19 down; A/B are 20/21 (physical Z/X). Directional and A/B controls hold the DOWN bits until release. Text/paste keys are serialized until the guest clears PRESSED, checked between short CPU slices rather than once per display frame. No host assembly is involved. Eight four-byte touch slots at 0680 contain normalized X/Y (0-255), pressure (1-255 while down, zero when released), and a reserved zero byte. Normalize against the full canvas; guest pixel coordinates are X*WIDTH/256 and Y*height/256. Focus loss releases held controls and touch slots. MIDI uses the existing channel-message queue; SysEx/system messages are not forwarded. Monitor allocation: 1000-7FFF User instructions, appended sequentially with a temporary HCF. 8000 onward Monitor code, strings, workspace, line buffer and mnemonic table. The boot screen reports the assembled monitor size in bytes. CFFE Initial user stack pointer (grows down). D000-D3FF 1024 writable character cells, initially ASCII spaces. E000-E3FF 1024 writable font bytes: 128 glyphs, eight rows, bit 0 left. F000 Monitor stack pointer (grows down). The guest renders cells into the existing 256x256 framebuffer with OUT. No text-mode hardware or host assembler is involved in entered instructions. The page forwards lowercase ASCII and cursor controls through existing key-ID bitmaps, serializing keys until the guest clears each pressed bit. Key IDs 8 and 13 mean Backspace and Enter; 16-19 mean left, right, up and down. The guest edits character cells at a visible cursor. Enter assembles the current 32-character screen row (after an optional four-digit prompt), then moves to the next row. Output, Enter and Down past the bottom row scroll the screen up one row. The monitor requests sound, text keyboard and directions. Audio starts on a user gesture and transports the VM's stereo output. Supported prompt syntax is documented in MANUAL.txt. Numbers are unsigned 16-bit decimal or hexadecimal ($ / 0x). MOV and MOVB support direct and register-indirect memory operands, using *address / *register or brackets. MOVB uses the low byte of a register; it rejects immediate values above 255. LDB/LDW destination-register, address load a byte/word; LDB zero-extends. STB/STW address, value store a byte/word. Their address operand accepts a bare number or a register holding the address, as well as explicit memory syntax. They assemble to existing MOV/MOVZX instructions. RETI assembles and advances the prompt without immediate execution. It runs normally when reached by an IRQ handler. The monitor initializes saved IRQ permissions to 0F and restores them on trap entry, so configured timers keep running at the prompt. Handlers must preserve any registers they use. Memory-to-memory moves and labels remain open. Branches and PC writes can bypass the trailing HCF; Reset discards RAM and boots again. All addresses, layouts and syntax remain tentative. CPU semantics ============= Eight word registers, with `.lo`/`.hi` byte aliases. MOV and zero/sign-extending loads are flag-neutral. Arithmetic, logical operations, shifts, multiply, divide and remainder retain the existing N/Z/C/V meanings: C is carry on ADD, borrow on SUB/CMP. Compare does not store a result. Signed branches use N xor V; BMI/BPL inspect N alone, BVS/BVC inspect V alone. Regular operations: MOV, ADD, SUB, AND, OR, XOR, CMP, SHL, SHR, SAR, MUL, DIV, MOD, LEA, MOVZX, MOVSX. Byte OUT/IN write/read framebuffer pixels. Memory operands include [register], [register+signed-byte], [register++], [--register], and absolute word addresses. LEA computes an address without reading memory or changing flags. Register copies are one byte; common byte loads/stores are two bytes. Full operand forms remain available through FF. General math extensions (word register pairs, destination a..sp, source a..pc): SMULSHIFT dst,src,shift computes the exact signed 16x16 product, arithmetic shifts it right by an immediate 0..31 (rounding toward negative infinity), and stores the low word. N/Z describe that word; C=0; V=1 iff the shifted signed result is outside -32768..32767. Shift 16 is signed high product. UMULHI dst,src stores the unsigned product's high word; N/Z describe it, C=V=0. ADC adds source and incoming C; SBC subtracts source and incoming C (borrow). N/Z describe the low word, C is unsigned carry/borrow, V indicates signed overflow including incoming C. MOV can preserve carry between limbs. These operations cost 4/3/3/3 bytes/cycles respectively. PC as source means the continuation address. Invalid register descriptors or shifts trap before any architectural state, flags, instructions or cycles change. Older valid v6 byte sequences retain their meaning; v5 does not acquire these operations. DIV/MOD remain unsigned word operations; there is no new division mode. ABS dst computes signed absolute value, N/Z describe the low word, C=0, V=1 only for -32768 (result 8000 hex). SMIN/SMAX dst,src select the signed minimum/maximum; N/Z describe the result, C=V=0. All three cost 3 bytes/cycles, use the same word-register restrictions, and leave all other registers alone. Timer IRQs ---------- Tick k of a channel happens at cycle ceil(k × 60,000,000 / Hz), counted from reset. Writing the rate's high byte commits it; the grid does not move, so the next tick is the next grid point of the new rate. Rate 0 turns the channel off and drops its pending tick. A rate above 600 traps 8003. Each channel holds one pending tick. Further ticks before delivery add one to its missed count. IRQ3 is display-gated: its tick is dropped (and counts as missed) until the display has shown the frame of the last SYS PRESENT. The CPU checks for IRQs only between instructions. It delivers the lowest- numbered channel that is pending, enabled in 0618, and lower-numbered than every set bit of 0619. So IRQ0 has the highest priority, a higher channel preempts a lower handler, a lower one waits, and no channel preempts itself. A masked tick stays pending. Entry costs 4 cycles. It pushes PC, then flags, so [sp] = flags and [sp+2] = PC, sets the channel's bit in 0619 and jumps to the vector. A vector is only an address; 0000 runs the code in the registers. RETI pops flags and PC and clears the lowest set bit of 0619. A pending IRQ is entered right after RETI, before any instruction of the interrupted code. BLIT is atomic: up to 65,536 cycles (1.1 ms) of IRQ latency. SLEEP can be interrupted; it continues after RETI and ends at the cycle it would have ended at without the interruption. Byte encoding ============= All numbers here are hex; multibyte extensions are little endian. Register indices: 0=a,1=b,2=x,3=y,4=p,5=r,6=sp,7=pc. Opcode families --------------- 00 SYS service: 00 LOAD8 (ID byte, destination word), 01 LOAD16, 02 LOAD32 (also SYS LOAD), 03 PRESENT, 04 FIND, 05 LOADN 01 undefined 02 RETI 03 CALL absolute word 04 CALL packed word source (destination bits must be zero) 05 SLEEP unsigned word duration 06 BLIT 07 HCF (guest trap or host halt) 08–0F MOV word immediate: destination = opcode&7, two payload bytes 10–17 INC word register 18–1F DEC word register 20–2E Short branch + signed byte displacement 2F RET 30–3E Absolute branch + word target 3F stack/math descriptor: 00=two-byte NOP alias, 08–0F=PUSH register, 10–17=POP register; 20=SMULSHIFT, 21=UMULHI, 22=ADC, 23=SBC, 24=ABS, 25=SMIN, 26=SMAX. Math has a following register descriptor: bits0–2=destination 0–6, bits3–5=source 0–7, bits6–7 MUST be zero. ABS requires bits3–7 zero. SMULSHIFT then has one unsigned shift byte 00–1F. Other descriptors/shift bytes trap atomically. 40–7F MOV word register/register: destination=(opcode>>3)&7, source=opcode&7. 40 (MOV a,a) is the canonical one-byte NOP. 80–AF MOVZX byte memory -> word register: dst=(opcode-80)>>3, base=opcode&7, followed by signed displacement byte. dst is 0–5. B0–DF MOV low byte -> byte memory: src=(opcode-B0)>>3, base=opcode&7, followed by signed displacement byte. src is 0–5. E0–EE packed word operation + descriptor/extensions EF stores + store descriptor/extensions F0–FE packed low-byte operation + descriptor/extensions FF original full operand encoding (separate old-semantic decoder) Branch index (added to 20 or 30): 0 BRA,1 BZ,2 BNZ,3 BLT,4 BGE,5 BLO,6 BHS,7 BLE,8 BGT,9 BLS,A BHI, B BMI,C BPL,D BVS,E BVC. Packed operation index (low nibble): 0 MOV,1 ADD,2 SUB,3 AND,4 OR,5 XOR,6 CMP,7 SHL,8 SHR,9 SAR, A MUL,B DIV,C LEA word / OUT byte,D MOVZX word / IN byte,E MOD. LEA rejects auto-update and non-address sources. MOVZX targets a non-PC word register and consumes a BYTE source even though its opcode is in the E family. OUT descriptor destination is the source byte register; its packed source identifies the framebuffer destination address. IN reads a framebuffer byte. ALU/source descriptor --------------------- Bits 0–2: destination register (low-byte alias for F operations). Bits 3–5: source ID. Bits 6–7: source category: 00 Register. ID 0–7 selects a word or corresponding low byte. 01 Literal: ID0 = unsigned byte payload ID1 = 0; ID2 = 1; ID3 = 2; ID4 = 4; ID5 = 8; ID6 = 0F; ID7 = FF There is NO overlap between literal-class and byte-payload descriptors. 10 Memory [register+signed-byte], ID selects base; one displacement payload. 11 Extended: ID0 = absolute byte address payload ID1 = absolute word address payload ID2 = [register++] with base-index payload 0–7 ID3 = [--register] with base-index payload 0–6 (PC update forbidden) ID4 = word immediate payload, word operations only ID5–7 trap Widths and source/destination validity are checked before execution. SP/PC and high-byte cases missing a compact form use the full operand escape, not an assembler feature omission. MOV to PC takes effect as an ordinary jump. EF store descriptor ------------------- Top two bits select the family: 00 Word register -> [base+signed-byte] bits0–2=base, bits3–5=source word register, displacement payload. 01 Immediate -> [base+signed-byte] bits0–2=base; bits3–4=literal kind; bit5=word (0=byte); displacement payload, then literal payload when required. 10 Immediate -> absolute address bit0=word address (0=byte address); bits1–2=literal kind; bits3–4 MUST be zero; bit5=word data (0=byte); address payload, then literal payload when required. Literal kinds for 01/10: 0 zero, no data payload 1 all ones for selected width, no data payload 2 unsigned byte payload (zero-extended for word data) 3 word payload, WORD data only; byte data with kind3 traps 11 Register -> absolute/auto memory bits3–5=source register; bits0–2=subtype: 0 word to absolute byte address 1 word to absolute word address 2 word to [base++] 3 word to [--base] 4 low byte to absolute byte address 5 low byte to absolute word address 6 low byte to [base++] 7 low byte to [--base] Auto forms have one base-index payload; pre-PC update is forbidden. Escapes ------- FF consumes an original opcode below 80 hex, then ONLY the original operand semantics. It does not reinterpret old 6C/6D as new LEA/MOVZX or enable invalid old word OUT/IN. Original operand widths, validation, alias transaction rules and immediate interpretation remain. This supports uncommon high-byte loads, stores, signed widening and old auto-update forms without doubling fast opcodes.