Trait Board
pub trait Board {
// Required methods
fn cpu_read(&mut self, addr: u16) -> u8;
fn cpu_write(&mut self, addr: u16, val: u8);
fn tier(&self) -> Tier;
// Provided methods
fn tick(&mut self) { ... }
fn tick_coprocessor(&mut self) { ... }
fn snoop_write(&mut self, addr: u16, val: u8) { ... }
fn snoop_read(&mut self, addr: u16, val: u8) { ... }
fn take_oob_pokes(&mut self) -> Vec<(u16, u8)> { ... }
}Expand description
A cartridge bankswitch board.
The board owns the ROM image (and any on-cart RAM / coprocessor) and decodes
every CPU access in the $1000..=$1FFF cartridge window. Schemes switch
banks by detecting “hotspot” addresses on either a read or a write, so both
cpu_read and cpu_write must run the bank logic even when the access is
nominally a fetch.
Required Methods§
fn cpu_read(&mut self, addr: u16) -> u8
fn cpu_read(&mut self, addr: u16) -> u8
CPU-side read through the board’s current bank mapping. addr is the
full 13-bit CPU address; the board masks to its window. Must also apply
any read-triggered hotspot bank switch (e.g. F8’s $1FF8/$1FF9).
Provided Methods§
fn tick(&mut self)
fn tick(&mut self)
Per-CPU-cycle coprocessor hook for DPC-style boards (the DPC/DPC+ music fetchers + the ARM in DPC+ advance on the CPU clock). Default no-op so the overwhelming majority of plain-ROM boards pay nothing.
fn tick_coprocessor(&mut self)
fn tick_coprocessor(&mut self)
Drive any coprocessor that is clocked independently of the CPU (e.g. the DPC random-number / oscillator clock). Default no-op.
fn snoop_write(&mut self, addr: u16, val: u8)
fn snoop_write(&mut self, addr: u16, val: u8)
Observe a CPU write to a NON-cart-window address (addr & 0x1000 == 0, i.e. TIA/RIOT space) before the Bus routes it there. Real 2600
cartridge edge connectors are wired to every address line, not just
A12 — several classic BestEffort schemes bankswitch on writes the
console itself thinks are plain TIA/RIOT accesses: 3F/3E (Tigervision)
trigger on any write whose low byte is $3F/$3E, UA on $220/$240,
0840 on $800/$840 — all deep in TIA/RIOT-mirrored space, not
$1000+. Default no-op so the overwhelming majority of boards (which
only care about their own $1000..=$1FFF window) pay nothing.
fn snoop_read(&mut self, addr: u16, val: u8)
fn snoop_read(&mut self, addr: u16, val: u8)
Observe a CPU read of a NON-cart-window address (addr & 0x1000 == 0), called AFTER the Bus computes the value TIA/RIOT would return
(passed as val) — the board only OBSERVES, it never redirects the
read; UA/0840 just need the access address, while FE additionally
needs the observed value itself (a JSR return-address byte pushed to
the stack, which happens to sit at $01FE, encodes which bank to
switch to). Default no-op, same reasoning as Self::snoop_write.
fn take_oob_pokes(&mut self) -> Vec<(u16, u8)>
fn take_oob_pokes(&mut self) -> Vec<(u16, u8)>
Drain any out-of-band RIOT-RAM pokes this board staged, to be
applied directly (bypassing normal bus routing/side-effects) —
mirrors Stella’s System::pokeOob, used by CartridgeAR to hand a
loaded game’s bank-switch/start-address bytes to its dummy BIOS stub
via zero-page RIOT RAM the BIOS then reads back through a normal CPU
instruction. Default empty (no allocation until a board actually
pushes to it); only BankAr currently uses this.