Memory map & I/O Registers
(TODO before release: clean up here and add labels from jam.inc)
Memory map
- $0000-$0001: graphics port (not implemented yet)
- $0002-$0003: reserved for later use
- $0004-$0007: zeropage RAM reserved for kernel
- $04/5: temporary vector used for PRINT and CP/M fs
- $06: save processor status for PRINT
- $07: save accumulator for PRINT
- $0008-$00FF: zeropage RAM for generic use
- $0008-$000F: zeropage RAM used by WozMon
- $00E3-$00FF: zeropage RAM used by TIM
- $00F6-$00FF: zeropage RAM used by System Monitor
- $0100-$01FF: processor stack
- $0200-$03FF: RAM reserved for kernel (e.g. CP/M fs, VT100)
- $0400-$CFFF: RAM for generic use
- $D000-$DDFF: I/O which can be provided by external boards
- $D000-$D3FF: scratch RAM that can by exchanged with $0000-$03FF by writing to $DF03 (not accessable directly from 65C02)
- $DE00-$DEFF: I/O space reserved for future use provided by main RP2040 board
- $DF00-$DFFF: I/O provided by main RP2040 board
- $E000-$FFFF: bank 0 (RAM, used to load CP/M 65)
- $E000-$FFFF: bank 1 (ROM, kernal, custom firmware)
- $E000-$FFFF: bank 2 (ROM, tools e.g. filebrowser)
- $E000-$FFFF: bank 3 (ROM, OSI BASIC)
- $FF00-$FFFF: bankswitching code, BRK handler, I/O routines (BIOS)
- same on all ROM banks
- can be copied to RAM using code at $0100 after loading a bootblock
- is copied to RAM before running SX4 file
- can also copied to RAM by using BRK #$0B (see below)
I/O Registers
Overview
- $0000-$0001: VGA
- $0002-$0003: (reserved)
- $D000-$D2FF: (undefined)
- $D300-$D3FF: 32x32 LED Framebuffer (suggested)
- $D400-$D4FF: Sound: SID clone(s), mod player (suggested)
- $D500-$DDFF: (undefined)
- $DE00-$DEFF: (reserved)
- $DF00-$DF7F: (internal: see below)
- $DF80-$DFFF: (scratch RAM for internal drive access)
Miscellaneous ($DF00-$DF0F)
- $DF00: (R/W) bank select register for $E000-$FFFF ROM starts at bank 1 (default), bank 0 is RAM; more banks can be added, if bank set > max_bank, bank is set to default. This is also the only point where the system does not behave asynchronous: a bankswitch will be effective immediately after the write and before the next instruction
- $DF01: (S) trap: stop CPU and jump into debugging console
- $DF02: (R) random value
- $DF03: (W) swap out pages $00-$03:
- lower four bits contain banks,
- bits 4,5 define mode:
- $0x --> nothing
- $1x --> store
- $2x --> read
- $3x --> swap
- bits 6,7 are reserved for future usage
- value is set back to $00, once action is completed
- $DF04: (R) CPU capabilities
- using bit set to indicate CPU features:
- $01: NMOS
- $02: CMOS
- $04: BIT (RE)SET
- $08: Z reg
- $10: 16 bit
- $20: ROR instruction missing
- This means for known CPUs:
- $01: 6502 Rev.D
- $02: 65SC02
- $06: 65C02
- $0e: 65CE02
- $12: 65816
- $21: 6502 Rev.A
- using bit set to indicate CPU features:
- $DF05-$DF0A: reserved for future use
- $DF0B: UART config:
- bit 0: enable crlf conversion
- bit 1: enable flow control (removed due to rework)
- bit 2-3: select auto UTF-8 conversion
- 00: off
- 01: standard Sorbus conversion (not fully defined yet)
- 10: reserved
- 11: reserved
- bit 6: overflow in output uart fifo occurred (will be cleared on read)
- bit 7: overflow in input uart fifo occurred (will be cleared on read)
- $DF0C: (R) UART in queue read
- $DF0D: (R) serial in queue size (up to 250, 255: error)
- $DF0E: (W) serial out queue write
- $DF0F: (R) serial out queue size (up to 127, >127: error)
Timers ($DF10-$DF1F)
- 8 different 16 bit timers are available allowing for an own timer for every combination, see table below
- writing to lowbyte also stops timer
- writing to highbyte also starts timer
- reading from either low- or highbyte shows if timer triggered interrupt ($80) and also acknoledges interrupt
| Address | Repeating | Interrupt | Time Source | Note |
|---|---|---|---|---|
| $DF10/1 | repeat | IRQ | cycles | defunc'ed |
| $DF12/3 | oneshot | IRQ | cycles | defunc'ed |
| $DF14/5 | repeat | NMI | cycles | defunc'ed |
| $DF16/7 | oneshot | NMI | cycles | defunc'ed |
| $DF18/9 | repeat | IRQ | 10ths of ms | |
| $DF1A/B | repeat | NMI | 10ths of ms | |
| $DF1C/D | oneshot | IRQ | 10ths of ms | |
| $DF1E/F | oneshot | NMI | 10ths of ms |
Note: when entering meta mode, the ms based timers will be cancelled. The will be restarted from their initial value when meta mode is exited.
Watchdog ($DF20-$DF23)
Watchdog will be triggered after the specified number milliseconds.
- counter is 24 bit
- base address: $DF20
- write to base address + 0: start watchdog, if milliseconds > 0
- write to base address + 1: stop, set milliseconds low byte
- write to base address + 2: stop, set milliseconds mid byte
- write to base address + 3: stop, set milliseconds high byte
- read on $DF20 shows watchdog active (bit7: watchdog running)
- write to $DF20 also re-arms the watchdog, value written does not matter
- triggered watchdog is handled similar to trap ($DF01)
Cyclecount ($DF24-$DF27)
- read only 32 bit register
- writing to any address of the range copies the cycle counter to RAM
- intended to be used for measuring speed of code
- address still subject to change
Variables Used By Kernel ($DF2C-$DF2F)
- $DF2C: bank
- $DF2D: accumulator
- $DF2E: X index register
- $DF2F: Y index register
- $DF76: Z index register (65CE02 userland only, this might change)
This are just variables used during handling an interrupt service call.
Variables Used By System Monitor ($DF30-$DF3F)
This area of I/O is just used as conventional RAM to store data away from the area used by conventional programs.
- $DF30: saved PC lo
- $DF31: saved PC hi
- $DF32: bank
- $DF33: accumulator
- $DF34: X index register
- $DF35: Y index register
- $DF36: stack pointer
- $DF37: processor status
Internal Drive ($DF70-$DF77)
System provides 36864 blocks of 128 bytes = 4.5MB Data stored in flash @ 0x10400000 (12MB, ~<6MB payload with wear leveling)
- base address: $DF70
- base address + $0: LBA low
- base address + $1: LBA high
- base address + $2: DMA memory address low
- base address + $3: DMA memory address high
- base address + $4: (S) read sector (strobe, adjusts DMA memory and LBA)
- base address + $5: (S) write sector (strobe, also adjusts)
- base address + $6: (unused, see variables used by kernel)
- base address + $7: (S) flash discard
Valid values are:
- DMA memory start address:
- $0004-$CF80
- $DF80-$FF80 (will always use RAM under ROM)
- LBA:
- $0000-$7FFF (used by CP/M-fs)
- $8000-$8FFF (reserved for future use, e.g. ROM-able Forth)
Each transfer stops CPU until transfer is completed. Reading from strobe registers return result of last access. (A value of $80 indicates activity, $Fx indicates error, $00 success.) Memory address will be increased by $0080, LBA by $0001 after successful read or write to prepare for next sector transfer.
RAM Vectors ($DF78-$DF7F)
These vectors are RAM to support installing own handlers for interrupts
- $DF78/$DF79 user BRK routine (if BRK operand is $00 or out of scope)
- $DF7A/$DF7B NMI ($FFFA/B point to jmp ($DF7A))
- $DF7C/$DF7D user IRQ routine (for handling non-BRK)
- $DF7E/$DF7F IRQ ($FFFE/F point to jmp ($DF7E))
Note: TIM overwrites vectors for own debugging purposes, WozMon doesn't.
Interrupt Handling
- $FFFE/F is triggered by IRQ-line or BRK
- jmp ($DF7E) -> default setup to handler in $FF00 area
- handler checks if trigger was IRQ or BRK
- if IRQ -> jmp ($DF7C)
- if BRK get operand after BRK
- if operand is known, perform kernel action
- if operand is out of scope -> jmp ($DF78) (default: System Monitor)
Note: as this handling is rather complex it takes about 100 cycles to run a software interrupt to call a function. This is the trade-in for convenience. Also, all registers get saved/restored during a software interrupt.
Scratchpad RAM ($DF80-$DFFF)
128 bytes of RAM intended to be used to store a sector from internal drive, e.g. directory data.
Unused addresses in $DF00-$DF7F behave like RAM, except that they can't be used with internal drive DMA.