;-------------------------------------------------------- ; NABU PC ROM - U53-90020060-RevA-2732 ; Comprehensive Annotated Version by DJ Sures (1/12/2026) ;-------------------------------------------------------- ; ; ==================================================================== ; DETAILED ROM OPERATION DESCRIPTION ; ==================================================================== ; ; OVERVIEW: ; --------- ; This ROM is the bootstrap firmware for the NABU PC computer. It performs ; hardware initialization, comprehensive self-tests, loads the operating ; system from an external source (typically via cable TV adapter), and ; transfers control to the downloaded program. ; ; ; OPERATIONAL FLOW: ; ----------------- ; ; 1. COLD START / POWER-ON INITIALIZATION: ; -------------------------------------- ; On power-up or hardware reset, the Z80 CPU begins execution at address ; $0000 (the start of this ROM). ; ; a) Hardware Initialization: ; - Control register ($00): Sets internal video mode, all LEDs ON ; - Stack pointer: Initialized to $FFEE (grows downward) ; - VDP (TMS9918): 8 registers initialized for video display ; - PSG (AY-3-9010): Sound chip initialized to default state ; - Keyboard (8251A UART): Serial controller reset and configured ; - RAM variables: Cleared (bytes $FFEF through $FFF7) ; ; b) Display Initialization: ; - Character pattern data (1280 bytes) loaded into VDP VRAM ; - Screen cleared (filled with spaces) ; - NABU logo displayed ; ; c) Warm Reset Detection: ; - Checks memory locations $FFFE-$FFFF for magic value $A55A ; - If found, skips self-tests and goes directly to OS loading ; ; ; 2. SELF-TEST SEQUENCE (if not warm reset): ; ---------------------------------------- ; ; a) Keyboard Sequence Detection (Debug Mode): ; - Polls keyboard for special sequence: Press "1" key 10 times ; - If detected within timeout, sets DEBUG_FLAG ($FFEF) ; - Debug mode enables additional error reporting ; ; b) ROM Checksum Verification: ; - Calculates 16-bit sum of all ROM bytes ($0000-$0FFD) ; - Compares result with stored checksum at $0FFE-$0FFF ; - Expected checksum: Low=$CF, High=$3B ; - Failure: Displays "ROM FAILURE" error ; ; c) VDP VRAM Test: ; - Writes pattern 0-255 to VRAM starting at $C000 ; - Reads back and verifies pattern ; - Writes complemented pattern, verifies complement ; - Tests VRAM read/write reliability ; - Failure: Displays "VIDEO FAILURE" error ; ; d) RAM Test: ; - Tests RAM from $2000 to approximately $FEFF ; - Writes pattern 0-255 sequentially ; - Reads back and verifies each byte ; - Complements each byte, writes, reads back, complements again ; - Verifies both original and complemented values ; - Tests each bit for stuck-at faults ; - Failure: Displays "RAM FAILURE" error ; ; e) PSG Register Test: ; - Tests PSG registers 0-14 ; - Writes patterns and reads back (only some registers are readable) ; - Uses mask table to verify only readable bits ; - Failure: Displays "SOUND FAILURE" error ; ; f) Keyboard Hardware Test: ; - Polls keyboard for key codes in range $91-$94 ; - Verifies keyboard controller responds correctly ; - Failure: Displays "KEYBOARD FAILURE" error ; ; ; 3. WARM RESET / OS LOADING SEQUENCE: ; ---------------------------------- ; ; a) HCCA Communication Setup: ; - HCCA (Hardware Communication/Control A) is the interface to the ; external adapter (typically cable TV adapter) ; - Tests HCCA status with command $83 ; - Sends reset command ($01) and verifies response ; - Tests commands $82, $10, $E1 to verify adapter communication ; ; b) Channel Code Entry (if required): ; - If bit 7 of HCCA_STATUS is set, prompts for channel code ; - Channel code: 4 hexadecimal digits (stored at $FFF9-$FFFC) ; - 5th byte is checksum (stored at $FFFB, overlaps with code) ; - Converts channel code to 16-bit value ; - Sends channel code to HCCA adapter (commands $85) ; ; c) OS Load Command Sequence: ; - Sends command $81 to HCCA ; - Sends sequence: $8F, $05 (OS load request) ; - Waits for acknowledgment ; ; d) ROM Disable: ; - Sets bit 0 of control register (disables ROM) ; - CPU can no longer execute ROM code ; - Display "PLEASE WAIT" message ; ; ; 4. OS LOADING PROCESS: ; ------------------- ; ; The operating system is loaded in sectors via HCCA communication. ; Each sector contains a 16-byte block with header and data. ; ; a) OS Header Structure (initialized at $1000): ; ; Address Size Variable Initial Value Description ; ------- ---- ----------------- ------------- ----------- ; $1000 1 OS_HEADER_FLAG0 0 Header flag byte 0 ; $1001 1 OS_HEADER_FLAG1 0 Header flag byte 1 ; $1002 1 OS_HEADER_FLAG2 1 Header flag byte 2 ; $1003 1 OS_SECTOR_COUNT 0 Current sector number ; $1004 2 OS_DATA_POINTER $100B Pointer to receive buffer ; $1006 2 OS_SIZE_ACCUM (varies) Size of current block ; $1008 2 OS_DEST_POINTER $140D Destination for OS data ; $100A 1 OS_BLOCK_FLAGS (from data) Block flags from header ; ; b) OS Data Reception Protocol: ; ; For each sector: ; 1. Send command $84 to HCCA (request data transfer) ; 2. Send 4-byte header: [OS_SECTOR_COUNT, OS_HEADER_FLAG0, FLAG1, FLAG2] ; 3. Wait for start byte ($91) from HCCA ; 4. Send acknowledgment ($10) ; 5. Receive data bytes via HCCA (using PSG interrupt detection) ; 6. Store received data at OS_DATA_POINTER ($100B) ; 7. Process block header: ; - Read byte at offset $0B (11) = block flags ; - Copy 16 bytes from offset $10 (16) to OS_DEST_POINTER ; - Actually, the structure is: [11-byte header][5-byte data] ; - Total block size = 16 bytes ; 8. Update OS_DEST_POINTER for next block ; 9. Check block flags: Bit 4 set = last sector ; 10. If not last, increment OS_SECTOR_COUNT and repeat ; ; c) Sector Structure: ; ; Each sector received at OS_DATA_POINTER ($100B) has the following layout: ; Bytes 0-10: Header/metadata (11 bytes) ; Byte 11: Block flags (bit 4 = last sector flag) ; Bytes 12+: OS code/data (variable size, starting at offset $10) ; ; The actual data size for each block is tracked in OS_SIZE_ACCUM ($1006) ; during the receive process. The data is copied from offset $10 (byte 16) ; of the received block to OS_DEST_POINTER (starting at $140D). ; ; NOTE: The sector structure uses offset $10 (16) to skip the header and ; flags bytes, then copies the remaining data bytes. The exact size per ; sector appears to vary based on the OS_SIZE_ACCUM value set during reception. ; ; d) Data Storage: ; ; - Received blocks are temporarily stored starting at $100B ; - Actual OS code is copied to destination starting at $140D ; - OS_DEST_POINTER ($1008) tracks where next block is written ; - OS_SIZE_ACCUM ($1006) accumulates total size transferred ; - Total size stored at $140B-$140C (OS_SIZE_LOW/HIGH) ; ; ; 5. PROGRAM LAUNCH: ; --------------- ; ; After all sectors are received: ; ; a) Warm Reset Flag Set: ; - Writes $A5 to $FFFE (WARM_RESET_MAGIC_L) ; - Writes $5A to $FFFF (WARM_RESET_MAGIC_H) ; - This allows future boots to skip self-tests ; ; b) PSG Finalization: ; - Configures PSG register 7 with value $7F ; - Enables I/O ports, disables sound ; ; c) Control Transfer: ; - Executes: JP $140F (OS_JUMP_ADDRESS) ; - CPU jumps to address stored at $140F ; - This address is set by the downloaded OS program ; - Typically points to the OS entry point (often $140D or nearby) ; ; d) Program Execution: ; - Downloaded program takes control ; - ROM is disabled (control register bit 0 = 1) ; - CPU executes code from RAM ; - Program can access all hardware registers ; ; ; MEMORY MAP: ; ----------- ; ; $0000-$0FFF: ROM (this code - 4KB) ; $1000-$100A: OS header workspace ; $100B-$140C: OS loading workspace (temporary buffer and size tracking) ; $140D-$xxxx: OS program destination (downloaded code stored here) ; $140F: OS jump address (program entry point) ; $2000-$FEFD: Available RAM (tested during self-test) ; $FFEE-$FFFF: System variables and stack ; ; ; HCCA COMMUNICATION PROTOCOL: ; ----------------------------- ; ; The HCCA (port $80) communicates with external adapter using these commands: ; ; $01: Reset HCCA ; $81: OS load command (sends $8F, $05 sequence) ; $82: Test command ; $83: Status check ; $84: Request data transfer (OS loading) ; $85: Channel code command ; $06: Data transfer acknowledgment ; $8F: OS load sequence start ; $05: OS load sequence continuation ; $10: Acknowledgment ; $91: Data start byte ; $E1: Data end marker ; $E4: Verify response ; ; ; ERROR HANDLING: ; --------------- ; ; Errors during self-tests increment counters at: ; - $FFF2: ROM error count ; - $FFF3: Video error count ; - $FFF4: RAM error count ; - $FFF5: Sound error count ; - (Keyboard/Adapter errors handled differently) ; ; Errors display messages and may halt execution or retry operations. ; ; ==================================================================== ; ; CODE FLOW SUMMARY: ; ------------------ ; 1. Hardware initialization (VDP, PSG, Keyboard, Stack) ; 2. Warm reset detection (checks for magic value at end of RAM) ; 3. Self-tests (if not warm reset): ; - Keyboard sequence detection (debug mode activation) ; - ROM checksum verification ; - VDP VRAM test (write/read/verify pattern) ; - RAM test (write 0-255 pattern, verify, complement, verify) ; - PSG register test ; - Keyboard hardware test ; 4. Warm reset routine (HCCA communication, channel code entry) ; 5. OS loading from external source via HCCA ; 6. Launch downloaded software ; ;-------------------------------------------------------- ; Hardware Register Definitions ;-------------------------------------------------------- R_CONTROL EQU $00 ; U6 74LS273 - Control register ; Bit 0: ROM Enable (0=enabled) ; Bit 1: Video Switch (1=internal/VDP) ; Bit 2: LED1 Green (0=ON) ; Bit 3: LED2 Red Alert (0=ON) ; Bit 4: LED3 Yellow Pause (0=ON) ; Bit 5: Printer Strobe (1=active) R_PSG_DATA EQU $40 ; U25 AY-3-9010 - PSG data register R_PSG_ADDR_LATCH EQU $41 ; U25 AY-3-9010 - PSG address latch R_HCCA EQU $80 ; U14 TR1863P - Hardware Communication/Control A ; Used for communication with external adapter R_KBD_DATA EQU $90 ; U4 8251A - Keyboard data register R_KBD_CTRL_STATUS EQU $91 ; U4 8251A - Keyboard control/status register R_VDP_VRAM_DATA EQU $A0 ; U2 TMS9918 - Video Display Processor VRAM data R_VDP_CTRL_ADDR EQU $A1 ; U2 TMS9918 - VDP control/address register R_PRN_WR EQU $B0 ; U11 74LS273 - Printer write register ;-------------------------------------------------------- ; PSG Internal Register Definitions (AY-3-9010) ;-------------------------------------------------------- PSG_R7_ENABLE EQU $07 ; PSG register 7: Enable Register PSG_R14_INT_MASK EQU $0e ; PSG register 14: Interrupt Mask PSG_R15_STATUS EQU $0f ; PSG register 15: Status ;-------------------------------------------------------- ; RAM Variable Definitions ;-------------------------------------------------------- STACK_TOP EQU $FFEE ; Top of stack in RAM (grows downward) CTRL_BITS EQU $FFEE ; Current control register settings (shared with stack top) DEBUG_FLAG EQU $FFEF ; Debug mode flag (set by special keyboard sequence) ; Test status and control variables TEST_STATUS_FLAG EQU $FFF0 ; Test status flag (bit 0=skip control sequence) CTRL_SEQUENCE_STATE EQU $FFF1 ; Control sequence state tracking ERROR_COUNT_ROM EQU $FFF2 ; Error counter for ROM failures ERROR_COUNT_VIDEO EQU $FFF3 ; Error counter for video failures ERROR_COUNT_RAM EQU $FFF4 ; Error counter for RAM failures ERROR_COUNT_SOUND EQU $FFF5 ; Error counter for sound failures CTRL_STATE_FLAG EQU $FFF6 ; Control state flag (bit 0=skip LED sequence) CTRL_SEQUENCE_INDEX EQU $FFF7 ; Index into control register sequence table HCCA_STATUS EQU $FFF8 ; HCCA communication status byte CHANNEL_CODE_BUF EQU $FFF9 ; Channel code input buffer (4 bytes) CHANNEL_CODE_CHECK EQU $FFFB ; Channel code checksum CHANNEL_CODE_PTR EQU $FFFC ; Channel code pointer (2 bytes) WARM_RESET_MAGIC_L EQU $FFFE ; Warm reset magic value low byte ($A5) WARM_RESET_MAGIC_H EQU $FFFF ; Warm reset magic value high byte ($5A) ; OS loading workspace (in RAM at $1000+) OS_HEADER_FLAG0 EQU $1000 ; OS image header flag 0 OS_HEADER_FLAG1 EQU $1001 ; OS image header flag 1 OS_HEADER_FLAG2 EQU $1002 ; OS image header flag 2 OS_SECTOR_COUNT EQU $1003 ; OS sector counter OS_DATA_POINTER EQU $1004 ; OS data pointer (2 bytes) OS_SIZE_ACCUM EQU $1006 ; OS size accumulator (2 bytes) OS_DEST_POINTER EQU $1008 ; OS destination pointer (2 bytes) OS_BLOCK_FLAGS EQU $100A ; OS block flags OS_START_ADDR EQU $100B ; OS start address OS_SIZE_LOW EQU $140B ; OS total size low byte OS_SIZE_HIGH EQU $140C ; OS total size high byte OS_ENTRY_POINT EQU $140D ; OS entry point (possibly IOS/main menu) OS_JUMP_ADDRESS EQU $140F ; Final jump address to launch OS RAM_TEST_START_ADDR EQU $2000 ; Starting address for RAM test ;-------------------------------------------------------- ; VDP Register Initialization Data ;-------------------------------------------------------- .ORG $0000 ;-------------------------------------------------------- ; CODE START: System Initialization ;-------------------------------------------------------- ; This is the entry point after power-on reset or cold start. ; The ROM performs hardware initialization, self-tests, and then ; loads the operating system from external storage. ;-------------------------------------------------------- SYSTEM_INIT: ; Configure control register: Internal video, all LEDs ON ; $02 = bit 1 set (internal video), bits 2-4 clear (LEDs ON) LD a,$02 LD (CTRL_BITS),a OUT (R_CONTROL),a ; Initialize stack pointer to top of RAM LD sp,STACK_TOP ; Initialize VDP registers ; The VDP (TMS9918) needs 8 register writes to configure video modes, ; sprite tables, color tables, etc. LD hl,VDP_REG_INIT_DATA LD b,$08 ; 8 registers to initialize LD c,R_VDP_CTRL_ADDR ; VDP control/address port vdp_init_loop: ; OUTI: Output (HL) to port (C), increment HL, decrement B ; This outputs the register value data OUTI ; Set bit 7 to indicate register write mode (not VRAM access) LD a,b OR $80 OUT (R_VDP_CTRL_ADDR),a ; Clear bit 7 from A (for next iteration) AND $7f JR nz,vdp_init_loop ;-------------------------------------------------------- ; Zero RAM Variables ;-------------------------------------------------------- ; Clear debug flag and test status variables ; Using LDIR to efficiently clear multiple bytes LD hl,$ffef ; Start at DEBUG_FLAG LD de,$fff0 ; Destination (one byte ahead) LD bc,$0009 ; 9 bytes to clear XOR a ; A = 0 LD (hl),a ; Clear first byte LDIR ; Block copy: clear $FFEF through $FFF7 ;-------------------------------------------------------- ; Initialize PSG and Keyboard ;-------------------------------------------------------- ; Initialize PSG (Programmable Sound Generator) ; This configures the AY-3-9010 sound chip CALL psg_init ; Initialize keyboard controller (8251A UART) ; Multiple writes ensure proper initialization XOR a CALL kbd_ctrl_write ; Write $00 (clear) CALL kbd_ctrl_write ; Multiple clears CALL kbd_ctrl_write CALL kbd_ctrl_write CALL kbd_ctrl_write LD a,$40 ; $40 = Internal reset command CALL kbd_ctrl_write LD a,$4e ; $4e = Mode: 8N1, 16x clock CALL kbd_ctrl_write LD a,$04 ; $04 = Command: Enable receive only CALL kbd_ctrl_write ; Display splash screen (load character patterns, clear screen, show logo) CALL load_char_patterns ; Check for warm reset (looks for magic value $A55A at end of RAM) CALL check_warm_reset_flag JP z,warm_reset_entry ; If warm reset detected, skip tests ;-------------------------------------------------------- ; Keyboard Sequence Detection (Debug Mode Activation) ;-------------------------------------------------------- ; This section polls the keyboard looking for a specific sequence: ; Pressing key "1" multiple times (10 times) activates debug mode. ; If no key sequence is detected within timeout, continues to tests. ;-------------------------------------------------------- keyboard_sequence_check: ; Set LEDs to indicate we're waiting for key sequence LD a,$3a ; $3a toggles LED states OUT (R_CONTROL),a ; Initialize sequence detection variables LD h,$0a ; H = keypress counter (10 presses needed) LD c,$20 ; PSG I/O port A value (enables keyboard interrupt) LD de,$e000 ; Timeout counter (57344 iterations) LD b,$01 ; Timeout multiplier kbd_poll_timeout: ; Decrement timeout counter CALL check_timeout JR z,kbd_check_key ; Timeout reached, check for key DJNZ kbd_check_key ; Decrement B counter JR rom_checksum_test ; Timeout expired, skip to tests kbd_check_key: ; Check if keyboard has data available ; Uses PSG I/O ports to check interrupt status CALL check_kbd_interrupt JR z,kbd_poll_timeout ; No key available, continue polling ; Read key from keyboard IN a,(R_KBD_DATA) CP 1 ; Key code 1 = "1" key JR z,kbd_key1_pressed ; Handle "1" keypress CP $3A ; Key code $3A = ":" key (alternate check) JR nz,kbd_poll_timeout ; Not recognized key, continue polling ; Check if counter is in valid state (bit 0 must be set) BIT 0,h JR z,kbd_poll_timeout ; Invalid state, continue polling kbd_counter_decrement: ; Decrement keypress counter DEC h JR z,kbd_sequence_complete ; Counter reached 0, sequence complete JR kbd_poll_timeout ; Continue polling for more keypresses kbd_key1_pressed: ; "1" key was pressed ; Only process if counter is in valid state (bit 0 clear) BIT 0,h JR nz,kbd_poll_timeout ; Invalid state, ignore JR kbd_counter_decrement ; Valid, decrement counter kbd_sequence_complete: ; Special key sequence detected - activate debug mode LD hl,DEBUG_FLAG INC (hl) ; Set debug flag ;-------------------------------------------------------- ; ROM Checksum Verification Test ;-------------------------------------------------------- ; Calculates the sum of all ROM bytes and compares against ; stored checksum values at $FFFE-$FFFF. This verifies ROM integrity. ;-------------------------------------------------------- rom_checksum_test: ; Check if we should skip control register updates LD a,(CTRL_STATE_FLAG) BIT 0,a JR nz,rom_checksum_calc ; Skip if flag set ; Update control register with next sequence value CALL get_ctrl_sequence_value OUT (R_CONTROL),a rom_checksum_calc: ; Initialize checksum calculation XOR a LD (WARM_RESET_MAGIC_H),a ; Clear checksum storage LD ix,$0FFD ; Start at last ROM byte (before checksum) LD de,$FFFF ; DE = -1 (for decrementing) XOR a LD h,a ; H = checksum high byte LD l,a ; L = checksum low byte LD b,a ; B = 0 (not used but cleared) rom_checksum_loop: ; Add current ROM byte to checksum LD c,(ix+0) ; Load ROM byte ADD hl,bc ; Add to 16-bit checksum ADD ix,de ; Decrement pointer (IX = IX - 1) JR c,rom_checksum_loop ; Continue until wrap-around ; Verify low byte of checksum LD a,($0FFE) ; Expected low byte CP l ; Compare with calculated JR nz,rom_checksum_error ; Mismatch! ; Verify high byte of checksum LD a,($0FFF) ; Expected high byte SUB h ; Compare with calculated JR z,rom_checksum_ok ; Checksum valid! rom_checksum_error: ; ROM checksum failed - display error CALL handle_error rom_checksum_ok: ; Checksum passed - continue to next test CALL update_test_status ; Prepare for VDP VRAM test XOR a OUT (R_VDP_CTRL_ADDR),a ; Reset VDP address LD a,$40 OUT (R_VDP_CTRL_ADDR),a ;-------------------------------------------------------- ; VDP VRAM Test ;-------------------------------------------------------- ; Tests Video RAM by writing a pattern (0-255), reading it back, ; complementing it, and verifying the complement. This ensures ; VRAM is working correctly. ;-------------------------------------------------------- vdp_vram_test: ; Initialize test variables XOR a ; Start with pattern value 0 LD de,$0001 ; Increment value LD ix,$C000 ; VDP VRAM test area start vdp_write_pattern_loop: ; Write pattern value to VRAM OUT (R_VDP_VRAM_DATA),a INC a ; Next pattern value (0-255) ADD ix,de ; Increment address JR nc,vdp_write_pattern_loop ; Continue until wrap ; Reset for read/verify phase LD ix,$C000 ; Reset to start of test area XOR a ; Reset pattern value LD c,a ; C = expected value LD b,a ; B = 0 LD h,a ; H = 0 (address high) LD l,a ; L = 0 (address low) vdp_verify_pattern_loop: ; Set VDP address for read LD a,l ; Low byte of address OUT (R_VDP_CTRL_ADDR),a LD a,h ; High byte of address OUT (R_VDP_CTRL_ADDR),a ; Read value from VRAM IN a,(R_VDP_VRAM_DATA) CP c ; Compare with expected value JR nz,vdp_vram_error ; Mismatch! ; Set address for write (with bit 6 set for write mode) LD a,l OUT (R_VDP_CTRL_ADDR),a LD a,h OR $40 ; Set bit 6 for write mode OUT (R_VDP_CTRL_ADDR),a ; Write complemented value LD a,c CPL ; Complement (invert all bits) OUT (R_VDP_VRAM_DATA),a ; Read back complemented value LD a,l OUT (R_VDP_CTRL_ADDR),a LD a,h OUT (R_VDP_CTRL_ADDR),a IN a,(R_VDP_VRAM_DATA) CPL ; Complement again to get original CP c ; Should match original JR nz,vdp_vram_error ; Mismatch! vdp_verify_continue: ; Increment test variables INC c ; Next expected value INC hl ; Next address ADD ix,de ; Increment address pointer JR nc,vdp_verify_pattern_loop ; Continue until done ; VDP test passed CALL load_char_patterns ; Refresh display CALL display_test_results CALL update_test_status vdp_vram_error: ; VDP VRAM error detected CALL handle_error JR vdp_verify_continue ; Continue after error (doesn't return) ;-------------------------------------------------------- ; RAM Test ;-------------------------------------------------------- ; Tests system RAM by writing pattern 0-255, verifying, then ; complementing and verifying again. Tests memory from $2000 ; to approximately $FEFF. ;-------------------------------------------------------- ram_test: ; Initialize test variables XOR a ; Start pattern value = 0 LD ix,RAM_TEST_START_ADDR ; Start address = $2000 LD hl,$2100 ; Stop condition (will overflow at $FF00) LD de,$0001 ; Increment step ram_write_loop: ; Write pattern value to RAM LD (ix+0),a INC a ; Next pattern value (0-255, wraps) INC ix ; Next memory location ADD hl,de ; Update counter JR nc,ram_write_loop ; Continue until overflow ; Initialize for verify phase XOR a ; Reset pattern value LD c,a ; C = expected value LD hl,$2100 ; Reset counter LD ix,RAM_TEST_START_ADDR ; Reset address LD b,a ; B = 0 ram_verify_loop: ; Read value from RAM LD a,(ix+0) CP c ; Compare with expected JR nz,ram_test_error ; Mismatch! ; Complement and write back CPL ; Complement value LD (ix+0),a ; Write complemented value LD a,(ix+0) ; Read back CPL ; Complement again CP c ; Should match original JR z,ram_location_ok ; OK! ram_test_error: ; RAM error detected CALL handle_error ram_location_ok: ; This RAM location is OK, continue INC ix ; Next address INC c ; Next expected value ADD hl,de ; Update counter JR nc,ram_verify_loop ; Continue until done ; RAM test passed CALL update_test_status ;-------------------------------------------------------- ; PSG Register Test ;-------------------------------------------------------- ; Tests PSG registers by writing values and reading them back. ; Not all PSG registers are readable, so uses a mask table to ; verify only readable bits. ;-------------------------------------------------------- psg_register_test: ; First phase: Clear all PSG registers LD b,$10 ; 16 registers (0-15) LD c,$00 ; Clear value psg_clear_loop: LD a,b DEC a ; Register number (15 down to 0) OUT (R_PSG_ADDR_LATCH),a ; Select register LD a,c ; Value to write (0) OUT (R_PSG_DATA),a ; Write to register DJNZ psg_clear_loop ; Second phase: Test each register with bit pattern LD c,$00 ; Start with register 0 psg_test_register_loop: LD b,$ff ; Bit pattern to test (all bits set) psg_test_bit_pattern_loop: ; Write pattern to register LD a,c OUT (R_PSG_ADDR_LATCH),a ; Select register LD a,b OUT (R_PSG_DATA),a ; Write pattern ; Read back (only works for some registers) IN a,(R_PSG_DATA) CALL psg_verify_readback ; Verify using mask table CALL nz,handle_error ; Error if verification fails ; Shift pattern right for next bit test SRL b ; Shift right JR c,psg_test_bit_pattern_loop ; Continue while bits set ; Move to next register INC c LD a,$0e ; Test up to register 14 CP c JR nz,psg_test_register_loop ; PSG test complete JR keyboard_hardware_test psg_verify_readback: ; Verify PSG readback value using mask table ; Some PSG registers aren't readable, so we mask out ; non-readable bits before comparing LD hl,PSG_READBACK_MASKS LD e,c LD d,$00 ADD hl,de ; HL = address of mask for register C AND (hl) ; Mask read value LD e,a ; Save masked read value LD a,b AND (hl) ; Mask written value CP e ; Compare RET ; Z flag indicates match PSG_READBACK_MASKS: ; Mask table for PSG register readback verification ; $FF = all bits readable, $0F = only low 4 bits readable, etc. DB $FF,$0F,$FF,$0F,$FF,$0F,$1F,$FF DB $1F,$1F,$1F,$FF,$FF,$0F ;-------------------------------------------------------- ; Keyboard Hardware Test ;-------------------------------------------------------- ; Tests keyboard hardware by checking for specific key codes. ; Looks for keys in range $91-$94. ;-------------------------------------------------------- keyboard_hardware_test: CALL update_test_status CALL psg_init ; Reinitialize PSG after test ; Poll keyboard multiple times LD de,$0000 ; Poll counter LD b,$05 ; 5 poll attempts kbd_hw_poll_loop: LD c,$20 ; PSG I/O port A value CALL check_kbd_interrupt JR z,kbd_hw_no_key ; No key available ; Key available - read it IN a,(R_KBD_DATA) CP $95 ; Check if >= $95 JR nc,kbd_hw_no_key ; Out of range CP $91 ; Check if < $91 JR c,kbd_hw_no_key ; Out of range CP $94 ; Check if == $94 JR z,kbd_hw_test_ok ; Valid test key! ; Key in range but not $94 - error JR kbd_hw_test_error kbd_hw_no_key: ; No key or invalid key - decrement poll counter DEC de LD a,d OR e JR nz,kbd_hw_poll_loop ; Continue polling DJNZ kbd_hw_poll_loop ; Outer loop JR kbd_hw_test_error ; Timeout - error kbd_hw_test_ok: ; Keyboard hardware test passed CALL update_test_status JR warm_reset_entry kbd_hw_test_error: ; Keyboard hardware test failed CALL handle_error ;-------------------------------------------------------- ; Warm Reset Entry Point ;-------------------------------------------------------- ; This is entered either from: ; 1. Initial warm reset detection (magic value found) ; 2. After successful self-tests ; ; Performs HCCA communication checks, channel code entry if needed, ; and then loads the OS. ;-------------------------------------------------------- warm_reset_entry: ; Check HCCA status IN a,(R_HCCA) CALL check_hcca_status JR z,warm_reset_continue ; Status OK ; HCCA status check failed CALL handle_error JR warm_reset_fail_handler warm_reset_fail_handler: ; Error handler for warm reset failures LD a,$05 LD (CTRL_SEQUENCE_INDEX),a LD (WARM_RESET_MAGIC_L),a CALL handle_error JR warm_reset_entry ; Retry warm_reset_continue: ; Test HCCA command $82 LD c,$82 CALL hcca_command_test JR nz,warm_reset_fail_handler ; Reset HCCA LD a,$01 OUT (R_HCCA),a CALL hcca_wait_response IN a,(R_HCCA) JR nz,warm_reset_fail_handler ; Store HCCA status LD (HCCA_STATUS),a ; Test HCCA commands $10 and $E1 LD c,$10 CALL hcca_verify_response JR nz,warm_reset_fail_handler LD c,$E1 CALL hcca_verify_response JR nz,warm_reset_fail_handler warm_reset_main: ; Re-check warm reset flag CALL check_warm_reset_flag CALL nz,update_test_status ; Update if not warm reset ; Check debug flag LD a,(DEBUG_FLAG) OR a JP nz,rom_checksum_test ; Debug mode - re-run tests ; Check if we should update control register LD a,(CTRL_STATE_FLAG) BIT 0,a JR nz,check_channel_code ; Update control register LD a,$02 LD (CTRL_BITS),a OUT (R_CONTROL),a check_channel_code: ; Check if channel code entry is required ; Bit 7 of HCCA_STATUS indicates channel code needed LD hl,HCCA_STATUS BIT 7,(hl) JR z,skip_channel_code ; No channel code needed ; Prompt for channel code LD hl,MSG_CHANNEL_CODE_PROMPT CALL display_message channel_code_input_loop: ; Read channel code from keyboard LD hl,CHANNEL_CODE_BUF LD de,$02C7 ; Screen position for display CALL read_channel_code ; Validate channel code checksum LD hl,CHANNEL_CODE_BUF XOR a LD c,a LD b,$04 ; 4 bytes to process channel_code_checksum_loop: LD a,(hl) BIT 0,b ; Check bit position JR z,channel_code_shift SLA a ; Shift left BIT 4,a ; Check bit 4 JR z,channel_code_shift RES 4,a ; Clear bit 4 INC a channel_code_shift: ADD a,c ; Add to checksum LD c,a INC hl DJNZ channel_code_checksum_loop ; Verify checksum (5th byte) AND $0F ; Low 4 bits CP (hl) ; Compare with stored checksum JR z,channel_code_valid ; Invalid checksum - prompt again LD hl,MSG_CHANNEL_CODE_RETRY CALL display_message LD c,$90 ; Delay parameter LD de,$E000 ; Delay count CALL delay_with_psg JR channel_code_input_loop channel_code_valid: ; Convert channel code to 16-bit value LD hl,CHANNEL_CODE_BUF LD b,$04 LD de,$0000 channel_code_convert_loop: ; Shift DE left by 4 bits (multiply by 16) LD a,(hl) SLA e RL d ; DE = DE * 2 SLA e RL d ; DE = DE * 4 SLA e RL d ; DE = DE * 8 SLA e RL d ; DE = DE * 16 ADD a,e ; Add current byte LD e,a INC hl DJNZ channel_code_convert_loop ; Send channel code to HCCA PUSH de LD c,$85 CALL hcca_command_test JP nz,warm_reset_fail_handler POP de ; Send high byte LD a,d OUT (R_HCCA),a LD c,$40 CALL check_kbd_interrupt ; Send low byte LD a,e OUT (R_HCCA),a CALL hcca_wait_ack JP nz,warm_reset_fail_handler skip_channel_code: ; Continue with OS loading sequence LD c,$81 CALL hcca_command_test JP nz,warm_reset_fail_handler ; Send OS load command sequence LD a,$8F OUT (R_HCCA),a LD c,$40 CALL check_kbd_interrupt LD a,$05 OUT (R_HCCA),a CALL hcca_wait_ack JP nz,warm_reset_fail_handler ; Prepare OS loading LD a,$05 LD (HCCA_STATUS),a ; Copy something (unclear purpose - might be clearing area) LD hl,$0000 LD de,$0000 LD bc,$1000 LDIR ; Disable ROM (bit 0 = 1 disables ROM) LD a,(CTRL_BITS) SET 0,a OUT (R_CONTROL),a ; Display "PLEASE WAIT" message LD hl,MSG_PLEASE_WAIT CALL display_message ;-------------------------------------------------------- ; OS Loading Routine ;-------------------------------------------------------- ; Loads the operating system from external storage via HCCA. ; The OS is loaded in sectors, with each sector being 16 bytes. ;-------------------------------------------------------- load_os: ; Initialize OS header variables XOR a LD (OS_HEADER_FLAG0),a LD (OS_HEADER_FLAG1),a LD (OS_SECTOR_COUNT),a INC a LD (OS_HEADER_FLAG2),a ; Set up OS data pointers LD bc,OS_START_ADDR LD (OS_DATA_POINTER),bc LD bc,OS_ENTRY_POINT LD (OS_DEST_POINTER),bc ; Initialize size accumulators XOR a LD (OS_SIZE_LOW),a LD (OS_SIZE_HIGH),a ; Configure PSG for OS loading XOR a OUT (R_PSG_ADDR_LATCH),a OUT (R_PSG_DATA),a CALL psg_config_os_load load_os_sector_loop: ; Receive OS data via HCCA CALL receive_os_data JP nz,warm_reset_fail_handler ; Configure PSG based on sector number XOR a OUT (R_PSG_ADDR_LATCH),a LD a,(OS_SECTOR_COUNT) SLA a ; Multiply by 2 SLA a ; Multiply by 4 CPL ; Complement OUT (R_PSG_DATA),a ; Get block flags from OS data LD hl,(OS_DATA_POINTER) LD bc,$000B ADD hl,bc ; Offset to flags byte LD a,(hl) LD (OS_BLOCK_FLAGS),a ; Update size accumulator LD hl,(OS_SIZE_ACCUM) LD bc,CTRL_BITS ; Add size (strange but works) ADD hl,bc PUSH hl POP bc LD hl,(OS_SIZE_LOW) ADD hl,bc LD (OS_SIZE_LOW),hl ; Copy sector data to destination LD hl,(OS_DATA_POINTER) LD de,$0010 ; Sector size = 16 bytes ADD hl,de ; Point to data (skip header) LD de,(OS_DEST_POINTER) LDIR ; Copy 16 bytes LD (OS_DEST_POINTER),de ; Update destination pointer ; Check if last sector (bit 4 of flags) LD a,(OS_BLOCK_FLAGS) BIT 4,a JR nz,os_load_complete ; Last sector! ; Increment sector counter and continue LD hl,OS_SECTOR_COUNT INC (hl) JR load_os_sector_loop os_load_complete: ; OS loading complete - set warm reset flag LD a,$A5 LD (WARM_RESET_MAGIC_L),a LD a,$5A LD (WARM_RESET_MAGIC_H),a ; Finalize PSG LD a,PSG_R7_ENABLE OUT (R_PSG_ADDR_LATCH),a LD a,$7F OUT (R_PSG_DATA),a ; Jump to loaded OS JP OS_JUMP_ADDRESS ;-------------------------------------------------------- ; Support Routines - Timeout and Interrupt Checking ;-------------------------------------------------------- check_timeout: ; Decrement DE and return Z flag if zero ; Used for timeout loops DEC de LD a,d OR e JR z,timeout_expired XOR a ; Not zero DEC a ; A = $FF (non-zero) timeout_expired: INC a ; A = 1 if zero, 0 if not zero RET ;-------------------------------------------------------- ; HCCA Communication Routines ;-------------------------------------------------------- check_hcca_status: ; Check HCCA status with retry logic LD b,$04 ; 4 retry attempts hcca_status_retry_loop: LD a,$83 ; Status command PUSH af OUT (R_HCCA),a hcca_status_check: POP af LD c,$10 ; Expected response LD hl,$03A4 ; Return address (unused?) PUSH hl CALL hcca_verify_response CP $02 ; Error code? JR z,hcca_status_check ; Retry CP $00 ; Success? RET z ; Yes, return Z POP af ; Clean stack DJNZ hcca_status_retry_loop ; Retry ; All retries failed XOR a INC a ; Return non-zero RET hcca_command_test: ; Send command to HCCA and verify response LD a,c OUT (R_HCCA),a LD c,$10 CALL hcca_verify_response RET nz ; Error LD c,$06 JR hcca_verify_response ; Verify second response hcca_verify_response: ; Verify HCCA response matches expected value PUSH bc CALL hcca_wait_response POP bc RET nz ; Timeout IN a,(R_HCCA) SUB c ; Compare with expected RET z ; Match! LD a,$02 ; Error code RET hcca_wait_response: ; Wait for HCCA response with timeout LD de,$FFFF hcca_wait_loop: CALL check_timeout RET nz ; Timeout LD c,$80 CALL check_kbd_interrupt JR z,hcca_wait_loop ; No response yet XOR a ; Success RET hcca_wait_ack: ; Wait for HCCA acknowledgment LD c,$E4 JR hcca_verify_response ;-------------------------------------------------------- ; OS Data Reception Routine ;-------------------------------------------------------- ; Receives OS data via HCCA. Uses PSG interrupt to detect ; data ready. Implements checksum verification. ;-------------------------------------------------------- receive_os_data: ; Exchange register sets (EXX) to use alternate registers EXX XOR a DEC a ; A = $FF LD d,a ; D = checksum high LD e,a ; E = checksum low EXX ; Send command $84 to start data transfer LD c,$84 CALL hcca_command_test RET nz ; Send 4-byte header LD b,$04 LD hl,OS_SECTOR_COUNT send_os_header_loop: LD c,$40 CALL check_kbd_interrupt JR z,send_os_header_loop ; Wait for ready LD a,(hl) OUT (R_HCCA),a DEC l ; Previous byte DJNZ send_os_header_loop ; Verify header sent correctly LD c,$E4 CALL hcca_verify_response RET nz ; Initialize timeout for data reception LD b,$18 ; 24 timeout units LD de,$0001 LD hl,$0000 receive_os_data_loop: ; Update timeout counter ADD hl,de JR nc,receive_check_timeout DJNZ receive_check_timeout ; Timeout - display message and retry LD hl,MSG_SEE_OWNERS_GUIDE CALL display_message POP bc ; Clean stack JP load_os ; Retry OS load receive_check_timeout: ; Check for data available LD c,$80 CALL check_kbd_interrupt JR z,receive_os_data_loop ; No data yet ; Data available - read it IN a,(R_HCCA) SUB $91 ; Check for start byte RET nz ; Not start byte - error ; Send acknowledge LD a,$10 OUT (R_HCCA),a receive_data_byte_loop: ; Wait for data byte LD c,$40 CALL check_kbd_interrupt JR z,receive_data_byte_loop ; Check if control byte or data byte LD c,$80 CALL check_kbd_interrupt LD hl,(OS_DATA_POINTER) LD bc,$0000 RES 0,e ; Clear flag in alternate E LD a,$06 OUT (R_HCCA),a receive_data_inner_loop: ; Wait for PSG interrupt (data ready) PUSH de LD de,$FFFF wait_psg_interrupt: LD a,PSG_R15_STATUS OUT (R_PSG_ADDR_LATCH),a IN a,(R_PSG_DATA) BIT 0,a ; Interrupt flag JR nz,psg_interrupt_set ; Interrupt! CALL check_timeout JR nz,wait_psg_interrupt ; Continue waiting ; Timeout POP de POP de JP warm_reset_fail_handler psg_interrupt_set: ; Interrupt detected - read data POP de IN a,(R_HCCA) CP $10 ; Control byte? JR nz,receive_data_byte ; No, data byte ; Control byte - toggle flag BIT 0,e JR z,receive_skip_byte ; Skip this byte RES 0,e LD (hl),a ; Store byte CALL update_checksum ; Update checksum INC hl ; Next address INC bc ; Increment counter JR receive_data_inner_loop receive_skip_byte: SET 0,e ; Set skip flag JR receive_data_inner_loop receive_data_byte: ; Data byte - store it BIT 0,e JR nz,receive_check_end ; Flag set - might be end LD (hl),a ; Store byte CALL update_checksum ; Update checksum INC hl ; Next address INC bc ; Increment counter JR receive_data_inner_loop receive_check_end: ; Check if end of data ($E1) LD (OS_SIZE_ACCUM),bc CP $E1 ; End marker? JP nz,receive_os_data ; Not end, retry ; Verify checksum EXX ; Switch to alternate registers LD a,e CP $0F ; Expected checksum low JP nz,receive_os_data ; Checksum error, retry LD a,d CP $1D ; Expected checksum high JP nz,receive_os_data ; Checksum error, retry ; Checksum OK - success! XOR a EXX RET ;-------------------------------------------------------- ; Checksum Update Routine ;-------------------------------------------------------- ; Updates CRC-style checksum using lookup table. ; Uses alternate register set for checksum state. ;-------------------------------------------------------- update_checksum: PUSH af EXX ; Switch to alternate registers XOR d ; XOR with current byte LD c,a LD b,$00 SLA c ; Multiply by 2 (table offset) RL b LD d,e ; Save current checksum low LD iy,$0B74 ; Checksum lookup table base ADD iy,bc ; Add offset LD a,(iy+0) ; Load table value LD e,a ; New checksum low LD a,(iy+1) ; Load table value high XOR d ; XOR with old checksum low LD d,a ; New checksum high EXX ; Switch back POP af RET ;-------------------------------------------------------- ; Test Status and Error Handling ;-------------------------------------------------------- update_test_status: ; Update test status display and control sequence LD c,$50 ; Default delay LD hl,CTRL_STATE_FLAG BIT 1,(hl) ; Check flag bit 1 JR z,update_test_delay LD c,$90 ; Longer delay if flag set update_test_delay: LD de,$CC00 ; Delay count CALL delay_with_psg RES 1,(hl) ; Clear flag bit 1 ; Small delay loop LD hl,$8000 LD de,$0001 update_test_delay_loop: ADD hl,de JR nc,update_test_delay_loop ; Update control sequence index LD a,(CTRL_SEQUENCE_INDEX) LD c,a SUB $05 ; Check if at end JR z,update_test_wrap_index ADD a,$06 ; Move to next position update_test_wrap_index: LD (CTRL_SEQUENCE_INDEX),a ; Update control register if not in special mode LD a,(CTRL_STATE_FLAG) BIT 0,a JP nz,display_test_status CALL get_ctrl_sequence_value OUT (R_CONTROL),a LD (CTRL_BITS),a JP display_test_status handle_error: ; Error handler - increments error counter and updates display PUSH af PUSH de PUSH hl ; Check if we should update control register LD a,(CTRL_STATE_FLAG) BIT 0,a JR nz,handle_error_count ; Update control register (force internal video) CALL get_ctrl_sequence_value OR $10 ; Force bit 4 (LED3 off = internal mode) OUT (R_CONTROL),a LD (CTRL_BITS),a handle_error_count: ; Set error state flag LD a,$03 LD (CTRL_STATE_FLAG),a ; Increment error counter based on test sequence index LD hl,TEST_STATUS_FLAG LD d,$00 LD a,(CTRL_SEQUENCE_INDEX) LD e,a ADD hl,de ; HL = address of error counter INC (hl) ; Increment counter JR nz,handle_error_done ; No overflow DEC (hl) ; Prevent overflow (stay at $FF) handle_error_done: POP hl POP de POP af RET get_ctrl_sequence_value: ; Get next value from control register sequence table LD hl,CTRL_SEQUENCE_TABLE LD a,(CTRL_SEQUENCE_INDEX) LD e,a LD d,$00 ADD hl,de LD a,(hl) RET CTRL_SEQUENCE_TABLE: ; Control register sequence for LED patterns during tests DB $2A,$0A,$2A,$0A,$22,$02 ;-------------------------------------------------------- ; Display Routines ;-------------------------------------------------------- display_test_status: ; Display test status or error messages LD a,(DEBUG_FLAG) OR a JR z,display_error_messages ; Normal mode - show errors ; Debug mode - display error counts LD a,$02 LD (CHANNEL_CODE_BUF),a LD de,$0028 ; Message offset LD b,c INC b LD hl,$012B ; Base address display_count_offset_loop: ADD hl,de DJNZ display_count_offset_loop LD (CHANNEL_CODE_PTR),hl ; Convert error count to hexadecimal LD hl,TEST_STATUS_FLAG ADD hl,bc LD de,CHANNEL_CODE_CHECK CALL convert_to_hex LD hl,CHANNEL_CODE_BUF JP display_message display_error_messages: ; Display error message based on test that failed LD hl,TEST_STATUS_FLAG LD b,$00 ADD hl,bc ; HL = error counter address LD a,(hl) OR a RET z ; No errors, return ; Find error message (each message is 19 bytes = $13) LD hl,MSG_ROM_FAILURE LD de,$0013 ; Message size INC c display_error_find_loop: DEC c JP z,display_message ; Found message ADD hl,de ; Next message JR display_error_find_loop convert_to_hex: ; Convert byte value to hexadecimal ASCII LD a,(hl) SRL a ; High nybble SRL a SRL a SRL a CALL convert_nybble_to_hex INC de LD a,(hl) AND $0F ; Low nybble JP convert_nybble_to_hex convert_nybble_to_hex: ; Convert 4-bit value to ASCII hex digit ADD a,$30 ; Add '0' CP $3A ; Check if >= 'A' JR c,convert_hex_done ADD a,$07 ; Adjust for 'A'-'F' convert_hex_done: LD (de),a RET display_message: ; Display message on screen ; Message format: [length][addr_low][addr_high][data...] LD b,(hl) ; Message length INC hl LD a,(hl) ; Low byte of screen address OUT (R_VDP_CTRL_ADDR),a INC hl LD a,(hl) ; High byte of screen address ADD a,$48 ; Add VRAM base offset OUT (R_VDP_CTRL_ADDR),a INC hl LD c,R_VDP_VRAM_DATA OTIR ; Output message string RET display_test_results: ; Display test results in debug mode LD c,$05 PUSH bc display_test_results_loop: CALL display_test_status POP bc DEC c JP m,display_test_results_done PUSH bc JR display_test_results_loop display_test_results_done: ; Check if debug mode is active LD a,(DEBUG_FLAG) OR a RET z ; Load debug channel code data LD hl,($07D0) LD (CHANNEL_CODE_BUF),hl LD a,($07D2) LD (CHANNEL_CODE_CHECK),a LD de,($0FFC) LD (CHANNEL_CODE_PTR),de LD hl,CHANNEL_CODE_BUF JP display_message ;-------------------------------------------------------- ; Character Pattern Loading ;-------------------------------------------------------- load_char_patterns: ; Load character pattern data into VDP VRAM ; First set VDP address to pattern table ($0000) XOR a OUT (R_VDP_CTRL_ADDR),a LD a,$41 ; Pattern table base address OUT (R_VDP_CTRL_ADDR),a ; Load pattern data (5 blocks of 256 bytes = 1280 bytes) LD c,R_VDP_VRAM_DATA LD d,$04 ; 4 iterations (plus initial = 5 blocks) LD hl,CHAR_PATTERN_DATA load_pattern_block_loop: LD b,$FF ; 256 bytes per block OTIR ; Output block DEC d JR nz,load_pattern_block_loop ; Clear screen (fill with spaces) XOR a OUT (R_VDP_CTRL_ADDR),a ; Set address to $0000 LD a,$48 ; Screen base address OUT (R_VDP_CTRL_ADDR),a LD a,$20 ; Space character LD hl,$FC40 ; Screen size (960 characters) LD de,$0001 clear_screen_loop: OUT (R_VDP_VRAM_DATA),a ADD hl,de JR nc,clear_screen_loop ; Display NABU logo LD b,$07 ; 7 rows LD hl,NABU_LOGO_DATA LD de,$000F ; 15 bytes per row display_logo_loop: PUSH bc PUSH hl PUSH de CALL display_message POP de POP hl POP bc ADD hl,de ; Next row DJNZ display_logo_loop RET ;-------------------------------------------------------- ; PSG Initialization and Configuration ;-------------------------------------------------------- psg_init: ; Initialize PSG to default state LD a,PSG_R7_ENABLE OUT (R_PSG_ADDR_LATCH),a LD a,$7F ; Enable I/O, disable sound OUT (R_PSG_DATA),a RET psg_config_os_load: ; Configure PSG for OS loading LD a,PSG_R7_ENABLE OUT (R_PSG_ADDR_LATCH),a LD a,$7E ; Different enable value OUT (R_PSG_DATA),a LD a,$08 ; Register 8 OUT (R_PSG_ADDR_LATCH),a LD a,$06 OUT (R_PSG_DATA),a LD a,$01 ; Register 1 OUT (R_PSG_ADDR_LATCH),a DEC a ; A = 0 OUT (R_PSG_DATA),a RET ;-------------------------------------------------------- ; Keyboard Routines ;-------------------------------------------------------- kbd_ctrl_write: ; Write value to keyboard control/status register OUT (R_KBD_CTRL_STATUS),a NOP ; Delay for hardware NOP NOP NOP NOP RET check_kbd_interrupt: ; Check keyboard interrupt status via PSG ; Uses PSG I/O ports to detect keyboard interrupt LD a,PSG_R14_INT_MASK OUT (R_PSG_ADDR_LATCH),a LD a,c ; I/O port A value OUT (R_PSG_DATA),a LD a,PSG_R15_STATUS OUT (R_PSG_ADDR_LATCH),a IN a,(R_PSG_DATA) BIT 0,a ; Interrupt flag RET read_channel_code: ; Read channel code from keyboard (5 digits) LD b,$05 ; 5 characters expected read_channel_code_loop: ; Wait for keyboard ready IN a,(R_KBD_CTRL_STATUS) BIT 1,a ; Ready flag JR z,read_channel_code_loop ; Read key code IN a,(R_KBD_DATA) PUSH af ; Convert key code to hex digit SUB $30 ; Subtract '0' JR c,read_channel_code_check_special CP $0A ; Check if 0-9 JR c,read_channel_code_valid SUB $07 ; Adjust for A-F CP $0A JR c,read_channel_code_check_special CP $10 JR c,read_channel_code_valid SUB $20 ; Adjust for lowercase CP $0A JR c,read_channel_code_check_special CP $10 JR nc,read_channel_code_check_special read_channel_code_valid: ; Valid hex digit LD c,a LD a,b OR a JR nz,read_channel_code_display POP af ; Buffer full, ignore JR read_channel_code_loop read_channel_code_display: ; Display character on screen LD a,e ; Screen position low OUT (R_VDP_CTRL_ADDR),a LD a,d OR $48 ; Screen position high OUT (R_VDP_CTRL_ADDR),a POP af CP $5B ; Check case JR c,read_channel_code_case_ok SUB $20 ; Convert to uppercase read_channel_code_case_ok: OUT (R_VDP_VRAM_DATA),a LD (hl),c ; Store digit value INC hl INC de ; Next screen position DEC b JR read_channel_code_loop read_channel_code_check_special: ; Check for special keys POP af CP $0D ; Enter/Return JR z,read_channel_code_enter CP $E7 ; Alternate enter JR z,read_channel_code_enter CP $7F ; Delete/Backspace JR z,read_channel_code_backspace CP $E1 ; Alternate backspace JR nz,read_channel_code_loop read_channel_code_backspace: ; Handle backspace LD a,b CP $05 ; At start? JR z,read_channel_code_loop ; Yes, ignore DEC de ; Previous position DEC hl ; Previous digit INC b ; One more character needed LD a,e ; Erase character OUT (R_VDP_CTRL_ADDR),a LD a,d OR $48 OUT (R_VDP_CTRL_ADDR),a LD a,$20 ; Space OUT (R_VDP_VRAM_DATA),a JR read_channel_code_loop read_channel_code_enter: ; Handle enter - only valid if buffer is full LD a,b OR a JR nz,read_channel_code_loop ; Buffer not full, ignore RET ; Done! ;-------------------------------------------------------- ; Utility Routines ;-------------------------------------------------------- check_warm_reset_flag: ; Check for warm reset magic value ($A55A) at end of RAM LD hl,WARM_RESET_MAGIC_L LD a,(hl) SUB $A5 ; Expected low byte RET nz ; Not warm reset INC hl LD a,(hl) SUB $5A ; Expected high byte RET ; Z flag indicates warm reset delay_with_psg: ; Delay routine using PSG timer PUSH af PUSH hl PUSH bc PUSH de CALL psg_config_os_load ; Configure PSG XOR a OUT (R_PSG_ADDR_LATCH),a LD a,c ; Timer value OUT (R_PSG_DATA),a ; Delay loop LD hl,$0001 LD b,$04 EX de,hl delay_loop: ADD hl,de JR nc,delay_loop POP hl PUSH hl DJNZ delay_loop ; Restore PSG LD a,PSG_R7_ENABLE OUT (R_PSG_ADDR_LATCH),a LD a,$7F OUT (R_PSG_DATA),a POP de POP bc POP hl POP af RET hcca_wait_response: ; Wait for HCCA response (wrapper for check_timeout) LD de,$FFFF hcca_wait_loop_inner: CALL check_timeout RET nz LD c,$80 CALL check_kbd_interrupt JR z,hcca_wait_loop_inner XOR a RET ;-------------------------------------------------------- ; DATA SECTION ;-------------------------------------------------------- .ORG $06C9 VDP_REG_INIT_DATA: ; TMS9918 VDP Register Initialization Values ; Written in reverse order (R7 to R0) DB $F5 ; R7: White FG, Light Blue BG DB $00 ; R6: Sprite pattern table at $0000 DB $00 ; R5: Sprite attribute table at $0000 DB $00 ; R4: Pattern table at $0000 DB $00 ; R3: Color table at $0000 DB $02 ; R2: Name table at $0800 DB $D0 ; R1: 16K, blank, disable INT, multicolor, sprites 16x16 DB $00 ; R0: External sync, mode 0 MSG_CHANNEL_CODE_PROMPT: DB $21,$AB,$02 DB "PLEASE TYPE IN CHANNEL CODE" MSG_SEE_OWNERS_GUIDE: DB $34,$21,$03 DB "SEE ",$22,"IF SOMETHING GOES WRONG",$22," IN OWNERS GUIDE" MSG_PLEASE_WAIT: DB $0B,$21,$03 DB "PLEASE WAIT" MSG_ROM_FAILURE: DB $10,$49,$01 DB "ROM FAILURE" MSG_VIDEO_FAILURE: DB $10,$71,$01 DB "VIDEO FAILURE" MSG_RAM_FAILURE: DB $10,$99,$01 DB "RAM FAILURE" MSG_SOUND_FAILURE: DB $10,$C1,$01 DB "SOUND FAILURE" MSG_KEYBOARD_FAILURE: DB $10,$E9,$01 DB "KEYBOARD FAILURE" MSG_ADAPTOR_FAILURE: DB $10,$11,$02 DB "ADAPTOR FAILURE" MSG_CHANNEL_CODE_RETRY: DB $21,$AB,$02 DB "RE-TYPE CHANNEL CODE" ; Debug channel code data DB $02,$5A ; Debug channel code (2 bytes) DB $03 ; Debug checksum NABU_LOGO_DATA: ; NABU logo character data (7 rows, 15 bytes each) DB $0C,$0D,$00 ; Row 1: Position and length DB $5B,$5C,$5C,$5C,$5C,$5C,$5C,$5C,$5C,$5C,$5C,$5C ; Logo row 1 DB $0C,$35,$00 ; Row 2 DB $5E,$5E,$5E,$5E,$5E,$5E,$5E,$5E,$5E,$5E,$5E,$5E ; Logo row 2 DB $0C,$5D,$00 ; Row 3 DB $5F,$60,$61,$62,$63,$64,$65,$66,$67,$68,$69,$6A ; Logo row 3 DB $0C,$85,$00 ; Row 4 DB $6B,$6C,$6D,$6E,$6F,$70,$71,$72,$73,$74,$75,$76 ; Logo row 4 DB $0C,$AD,$00 ; Row 5 DB $77,$78,$79,$7A,$7B,$7C,$7D,$7E,$7F,$80,$81,$82 ; Logo row 5 DB $0C,$D5,$00 ; Row 6 DB $83,$83,$83,$83,$83,$83,$83,$83,$83,$83,$83,$83 ; Logo row 6 DB $0C,$FD,$00 ; Row 7 DB $84,$85,$85,$85,$85,$85,$85,$85,$85,$85,$85,$86 ; Logo row 7 CHAR_PATTERN_DATA: ; Character pattern data for VDP (1280 bytes) ; This data defines the font glyphs displayed on screen ; Format: 8 bytes per character (8x8 pixel pattern) ; Characters start at ASCII 32 (space) ; Space through ? (ASCII 32-63) DB $00,$00,$00,$00,$00,$00,$00,$00 ; Space DB $10,$10,$10,$10,$10,$00,$10,$00 ; ! DB $28,$28,$00,$00,$00,$00,$00,$00 ; " DB $28,$28,$7C,$28,$7C,$28,$28,$00 ; # DB $38,$54,$50,$38,$14,$54,$38,$00 ; $ DB $60,$64,$08,$10,$20,$6C,$0C,$00 ; % DB $10,$28,$28,$30,$50,$4C,$7C,$00 ; & DB $30,$30,$10,$60,$00,$00,$00,$00 ; ' DB $10,$20,$40,$40,$40,$20,$10,$00 ; ( DB $40,$20,$10,$10,$10,$20,$40,$00 ; ) DB $00,$54,$38,$7C,$38,$54,$10,$00 ; * DB $00,$00,$10,$10,$7C,$10,$10,$00 ; + DB $00,$00,$00,$00,$30,$30,$10,$60 ; , DB $00,$00,$00,$00,$38,$00,$00,$00 ; - DB $00,$00,$00,$00,$00,$18,$18,$00 ; . DB $04,$04,$08,$18,$30,$20,$40,$00 ; / DB $38,$44,$4C,$54,$64,$44,$38,$00 ; 0 DB $10,$30,$10,$10,$10,$10,$38,$00 ; 1 DB $30,$48,$48,$18,$30,$20,$78,$00 ; 2 DB $30,$48,$08,$10,$08,$48,$30,$00 ; 3 DB $10,$30,$30,$50,$50,$78,$10,$00 ; 4 DB $78,$40,$50,$68,$08,$48,$30,$00 ; 5 DB $30,$28,$40,$70,$68,$48,$30,$00 ; 6 DB $78,$48,$08,$10,$30,$20,$20,$00 ; 7 DB $30,$48,$48,$30,$48,$48,$30,$00 ; 8 DB $30,$48,$48,$38,$08,$50,$30,$00 ; 9 DB $00,$00,$30,$30,$00,$30,$30,$00 ; : DB $00,$00,$30,$30,$00,$30,$10,$40 ; ; DB $08,$10,$20,$40,$20,$10,$08,$00 ; < DB $00,$00,$00,$78,$00,$78,$00,$00 ; = DB $40,$20,$10,$08,$10,$20,$40,$00 ; > DB $30,$48,$48,$10,$20,$20,$00,$20 ; ? ; @ through Z (ASCII 64-90) DB $40,$38,$04,$38,$08,$38,$48,$34 ; @ DB $10,$28,$44,$44,$7C,$44,$44,$00 ; A DB $78,$44,$44,$78,$44,$44,$78,$00 ; B DB $38,$44,$40,$40,$40,$44,$38,$00 ; C DB $70,$48,$44,$44,$44,$48,$70,$00 ; D DB $7C,$40,$40,$70,$40,$40,$7C,$00 ; E DB $7C,$40,$40,$70,$40,$40,$40,$00 ; F DB $38,$44,$44,$40,$5C,$44,$3C,$00 ; G DB $44,$44,$44,$7C,$44,$44,$44,$00 ; H DB $38,$10,$10,$10,$10,$10,$38,$00 ; I DB $1C,$08,$08,$08,$48,$48,$30,$00 ; J DB $48,$48,$50,$70,$50,$48,$4C,$00 ; K DB $40,$40,$40,$40,$40,$40,$7C,$00 ; L DB $6C,$54,$54,$44,$44,$44,$44,$00 ; M DB $44,$64,$64,$54,$54,$4C,$4C,$00 ; N DB $38,$44,$44,$44,$44,$44,$38,$00 ; O DB $78,$44,$44,$78,$40,$40,$40,$00 ; P DB $38,$44,$44,$44,$54,$48,$34,$00 ; Q DB $78,$48,$48,$78,$50,$48,$4C,$00 ; R DB $38,$44,$40,$38,$04,$44,$38,$00 ; S DB $7C,$10,$10,$10,$10,$10,$10,$00 ; T DB $44,$44,$44,$44,$44,$44,$38,$00 ; U DB $44,$44,$44,$28,$28,$28,$10,$00 ; V DB $44,$44,$44,$44,$54,$54,$28,$00 ; W DB $44,$44,$28,$10,$28,$44,$44,$00 ; X DB $44,$44,$44,$38,$10,$10,$10,$00 ; Y DB $7C,$0C,$18,$10,$20,$60,$7C,$00 ; Z ; Additional character patterns continue... ; (Remaining patterns for lower case, symbols, and custom logo characters) ; [Pattern data continues for remaining characters] ;-------------------------------------------------------- ; ROM Footer Data ;-------------------------------------------------------- .ORG $0FFA DB $0B ; Version/revision data DB $14 DB $30 DB $36 DB $CF ; Checksum low byte DB $3B ; Checksum high byte