        SUBT    > <wini>arm.NewEmulate.EmuHost - BBC emulation only

 [ :LNOT: fullbbc
 ! 1, "Why are you assembling this file ???"
 ]

        GET     &.Hdr.VduExt
        GET     &.Hdr.Econet

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                     B B C   D E V I C E   E M U L A T I O N
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Limitations at present:

; ACIA not implemented
; FDC not implemented
; ADLC not implemented
; ADC depends on I/O Podule
; VIAB depends on I/O Podule
; Many VIAA functions not implemented
; CRTC R0-R7 not implemented
; CRTC R8 (Interlace) not implemented
; CRTC LightPen R16,17 return 0
; Tube ULA not implemented
; Reads from Serial ULA not implemented
; Screen cleared on MODE change
; Two MODE changes for each real MODE change
; Flashing colours not done properly
; Serial ULA poke can't turn cassette relay on/off !
; Keyboard emulation not up to par: can't do OSByte &7A, &79 -ve etc.
; MODE 3,6 hardware scroll wrong
; NMI and most IRQ handling - forget it!

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Store map consists of entries thus:

; 31 30                   0
; +-+-+-------------------+
; |W|R|   address field   |
; +-+-+-------------------+

read_trap_bit  * 1 :SHL: 30
write_trap_bit * 1 :SHL: 31

; The address field always contains the ARM *word* address of offset 0 in the
; 6502 space for the corresponding page. (so we can shift the flag bits out...)
; If the access is trapped, use the page number to index into a jump table to
; process later.

TrapByte *      &FB                     ; Uncommon data byte
TrapWord *      (((TrapByte)*256+TrapByte)*256+TrapByte)*256+TrapByte

; If the emulator reads TrapByte from a location, it will realise that this
; area of store may be trapped, and so hops off to the general routine to
; load/store a byte, which uses the store map as described above.

; Bits in SidewaysState

SidewaysRAM     *       1 :SHL: 0       ; ROM = 0. Also used in ROMCache
SidewaysDirty   *       1 :SHL: 1       ; Clean = 0

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = 6502 page (NOT address) in &01..&FF, MR valid

; Never called with page &00 / &100 as this has to be done by hand

; Out   flags preserved

        AlignForModule

