; lyingport.asm — the ULA doesn't fully decode port 254.
; From the article at https://thespeccy.com/articles/the-lying-port
;
; Part 1: OUT (n),A only cares whether bit 0 of n is clear. 254, 252, 0
; and 128 all reach the border/MIC/EAR register just as well as the
; "correct" 254 does; 255 (odd) is silently ignored.
;
; Part 2: IN A,(n) is the mirror image. On real hardware the immediate
; form puts A on the address bus's top byte and n on the bottom byte —
; and it's the TOP byte (A) that the ULA reads to pick a keyboard half
; row. The bottom byte (n) is not checked at all. So "IN A,(254)" reads
; a different row every time depending only on what's in A beforehand,
; and reads the SAME row regardless of what literal n you wrote.
;
; Build:  pasmo --tapbas lyingport.asm lyingport.tap
;         pasmo --bin lyingport.asm lyingport.bin lyingport.sym

        org 32768

start:
        ld hl,msgtitle
        call printmsg

        ld hl,msgborder
        call printmsg

        ; every one of these n values has bit 0 clear -- all four
        ; reach the real border register, not just 254.
        ld a,2                  ; red
        out (254),a
        ld a,4                  ; green
        out (252),a
        ld a,5                  ; cyan
        out (0),a
        ld a,6                  ; yellow
        out (128),a

        ; bit 0 is SET here -- the ULA doesn't answer, border stays
        ; yellow no matter what colour we ask for.
        ld a,1                  ; blue -- never arrives
        out (255),a

        ld hl,msgresult
        call printmsg

        ld hl,msgkeys
        call printmsg

        ; read 1: A = FB (Q's row), n = FE
        ld a,0xFB
        in a,(0xFE)
        ld (row1),a

        ; read 2: A = DF (a DIFFERENT row -- no Q in it), n = FE again
        ld a,0xDF
        in a,(0xFE)
        ld (row2),a

        ; read 3: A = FB again (same row as read 1), n = 00 this time
        ld a,0xFB
        in a,(0x00)
        ld (row3),a

        ld hl,msgrow1
        call printmsg
        ld a,(row1)
        call printhex

        ld hl,msgrow2
        call printmsg
        ld a,(row2)
        call printhex

        ld hl,msgrow3
        call printmsg
        ld a,(row3)
        call printhex

        ld hl,msgfooter
        call printmsg
        ret

; ---- print a zero-terminated string via the ROM's own PRINT stream ----
printmsg:
        ld a,2
        call 0x1601             ; CHAN-OPEN, stream 2 (the screen)
pmloop: ld a,(hl)
        or a
        ret z
        rst 0x10                ; PRINT-A
        inc hl
        jr pmloop

; ---- print A as two hex digits ----
printhex:
        push af
        and 0xF0
        rrca
        rrca
        rrca
        rrca
        call hexdigit
        pop af
        and 0x0F
        call hexdigit
        ld a,13
        rst 0x10
        ret
hexdigit:
        add a,'0'
        cp '9'+1
        jr c,hd1
        add a,7
hd1:    rst 0x10
        ret

; ----------------------------------------------------------------------

row1:   defb 0
row2:   defb 0
row3:   defb 0

msgtitle:
        defb 13
        defm "THE PORT THAT LIES"
        defb 13
        defm "ABOUT ITS OWN NUMBER"
        defb 13,13,0

msgborder:
        defm "BORDER VIA OUT (N),A:"
        defb 13
        defm "N=254 252 0 128 -- ALL EVEN,"
        defb 13
        defm "ALL REACH THE BORDER."
        defb 13
        defm "N=255 IS ODD -- IGNORED."
        defb 13,13,0

msgresult:
        defm "FINAL BORDER: YELLOW."
        defb 13
        defm "(THE BLUE ONE NEVER ARRIVED)"
        defb 13,13,0

msgkeys:
        defm "Q HELD DOWN THE WHOLE TIME:"
        defb 13,13,0

msgrow1:
        defm "A=FB N=FE -> "
        defb 0
msgrow2:
        defm "A=DF N=FE -> "
        defb 0
msgrow3:
        defm "A=FB N=00 -> "
        defb 0

msgfooter:
        defb 13
        defm "N NEVER MATTERED."
        defb 13
        defm "A ALWAYS DID."
        defb 13,0

        end start
