; ; ][Vision ; ; Created by Kris Kennaway on 07/01/2019. ; Copyright © 2019 Kris Kennaway. All rights reserved. ; ; W5100/Uthernet II code based on "TCP SOCKET DEMO FOR W5100/UTHERNET II" by D. Finnigan. ; ; Multiplexed audio/video decoder for 64K, 1MHz Apple II systems with Uthernet II, ; supporting: ; - 5 bit DAC audio at ~14KHz ; - 56 KB/sec video update bandwidth ; ; This is sufficient for ~7.5 full page redraws of the hires screen per second, although the ; effective frame rate is typically higher, when there are only partial changes between ; frames. ; ; Fitting this in 64K together with ProDOS is pretty tight. We make use of 3 memory ; segments: ; ; LOWCODE (0x800 - 0x1fff) ; HGR (0x2000 - 0x3fff): code needed only at startup, which will be erased as soon as we start playing a video ; CODE (0x4000 - 0xbaff): rest of main memory unused by ProDOS .include "apple2.inc" ; Write symbol table to .dbg file, so that we can read opcode offsets in the video ; transcoder. .DEBUGINFO .proc main .segment "HGR" ; TODO: make these configurable SRCADDR: .byte 10,0,65,02 ; 10.0.65.02 W5100 IP FADDR: .byte 10,0,0,1 ; 10.0.0.1 FOREIGN IP FPORT: .byte $07,$b9 ; 1977 FOREIGN PORT MAC: .byte $00,$08,$DC,$01,$02,$03 ; W5100 MAC ADDRESS ; SLOT 1 I/O ADDRESSES FOR THE W5100 ; Change this to support the Uthernet II in another slot ; ; TODO: make slot I/O addresses customizable at runtime - would probably require somehow ; compiling a list of all of the binary offsets at which we reference $C09x and patching ; them in memory or on-disk. WMODE = $C094 WADRH = $C095 WADRL = $C096 WDATA = $C097 ;;; headerlen = $07 ; video header length ; some dummy addresses in order to pad cycle counts zpdummy = $08 dummy = $ffff ptr = $06 ; TODO: we only use this for connection retry count HGRZP = $E6 ; ZP location used by HGR internals to track page to clear ; soft-switches KBD = $C000 STORE80ON = $C001 COL80ON = $C00D KBDSTRB = $C010 TICK = $C030 ; where the magic happens TEXTOFF = $C050 FULLSCR = $C052 PAGE2OFF = $C054 PAGE2ON = $C055 HIRESON = $C057 DHIRESON = $C05E ; MONITOR SUBROUTINES HGR = $F3E2 HGR0 = $F3F2 ; internal entry point within HGR that doesn't set soft-switches COUT = $FDED PRBYTE = $FDDA PRNTAX = $F941 PRODOS = $BF00 ; ProDOS MLI entry point RESET_VECTOR = $3F2 ; Reset vector ; W5100 LOCATIONS MACADDR = $0009 ; MAC ADDRESS SRCIP = $000F ; SOURCE IP ADDRESS RMSR = $001A ; RECEIVE BUFFER SIZE ; SOCKET 0 LOCATIONS S0MR = $0400 ; SOCKET 0 MODE REGISTER S0CR = $0401 ; COMMAND REGISTER S0SR = $0403 ; STATUS REGISTER S0LOCALPORT = $0404 ; LOCAL PORT S0FORADDR = $040C ; FOREIGN ADDRESS S0FORPORT = $0410 ; FOREIGN PORT S0TXRR = $0422 ; TX READ POINTER REGISTER S0TXWR = $0424 ; TX WRITE POINTER REGISTER S0RXRSR = $0426 ; RX RECEIVED SIZE REGISTER S0RXRD = $0428 ; RX READ POINTER REGISTER ; SOCKET 0 PARAMETERS RXBASE = $6000 ; SOCKET 0 RX BASE ADDR RXMASK = $1FFF ; SOCKET 0 8KB ADDRESS MASK TXBASE = $4000 ; SOCKET 0 TX BASE ADDR TXMASK = RXMASK ; SOCKET 0 TX MASK ; SOCKET COMMANDS SCOPEN = $01 ; OPEN SCCONNECT = $04 ; CONNECT SCDISCON = $08 ; DISCONNECT SCSEND = $20 ; SEND SCRECV = $40 ; RECV ; SOCKET STATUS STINIT = $13 STESTABLISHED = $17 ; this is the main binary entrypoint (it will be linked at 0x800) .segment "LOWCODE" JMP bootstrap ; Put code only needed at startup in the HGR page, we'll toast it when we're ; done starting up .segment "HGR" ; RESET AND CONFIGURE W5100 bootstrap: ; install reset handler LDA #exit STA RESET_VECTOR+1 EOR #$A5 STA RESET_VECTOR+2 ; checksum to ensure warm-start reset LDA #6 ; 5 RETRIES TO GET CONNECTION STA ptr ; NUMBER OF RETRIES reset_w5100: LDA #$80 ; reset STA WMODE LDA #3 ; CONFIGURE WITH AUTO-INCREMENT STA WMODE ; ASSIGN MAC ADDRESS LDA #>MACADDR STA WADRH LDA #S0MR STA WADRH LDA #RXBASE STA WADRH LDA #RXBASE ; High byte of socket 0 receive buffer LDX #headerlen ; End of header LDA #>S0RXRSR STA WADRH ; fall through ; This is the main audio/video decode loop. ; ; The outer loop waits for the socket buffer to contain >2K of pending data before ; dispatching to the inner loop. ; ; The inner loop is structured in terms of "player opcodes", which receive any parameters ; from the TCP stream, and conclude with 2 bytes that are used as JMP address to the next ; opcode. ; ; Everything here has the following invariants: ; - opcodes are expected to take 73 cycles ; - (though the NOP and TERMINATE opcodes don't do this but they're only used at the start/ ; end of the stream). ; - opcodes must maintain X=0 upon completion. ; - this is assumed in some of the tick opcodes as a trick to get an extra cycle ; via STA foo,X (5 cycles) instead of STA foo (4 cycles) ; ; During the ACK opcode and subsequent outer loop transit, we keep ticking the speaker ; every 36/37 cycles to maintain a multiple of 73 cycles. Because we guarantee that the ACK ; appears every 2048 bytes, this lets us simplify the accounting for the W5100 socket buffer ; management (moving the address pointer etc). ; Somewhat magically, the cycle timings all align on multiples of 73 (with tick intervals ; alternating 36 and 37 cycles, as in the "neutral" (i.e. 50% speaker duty cycle) ; op_tick_36_* opcodes), without much work needed to optimize this. ; ; With a 73 cycle fundamental opcode (speaker) period and 1MHz clock speed, this gives a ; 14364 Hz "carrier" for the audio DAC, which is slightly audible (at least to my ageing ; ears) but quite acceptable. ; ; i.e. we get about 14364 player opcodes/second, with the op_ack + checkrecv + op_nop ; "slow path" costing 2 opcodes. Each of the "fat" audio/video opcodes results in storing ; 4 video bytes, so we store about 56KB of video data per second. ; ; With 192x40 = 7680 visible bytes on the hires screen, this means we can do about 7.5 full ; page redraws/sec; but the effective frame rate will usually be much higher than this ; since we only prioritize the parts of the screen that are changing between frames. ; Wait until we have enough received data, and ; ; Notice that this has the only conditional opcodes in the entire decode loop ;-) ; ; Calling invariants: ; X = 0 ; Y register has the high byte of the W5100 address pointer in the RX socket code, so we ; can't trash this until we are ready to point back there. checkrecv: STA TICK ; 4 LDA #3 pad cycles already; can't branch to tail NOP ; 2 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 ; vector to next opcode LDA WDATA ; 4 STA @D+2 ; 4 LDA WDATA ; 4 STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_20 page ;4+(4+4+5+3+4)+3+46=73 .ident (.concat ("op_tick_20_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 STA zpdummy ; 3 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_46")) .endmacro ; TODO: this one actually has 21 cycles between ticks, not 22 .macro op_tick_22 page ; 4+(4+4+5+4+4)+3+3+42 .ident (.concat ("op_tick_22_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA TICK ; 4 STA zpdummy ; 3 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_42")) ; 3 + 42 .endmacro .macro op_tick_24 page ;4+(4+4+5+4+3+4)+3+42 .ident (.concat ("op_tick_24_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA zpdummy ; 3 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_42")) .endmacro .macro op_tick_26 page ; pattern repeats from op_tick_8 ; 4+(4+4+5+4+5+4)+3+37 .ident (.concat ("op_tick_26_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 STA TICK; 4 STA zpdummy ; 3 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_37")) ; 3 + 37 .endmacro .macro op_tick_28 page ; pattern repeats from op_tick_10 ; 4+(4+2+4+5+4+5+4)+3+38 .ident (.concat ("op_tick_28_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 NOP ; 2 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_38")) .endmacro .macro op_tick_30 page ; pattern repeats from op_tick_12 ;4+(4+4+5+4+5+4+4)+3+3+33 .ident (.concat ("op_tick_30_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA TICK ; 4 STA zpdummy ; 3 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_33")) ; 3 + 33 .endmacro .macro op_tick_32 page ; pattern repeats from op_tick_14 ;4+(4+4+5+4+5+4+2+4)+3+34 .ident (.concat ("op_tick_32_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 NOP ; 2 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_34")) .endmacro .macro op_tick_34 page ; pattern repeats from op_tick_16 ; 4+(4+4+5+4+5+4+4+4)+2+5+5+3+20 .ident (.concat ("op_tick_34_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 LDX WDATA ; 4 ; allows reordering STA ...,X outside ticks STA TICK ; 4 STA page << 8,Y ; 5 STA page << 8,X ; 5 LDX #$00 ; 2 restore X=0 invariant JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_20")) ; 3+20 .endmacro .macro op_tick_36 page ; pattern repeats from op_tick_18 ;4+(4+4+5+4+5+4+4+2+4)+5+5+2+2+4+4+4+4+3 .ident (.concat ("op_tick_36_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 LDX WDATA ; 4 NOP ; 2 STA TICK ; 4 STA page << 8,Y ; 5 STA page << 8,X ; 5 LDX #$00 ; 2 NOP ; 2 ; used >3 pad cycles between tick pair and restoring invariant; can't branch to tail LDA WDATA ; 4 STA @D+2 ; 4 LDA WDATA ; 4 STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_38 page ; pattern repeats from op_tick_20 ; 4 + (4+4+5+4+5+4+5+3+4)+3+28 .ident (.concat ("op_tick_38_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 STA zpdummy ; 3 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_28")) ; 3 + 28 .endmacro ; TODO: this one actually has 41 cycles between ticks, not 40 .macro op_tick_40 page ; pattern repeats from op_tick_22 ;4+(4+4+5+4+5+4+5+4+4)+3+3+24 .ident (.concat ("op_tick_40_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA TICK ; 4 STA zpdummy JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_24")) .endmacro .macro op_tick_42 page ; pattern repeats from op_tick_24 ;4+(4+4+5+4+5+4+5+4+3+4)+3+24 .ident (.concat ("op_tick_42_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA zpdummy ; 3 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_24")) ; 3 + 24 .endmacro .macro op_tick_44 page ; pattern repeats from op_tick_26 ; 4 + (4+4+5+4+5+4+5+4+5+4)+3+3+19 .ident (.concat ("op_tick_44_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 STA TICK; 4 STA zpdummy ; 3 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_19")) ; 3 + 19 .endmacro .macro op_tick_46 page ; pattern repeats from op_tick_28 ;4+(4+2+4+5+4+5+4+5+4+5+4)+3+20 .ident (.concat ("op_tick_46_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 NOP ; 2 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_20")) .endmacro .macro op_tick_48 page ; pattern repeats from op_tick_30 ;4+(4+4+5+4+5+4+5+4+5+4+4)+3+3+15 .ident (.concat ("op_tick_48_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA TICK ; 4 STA zpdummy ; 3 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_15")) ; 3 + 15 .endmacro .macro op_tick_50 page ; pattern repeats from op_tick_32 ;4+(4+4+5+4+5+4+5+4+5+4+2+4)+3+16 .ident (.concat ("op_tick_50_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 NOP ; 2 STA TICK ; 4 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_16")) .endmacro .macro op_tick_52 page ; pattern repeats from op_tick_34 ;4+(4+4+5+4+5+4+5+4+5+4+4+4)+2+3+12 .ident (.concat ("op_tick_52_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA .ident (.concat ("_op_tick_6_page_", .string(page), "_jmp"))+2 ; 4 STA TICK ; 4 NOP ; 2 JMP .ident(.concat("_op_tick_page_", .string(page), "_tail_12")) .endmacro .macro op_tick_54 page ; pattern repeats from op_tick_36 ; 4 + (4+4+5+4+5+4+5+3+3+4+5+4+4)+4+4+4+3 .ident (.concat ("op_tick_54_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA zpdummy ; 3 STA zpdummy ; 3 STA TICK ; 4 ; used >3 pad cycles between tick pair; can't branch to tail STA @D+2 ; 4 LDA WDATA ; 4 STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_56 page ; 4+(4+4+5+4+5+4+5+4+5+4+4+4+4)+2+4+4+3 .ident (.concat ("op_tick_56_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA @D+2 ; 4 STA dummy ; 4 STA TICK ; 4 ; used >3 pad cycles between tick pair; can't branch to tail NOP ; 2 LDA WDATA ; 4 STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_58 page ; pattern repeats from op_tick_40 ;4+(4+4+5+4+5+4+5+4+5+4+4+3+3+4)+4+4+3 .ident (.concat ("op_tick_58_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA @D+2 ; 4 STA zpdummy ; 3 STA zpdummy ; 3 STA TICK ; 4 ; used >3 pad cycles between tick pair; can't branch to tail LDA WDATA ; 4 STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_60 page ; 4+(4+4+5+4+5+4+5+4+5+4+4+4+4+4)+2+4+3 .ident (.concat ("op_tick_60_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA @D+2 ; 4 LDA WDATA ; 4 STA dummy ; 4 STA TICK ; 4 ; used >3 pad cycles between tick pair; can't branch to tail NOP ; 2 STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_62 page ;4+(4+4+5+4+5+4+5+4+5+4+4+4+3+3+4)+4+3 .ident (.concat ("op_tick_62_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA @D+2 ; 4 LDA WDATA ; 4 STA zpdummy ; 3 STA zpdummy ; 3 STA TICK ; 4 ; used >3 pad cycles between tick pair; can't branch to tail STA @D+1 ; 4 @D: JMP op_nop ; 3 .endmacro .macro op_tick_64 page ;4+(4+4+5+4+5+4+5+4+5+4+4+4+4+4+4)+2+3 .ident (.concat ("op_tick_64_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA @D+2 ; 4 LDA WDATA ; 4 STA @D+1 ; 4 STA dummy ; 4 STA TICK ; 4 NOP ; 2 @D: JMP op_nop ; 3 .endmacro .macro op_tick_66 page ; pattern repeats from op_tick_8 ; 4+(4+4+5+4+5+4+5+4+5+4+4+4+3+4+3+4)+3 .ident (.concat ("op_tick_66_page_", .string(page))): STA TICK ; 4 LDA WDATA ; 4 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDY WDATA ; 4 STA page << 8,Y ; 5 LDA WDATA ; 4 STA @D+2 ; 4 LDA WDATA ; 4 STA @D+1 ; 4 STA zpdummy ; 3 STA zpdummy ; 3 STA TICK ; 4 @D: JMP op_nop ; 3 .endmacro ; convenience macro for enumerating all tick opcodes for a page .macro op_tick page op_tick_4 page op_tick_6 page op_tick_8 page op_tick_10 page op_tick_12 page op_tick_14 page op_tick_16 page op_tick_18 page op_tick_20 page op_tick_22 page op_tick_24 page op_tick_26 page op_tick_28 page op_tick_30 page op_tick_32 page op_tick_34 page op_tick_36 page op_tick_38 page op_tick_40 page op_tick_42 page op_tick_44 page op_tick_46 page op_tick_48 page op_tick_50 page op_tick_52 page op_tick_54 page op_tick_56 page op_tick_58 page op_tick_60 page op_tick_62 page op_tick_64 page op_tick_66 page .endmacro ; now pack the tick opcodes into memory .segment "LOWCODE" op_tick 32 op_tick 33 op_tick 34 op_tick 35 op_tick 36 op_tick_4 63 op_tick_6 63 op_tick_8 63 op_tick_10 63 op_tick_12 63 op_tick_14 63 op_tick_16 63 op_tick_18 63 op_tick_20 63 op_tick_22 63 op_tick_24 63 .segment "CODE" op_tick 37 op_tick 38 op_tick 39 op_tick 40 op_tick 41 op_tick 42 op_tick 43 op_tick 44 op_tick 45 op_tick 46 op_tick 47 op_tick 48 op_tick 49 op_tick 50 op_tick 51 op_tick 52 op_tick 53 op_tick 54 op_tick 55 op_tick 56 op_tick 57 op_tick 58 op_tick 59 op_tick 60 op_tick 61 op_tick 62 op_tick_26 63 op_tick_28 63 op_tick_30 63 op_tick_32 63 op_tick_34 63 op_tick_36 63 op_tick_38 63 op_tick_40 63 op_tick_42 63 op_tick_44 63 op_tick_46 63 op_tick_48 63 op_tick_50 63 op_tick_52 63 op_tick_54 63 op_tick_56 63 op_tick_58 63 op_tick_60 63 op_tick_62 63 op_tick_64 63 op_tick_66 63 op_terminate: LDA KBDSTRB ; clear strobe @0: ; Wait for keypress LDA KBD BPL @0 @1: ; key pressed LDA KBDSTRB ; clear strobe JMP exit ; Manage W5100 socket buffer and ACK TCP stream. ; ; In order to simplify the buffer management we expect this ACK opcode to consume ; the last 4 bytes in a 2K "TCP frame". i.e. we can assume that we need to consume ; exactly 2K from the W5100 socket buffer. op_ack: STA TICK ; 4 ; allow flip-flopping the PAGE1/PAGE2 soft switches to steer writes to MAIN/AUX screens ; actually this allows touching any $C0XX soft-switch, in case that is useful somehow LDA WDATA ; 4 STA @D+1 ; 4 @D: STA $C0FF ; 4 low-byte is modified LDA WDATA ; 4 dummy read of last byte in TCP frame ; Save the W5100 address pointer so we can come back here later ; We know the low-order byte is 0 because Socket RX memory is page-aligned and so is 2K frame. ; IMPORTANT - from now on until we restore this in RECV, we can't trash the Y register! LDY WADRH ; 4 ; Read Received Read pointer LDA #>S0RXRD ; 2 STA WADRH ; 4 LDX #S0CR ; HIGH BYTE NEEDED STA WADRH LDA #