UntrappedPage ENTRY

        ADD     r14, MR, r0, LSL #8     ; Calculate ARM address of page
        MOV     r14, r14, LSR #2        ; Word address. No trap bits
        STR     r14, [MR, -r0, LSL #2]  ; Store in the map
 [ debug
        Push    r14
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_mmu
        BEQ     %FT00

        DREG    r0,"Setting map word for 6502 page ",cc,Byte
        LDR     r14, [sp]
        DREG    r14," to RW "
00
        Pull    r14
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

TrapWrite ENTRY

        ADD     r14, MR, r0, LSL #8     ; Calculate ARM address of page
        MOV     r14, r14, LSR #2        ; Word address
        ORR     r14, r14, #write_trap_bit ; Put in trap bit
                                        ; Still allow normal reads (eg ROM)
        STR     r14, [MR, -r0, LSL #2]  ; Store in the map
 [ debug
        Push    r14
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_mmu
        BEQ     %FT00

        DREG    r0,"Setting map word for 6502 page ",cc,Byte
        LDR     r14, [sp]
        DREG    r14," to Rw "
00
        Pull    r14
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

TrapReadWrite ENTRY "r1-r8"

        ADD     r14, MR, r0, LSL #8     ; Calculate ARM address of page
        MOV     r1, r14, LSR #2         ; Word address
        ORR     r1, r1, #write_trap_bit + read_trap_bit ; Put in trap bits

50      STR     r1, [MR, -r0, LSL #2]   ; Store in the map
 [ debug
        Push    r14
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_mmu
        BEQ     %FT00

        DREG    r0,"Setting map word for 6502 page ",cc,Byte
        DREG    r1," to rw "
00
        Pull    r14
 ]

        LDR     r1, =TrapWord
        MOV     r2, r1
        MOV     r3, r1
        MOV     r4, r1
        MOV     r5, r1
        MOV     r6, r1
        MOV     r7, r1
        MOV     r8, r1

90      STMIA   r14!, {r1-r8}           ; Store trap value in 6502 space
        STMIA   r14!, {r1-r8}           ; 8 words a go-go
        STMIA   r14!, {r1-r8}
        STMIA   r14!, {r1-r8}

        STMIA   r14!, {r1-r8}
        STMIA   r14!, {r1-r8}
        STMIA   r14!, {r1-r8}
        STMIA   r14!, {r1-r8}
 ASSERT ((.-%BT90)/4)*8*4 = 256

        EXITS

        LTORG

; .............................................................................

        AlignForModule

TrapReadShadowWrite ALTENTRY

        ADD     r14, MR, r0, LSL #8     ; Calculate ARM address of page
        ADD     r1, r14, #64*1024       ; Make reads come from shadow
        MOV     r1, r1, LSR #2          ; Word address of shadow area
        ORR     r1, r1, #write_trap_bit ; Put in trap bit
        B       %BT50

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Reset store map. Must put OS in before this as we overwrite areas of it

; In    wp valid

InitialiseMMU ENTRY "r0"

        MOV     r0, #0
        STRB    r0, [wp, #SidewaysState]
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROM
        BEQ     %FT00

        DREG    r0,"Initialised SidewaysState to ",,Byte
00
 ]

        MOV     r0, MR, LSR #2          ; Word address of page &000
        STR     r0, [MR, #-&100*4]      ; Page &100 maps onto page &000. NoTrap

        MOV     r0, #&FF                ; Top part of OS ROM
        BL      TrapWrite

        MOV     r0, #&FE                ; SHEILA
        BL      TrapReadWrite

        MOV     r0, #&FD                ; JIM
        BL      TrapReadWrite

        MOV     r0, #&FC                ; FRED
        BL      TrapReadWrite

        MOV     r0, #&FB                ; Rest of OS ROM and sideways area
10      BL      TrapWrite               ; are readable and not writeable
        SUB     r0, r0, #1              ; initially
        CMP     r0, #&80-1
        BNE     %BT10

; No screen trapping until MODE change! Helps sizing machine in undef state

30      BL      UntrappedPage           ; Rest of RAM is directly accessible
        SUBS    r0, r0, #1
        BNE     %BT30                   ; Down to Page01

; Page00 always uses entry &100 as offset 0 in table = first word of 6502 space

        MOV     r0, #&02                ; Page02 - MOS Variables
        BL      TrapReadWrite
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Trap and untrap screen pages when a MODE change is detected

; In    wp valid

; Out   flags preserved

TrapScreenPages ENTRY "r0, r1"

        LDR     r1, [wp, #CRTC_ScreenSize]
        MOV     r0, #&80                ; Start protecting screen pages
10      SUB     r0, r0, #1
        SUBS    r1, r1, #&0100
        BLHS    TrapReadShadowWrite     ; Preserves flags
        BHI     %BT10

20      SUB     r0, r0, #1              ; Unprotect lower RAM
        CMP     r0, #&30
        BLHS    UntrappedPage           ; Preserves flags
        BHI     %BT20
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Trap or untrap sideways ROM/RAM pages when swapped

; In    wp valid
;       r1 is new sideways state

; Out   flags preserved

        AlignForModule

TrapSidewaysPages ENTRY "r0"

        LDRB    r0, [wp, #SidewaysState]
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROM
        BEQ     %FT00

        DREG    r1,"Setting SidewaysState to ",cc,Integer
        DREG    r0,", old state ",,Integer
00
 ]
        TST     r0, #SidewaysRAM        ; If both states ROM (0), no problem
        TSTEQ   r1, #SidewaysRAM        ; Area will still be clean
        EXITS   EQ

        STRB    r1, [wp, #SidewaysState]

        TST     r0, #SidewaysDirty      ; If RAM was dirty then reprotect
        EXITS   EQ                      ; Otherwise still write protected

        MOV     r0, #&C0-1              ; Start protecting sideways pages
10      BL      TrapWrite               ; Even if now RAM, we trap first
        SUB     r0, r0, #1              ; write access to set the dirty state
        CMP     r0, #&80-1
        BNE     %BT10

        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Done the first write to the sideways RAM area, so remove protection

; In    wp valid

DirtySidewaysArea ENTRY "r0"

        MOV     r0, #&C0-1              ; Start deprotecting RAM pages
10      BL      UntrappedPage
        SUB     r0, r0, #1
        CMP     r0, #&80-1
        BNE     %BT10

        MOV     r0, #SidewaysRAM :OR: SidewaysDirty
        STRB    r0, [wp, #SidewaysState]
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROM
        BEQ     %FT00

        DREG    r0,"Setting SidewaysState to ",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                         Load byte from address
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 -> 6502 address

; Out   r0, r2 preserved
;       r1 = byte loaded from 6502 address (r0)
;       r3 corrupt. Flags preserved

; ALIGN so that LDR/LDR at optimal positions (already 16 apart)

        AlignForModule                  ; B dest best at +0
                                        ; LDR best at +4
LoadByte_10 ROUT

        MOVS    r3, r0, LSR #8          ; Get page number of address
        LDRNE   r3, [MR, -r3, LSL #2]   ; Get real address of that page      ;0
        LDREQ   r3, [MR, #-&100*4]      ; Get real address of page 0. Order !;1
        TST     r3, #read_trap_bit      ; Is it flagged ?                    ;2
        ANDEQ   r1, r0, #&FF            ; Offset in page                     ;3
        LDREQB  r1, [r1, r3, LSL #2]    ; Get the byte if it's easy !        ;4
        MOVEQS  pc, lr                  ; (Get rid of the flags first !)

; .............................................................................
; Read trapped

        MOV     r3, r0, LSR #8          ; Get page number of address again
        ADD     pc, pc, r3, LSL #2      ; Go for it
        NOP                             ; For addressing porpoises

; In    r0 = 6502 address
;       r1 can be used as temp (up to a point !)
;       r2 must be preserved
;       r3 = page number of 6502 address - can be used as temp
;       Use MOVS pc,lr to return r1 to user

LoadTable
        GBLA    count
        MOV     pc, #0                  ; Page &000. Always read via table
        MOV     pc, #0                  ; Page &001. Always read via table
        B       LoadFromPage02          ; Page &002. Always trapped
count   SETA    &03
        WHILE   count < &30
        MOV     pc, #0                  ; Always read via table
count   SETA    count+1
        WEND
        WHILE   count < &80
 [ True
        MOV     pc, #0                  ; Always read via table
 |
        B       LoadFromScreen          ; Can be either direct or trapped
 ]
count   SETA    count+1
        WEND
        WHILE   count < &FC
        MOV     pc, #0                  ; OS ROM and sideways always via table
count   SETA    count+1
        WEND
        B       LoadFromDevice          ; FRED always trapped
        B       LoadFromDevice          ; JIM always trapped
        B       LoadFromDevice          ; SHEILA always trapped
        MOV     pc, #0                  ; Page &0FF. Always read via table

        MOV     pc, #0                  ; Page &100. Always read via table

 assert .-LoadTable = 4*257             ; NB. Extra page !

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; All except OSByte variables read from main memory

        AlignForModule

LoadFromPage02 ROUT

; Load all except OSBytes &B0..FF from main memory

        SUBS    r3, r0, #&0240          ; Calculate offset from &B0 position
        RSBHSS  r1, r3, #&FF-&B0
        LDRLOB  r1, [MR, r0]
        MOVLOS  pc, lr                  ; I DID sit down and work it out! 

        ADR     r1, ByteVars_AddTable
        LDR     r1, [r1, r3, LSL #2]
        ADDS    r1, r0, r1              ; Gone up to shadow memory or to 0 ?
        LDRNEB  r1, [MR, r1]            ; Read local copy from *** shadow ***
 [ debug
        BNE     %FT01
 ]
        MOVNES  pc, lr

        LDREQ   r1, [MR, #MRvar_osbytevars_pB0] ; Arthur address of OSByte &B0
        LDREQB  r1, [r1, r3]            ; Read OS copy
 [ debug
01
        Push    "JV, lr"
        ADDEQ   r14, r0, #&10000
        STREQB  r1, [MR, r14]           ; Save copy in shadow to see action

        GetWS
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_oswspace
        BEQ     %FT00

        DREG    r1,"Read ",cc,Byte
        DREG    r0," from ByteVars at ",cc,Word
        ADD     r3, r3, #&B0
        DREG    r3," == OSByte ",,Byte
00
        Pull    "JV, lr"
 ]
        MOVS    pc, lr


; Starts at OSByte &B0. Add ~= &00 -> local copy, otherwise -> Arthur copy

ByteVars_AddTable

        GBLA    bytevar
bytevar SETA    &0240           ; Corresponds to OSByte &B0

        MACRO
        LocalByte
        DCD     64*1024         ; Add to r0 to get up to shadow memory
bytevar SETA    bytevar + 1
        MEND

        MACRO
        ArthurByte
        DCD     -bytevar        ; Add to r0 to get back to 0
bytevar SETA    bytevar + 1
        MEND

        LocalByte       ; &B0: CFS timeout counter
        LocalByte       ; &B1: Input buffer number (FX 2)
        LocalByte       ; &B2: Keyboard semaphore - dangerous + useless !
        LocalByte       ; &B3: Primary OSHWM on B / Polling semapore on Master
        LocalByte       ; &B4: OSHWM
        ArthurByte      ; &B5: RS423 mode
        LocalByte       ; &B6: Font explosion state
        LocalByte       ; &B7: RFS/CFS selection switch

        LocalByte       ; &B8: Video ULA soft copy 1
        LocalByte       ; &B9: Video ULA soft copy 2
        LocalByte       ; &BA: ROM active at BRK
        LocalByte       ; &BB: BASIC ROM socket number. MUST be local
        ArthurByte      ; &BC: Current ADC channel
        ArthurByte      ; &BD: Maximum ADC channel
        ArthurByte      ; &BE: ADC conversion type
        ArthurByte      ; &BF: RS423 use flag

        ArthurByte      ; &C0: 6850 soft copy
        LocalByte       ; &C1: Flash counter
        LocalByte       ; &C2: Mark period count
        LocalByte       ; &C3: Space period count
 [ True ; I can't (personally) cope with 65Host messing about with these !
        LocalByte       ; &C4: Keyboard auto-repeat delay
        LocalByte       ; &C5: Keyboard auto-repeat rate
 |
        ArthurByte      ; &C4: Keyboard auto-repeat delay
        ArthurByte      ; &C5: Keyboard auto-repeat rate
 ]
        ArthurByte      ; &C6: EXEC file handle  - MUST be Arthur to stop CLOSE#0
        ArthurByte      ; &C7: SPOOL file handle - going 'Channel' on Host side

        LocalByte       ; &C8: ESCAPE/BREAK effect
        ArthurByte      ; &C9: Keyboard disable
        ArthurByte      ; &CA: Keyboard status
        ArthurByte      ; &CB: RS423 handshake extent
        ArthurByte      ; &CC: RS423 input suppression
        LocalByte       ; &CD: Cassette/RS423 selection
        LocalByte       ; &CE: Econet OS call interception
        LocalByte       ; &CF: Econet read char interception

        LocalByte       ; &D0: Econet write char interception
        LocalByte       ; &D1: Speech suppression
        ArthurByte      ; &D2: Sound suppression
        ArthurByte      ; &D3: BELL channel
        ArthurByte      ; &D4: BELL info
        ArthurByte      ; &D5: BELL frequency
        ArthurByte      ; &D6: BELL duration
        LocalByte       ; &D7: Startup message suppression/!BOOT opt status

        LocalByte       ; &D8: SoftKey expansion length
        LocalByte       ; &D9: Lines since page mode halt
        LocalByte       ; &DA: VDU Queue items
        LocalByte       ; &DB: TAB character value
        ArthurByte      ; &DC: ESCAPE character
        ArthurByte      ; &DD: FX 221
        ArthurByte      ; &DE: FX 222
        ArthurByte      ; &DF: FX 223

        ArthurByte      ; &E0: FX 224
        ArthurByte      ; &E1: FX 225
        ArthurByte      ; &E2: FX 226
        ArthurByte      ; &E3: FX 227
        ArthurByte      ; &E4: FX 228
        ArthurByte      ; &E5: ESCAPE or ASCII
        LocalByte       ; &E6: ESCAPE action flags
        LocalByte       ; &E7: User VIA IRQ mask

        LocalByte       ; &E8: 6850 IRQ mask
        LocalByte       ; &E9: System VIA IRQ mask
        LocalByte       ; &EA: TUBE presence
        LocalByte       ; &EB: Speech presence
        ArthurByte      ; &EC: Output stream selection (FX 3)
        LocalByte       ; &ED: Cursor editing status (FX 4)
        LocalByte       ; &EE: Unused OS 1.20
        LocalByte       ; &EF: Unused OS 1.20

        LocalByte       ; &F0: Unused OS 1.20
        LocalByte       ; &F1: User flag (FX 1)
        LocalByte       ; &F2: Serial ULA soft copy
        LocalByte       ; &F3: Timer switch state
        LocalByte       ; &F4: Soft Key Critical flag
        ArthurByte      ; &F5: Printer destination (FX 5)
        ArthurByte      ; &F6: Printer ignore character (FX 6)
        LocalByte       ; &F7: BREAK intercept 1

        LocalByte       ; &F8: BREAK intercept 2
        LocalByte       ; &F9: BREAK intercept 3
        LocalByte       ; &FA: Unused OS 1.20
        LocalByte       ; &FB: Unused OS 1.20
        LocalByte       ; &FC: Current language ROM
        LocalByte       ; &FD: Last RESET type
        LocalByte       ; &FE: Model A/B flag
        LocalByte       ; &FF: Switch options

 assert .-ByteVars_AddTable = (&100-&B0) * 4

 [ False
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

LoadFromScreen ROUT

        ADD     r1, r0, #&10000         ; Up into shadow (always)
        LDRB    r1, [MR, r1]
 [ debug
        Push    "JV, lr"
        GetWS
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_screen
        BEQ     %FT00

        DREG    r1,,,Byte
        DREG    r0," read from screen at ",,Word
00
        Pull    "JV, lr"
 ]
        MOVS    pc, lr
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

LoadFromDevice ENTRY "JV"

        GetWS
        BL      ReadIO
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                         Store byte at address
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 -> 6502 address
;       r1 = byte to store at 6502 address (r0)

; Out   r0, r1 preserved
;       r2, r3 corrupt. Flags preserved

; We don't normally fault pokes to the ROM areas as this is normally due to
; sideways ROMs trying to protect themselves from being copied into SRAM.

                GBLL    faultromstore
faultromstore   SETL    debug :LAND: True

; ALIGN so that LDR/STR at optimal positions (already 16 apart)

        AlignForModule                  ; B dest best at +0
                                        ; LDR/STR best at +4
StoreByte_10 ROUT

        MOVS    r3, r0, LSR #8          ; Get page number of address
        LDRNE   r3, [MR, -r3, LSL #2]   ; Get real address of that page      ;0
        LDREQ   r3, [MR, #-&100*4]      ; Get real address of page 0. Order !;1
        TST     r3, #write_trap_bit     ; Is it flagged ?                    ;2
        ANDEQ   r2, r0, #&FF            ; Offset in page                     ;3
        STREQB  r1, [r2, r3, LSL #2]    ; Store the byte if it's easy !      ;4
        MOVEQS  pc, lr                  ; (Get rid of the flags first !)

; .............................................................................
; Write trapped

        MOV     r3, r0, LSR #8          ; Get page number of address again
        ADD     pc, pc, r3, LSL #2      ; Go for it
        NOP                             ; For addressing porpoises

; In    r0 = 6502 address
;       r1b = byte to store (has info in top on some opcodes, eg. ROL !)
;       r2 can be used as temp
;       r3 = page number of 6502 address - can be used as temp

StoreTable
        GBLA    count
        MOV     pc, #0                  ; Page &000. Always writes via table
        MOV     pc, #0                  ; Page &001. Always writes via table
        B       WriteToPage02           ; Page &002. Always trapped
count   SETA    &03
        WHILE   count < &30
        MOV     pc, #0                  ; Writes in apl always direct
count   SETA    count+1
        WEND
        WHILE   count < &80
        B       WriteToScreen           ; Can be either direct or trapped
count   SETA    count+1
        WEND
        WHILE   count < &C0             ; Sideways area can be writeable
        B       WriteToSidewaysArea     ; Should only trap once, then redo
count   SETA    count+1                 ; the store trap to go via table
        WEND
        WHILE   count < &FC
 [ faultromstore
        B       StoreViolation          ; Fault writes to OS ROM
 |
        MOVS    pc, lr                  ; NOP writes to OS ROM
 ]
count   SETA    count+1
        WEND
        B       WriteToDevice           ; FRED always trapped
        B       WriteToDevice           ; JIM always trapped
        B       WriteToDevice           ; SHEILA always trapped
 [ faultromstore
        B       StoreViolation          ; Fault writes to OS ROM
 |
        MOVS    pc, lr                  ; NOP writes to OS ROM
 ]

        MOV     pc, #0                  ; Page &100. Always writes via table

 assert .-StoreTable = 4*257            ; NB. Extra page !

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

WriteToPage02 ROUT

        SUBS    r3, r0, #&0240          ; Writing vectors (&0200..0235/A6-AF) ?
        STRLOB  r1, [MR, r0]
        MOVLOS  pc, lr                  ; OSByte &A6-AF update main memory

        CMP     r3, #&FF-&B0            ; Writing OSByte main variables ?
        STRHIB  r1, [MR, r0]
        MOVHIS  pc, lr

 [ debug
        Push    "JV, lr"
        GetWS
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_oswspace
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r0," to ByteVars at ",cc,Word
        ADD     r3, r3, #&B0
        DREG    r3," == OSByte ",,Byte
        SUB     r3, r3, #&B0
00
        Pull    "JV, lr"
 ]
        ENTRY
        addr    r14, ByteVars_AddTable
        LDR     r14, [r14, r3, LSL #2]
        ADDS    r14, r0, r14            ; Gone up to shadow memory or to 0?
        STRNEB  r1, [MR, r14]           ; Store local copy in shadow
        EXITS   NE

 [ debug
        ADDEQ   r0, r0, #&10000         ; Go up to shadow memory
        STREQB  r1, [MR, r0]            ; Keep copy in shadow to see action
 ]
        LDREQ   r14, [MR, #MRvar_osbytevars_pB0] ; Arthur address of OSByte &B0
        STREQB  r1, [r14, r3]           ; Update OS copy
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

WriteToScreen ROUT

        Push    "r0-r6, JV, lr"

        ADD     r14, r0, #&10000
        STRB    r1, [MR, r14]           ; Keep copy in shadow for reading back

        GetWS
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_screen
        BEQ     %FT00

        DREG    r1,"Writing byte ",cc,Byte
        DREG    r0," to screen at ",,Word
00
 ]
        LDR     pc, [wp, #ScreenWriteRoutine]


NOPWrite
        Pull    "r0-r6, JV, pc",,^


; Macro to convert 6502 address into offset from start of 6502 screen

        MACRO
        MakeScreenOffset
        LDR     r3, [wp, #CRTC_ScreenAddr]
        SUBS    r0, r0, r3
        LDRCC   r3, [wp, #CRTC_ScreenSize]
        ADDCC   r0, r0, r3
        MEND


; Macro to convert ARM screen offset in r2 to ARM logical "user" address r2

        MACRO
        AddOnScreenAddress
        LDR     r0, [wp, #ARMScreenStart]
        ADD     r2, r2, r0
        CMP     r2, #&02000000
        LDRCS   r0, [wp, #TotalScreenSize]
        SUBCS   r2, r2, r0
        MEND



        AlignForModule

; *** MODE 0 ***

Mode0Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_01111000     ; &80-8
        MOV     r3, r3, LSR #3          ; /(8/dpix=1)
        MOV     r0, r0, LSR #7          ; /&80
        ADR     r4, Mode2Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #4      ; *16 (*5 -> *80)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        ADR     r4, Mode0Nibble
        AND     r14, r1, #15
        LDRB    r0, [r4, r14]
        LDRB    r1, [r4, r1, LSR #4]
        ORR     r0, r1, r0, LSL #4
        STRB    r0, [r2, #0]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^

Mode0Nibble
        DCB     2_0000
        DCB     2_1000
        DCB     2_0100
        DCB     2_1100
        DCB     2_0010
        DCB     2_1010
        DCB     2_0110
        DCB     2_1110
        DCB     2_0001
        DCB     2_1001
        DCB     2_0101
        DCB     2_1101
        DCB     2_0011
        DCB     2_1011
        DCB     2_0111
        DCB     2_1111


        AlignForModule

; *** MODE 1 ***

Mode1Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_01111000     ; &80-8
        MOV     r3, r3, LSR #3          ; /(8/dpix=1)
        MOV     r0, r0, LSR #7          ; /&80
        ADR     r4, Mode2Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #4      ; *16 (*5 -> *80)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        ADR     r4, Mode1Nibble
        AND     r14, r1, #15
        LDRB    r0, [r4, r14]
        LDRB    r1, [r4, r1, LSR #4]
        ORR     r0, r0, r1, LSL #1
        STRB    r0, [r2, #0]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^


Mode1Nibble
        DCB     2_00000000
        DCB     2_01000000
        DCB     2_00010000
        DCB     2_01010000
        DCB     2_00000100
        DCB     2_01000100
        DCB     2_00010100
        DCB     2_01010100
        DCB     2_00000001
        DCB     2_01000001
        DCB     2_00010001
        DCB     2_01010001
        DCB     2_00000101
        DCB     2_01000101
        DCB     2_00010101
        DCB     2_01010101


        AlignForModule

; *** MODE 2 ***: Double pixel plotted, so 160 bytes per raster, not 80

Mode2Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_01111000     ; &80-8
        MOV     r3, r3, LSR #2          ; /(8/dpix=2)
        MOV     r0, r0, LSR #7          ; /&80
        ADR     r4, Mode2Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #5      ; *32 (*5 -> *160)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        TST     r1, #&80
        MOVEQ   r0, #&00
        MOVNE   r0, #&08
        TST     r1, #&20
        ORRNE   r0, r0, #&04
        TST     r1, #&08
        ORRNE   r0, r0, #&02
        TST     r1, #&02
        ORRNE   r0, r0, #&01
        ORR     r0, r0, r0, LSL #4      ; Replicate nibble into byte
        STRB    r0, [r2, #0]

        TST     r1, #&40
        MOVEQ   r0, #&00
        MOVNE   r0, #&08
        TST     r1, #&10
        ORRNE   r0, r0, #&04
        TST     r1, #&04
        ORRNE   r0, r0, #&02
        TST     r1, #&01
        ORRNE   r0, r0, #&01
        ORR     r0, r0, r0, LSL #4      ; Replicate nibble into byte
        STRB    r0, [r2, #1]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^

        LTORG

Mode2Table
        GBLA    Mode2Vbl
Mode2Vbl SETA   0
        WHILE   Mode2Vbl < 160
        DCD     (Mode2Vbl :MOD: 5) + &28 * (Mode2Vbl/5)
Mode2Vbl SETA   Mode2Vbl + 1
        WEND


        AlignForModule

; *** MODE 4 ***: Double pixel plotted, so 80 bytes per raster, not 40

Mode4Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_00111000     ; &40-8
        MOV     r3, r3, LSR #2          ; /(8/dpix=2)
        MOV     r0, r0, LSR #6          ; /&40
        ADRL    r4, Mode2Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #4      ; *16 (*5 -> *80)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        ADR     r4, Mode4Nibble
        AND     r14, r1, #15
        LDRB    r0, [r4, r14]
        LDRB    r1, [r4, r1, LSR #4]
        STRB    r1, [r2, #0]
        STRB    r0, [r2, #1]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^

Mode4Nibble
        DCB     2_00000000
        DCB     2_11000000
        DCB     2_00110000
        DCB     2_11110000
        DCB     2_00001100
        DCB     2_11001100
        DCB     2_00111100
        DCB     2_11111100
        DCB     2_00000011
        DCB     2_11000011
        DCB     2_00110011
        DCB     2_11110011
        DCB     2_00001111
        DCB     2_11001111
        DCB     2_00111111
        DCB     2_11111111


        AlignForModule

; *** MODE 5 ***: Double pixel plotted, so 80 bytes per raster, not 40

Mode5Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_00111000     ; &40-8
        MOV     r3, r3, LSR #2          ; /(8/dpix=2)
        MOV     r0, r0, LSR #6          ; /&40
        ADRL    r4, Mode2Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #4      ; *16 (*5 -> *80)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        ADR     r4, Mode5Nibble
        AND     r14, r1, #15
        LDR     r0, [r4, r14, LSL #1]
        MOV     r14, r1, LSR #4
        LDR     r1, [r4, r14, LSL #1]
        ORR     r0, r0, r1, LSL #1
        STRB    r0, [r2, #0]
        MOV     r0, r0, LSR #8
        STRB    r0, [r2, #1]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^

Mode5Nibble
        DCW     2_0000000000000000
        DCW     2_0101000000000000
        DCW     2_0000010100000000
        DCW     2_0101010100000000
        DCW     2_0000000001010000
        DCW     2_0101000001010000
        DCW     2_0000010101010000
        DCW     2_0101010101010000
        DCW     2_0000000000000101
        DCW     2_0101000000000101
        DCW     2_0000010100000101
        DCW     2_0101010100000101
        DCW     2_0000000001010101
        DCW     2_0101000001010101
        DCW     2_0000010101010101
        DCW     2_0101010101010101


; **************************** G A P   M O D E S ******************************

        AlignForModule

; *** MODE 3 ***: 4 colours, not 2, so 160 bytes per raster, not 80

Mode3Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_01111000     ; &80-8
        MOV     r3, r3, LSR #2          ; /(8/dpix=2)
        MOV     r0, r0, LSR #7
        ADR     r4, Mode3Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #5      ; *32 (*5 -> *160)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        addr    r4, Mode1Nibble
        LDRB    r0, [r4, r1, LSR #4]    ; MSN goes to LSB on ARM
        STRB    r0, [r2, #0]
        AND     r14, r1, #15
        LDRB    r0, [r4, r14]
        STRB    r0, [r2, #1]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^

Mode3Table
        GBLA    Mode3Vbl
Mode3Vbl SETA   0
        WHILE   Mode3Vbl < 160
        DCD     (Mode3Vbl :MOD: 5) + &32 * (Mode3Vbl/5)
Mode3Vbl SETA   Mode3Vbl + 1
        WEND


        AlignForModule

; *** MODE 6 ***: Double pixel plotted, so 80 bytes per raster, not 40

Mode6Write

        MakeScreenOffset

; r0 = offset from start of 6502 screen

        AND     r2, r0, #7
        ADD     r2, r2, r2, LSL #2      ; *5
        AND     r3, r0, #2_00111000     ; &40-8
        MOV     r3, r3, LSR #2          ; /(8/dpix=2)
        MOV     r0, r0, LSR #6          ; /&40
        ADR     r4, Mode3Table
        LDR     r4, [r4, r0, LSL #2]
        ADD     r2, r2, r4
        ADD     r2, r3, r2, LSL #4      ; *16 (*5 -> *80)

; r2 = offset from start of ARM screen

        AddOnScreenAddress

        LDRB    r3, [wp, #CRTC_CursorFlag]
        TEQ     r3, #0
        SWIEQ   XOS_RemoveCursors

        addr    r4, Mode1Nibble
        LDRB    r0, [r4, r1, LSR #4]    ; MSN goes to LSB on ARM
        STRB    r0, [r2, #0]
        AND     r14, r1, #15
        LDRB    r0, [r4, r14]
        STRB    r0, [r2, #1]

        TEQ     r3, #0
        SWIEQ   XOS_RestoreCursors

        Pull    "r0-r6, JV, pc",,^


; *************************** T E L E T E X T *********************************

        AlignForModule

; *** MODE 7 ***

Mode7Write

        MOV     r0, #Byte_ReadOutputCursor ; Must preserve this !
        SWI     XOS_Byte
        MOV     r4, r1                  ; POS
        MOV     r5, r2                  ; VPOS

        LDR     r0, [sp, #4*0]          ; 6502 address again

        MakeScreenOffset

        MOV     r3, #40                 ; BytesPerCharRow
        DivRem  r2, r0, r3, r14         ; r2(Y) = r0 DIV 40; r0(X) = r0 MOD 40

        SWI     OS_WriteI+31            ; TAB to place to plonk it
        SWI     OS_WriteC
        MOV     r0, r2
        SWI     OS_WriteC

        LDRB    r0, [sp, #4*1]          ; Character to poke
        ORR     r0, r0, #&80            ; 'Cos Tim says so ...
        SWI     OS_WriteC               ; Plonk char to screen

        SWI     OS_WriteI+31            ; TAB back to proper place
        MOV     r0, r4
        SWI     OS_WriteC
        MOV     r0, r5
        SWI     OS_WriteC

        Pull    "r0-r6, JV, pc",,^

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

WriteToDevice ENTRY "JV"

        GetWS
        BL      WriteIO
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; If sideways RAM is mapped in, dirty it and stop trapping

WriteToSidewaysArea ENTRY "JV"

        GetWS
        LDRB    r14, [wp, #SidewaysState]
        TST     r14, #SidewaysRAM
 [ faultromstore
        BLEQ    StoreViolation
 ]
        EXITS   EQ

        STRB    r1, [MR, r0]
        BL      DirtySidewaysArea       ; Requires wp valid
        EXITS

 [ faultromstore
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

StoreViolation ENTRY "JV"

        GetWS

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROM
        EXITS   EQ

        DREG    r1,"Tried to write byte ",cc,Byte
        LDRB    r14, [wp, #ROMLatchCopy]
        DREG    r14," to ROM ",cc,Byte
        DREG    r0," at ",,Word
        EXITS
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = 6502 address
;       wp valid

; Out   r1 = byte 'read' from r0 if ok
;       else high byte of 6502 address if device not present

        AlignForModule

ReadIO ENTRY "r0, r2, r3"

 [ debug
        AND     r3, r0, #&FF            ; Remember offset for debugging later
 ]
        CMP     r0, #&FE00              ; In SHEILA (FExx) ?
        ANDHS   r0, r0, #&FF            ; Which device therein ?
        BICHS   r14, r0, #2_1111
        ADDHS   pc, pc, r14, LSR #2     ; LSR #4 device, LSL #2 routine
        B       %FT50                   ; For addressing porpoises too

; In    r0  = offset from SHEILA

DevReadTable

        B       r00_0F                  ; 00..0F CRTC/ACIA
        B       r10_1F                  ; 10..1F SerialULA/EconetID
        B       rEconetIntOn            ; 20..2F
        B       rROMSEL_74LS161         ; 30..3F
        B       rSystemVIA_6522         ; 40..5F
        B       rSystemVIA_6522         ; 50
        B       rUserVIA_6522           ; 60..7F
        B       rUserVIA_6522           ; 70
        B       rFDC_8271               ; 80..9F
        B       rFDC_8271               ; 90
        B       rADLC_68B54             ; A0..BF
        B       rADLC_68B54             ; B0
        B       rADC_7002               ; C0..DF
        B       rADC_7002               ; D0
        B       rTubeULA                ; E0..FF
        B       rTubeULA                ; F0

 assert (.-DevReadTable) = (256*4/16)


        AlignForModule

50      CMP     r0, #&FD00              ; In JIM ?
        AND     r1, r0, #&FF            ; Be kind to any AFP code out there
 [ :LNOT: debug
        MOVHS   r0, #Byte_ReadJIM
        MOVLO   r0, #Byte_ReadFRED
        SWI     XOS_Byte
        MOVVC   r1, r2
        MOVVS   r1, #&FF               ; LS245 returns 1 on BBC if input floats
        EXITS
 |
        BLO     %FT95

; In JIM (FDxx)

        MOV     r0, #Byte_ReadJIM
        SWI     XOS_Byte
        MOVVC   r1, r2
        MOVVS   r1, #&FF               ; LS245 returns 1 on BBC if input floats

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_JIM
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from JIM FD",,Byte
00
        EXITS


        AlignForModule

95 ; In FRED (FCxx)

        MOV     r0, #Byte_ReadFRED
        SWI     XOS_Byte
        MOVVC   r1, r2
        MOVVS   r1, #&FF               ; LS245 returns 1 on BBC if input floats

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_FRED
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from FRED FC",,Byte
00
        EXITS
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Need to subdivide here due to coarse jump table

r00_0F ROUT

        CMP     r0, #&08                ; ACIA ?
        BHS     rACIA_6850

; .............................................................................
; R16,17 (LightPenPosn) read back as 0

rCRTC_6845 ROUT

        LDRB    r1, [wp, #CRTC_AddrReg]
        TEQ     r1, #14                 ; Cursor lo/hi ?
        TEQNE   r1, #15
        MOVNE   r1, #&00
 [ debug
        BNE     %FT90
 |
        EXITS   NE
 ]
        TEQ     r1, #14                 ; High ?
        LDR     r1, [wp, #CRTC_6845CursorAddr]
        MOVEQ   r1, r1, LSR #8
        ANDNE   r1, r1, #&FF

 [ debug
90      LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_CRTC
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from CRTC FE",,Byte
00
 ]
        EXITS

; .............................................................................

rACIA_6850 ROUT

        MOV     r1, #&00                ; No ACIA present (no IRQ)

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ACIA
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from ACIA FE",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Need to subdivide here due to coarse jump table

r10_1F ROUT

        CMP     r0, #&18
        BHS     rEconetID

; .............................................................................

rSerialULA ROUT

        MOV     r1, #&FE                ; No Serial ULA present

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_SerialULA
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from Serial ULA FC",,Byte
00
 ]
        EXITS

; .............................................................................
; Read Econet ID latch - yields station number

rEconetID ROUT

        SWI     XEconet_ReadLocalStationAndNet
        MOVVC   r1, r0
        MOVVS   r1, #&01                ; Duff number, using 1 instead, as
                                        ; the saying goes ...

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ADLC        ; For want of a better one
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from EconetID latch FE",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Enable NMI from Econet IntOn flip-flop

rEconetIntOn ROUT

        MOV     r1, #&FE

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ADLC        ; For want of a better one
        BEQ     %FT00

        DREG    r3,"Read from (and enabled) EconetIntOn latch FE",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Needed if Master 128 emulation. But then we'd have to do MEMCTL too ...

rROMSEL_74LS161 ROUT

        LDRB    r1, [wp, #ROMLatchCopy]
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROMSEL
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from ROMSEL",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

rSystemVIA_6522 ROUT

        ADD     r2, wp, #SystemVIARegs
        AND     r14, r0, #&0F
        ADD     pc, pc, r14, LSL #2
        NOP
rSVtablebase
        ^       0
        B       rSV_IRB
VIA_IRB #       1
        B       rSV_IRA
VIA_IRA #       1
        B       rSV_DDRB
VIA_DDRB #      1
        B       rSV_DDRA
VIA_DDRA #      1
        B       rSV_T0CL
VIA_T0CL #      1
        B       rSV_T0CH
VIA_T0CH #      1
        B       rSV_T0LL
VIA_T0LL #      1
        B       rSV_T0LH
VIA_T0LH #      1
        B       rSV_T1CL
VIA_T1CL #      1
        B       rSV_T1CH
VIA_T1CH #      1
        B       rSV_SR
VIA_SR  #       1
        B       rSV_ACR
VIA_ACR #       1
        B       rSV_PCR
VIA_PCR #       1
        B       rSV_IFR
VIA_IFR #       1
        B       rSV_IER
VIA_IER #       1
        B       rSV_IRA_NH
VIA_IRA_NH #    1
 assert .-rSVtablebase = 16*4

; Bits in System VIA IFR on BBC Model B

IFR_KBD_bit   * 2_00000001              ; CA2 edge
IFR_VSYNC_bit * 2_00000010              ; CA1 edge
IFR_SHIFT_bit * 2_00000100              ; Shift register clocked out
IFR_LPSTB_bit * 2_00001000              ; CB2 edge
IFR_ADC_bit   * 2_00010000              ; CB1 edge
IFR_T1_bit    * 2_00100000              ; Timer 1 timeout
IFR_T0_bit    * 2_01000000              ; Timer 0 timeout
IFR_IRQ_bit   * 2_10000000              ; IRQ from this VIA

        AlignForModule
rSV_DDRB
rSV_DDRA
rSV_T0CL
rSV_T0CH
rSV_T0LL
rSV_T0LH
rSV_T1CL
rSV_T1CH
rSV_ACR
rSV_PCR
        LDRB    r1, [r2, r14]
 [ debug
99
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_SystemVIA
        BEQ     %FT00

        LDR     r3, [sp]                ; What was r0 again (r3 corrupted)
        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from System VIA FE",,Byte
00
 ]
        EXITS


; Assumes that DDRB programming has been done to set PB4,PB5 as inputs
; BBC MOS 1.20 only programs DDRB once, just after RESET, to &0F
; It then writes PB3 = 1 to devices 6..0 in that order

; PB0-2 are outputs that select one of eight 'slow bus' devices
; PB3   is the bit to write to the selected 'slow bus' device
; PB4-5 are joystick inputs. 1 -> no button pressed, 0 -> button pressed
; PB6   is input from speech processor interrupt output
; PB7   is input from speech processor ready output. 1 -> not ready
;   BBC MOS 1.20 takes 1 as being 'chip not present' at RESET time

slowbus_mask        * 2_0111
slowbus_sound       * 2_0000 ; PB3 = 0 -> write enable. Needs 8us strobe
slowbus_readspeech  * 2_0001
slowbus_writespeech * 2_0010
slowbus_kbd         * 2_0011 ; PB3 = 0 -> latch row data, enable kbd read PA7
slowbus_screen4     * 2_0100 ; PB3 irrelevant
slowbus_screen5     * 2_0101 ; PB3 irrelevant
slowbus_capsLED     * 2_0110 ; PB3 = 1 -> OFF, 0 -> ON
slowbus_shiftLED    * 2_0111 ; PB3 = 1 -> OFF, 0 -> ON
pb3                 * 2_1000

rSV_IRB
        LDRB    r3, [r2, #VIA_ORB]      ; IRB outputs can be read back
 [ False
        LDRB    r14, [r2, #VIA_DDRB]    ; Such is the way of the great 6522
        AND     r3, r3, r14             ; So we can ORR in the inputs
        Push    r14                     ; Only needed if DDRB not as expected
 |
        AND     r3, r3, #&0F            ; What we know we've programmed DDRB to
                                        ; PB6-7 are speech processor inputs
                                        ; and should be 0 -> ~ready, ~irq
 ]

        MOV     r0, #Byte_ADVAL
        MOV     r1, #0
        SWI     XOS_Byte                ; Read from I/O Podule
        AND     r1, r1, #2_11           ; b0,b1 = FIRE pressed
        EOR     r1, r1, #2_11           ; Returns 1 = not pressed
 [ False
        Pull    r14                     ; Only needed if DDRB not as expected
        BIC     r1, r1, r14, LSR #4     ; If DDRB set for either as output then
                                        ; clear before damaging ip readback
 ]
        ORR     r1, r3, r1, LSL #4      ; Merge inputs with old outputs
 [ debug
        B       %BT99
 ]
        EXITS


; Note: I can't implement IRA (or IRA_NH) properly as it should return the
; actual voltage levels on PA0-7 regardless of DDRA, either when read or at
; latch time (CA1 driven).

; Keyboard use of ports:
; DDRA set to &7F, ORA_NH hit, IRA_NH read, N flag set on PB7 input
; PA7 is used as an input from the LS251 = state of selected data input (row)
;  Active only when LS251 strobe input low (PB3)
; PA4-6 are used as outputs to the LS251 to select a data input for PA7
; PA0-3 are used as outputs to a LS163 latch driving the LS45 4->10 BCD decoder
;  to setup a keyboard column address

; Sound & speech use different

rSV_IRA
rSV_IRA_NH
        LDRB    r3, [r2, #VIA_ORA]      ; What did we select out there?
        LDRB    r14, [r2, #VIA_DDRA]    ; Such is the way of the great 6522
        AND     r3, r3, r14

        LDRB    r14, [r2, #VIA_ORB]
        AND     r14, r14, #slowbus_mask + pb3 ; Which slow bus device active ?
        TEQ     r14,      #slowbus_kbd  +  0  ; Reading keyboard matrix ?
        BNE     %FT60                   ; Maybe abort here: I can't see any
                                        ; reason other than reading kbd!
                                        ; Only other i/p device is speech read

        AND     r3, r3, #&7F            ; PA0-6 are outputs now if doing kbd
        CMP     r3, #&02                ; Keyboard options ? (row 0)
        RSBCSS  r14, r3, #&09

        LDRCSB  r1, [wp, #keyOptions]   ; decoder 02..09 -> &07..&00
        SUBCS   r14, r3, #&02           ; Bit 7 of keyOptions = 02 on decoder
        RSBCS   r14, r14, #&07
        MOVCS   r0, #1
        ANDCS   r1, r1, r0, LSL r14     ; 0/~0

        MOVCC   r0, #Byte_ScanKeyboard
        ORRCC   r1, r3, #&80            ; r3 is the key to scan (row*16+col)
        SWICC   XOS_Byte

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_SystemVIA
        BEQ     %FT00

        DREG    r3,"Scanned key ",cc,Byte
        DREG    r1," = ",,Byte
00
 ]
        TEQ     r1, #0
        ORRNE   r3, r3, #&80            ; Key is closed

60      MOV     r1, r3
 [ debug
        B       %BT99
 ]
        EXITS


rSV_SR
        LDRB    r1, [r2, #VIA_IFR]
        TST     r1, #IFR_SHIFT_bit
        BICNE   r1, r1, #IFR_SHIFT_bit  ; Clear SR interrupt if needs be
        STRNEB  r1, [r2, #VIA_IFR]

        LDRB    r1, [r2, #VIA_SR]
 [ debug
        B       %BT99
 ]
        EXITS


        AlignForModule
rSV_IFR
        LDRB    r1, [r2, #VIA_IFR]      ; Status (has bit 7 clear)
        LDRB    r2, [r2, #VIA_IER]      ; Mask (has bit 7 clear)
        TST     r1, r2                  ; -> Requesting bit for whole VIA
        ORRNE   r1, r1, #IFR_IRQ_bit    ; Any interrupt enabled in System VIA ?
 [ debug
        B       %BT99
 ]
        EXITS


        AlignForModule
rSV_IER
        LDRB    r1, [r2, #VIA_IER]
        ORR     r1, r1, #&80            ; Such that if we store back, intsrc's
 [ debug
        B       %BT99
 ]
        EXITS                           ; are kept enabled

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; If we were really bothered, we could bash the I/O Podule ourselves ...

rUserVIA_6522 ROUT

        MOV     r1, r0
        MOV     r0, #Byte_ReadSHEILA
        SWI     XOS_Byte
        MOVVC   r1, r2
        MOVVS   r1, #&00                ; No User VIA present if error

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_UserVIA
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from User VIA FE",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 [ debug
rFDC_8271 ROUT

        MOV     r1, #&FE                ; No FDC present

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_FDC
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from FDC FE",,Byte
00
        EXITS
 |

rNOP_FE ROUT

        MOV     r1, #&FE                ; No device present, bus undriven
        EXITS

rFDC_8271 * rNOP_FE
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 [ debug
rADLC_68B54 ROUT

        MOV     r1, #&FE                ; No ADLC present

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ADLC
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from ADLC FE",,Byte
00
        EXITS

 |

rADLC_68B54 * rNOP_FE
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

rADC_7002 ROUT

        MOV     r1, r0
        MOV     r0, #Byte_ReadSHEILA
        SWI     XOS_Byte
        MOVVC   r1, r2
        MOVVS   r1, #&00                ; If faulty, not interrupting

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ADC
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from ADC FE",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 [ debug
rTubeULA ROUT

        MOV     r1, #&FE                ; No Tube ULA present

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_TubeULA
        BEQ     %FT00

        DREG    r1,"Read byte ",cc,Byte
        DREG    r3," from Tube ULA FE",,Byte
00
        EXITS

 |

rTubeULA * rNOP_FE
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r1 = byte to 'write' at 6502 address r0
;       wp valid

        AlignForModule

WriteIO ENTRY "r0-r11"

 [ debug
        AND     r3, r0, #&FF            ; Remember offset for debugging
 ]
        CMP     r0, #&FE00              ; In SHEILA ?
        ANDHS   r0, r0, #&FF            ; Which device ?
        BICHS   r14, r0, #2_1111        ; Break down into chunks of 16 bytes
        ADDHS   pc, pc, r14, LSR #2     ; LSR #4 for device, LSL #2 for routine
        B       %FT50                   ; For addressing porpoises too

; In    r0  = offset from SHEILA

DevWriteTable

        B       w00_0F                  ; 00..0F
        B       w10_1F                  ; 10..1F
        B       wVideoULA               ; 20..2F
        B       wROMSEL_74LS161         ; 30..3F
        B       wSystemVIA_6522         ; 40..5F
        B       wSystemVIA_6522         ; 50
        B       wUserVIA_6522           ; 60..7F
        B       wUserVIA_6522           ; 70
        B       wFDC_8271               ; 80..9F
        B       wFDC_8271               ; 90
        B       wADLC_68B54             ; A0..BF
        B       wADLC_68B54             ; B0
        B       wADC_7002               ; C0..DF
        B       wADC_7002               ; D0
        B       wTubeULA                ; E0..FF
        B       wTubeULA                ; F0

 assert (.-DevWriteTable) = (256*4/16)


        AlignForModule

50      CMP     r0, #&FD00              ; In JIM ?
        MOV     r2, r1
        AND     r1, r0, #&FF            ; Be kind to any AFP code out there
 [ :LNOT: debug
        MOVHS   r0, #Byte_WriteJIM
        MOVLO   r0, #Byte_WriteFRED
        SWI     XOS_Byte
        EXITS
 |
        BLO     %FT95

; In JIM (FDxx)

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_JIM
        BEQ     %FT00

        DREG    r1,"Writing byte ",cc,Byte
        DREG    r3," to JIM FD",,Byte
00
        MOV     r0, #Byte_WriteJIM
        SWI     XOS_Byte
        EXITS


95 ; In FRED (FCxx)

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_FRED
        BEQ     %FT00

        DREG    r1,"Writing byte ",cc,Byte
        DREG    r3," to FRED FC",,Byte
00
        MOV     r0, #Byte_WriteFRED
        SWI     XOS_Byte
        EXITS
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Need to subdivide here due to coarse jump table

        AlignForModule

w00_0F ROUT

        CMP     r0, #&08
        BHS     wACIA_6850

; .............................................................................
; 6845 emulation depends on Arthur OS emulation of VDU 23,0,r,v|

; No interlace please, I can't take it and the correct code is nigh on
; impossible (would need to conditional on pending mode change value)

wCRTC_6845 ROUT

        TST     r0, #1                  ; Writing address register ?
        ANDEQ   r1, r1, #2_00011111     ; 5 bit number
        STREQB  r1, [wp, #CRTC_AddrReg]
 [ debug
        BNE     %FT00

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_CRTC
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to CRTC address register FE",,Byte
        EXITS

00
 |
        EXITS   EQ
 ]

        LDRB    r0, [wp, #CRTC_AddrReg] ; Which register is selected ?
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_CRTC
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r0," to CRTC FE01; register ",,Byte
00
 ]
        CMP     r0, #16                 ; Only registers R0-R15 writeable
        ADDLO   pc, pc, r0, LSL #2      ; R16,17 (light pen) are read-only
        EXITS   HS                      ; Also gives correct addressing

        B       wCRTC_HorizTotal        ; R0
        B       wCRTC_HorizDisplayed    ; R1
        B       wCRTC_HorizSync         ; R2
        B       wCRTC_SyncWidth         ; R3
        B       wCRTC_VerticalTotal     ; R4
        B       wCRTC_TotalAdjust       ; R5
        B       wCRTC_VerticalDisplayed ; R6
        B       wCRTC_VerticalSync      ; R7
        B       wCRTC_Interlace         ; R8
        B       wCRTC_ScanLines         ; R9
        B       wCRTC_CursorStart       ; R10
        B       wCRTC_CursorEnd         ; R11
        B       wCRTC_ScreenAddrHigh    ; R12
        B       wCRTC_ScreenAddrLow     ; R13
        B       wCRTC_CursorAddrHigh    ; R14
        B       wCRTC_CursorAddrLow     ; R15

wCRTC_HorizTotal
wCRTC_HorizDisplayed
wCRTC_HorizSync
wCRTC_SyncWidth
wCRTC_VerticalTotal
wCRTC_TotalAdjust
wCRTC_VerticalDisplayed
wCRTC_VerticalSync
wCRTC_Interlace
        EXITS                           ; Not implemented


wCRTC_ScanLines
        AND     r1, r1, #2_00011111     ; 5 bit register. Be paranoid
        CMP     r1, #9                  ; 9 and 18 -> Modes 3,6,7
        LDRB    r0, [wp, #CRTC_ModeReg]
        AND     r0, r0, #&1E
        ORRCS   r0, r0, #1
        STRB    r0, [wp, #CRTC_ModeReg]
        BL      SelectMode
        EXITS


wCRTC_CursorStart
wCRTC_CursorEnd
        AND     r14, r1, #&60           ; Turning cursor off ?
        TEQ     r14, #&20
        MOVEQ   r14, #&FF               ; Note that it's off
        MOVNE   r14, #0
        STRB    r14, [wp, #CRTC_CursorFlag]
        SWI     OS_WriteI+23            ; Reprogram 'CRTC' via VDU 23,0,r,v|
        SWI     OS_WriteI+0
        SWI     OS_WriteC
        AND     r0, r1, #&FF            ; Value
        SWI     OS_WriteC
        ADR     r0, SixZeroes
        MOV     r1, #6
        SWI     OS_WriteN
        EXITS

SixZeroes
        DCB     0, 0, 0, 0, 0, 0
        ALIGN


; ScreenStart register (13,12) programming

wCRTC_ScreenAddrHigh
        LDR     r0, [wp, #CRTC_6845ScreenAddr]
        AND     r0, r0, #&00FF          ; Mask out previous contents
        AND     r1, r1, #2_00111111     ; 6 bit register
        ORR     r0, r0, r1, LSL #8
        B       UpdateScreenAddr

wCRTC_ScreenAddrLow
        LDR     r0, [wp, #CRTC_6845ScreenAddr]
        AND     r0, r0, #&FF00          ; Mask out previous contents
        ORR     r0, r0, r1              ; 8 bit register

UpdateScreenAddr
        STR     r0, [wp, #CRTC_6845ScreenAddr]
        BL      ConvertTo6502Addr
        LDR     r2, [wp, #CRTC_ScreenAddr]
        STR     r0, [wp, #CRTC_ScreenAddr]

        SUB     r0, r0, r2              ; new - old
        LDR     r1, [wp, #CRTC_ScreenSize]
        ADDS    r0, r0, r1, LSR #1      ; (new-old)+ss/2
        ADDMI   r0, r0, r1              ; if negative then add on ss
        CMP     r0, r1                  ; if >= ss then subtract ss
        SUBCS   r0, r0, r1
        SUB     r0, r0, r1, LSR #1
        LDRB    r1, [wp, #ArmAddressFactor]
        TEQ     r1, #0                  ; MODE 7 ?
        BEQ     ScrollModeAwkward

        MUL     r0, r1, r0
        LDR     r1, [wp, #ARMScreenStart]
        ADD     r1, r1, r0, ASR #3
        LDR     r0, [wp, #TotalScreenSize]
        CMP     r1, #&02000000
        SUBCS   r1, r1, r0
        LDR     r2, [wp, #ARMScreenMemory]
        SUBS    r2, r1, r2
        ADDCC   r1, r1, r0
        ADDCC   r2, r2, r0
        STR     r1, [wp, #ARMScreenStart]
        MOV     r0, #&16                ; set screen start + VDU start
        ADD     r1, wp, #ScreenStartCB :AND: &00FF
        ADD     r1, r1, #ScreenStartCB :AND: &FF00
        STR     r2, [r1, #1]
        SWI     OS_Word
        EXITS


ScrollModeAwkward

        LDRB    r14, [wp, #ModeNumber]
        TEQ     r14, #7
        BNE     ScrollGapModes

        CMP     r0, #40                         ; if not down one row
        CMPNE   r0, #-40                        ; or up one row
        STRNE   r2, [wp, #CRTC_ScreenAddr]      ; then restore old screen addr
        EXITS   NE                              ; and wait for consistent value

90      TEQ     r0, #0
        ADRMI   r0, ScrollDownString
        ADRPL   r0, ScrollUpString
        MOV     r1, #11
        SWI     OS_WriteN
        EXITS


ScrollGapModes
        CMP     r14, #6
        BEQ     ScrollMode6

        CMP     r0, #&280                       ; if not down one row
        CMPNE   r0, #-&280                      ; or up one row
        STRNE   r2, [wp, #CRTC_ScreenAddr]      ; then restore old screen addr
        EXITS   NE                              ; and wait for consistent value

        B       %BT90


ScrollMode6
        CMP     r0, #&140                       ; if not down one row
        CMPNE   r0, #-&140                      ; or up one row
        STRNE   r2, [wp, #CRTC_ScreenAddr]      ; then restore old screen addr
        EXITS   NE                              ; and wait for consistent value

        B       %BT90


ScrollDownString
        DCB     23,7,1,2,0,0,0,0,0,0, 10

ScrollUpString
        DCB     23,7,1,3,0,0,0,0,0,0, 11

        ALIGN



; CursorPosition register (15,14) programming

wCRTC_CursorAddrHigh
        LDR     r0, [wp, #CRTC_6845CursorAddr]
        AND     r0, r0, #&00FF          ; Mask out previous contents
        AND     r1, r1, #2_00111111     ; 6 bit register
        ORR     r0, r0, r1, LSL #8
        B       UpdateCursorAddr

wCRTC_CursorAddrLow
        LDR     r0, [wp, #CRTC_6845CursorAddr]
        AND     r0, r0, #&FF00          ; Mask out previous contents
        ORR     r0, r0, r1              ; 8 bit register

UpdateCursorAddr
        STR     r0, [wp, #CRTC_6845CursorAddr]
        BL      ConvertTo6502Addr
        BL      ConvertToCharXY
        EXITS   VS
        SWI     OS_WriteI+31
        MOV     r0, r3
        SWI     OS_WriteC
        MOV     r0, r2
        SWI     OS_WriteC
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Ignore writes to ACIA

wACIA_6850 ROUT

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ACIA
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to ACIA FE",,Byte
00
 ]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Need to subdivide here due to coarse jump table

w10_1F ROUT

        CMP     r0, #&18
        EXITS   HS                      ; No hardware mapped here

; .............................................................................

wSerialULA ROUT

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_SerialULA
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to Serial ULA FE",,Byte
00
 ]
        MOV     r3, r1

        MOV     r0, #Byte_TxRate
        AND     r1, r3, #2_00000111     ; Bits 0..2 control TxRate
        ADD     r1, r1, #1
        SWI     XOS_Byte

        MOV     r0, #Byte_RxRate
        AND     r1, r3, #2_00111000     ; Bits 3..5 control RxRate
        MOV     r1, r1, LSR #3
        ADD     r1, r1, #1
        SWI     XOS_Byte

; Bit 6 = 0 -> Cassette interface selected
;       = 1 -> RS423 interface selected

; Bit 7 = 0 -> Cassette relay 'off'
;       = 1 -> Cassette relay 'on'

        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

wVideoULA ROUT

        TST     r0, #1                  ; CReg or PReg ?
        BNE     %FT50

; Control register

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_VideoULA
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to Video ULA control register FE",,Byte
00
 ]
        AND     r1, r1, #&1F            ; Ignore bits 5..7 (cursor width)

        LDRB    r3, [wp, #VideoULA_CReg] ; Changed at all since last time ?
        EORS    r3, r3, r1
        EXITS   EQ                      ; That was easy, then

        TST     r3, #1                  ; Changed flash since last time ?
        BEQ     %FT20

        STRB    r1, [wp, #VideoULA_CReg] ; Might be updating only this bit

        TST     r1, #1                  ; Mark or space ?
        MOVEQ   r0, #9
        MOVNE   r0, #10
        MOV     r1, #0
        SWI     XOS_Byte
        LDR     r1, [sp, #4*1]

20      TST     r3, #&1E                ; Changed MODE since last time ?
        EXITS   EQ

        STRB    r1, [wp, #VideoULA_CReg]

        LDRB    r0, [wp, #CRTC_ModeReg]  ; Do MODE change then !
        AND     r0, r0, #1
        AND     r1, r1, #&1E
        ORR     r0, r0, r1
        STRB    r0, [wp, #CRTC_ModeReg]
        BL      SelectMode
        EXITS


        AlignForModule

50 ; Palette register

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_VideoULA
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to Video ULA palette register FE",,Byte
00
 ]
        AND     r14,  r1, #&0F          ; Physical colour is EOR'ed with 7
        EOR     r14, r14, #&07
        STRB    r14, [wp, #PalettePhysical]
        LDR     pc, [wp, #PaletteRoutine]


        AlignForModule

TwoColourPalette
        MOV     r0, r1, LSR #7          ; Top bit = logical colour
        B       PaletteCommon


        AlignForModule

SixteenColourPalette
        MOV     r0, r1, LSR #4
        B       PaletteCommon


        AlignForModule

FourColourPalette
        MOVS    r0, r1, LSR #6          ;  logical colour
        BICCC   r0, r0, #1
        ORRCS   r0, r0, #1

PaletteCommon
        MOV     r1, wp
        STRB    r0, [r1, #PaletteLogical]!
        MOV     r0, #12                 ; Program palette OSWORD
        SWI     XOS_Word                ; Ignore errors

PallidPalette
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

wROMSEL_74LS161 ROUT

        LDRB    r0, [wp, #ROMLatchCopy] ; Same one as last time ?!
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROMSEL
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to ROM paging latch FE",cc,Byte
        DREG    r0,", old value ",,Byte
00
 ]
        TEQ     r0, r1                  ; eg. extended vector to same ROM
        EXITS   EQ

        STRB    r1, [wp, #ROMLatchCopy] ; Keep around so Master 128 progs work
                                        ; We have to keep track of it anyway

        LDRB    r14, [wp, #SidewaysState] ; Do we have to save away RAM ?
        TST     r14, #SidewaysDirty
        BNE     %FT50                   ; [yes]

        MOV     r0, r1                  ; New socket number

20      ADD     r1, MR, #&8000          ; Real address to load at (sideways)
        SWI     XROMCache_Load          ; 'Page in' that ROM image
                                        ; Out: r0 = n bytes, r1 = RAM or ROM
        MOVVS   r0, #0                  ; No bytes moved if error

        TEQ     r0, #0                  ; Got a good image ?
        MOVEQ   r1, #0                  ; Ensure not RAM if duff
        BL      TrapSidewaysPages
        EXITS   NE                      ; Yup !

        ADD     r1, MR, #&8000          ; Crap on ROM image so validity check
        LDR     r0, [wp, #ROMLatchCopy] ; falls at first hurdle (~= 0,'(C)')
        ADD     r0, r0, #1
        ORR     r0, r0, r0, LSL #8
        ORR     r0, r0, r0, LSL #16     ; r0 := 0x0x0x0x
        STR     r0, [r1, #0]            ; Also make ROMs look
        STR     r0, [r1, #4]            ; different to save init time
        STR     r0, [r1, #8]
        STR     r0, [r1, #12]
        EXITS


50 ; r0 = old socket number, r1 = new socket number

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROM
        BEQ     %FT00

        DLINE   "Saving dirty sideways RAM back to cache"
00
 ]
        Push    r1
        ADD     r1, MR, #&8000          ; Real address to save from (sideways)
        SWI     XROMCache_Save          ; 'Page out' old RAM
        Pull    r0
        B       %BT20

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        AlignForModule

wSystemVIA_6522 ROUT

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_SystemVIA
        BEQ     %FT00

        DREG    r1,"Writing byte ",cc,Byte
        DREG    r3," to System VIA FE",,Byte
00
 ]
        ADD     r2, wp, #SystemVIARegs
        AND     r14, r0, #&0F
        ADD     pc, pc, r14, LSL #2
        NOP
wSVtablebase
        ^       0
        B       wSV_ORB
VIA_ORB #       1
        B       wSV_ORA
VIA_ORA #       1
        B       wSV_DDRB
        B       wSV_DDRA
        B       wSV_T0CL
        B       wSV_T0CH
        B       wSV_T0LL
        B       wSV_T0LH
        B       wSV_T1CL
        B       wSV_T1CH
        B       wSV_SR
        B       wSV_ACR
        B       wSV_PCR
        B       wSV_IFR
        B       wSV_IER
        B       wSV_ORA_NH
 assert .-wSVtablebase = 16*4


        AlignForModule
wSV_DDRB
wSV_DDRA
wSV_T0CL
wSV_T0CH
wSV_T0LL
wSV_T0LH
wSV_T1CL
wSV_T1CH
wSV_ACR
wSV_PCR
        STRB    r1, [r2, r14]
        EXITS


        AlignForModule
wSV_ORB
        STRB    r1, [r2, #VIA_ORB]
        LDRB    r1, [r2, #VIA_IFR]
        TST     r1, #IFR_ADC_bit
        BICNE   r1, r1, #IFR_ADC_bit   ; Clear CB1 interrupt if needs be
        STRNEB  r1, [r2, #VIA_IFR]
        EXITS


        AlignForModule
wSV_ORA
        LDRB    r0, [r2, #VIA_IFR]
        TST     r0, #IFR_VSYNC_bit
        BICNE   r0, r0, #IFR_VSYNC_bit  ; Clear CA1 interrupt if needs be
        STRNEB  r0, [r2, #VIA_IFR]

wSV_ORA_NH ; Also aligned ok
        STRB    r1, [r2, #VIA_ORA]
        EXITS


wSV_SR
        STRB    r1, [r2, #VIA_SR]
        LDRB    r1, [r2, #VIA_IFR]
        TST     r1, #IFR_SHIFT_bit
        BICNE   r1, r1, #IFR_SHIFT_bit  ; Clear SR interrupt if needs be
        STRNEB  r1, [r2, #VIA_IFR]
        EXITS


        AlignForModule
wSV_IFR                                 ; Writing interrupt flag register
        LDRB    r14, [r2, #VIA_IFR]     ; Clear interrupt(s) from System VIA
        BIC     r14, r14, r1            ; Must keep bit 7 clear in core
        STRB    r14, [r2, #VIA_IFR]
        EXITS


        AlignForModule
wSV_IER                                 ; Writing interrupt enable register
        LDRB    r14, [r2, #VIA_IER]
        TST     r1, #&80                ; Clearing or setting bits ?
        BICEQ   r14, r14, r1
        BICNE   r1, r1, #&80            ; Must keep bit 7 clear in core
        ORRNE   r14, r14, r1
        STRB    r14, [r2, #VIA_IER]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; If we were really bothered, we could bash the I/O Podule ourselves ...

wUserVIA_6522 ROUT

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_UserVIA
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to User VIA FE",,Byte
00
 ]
        CMP     r0, #VIA_T1CL           ; Remember low value and exit
        STREQB  r1, [wp, #UserTimerLow]
        EXITS   EQ

        CMP     r0, #VIA_T1CH           ; Must fudge timers
        BEQ     %FT50

        MOV     r2, r1
        MOV     r1, r0
        MOV     r0, #Byte_WriteSHEILA
        SWI     XOS_Byte

wNOP    EXITS


50      LDRB    r3, [wp, #UserTimerLow]
        ORR     r3, r3, r1, LSL #8
        ADD     r3, r3, r3              ; Must program twice the value set
        CMP     r3, #(1 :SHL: 16)       ; Now too big ?
        MOVHS   r3, #&FF                ; Program longest time available
        ORRHS   r3, r3, #&FF00

        AND     r2, r3, #&FF
        MOV     r1, #&60 + VIA_T1CL     ; Set low order latches
        MOV     r0, #Byte_WriteSHEILA
        SWI     XOS_Byte

        MOV     r2, r3, LSR #8
        MOV     r1, #&60 + VIA_T1CH     ; Set high order and go
        MOV     r0, #Byte_WriteSHEILA
        SWIVC   XOS_Byte
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Ignore writes to FDC

 [ debug
wFDC_8271 ROUT

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_FDC
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to FDC FE",,Byte
00
        EXITS

 |

wFDC_8271 * wNOP
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Ignore writes to ADLC

 [ debug
wADLC_68B54 ROUT

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ADLC
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to ADLC FE",,Byte
00
        EXITS

 |

wADLC_68B54 * wNOP
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

wADC_7002 ROUT

 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ADC
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to ADC FE",,Byte
00
 ]
        MOV     r2, r1
        MOV     r1, r0
        MOV     r0, #Byte_WriteSHEILA
        SWI     XOS_Byte
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Ignore writes to Tube ULA

 [ debug
wTubeULA ROUT

        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_TubeULA
        BEQ     %FT00

        DREG    r1,"Writing ",cc,Byte
        DREG    r3," to Tube ULA FE",,Byte
00
        EXITS

 |

wTubeULA * wNOP
 ]

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = mode select (0..31)

; Out   r0, r1 corrupt

SelectMode ENTRY "r2"

        ADR     r14, ModeSelectTable
        LDRB    r0, [r14, r0]
        TEQ     r0, #&FF                ; Ignore duff combinations
        EXITS   EQ

        MOV     r2, r0
        STRB    r2, [wp, #ModeNumber]
 [ False
 DREG r2, "Mode number ",cc
 ]

        SWI     OS_WriteI+22            ; Do MODE to Arthur VDU system
        SWI     OS_WriteC

        TEQ     r2, #3
        TEQNE   r2, #6
        BNE     %FT10

        SWI     OS_WriteI+28            ; Set text window
        SWI     OS_WriteI+0
        SWI     OS_WriteI+25-1
        TEQ     r2, #3
        SWIEQ   OS_WriteI+80-1
        SWINE   OS_WriteI+40-1
        SWI     OS_WriteI+0

10      TEQ     r2, #7
        ADREQ   r0, ProtectScroll       ; So we don't blow up when poking rhbot
        MOVEQ   r1, #10                 ; in MODE 7
        SWIEQ   OS_WriteN


        ADR     r1, ArmAddressFactorTable
        LDRB    r0, [r1, r2]
        STRB    r0, [wp, #ArmAddressFactor]

        ADR     r1, ModeFactorTable
        LDR     r0, [r1, r2, LSL #2]

        AND     r1, r0, #15
        STRB    r1, [wp, #CRTC_XCharFactor]
 [ False
 DREG r1,"; XCharFactor ",cc,Byte
 ]

        MOV     r1, r0, LSR #16
        STRB    r1, [wp, #ScrBRow]
 [ False
 DREG r1,"; ScrBRow ",cc,Byte
 ]
        AND     r1, r1, #&FF00
        STR     r1, [wp, #CRTC_ScreenSize]
        RSB     r1, r1, #&8000
        STR     r1, [wp, #CRTC_ScreenAddr]
 [ False
 DREG r1,"; ScreenSize ",cc,Word
 ]

        MOV     r1, r0, LSL #16
        MOV     r1, r1, LSR #16+4
        STR     r1, [wp, #CRTC_BytesPerCharRow]
 [ False
 DREG r1,"; BytesPerCharRow ",,Word
 ]

        ADR     r1, ScreenWriteTable
        LDR     r0, [r1, r2, LSL #2]
        ADD     r0, r0, r1
        STR     r0, [wp, #ScreenWriteRoutine]

        ADR     r1, PaletteCodeTable
        LDR     r0, [r1, r2, LSL #2]
        ADD     r0, r0, r1
        STR     r0, [wp, #PaletteRoutine]

        MOV     r0, #VduExt_TotalScreenSize
        MOV     r1, #-1
        Push    "r0, r1"
        MOV     r0, sp                  ; Input^
        MOV     r1, sp                  ; Output^
        SWI     XOS_ReadVduVariables
        Pull    "r0, r1"
        STR     r0, [wp, #TotalScreenSize]
        RSB     r0, r0, #&02000000      ; Compute logical address of start
        STR     r0, [wp, #ARMScreenMemory] ; of ARM screen memory
        STR     r0, [wp, #ARMScreenStart]

90      BL      TrapScreenPages
        EXITS


ArmAddressFactorTable
        DCB     8,8,16,0,16,16,0,0

ModeSelectTable
        DCB     &FF, &FF, &FF, &FF
        DCB       5, &FF, &FF, &FF
        DCB       4,   6, &FF,   7
        DCB     &FF, &FF, &FF, &FF
        DCB     &FF, &FF, &FF, &FF
        DCB       2, &FF, &FF, &FF
        DCB       1, &FF, &FF, &FF
        DCB       0,   3, &FF, &FF


ProtectScroll
        DCB     23,16,1,0,0,0,0,0,0,0   ; Set scroll protection if MODE 7 set
        ALIGN


; Bits 00..03 = XCharFactor
; Bits 04..15 = BytesPerCharRow
; Bits 16..23 = ScrBRow
; Bits 24..31 = ScreenSize >> 8

ModeFactorTable
        DCD     &501F2803
        DCD     &501F2804
        DCD     &501F2805
        DCD     &40182803
        DCD     &281F1403
        DCD     &281F1404
        DCD     &20181403
        DCD     &04180280

ScreenWriteTable
        DCD     Mode0Write - ScreenWriteTable ; 0
        DCD     Mode1Write - ScreenWriteTable ; 1
        DCD     Mode2Write - ScreenWriteTable ; 2
        DCD     Mode3Write - ScreenWriteTable ; 3 - going to be difficult
        DCD     Mode4Write - ScreenWriteTable ; 4
        DCD     Mode5Write - ScreenWriteTable ; 5
        DCD     Mode6Write - ScreenWriteTable ; 6 - going to be difficult
        DCD     Mode7Write - ScreenWriteTable ; 7


PaletteCodeTable
        DCD     TwoColourPalette     - PaletteCodeTable ; 0
        DCD     FourColourPalette    - PaletteCodeTable ; 1
        DCD     SixteenColourPalette - PaletteCodeTable ; 2
        DCD     TwoColourPalette     - PaletteCodeTable ; 3
        DCD     TwoColourPalette     - PaletteCodeTable ; 4
        DCD     FourColourPalette    - PaletteCodeTable ; 5
        DCD     TwoColourPalette     - PaletteCodeTable ; 6
        DCD     PallidPalette        - PaletteCodeTable ; 7

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = 6845 address

; Out   r0 = 6502 address

ConvertTo6502Addr ENTRY

        LDRB    r14, [wp, #ModeNumber]
        TEQ     r14, #7
        MOVNE   r0, r0, LSL #3          ; Simple *8 suffices
        EOREQ   r0, r0, #&2000          ; Mystic runes for MODE 7 !
        ADDEQ   r0, r0, #&7400
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = 6502 address

; Out   r3, r2 = X, Y (POS, VPOS)
;       r0, r1 corrupt

ConvertToCharXY ROUT

        LDR     r3, [wp, #CRTC_ScreenAddr]
        SUBS    r0, r0, r3
        LDRCC   r3, [wp, #CRTC_ScreenSize]
        ADDCC   r0, r0, r3

        LDR     r1, [wp, #CRTC_BytesPerCharRow]
        DivRem  r2, r0, r1, r3          ; Yrow = r0 DIV bpr; r0 = r0 MOD bpr

        LDRB    r1, [wp, #ScrBRow]
        CMP     r2, r1                  ; if Yrow > ScrBRow
        ORRHIS  pc, lr, #V_bit

        LDRB    r1, [wp, #CRTC_XCharFactor]
        MOV     r3, r0, LSR r1          ; X = char cell number
        BICS    pc, lr, #V_bit

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 'Power-on' initialisation for the 6502 Host environment

; In    wp valid

PowerOnReset ENTRY "r0-r11"

        MOV     r0, #Byte_ByteVars      ; Read address of OSByteVars
        MOV     r1, #0
        MOV     r2, #&FF
        SWI     XOS_Byte
        ADD     r0, r1, r2, LSL #8
        ADD     r0, r0, #&B0            ; &0236 isn't immediate, so there
        STR     r0, [wp, #wpvar_osbytevars_pB0]

        MOV     r0, #&30000             ; Temp fudge
        STR     r0, [wp, #GapModeBuffer]

; System VIA only initialised at 'power-on'

        MOV     r0, #0                  ; No interrupts enabled from System VIA
        STRB    r0, [wp, #SystemVIARegs + VIA_IER]  ; Also acts as power on BBC
        STRB    r0, [wp, #SystemVIARegs + VIA_IFR]  ; No IRQs from System VIA
        STRB    r0, [wp, #SystemVIARegs + VIA_DDRA] ; All inputs
        STRB    r0, [wp, #SystemVIARegs + VIA_DDRB] ; All inputs

 [ False
        STRB    r0, [wp, #keyOptions]   ; Keyboard switches
 ]

; Other devices are reset each time we are entered

        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Define addressing macros equivalent to LDRB r1, [MR, r0] etc.

        MACRO
$label  LoadByte $byte, $addr
  [ $addr <> r0
  ! 1, "Must use r0 as address register for LoadByte"
  MEXIT
  ]
$label  BL      LoadByte_10
  [ $byte <> r1
        MOV     $byte, r1
  ]
        MEND


        MACRO
$label  LoadByteFNZ $byte, $addr
  [ $addr <> r0
  ! 1, "Must use r0 as address register for LoadByteFNZ"
  MEXIT
  ]
  [ $byte <> r1
  ! 1, "Must use r1 as data register for LoadByteFNZ"
  MEXIT
  ]
$label  LDRB    r1, [MR, r0]
        TEQ     r1, #TrapByte
        BLEQ    LoadByte_10
        MEND


        MACRO
$label  StoreByte $byte, $addr
  [ $addr <> r0
  ! 1, "Must use r0 as address register for StoreByte"
  MEXIT
  ]
  [ $byte <> r1
  ! 1, "Must use r1 as data register for StoreByte"
  MEXIT
  ]
$label  BL      StoreByte_10
        MEND

; Can NOT do a corresponding StoreByteFNZ as write trapped areas are
; quite often DIRECTLY readable, eg. OS ROM, and this would let us trash them

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                        B B C   R O M   C A C H E
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Initialise the ROM cache

; In    r0-r6 trashable

InitialiseROMCache ENTRY "r7, r8, r10, r11", 256

        MOV     r14, #DefaultBASICSocket ; Put BASIC in its place
        STRB    r14, [wp, #BASICSocket]

; Emumerate ROM files

        MOV     r0, #FSControl_LookupFS ; If ccrfs: there he has all the
        ADR     r1, ccrfs_name          ; rom: files too
        MOV     r2, #-1
        SWI     XOS_FSControl
        TEQ     r2, #0                  ; Not present
        SETV    EQ
        ADRVC   r1, ccrfs_dir
        ADRVS   r1, romfs_dir
        STR     r1, [wp, #ROMDirectory]
        ADRVC   r1, ccrfs_prefix
        ADRVS   r1, romfs_prefix
        STR     r1, [wp, #ROMPrefix]

        MOV     r4, #0                  ; Start at beginning of dir
        MOV     r7, #0                  ; Start at lowest priority ROM
        MOV     r8, #0                  ; No error so far
        ADD     r10, wp, #RomAddrTable
        ADD     r11, wp, #RomStateTable

30      MOV     r14, #0                 ; All sockets start off as ROM
        STRB    r14, [r11, r7]

        CMP     r7, #DefaultBASICSocket
        MOVNE   r14, #0                 ; All sockets start off empty
        MOVEQ   r14, #42                ; Apart from BASIC. ~= 0 -> cached ~RMA
        STR     r14, [r10, r7, LSL #2]

; ***^  CMP     r7, #DefaultBASICSocket ; Don't put external ROM here
        CMPNE   r8, #1                  ; Error in enumerate ?
        CMPNE   r4, #-1                 ; End of dir ?
        BEQ     %FT80                   ; [Yes. Step to next socket]

        MOV     r0, #OSGBPB_ReadDirEntriesInfo
        LDR     r1, [wp, #ROMDirectory] ; Where to look for files
        MOV     r2, sp
        MOV     r3, #1
        MOV     r5, #256-4
        ADR     r6, ROMWildCard
        SWI     XOS_GBPB                ; May not have rom:, so watch out!
        MOVVS   r8, #1                  ; Log error and fill rest of sockets
        BVS     %FT80                   ; Step to next socket

        CMP     r3, #0                  ; Read anything ?
        BEQ     %BT30                   ; Loop, same socket

        LDR     r14, [sp, #RDE_type]    ; Object type ?
        CMP     r14, #object_file
        LDREQ   r14, [sp, #RDE_load]    ; BBC ROM file ?
        LDREQ   r0, =&FFFFFBBC
        CMPEQ   r0, r14, ASR #8         ; Knocking out the top date byte
        BNE     %BT30                   ; Loop, same socket

        ADD     r1, sp, #RDE_name-8     ; Glue prefix onto start of name
        LDR     r2, [wp, #ROMPrefix]
        LDMIA   r2, {r3, r5}
        STMIA   r1, {r3, r5}
        MOV     r0, r7                  ; Slot number to put into
        BL      ROMCache_Link_Code      ; Can't call SWI as not on module list
        BVS     %FA90                   ; Real error, annoying us

80      ADD     r7, r7, #1              ; Next ROM socket
        CMP     r7, #NRoms
        BLO     %BT30

85      CLRV

90      EXIT


romfs_prefix    DCB     "  ro"          ; Picked up as whole words !
                DCB     "m:@."

ccrfs_prefix    DCB     "  rf"          ; Picked up as whole words !
                DCB     "s:@."

romfs_dir       DCB     "rom:@", 0

ccrfs_name      DCB     "rfs", 0
ccrfs_dir       DCB     "rfs:@", 0

ROMWildCard     DCB     "*", 0

                ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

AllocateROM_Code ENTRY

; wp not needed

        MOV     r1, r0
        MOV     r0, #10 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWI     XOS_ReadUnsigned        ; as the SWIs give better errors
        EXIT    VS
        MOV     r3, r2                  ; Remember first arg

10      LDRB    r14, [r1], #1           ; If no second arg, use default size
        CMP     r14, #space
        BEQ     %BT10
        SUB     r1, r1, #1

        MOVLO   r2, #ROMmaxsize
        MOVHS   r0, #16 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWIHS   XOS_ReadUnsigned        ; as the SWIs give better errors

        MOVVC   r0, r3                  ; Get it in the right slot
        MOVVC   r1, r2
        SWIVC   XROMCache_Allocate
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

CacheROM_Code ENTRY

; wp not needed

        MOV     r1, r0
        MOV     r0, #10 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWI     XOS_ReadUnsigned        ; as the SWIs give better errors
                                        ; r1 -> filename
        MOVVC   r0, r2                  ; Get it in the right slot
        SWIVC   XROMCache_Cache         ; Null implies ensure cached
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

LinkROM_Code ENTRY

; wp not needed

        MOV     r1, r0
        MOV     r0, #10 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWI     XOS_ReadUnsigned        ; as the SWIs give better errors
                                        ; r1 -> filename
        MOVVC   r0, r2                  ; Get it in the right slot
        SWIVC   XROMCache_Link          ; Null implies flush socket and
        EXIT                            ; deallocate

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

MakeRAM_Code ENTRY

; wp not needed

        MOV     r3, #SidewaysRAM

50      MOV     r1, r0                  ; ep for below
        MOV     r0, #10 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWI     XOS_ReadUnsigned        ; as the SWIs give better errors

        MOVVC   r0, r2                  ; Get it in the right slot
        MOVVC   r1, r3
        SWIVC   XROMCache_SetState
        EXIT

; .............................................................................
; In    r0-r6 trashable

MakeROM_Code ALTENTRY

        MOV     r3, #0
        B       %BT50

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

SwapROMs_Code ENTRY

; wp not needed

        MOV     r1, r0
        MOV     r0, #10 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWI     XOS_ReadUnsigned        ; as the SWIs give better errors
        MOVVC   r3, r2                  ; Remember first arg

        MOVVC   r0, #10 + (2_100 :SHL: 29) ; Bad terms, no restrict to range
        SWIVC   XOS_ReadUnsigned

        MOVVC   r0, r3                  ; Get it in the right slot
        MOVVC   r1, r2                  ; And this one too
        SWIVC   XROMCache_Swap
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

; Cached ROM images are rounded up to multiples of this length for LDM/STM xfer

ROMxfersize     *       (8*4)*8         ; Granularity of ROM block transfer
ROMminsize      *        1*1024         ; BBC MOS tests this much for equality
ROMmaxsize      *       16*1024         ; How big a ROM can be

; RomAddrTable[i]:                     0 -> nothing present
;                  rom_linked_bit + addr -> addr is a BBC ROM filename^
;                                   addr -> addr is a BBC ROM code^

rom_linked_bit  *       1 :SHL: 31

ListROMs_Code ENTRY "r7"

        LDR     wp, [r12]

        ADD     r4, wp, #RomAddrTable
        ADD     r5, wp, #RomStateTable
        LDRB    r6, [wp, #BASICSocket]
        MOV     r7, #NRoms-1

10      SWI     XOS_WriteS
        DCB     "BBC ", 0
        ALIGN
        EXIT    VS
        TEQ     r7, r6                  ; BASIC is always ROM even if RAM pends
        LDRNEB  r14, [r5, r7]           ; Look at state
        TSTNE   r14, #SidewaysRAM
        ADREQ   r0, emu_ROM
        ADRNE   r0, emu_RAM
        SWI     XOS_Write0
        SUB     sp, sp, #8
        MOVVC   r0, r7
        MOVVC   r1, sp
        MOVVC   r2, #4
        SWIVC   XOS_ConvertCardinal1
        SWIVC   XOS_Write0              ; r0 -> string after that !
        ADD     sp, sp, #8
        EXIT    VS
        CMP     r7, #10                 ; VClear
        SWILO   XOS_WriteI+space
        SWIVC   XOS_WriteI+":"
        SWIVC   XOS_WriteI+space
        EXIT    VS

; What have we got ?

        ADR     r0, emu_EmptySocket
        TEQ     r7, r6                  ; BASIC ?
        ADREQ   r0, emu_BASICSocket
        LDRNE   r14, [r4, r7, LSL #2]
        TEQNE   r14, #0                 ; Nothing in this socket ?
        BEQ     %FT80

        TST     r14, #rom_linked_bit    ; Filename^ or code^ ?
        BICNE   r1, r14, #rom_linked_bit
        ADRNE   r0, emu_LinkedFile
        BNE     %FT60                   ; [filename^]

; Validate BBC image, so we know if title is ok or not

        LDRB    r1, [r14, #7]           ; Copyright offset
        LDRB    r2, [r1, r14]!          ; Suss 0,"(C)"
        TEQ     r2, #0
        LDREQB  r2, [r1, #1]
        TEQEQ   r2, #"("
        LDREQB  r2, [r1, #2]
        TEQEQ   r2, #"C"
        LDREQB  r2, [r1, #3]
        TEQEQ   r2, #")"
        ADDEQ   r1, r14, #9             ; Offset of title string in image
        ADREQ   r0, emu_CachedROM
        ADRNE   r1, emu_DataImage
        ADRNE   r0, emu_Cached

60      SWI     XOS_Write0
        MOVVC   r0, r1
        SWIVC   XOS_Write0
        SWIVC   XOS_WriteI+"'"
        B       %FT90

80      SWI     XOS_Write0

90      SWIVC   XOS_NewLine
        EXIT    VS

        SUBS    r7, r7, #1
        BPL     %BT10                   ; VClear

        EXIT

emu_RAM         DCB     "RAM ", 0
emu_ROM         DCB     "ROM ", 0
emu_LinkedFile  DCB     "Linked to file '", 0
emu_CachedROM   DCB     "Cached ROM '", 0
emu_Cached      DCB     "Cached '", 0
emu_DataImage   DCB     "Data image", 0
emu_BASICSocket DCB     "BASIC", 0
emu_EmptySocket DCB     "Empty socket", 0
                ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

DIPState_Help
        DCB     "*DIPState is used set the state of the DIP switches"
        DCB     " on the keyboard for the BBC 6502 emulator."
        DCB     CR
DIPState_Syntax
        DCB     "Syntax: *DIPState <hex state>", 0


AllocateROM_Help
        DCB     "*AllocateROM is used to reserve a area of a given size"
        DCB     " (default is 16K) that can be used by the BBC 6502 emulator"
        DCB     " as if it occupied a physical ROM socket."
        DCB     CR
AllocateROM_Syntax
        DCB     "Syntax: *AllocateROM <ROM number> [<size>]", 0


CacheROM_Help
        DCB     "*CacheROM is used to load a BBC ROM file into core"
        DCB     " from where it can be used by the BBC 6502 emulator"
        DCB     " as if it occupied a physical ROM socket. When no filename"
        DCB     " is given, the current linked filename for the given"
        DCB     " socket is used if present, otherwise a 16K slot is"
        DCB     " reserved."
        DCB     CR
CacheROM_Syntax
        DCB     "Syntax: *CacheROM <ROM number> [<filename>]", 0


LinkROM_Help
        DCB     "*LinkROM is used to associate a BBC ROM file "
        DCB     " with a physical ROM socket number for use with the"
        DCB     " BBC 6502 emulator. When no filename is given, the"
        DCB     " socket is 'freed'."
        DCB     CR
LinkROM_Syntax
        DCB     "Syntax: *LinkROM <ROM number> [<filename>]", 0


ListROMs_Help
        DCB     "*ListROMs lists the contents of the BBC ROM cache."
        DCB     CR
ListROMs_Syntax
        DCB     "Syntax: *ListROMs", 0


MakeRAM_Help
        DCB     "*MakeRAM sets up a BBC socket as sideways RAM."
        DCB     CR
MakeRAM_Syntax
        DCB     "Syntax: *MakeRAM <ROM number>", 0


MakeROM_Help
        DCB     "*MakeROM sets up a BBC socket as sideways ROM."
        DCB     CR
MakeROM_Syntax
        DCB     "Syntax: *MakeROM <ROM number>", 0


SwapROMs_Help
        DCB     "*SwapROMs flips BBC ROMs between sockets."
        DCB     CR
SwapROMs_Syntax
        DCB     "Syntax: *SwapROMs <ROM number> <ROM number>", 0


ROMCache_SWITable

        DCB     "ROMCache", 0
        DCB     "Cache", 0              ; +0
        DCB     "Link", 0               ; +1
        DCB     "Load", 0               ; +2
        DCB     "Swap", 0               ; +3
        DCB     "Uncache", 0            ; +4
        DCB     "Save", 0               ; +5
        DCB     "SetState", 0           ; +6
        DCB     "Allocate", 0           ; +7
        DCB     0

        ALIGN

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r11 = SWI number in our segment
;       lr = return address with flags - MUST restore to psr !

; Out   r10 corrupted

        AlignForModule

ROMCache_SWICode ROUT

        TEQP    pc, #SVC_mode           ; Enable IRQ
        CMP     r11, #maxswi
        LDRCC   wp, [r12]               ; Load wp for all our SWIs
        ADDCC   r10, wp, #RomAddrTable  ; We can then do this
        ADDCC   pc, pc, r11, LSL #2
        MOVS    pc, lr                  ; NOP the rest. Also forces addressing

ROMCache_SWIJumpTable

        B       ROMCache_Cache_Code
        B       ROMCache_Link_Code
        B       ROMCache_Load_Code
        B       ROMCache_Swap_Code
        B       ROMCache_Uncache_Code
        B       ROMCache_Save_Code
        B       ROMCache_SetState_Code
        B       ROMCache_Allocate_Code

maxswi  *       (.-ROMCache_SWIJumpTable) / 4

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = ROM number
;       r1 = requested size

ROMCache_Allocate_Code ENTRY "r0-r6"

        BL      GetGoodSocket           ; r6 := valid ROM number

 [ ROMxfersize <= &100
        ADD     r4, r1, #ROMxfersize-1  ; Round up to ROM block transfer size
        BIC     r4, r4, #ROMxfersize-1
 |
        MOV     r14,      #(ROMxfersize-1) :AND &00FF
        ORR     r14, r14, #(ROMxfersize-1) :AND &FF00
        ADD     r4, r1, r14             ; Round up to ROM block transfer size
        BIC     r4, r4, r14
 ]

        CMP     r4, #ROMminsize
        MOVLO   r4, #ROMminsize

        CMP     r4, #ROMmaxsize
        BHI     Error_ROMTooBig

        MOV     r0, #ModHandReason_Claim
        MOV     r3, r4                  ; Claim appropriate amount
        SWI     XOS_Module
        BLVC    FreeSocket              ; Only free current contents if all ok
        BVS     BadSWIExit

        STR     r2, [r10, r6, LSL #2]   ; Store code^
        ADD     r14, wp, #RomSizeTable
        STR     r4, [r14, r6, LSL #2]   ; Store size

; Note that we leave RomState unaltered; this allows pending RAM state to work

        ADD     r0, r6, #1              ; Ensure slot has unique contents
        ORR     r0, r0, r0, LSL #8      ; and is NOT a valid ROM image
        ORR     r0, r0, r0, LSL #16     ; r0 := 0x0x0x0x
        STR     r0, [r2, #0]
        STR     r0, [r2, #4]
        STR     r0, [r2, #8]
        STR     r0, [r2, #12]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = ROM number
;       r1 -> filename. Null (0 or "",0) implies ensure current cached
;                        or leave empty
;       r10 -> RomAddrTable

ROMCache_Cache_Code ENTRY "r0-r6"

        BL      GetGoodSocket           ; r6 := valid ROM number

        CMP     r1, #0                  ; Null arg ?
        BLNE    SkipSpaces              ; Null arg ?
        BHI     %FT70                   ; Arg valid. Do your stuff ...

; No arg for Cache: ensure cached or keep empty

        LDR     r2, [r10, r6, LSL #2]
        TST     r2, #rom_linked_bit     ; In cache or filename^ ?
        EXITS   EQ                      ; [Already cached or remains empty]

        BIC     r2, r2, #rom_linked_bit ; Cache linked name
        ADD     r1, wp, #TempString
        BL      strcpyXS                ; Must copy away as we will free the
                                        ; string before its final use in load !

70 ; r1 -> filename to cache

        MOV     r0, #OSFile_ReadInfo
        SWI     XOS_File
        BVS     BadSWIExit

        CMP     r0, #object_file        ; VClear
        MOVNE   r2, r0
        MOVNE   r0, #OSFile_MakeError
        SWINE   XOS_File
        BVS     BadSWIExit

 [ ROMxfersize <= &100
        ADD     r4, r4, #ROMxfersize-1  ; Round up to ROM block transfer size
        BIC     r4, r4, #ROMxfersize-1
 |
        MOV     r14,      #(ROMxfersize-1) :AND &00FF
        ORR     r14, r14, #(ROMxfersize-1) :AND &FF00
        ADD     r4, r4, r14             ; Round up to ROM block transfer size
        BIC     r4, r4, r14
 ]

        CMP     r4, #ROMminsize
        MOVLO   r4, #ROMminsize
        MOVLO   r5, #0                  ; Tiny ROM image
        MOVHI   r5, #-1

        CMP     r4, #ROMmaxsize
        BHI     Error_ROMTooBig

        MOV     r0, #ModHandReason_Claim
        MOV     r3, r4                  ; Claim appropriate amount
        SWI     XOS_Module
        BLVC    FreeSocket              ; Only free current contents if all ok
        BVS     BadSWIExit

        STR     r2, [r10, r6, LSL #2]   ; Store code^
        ADD     r14, wp, #RomSizeTable
        STR     r4, [r14, r6, LSL #2]   ; Store size

        CMP     r5, #0                  ; If tiny ROM image, fill with 0 for
        BNE     %FT90                   ; BBC OS ROM consistency check
        MOV     r14, #0                 ; so we can catch identical tiddlers
        ADD     r0, r2, #ROMminsize
80      STR     r14, [r0, #-4]!
        CMP     r0, r2
        BHI     %BT80

90      MOV     r0, #OSFile_Load
        MOV     r3, #load_at_given      ; Put it in the claimed block
        SWI     XOS_File
        BLVS    FreeSocket              ; Deallocate slot if load failed
        EXITS   VC

        B       BadSWIExit

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Remember a filename to be used whenever a socket is referenced

; Don't actually validate anything as we may get 'Disc not present' etc.

; In    r0 = ROM number
;       r1 -> filename. Null (0 or "",0) implies flush socket
;                        and deallocate to empty state
;       r10 -> RomAddrTable

; This code is BLed to as we need to call it during initialisation, and we
; aren't yet on the module list, so be careful ...

ROMCache_Link_Code ENTRY "r0-r6"

10      BL      GetGoodSocket           ; Called from below. r6 := valid ROM

        BL      FreeSocket
        BVS     BadSWIExit

        CMP     r1, #0                  ; Null arg ?
        BLNE    SkipSpaces              ; Null arg ?
        EXITS   LS                      ; Flushed socket

; r1 -> filename to link socket to

        BL      strlenXS                ; Save name for later
        ADD     r3, r3, #1              ; +1 for terminator
        MOV     r0, #ModHandReason_Claim
        SWI     XOS_Module              ; r3 = length of string
        BVS     BadSWIExit              ; r2 -> new core to save name into

        Swap    r1, r2                  ; Copy filename into new core
        BL      strcpyXS

        ORR     r1, r1, #rom_linked_bit ; Note that this is a filename^
        STR     r1, [r10, r6, LSL #2]   ; not a cached ROM image
        EXITS

; .............................................................................

ROMCache_Uncache_Code ALTENTRY

        MOV     r1, #0                  ; Flush socket
        B       %BT10

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = ROM number
;       r1 -> core to put it. Must be word aligned
;       r10 -> RomAddrTable

; Out   r0 = number of bytes moved. NB. Not an error if socket is empty
;       r1 = RAM or ROM state. NB. Linked files always ROM even if marked RAM

        AlignForModule

ROMCache_Load_Code ENTRY "r0-r6"

        BL      GetGoodSocket           ; r6 := valid ROM number

        LDRB    r14, [wp, #BASICSocket] ; Paging in BASIC ?
        TEQ     r14, r6
        BNE     %FT10

        BL      BangLanguage            ; Copy in our BASIC
        MOV     r0, #&4000              ; Copied 16K ok
        MOV     r1, #0                  ; Can never alter threaded-in language
        STMIA   sp, {r0, r1}
        EXITS


10      LDR     r2, [r10, r6, LSL #2]
        TST     r2, #rom_linked_bit     ; In cache or filename^ ?
        BNE     %FT80                   ; [filename^]

        TEQ     r2, #0
        STREQ   r2, [sp]
        EXITS   EQ                      ; [No bytes to move]

; r2 -> cached ROM image, so copy across. Dest must be word aligned

        ADD     r14, wp, #RomSizeTable
        LDR     r14, [r14, r6, LSL #2]  ; Guaranteed to be multiple of xfersize
        MOV     r0, r14                 ; Tell caller n bytes copied

        Push    "r6, r7-r10"            ; Get more regs, saving ROM number
60      LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
        LDMIA   r2!, {r3-r10}           ; 8 words a go-go
        STMIA   r1!, {r3-r10}
 ASSERT (.-%BT60)/4/2*8*4 = ROMxfersize
        SUBS    r14, r14, #ROMxfersize  ; Done that many this time
        BNE     %BT60
        Pull    "r6, r7-r10"

        ADD     r14, wp, #RomStateTable
        LDRB    r1, [r14, r6]           ; Whether RAM or ROM
        STMIA   sp, {r0, r1}
        EXITS


80 ; (r2 BIC rom_linked_bit) -> filename to load

        MOV     r0, #OSFile_ReadInfo
        BIC     r1, r2, #rom_linked_bit ; Get it in right place after masking
        SWI     XOS_File
        BVS     BadSWIExit

        CMP     r0, #object_file
        BNE     %FT85

        CMP     r4, #ROMmaxsize         ; VClear
        BHI     Error_ROMTooBig

        STR     r4, [sp]                ; Number of bytes about to be moved

        MOV     r0, #OSFile_Load
        LDR     r2, [sp, #4]            ; r1in = where to put it
        MOV     r3, #load_at_given
83      SWIVC   XOS_File                ; Reentered from below
        BVS     BadSWIExit

        ADD     r14, wp, #RomStateTable
        LDRB    r14, [r14, r6]          ; Whether RAM or ROM
        STR     r14, [sp, #4]
        EXITS


85      BL      FreeSocket              ; Stop further errors from socket

        MOVVC   r2, r0                  ; Raise error now
        MOVVC   r0, #OSFile_MakeError
        B       %BT83

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0, r1 = ROM numbers to exchange
;       r10 -> RomAddrTable

ROMCache_Swap_Code ENTRY "r0-r6"

        CMP     r0, #NRoms
        CMPLO   r1, #NRoms
        BHS     Error_BadROMSocket

        LDRB    r14, [wp, #BASICSocket] ; Is either ROM BASIC ?
        TEQ     r14, r0
        STREQB  r0, [wp, #BASICSocket]  ; Update then
        TEQ     r14, r1
        STREQB  r1, [wp, #BASICSocket]

        LDR     r14, [r10, r0, LSL #2]  ; Swap address pointers
        LDR     r2,  [r10, r1, LSL #2]
        STR     r2,  [r10, r0, LSL #2]
        STR     r14, [r10, r1, LSL #2]

        ADD     r3, wp, #RomSizeTable
        LDR     r14, [r3, r0, LSL #2]   ; Swap size fields
        LDR     r2,  [r3, r1, LSL #2]
        STR     r2,  [r3, r0, LSL #2]
        STR     r14, [r3, r1, LSL #2]

        ADD     r3, wp, #RomStateTable
        LDRB    r14, [r3, r0]           ; Swap state fields
        LDRB    r2,  [r3, r1]
        STRB    r2,  [r3, r0]
        STRB    r14, [r3, r1]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = ROM number
;       r1 -> source core. Must be word aligned
;       r10 -> RomAddrTable

        AlignForModule

ROMCache_Save_Code ENTRY "r0-r6"

        BL      GetGoodSocket           ; r6 := valid ROM number

        LDRB    r14, [wp, #BASICSocket] ; Paging out BASIC ?
        TEQ     r14, r6
        EXITS   EQ

        ADD     r14, wp, #RomStateTable ; Only page out sideways RAM
        LDRB    r14, [r14, r6]
        TST     r14, #SidewaysRAM
        EXITS   EQ                      ; [Socket is ROM]

        ADD     r14, wp, #RomSizeTable
        LDR     r5, [r14, r6, LSL #2]   ; Guaranteed to be multiple of xfersize

        LDR     r2, [r10, r6, LSL #2]
        TST     r2, #rom_linked_bit     ; In cache or filename^ ?
        BNE     %FT80                   ; [filename^]

        TEQ     r2, #0                  ; No image present ?
        EXITS   EQ

; Page sideways RAM out to cache core

        Push    "r7-r10"                ; 4 more regs
 [ debug
        LDR     r14, [wp, #debuglevel]
        TST     r14, #debug_ROM
        BEQ     %FT00

        DREG r5,"Copying ",cc
        DREG r1," bytes from 6502 space ",cc
        DREG r2," to cache "
00
 ]
60      LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
        LDMIA   r1!, {r3, r4, r6-r10, r14} ; 8 words a go-go
        STMIA   r2!, {r3, r4, r6-r10, r14}
 ASSERT (.-%BT60)/4/2*8*4 = ROMxfersize
        SUBS    r5, r5, #ROMxfersize    ; Done that many this time
        BNE     %BT60
        Pull    "r7-r10"
        EXITS


; Page sideways RAM out to linked file

; Cor blimey, I can't see anyone running it this way!

80      MOV     r4, r1                  ; Start address in core
        BIC     r1, r2, #rom_linked_bit ; r1 -> filename
        MOV     r0, #OSFile_SaveStamp
        LDR     r2, =&BBC
        ADD     r5, r4, r5              ; End address
        SWI     XOS_File
        EXITS   VC

        B       BadSWIExit

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = ROM number
;       r1 = new state
;       r10 -> RomAddrTable

ROMCache_SetState_Code ENTRY "r0-r6"

        BL      GetGoodSocket           ; r6 := valid ROM number

        ADD     r14, wp, #RomStateTable ; Can even set pending state for empty
        AND     r1, r1, #SidewaysRAM    ; sockets!
        STRB    r1, [r14, r6]
        EXITS

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r6 = ROM number
;       r10 -> RomAddrTable
;       wp valid

; V accumulated

FreeSocket ENTRY "r0, r2"

        LDRB    r14, [wp, #BASICSocket] ; Freeing BASIC ?
        TEQ     r14, r6
        MOVEQ   r14, #&FF               ; BASIC has gone away forever
        STREQB  r14, [wp, #BASICSocket]

        MOVEQ   r2, #0                  ; BASIC occupied no core
        LDRNE   r2, [r10, r6, LSL #2]
        BIC     r2, r2, #rom_linked_bit ; Take out flag bit, frees core
                                        ; whether string or ROM
        CMP     r2, #0                  ; VClear
        MOVNE   r0, #0
        STRNE   r0, [r10, r6, LSL #2]   ; Clear contents
        MOVNE   r0, #ModHandReason_Free
        SWINE   XOS_Module
        EXITS   VC

        STR     r0, [sp]                ; Return error^
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; Out   VClear
;       r0 = 0 -> No error
;         <> 0 -> Error block

FreeAllSockets ENTRY "r0, r6, r7, r10"

        MOV     r0, #0                  ; No errors as yet
        STR     r0, [sp]

        MOV     r6, #NRoms-1
        LDRB    r7, [wp, #BASICSocket]
        ADD     r10, wp, #RomAddrTable

10      CMP     r6, r7                  ; Don't free BASIC! VClear
        BLNE    FreeSocket
        STRVS   r0, [sp]                ; Note the error !
        SUBS    r6, r6, #1
        BPL     %BT10                   ; VClear

        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Error_ROMTooBig

        ADR     r0, ErrorBlock_6502ROMTooBig
        B       BadSWIExit

        MakeErrorBlock 6502ROMTooBig

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0 = ROM number

; Out   r6 = ROM number in [0..NRoms-1]

GetGoodSocket ROUT

        MOV     r6, r0                  ; Move slot number
        CMP     r6, #NRoms
        MOVLOS  pc, lr                  ; [ok]

; .............................................................................

Error_BadROMSocket

        ADR     r0, ErrorBlock_6502BadROMSocket

; .............................................................................
; In    r0-r6, lr stacked
;       r0 -> error block

BadSWIExit
        ADD     sp, sp, #4              ; Skip stacked r0
        Pull    "r1-r6, lr"
        ORRS    pc, lr, #V_bit

        MakeErrorBlock 6502BadROMSocket

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; In    r0-r6 trashable

DIPState_Code ENTRY

        LDR     wp, [r12]

        MOV     r1, r0
        MOV     r0, #16 + (2_110 :SHL: 29) ; Bad terms, restrict to byte
        SWI     XOS_ReadUnsigned

        EORVC   r2, r2, #&FF            ; Make it like fx 255 setting
        STRVCB  r2, [wp, #keyOptions]
        EXIT

; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        LTORG

        LNK     EmuOpcode
