Skip to main content

Bus

Struct Bus 

Source
pub struct Bus {
    pub ppu: Ppu,
    pub apu: Apu,
    pub cart: Option<Cart>,
    pub dma: Dma,
    pub clock: Clock,
    /* private fields */
}
Expand description

Everything mutable lives here.

Fields§

§ppu: Ppu

The video subsystem (PPU1 + PPU2).

§apu: Apu

The audio subsystem (SPC700 + S-DSP + ARAM).

§cart: Option<Cart>

The loaded cartridge (board mapping + any coprocessor), or None before a ROM loads.

§dma: Dma

The 8-channel DMA/HDMA controller ($420B/$420C, $43xx).

§clock: Clock

The master-clock phase + CPU timing registers.

Implementations§

Source§

impl Bus

Source

pub fn new(region: Region) -> Self

Construct a power-on Bus for the given console region.

§Panics

Panics only if the 128 KiB WRAM allocation cannot be sized to the fixed WRAM_SIZE array (an out-of-memory condition at power-on), which cannot happen for the constant size.

Source

pub fn sync_region_from_cart(&mut self)

Reconfigure the PPU’s region (line count / 50-vs-60 Hz status bit) from the installed cart’s header, auto-detecting NTSC vs PAL rather than requiring the frontend to guess or hardcode it. A no-op when no cart is installed. Region only ever affects the PPU’s line-count/status-bit timeline here — the differing NTSC/PAL master-clock rate (Hz) is a real-world audio/video pacing concern the frontend owns (docs/adr/0004); the core’s master-clock counter is a pure tick count, not wall-clock time, so nothing else in the core depends on which oscillator frequency a real console would use.

Source

pub fn set_joypad(&mut self, player: usize, state: u16)

Set the latched controller state for a player (0 = P1, 1 = P2). 12-bit BYsSUDLR.....

Source

pub fn joypad(&self, player: usize) -> u16

The latched controller state for a player (0 = P1, 1 = P2) — the read side of Self::set_joypad, for TAS movie recording (crate::movie::MovieRecorder) and the debugger overlay.

Source

pub fn set_port_device(&mut self, port: usize, device: PortDevice)

Select which peripheral is connected to controller port port (0 = port 1, 1 = port 2). Defaults to PortDevice::Gamepad on both ports (this project’s original, unchanged behavior) until a frontend explicitly calls this — a host/session configuration choice, not emulated state (matching Self::set_cheats/Self::set_watchpoints’s own “re-established by the frontend, not carried in a save-state” posture — a real SNES has no memory of what was plugged in across a power cycle either).

Source

pub fn port_device(&self, port: usize) -> PortDevice

The peripheral currently connected to controller port port — for the debugger overlay and the frontend’s own input-routing (v0.9.0).

Source

pub fn set_mouse( &mut self, port: usize, dx: i32, dy: i32, left: bool, right: bool, )

Feed one frame’s worth of SNES Mouse input for port port (only meaningful when that port’s device is PortDevice::Mouse). dx/dy are raw, unscaled host deltas since the last call — the SNES Mouse’s own speed multiplier and 127-unit clamp are applied internally at the hardware-accurate point (latch time), matching real hardware. Same “always replace, re-synced once per frame” convention as Self::set_joypad.

Source

pub fn set_superscope(&mut self, port: usize, x: i32, y: i32, buttons: u8)

Feed one frame’s worth of Super Scope input for port port (only meaningful when that port’s device is PortDevice::SuperScope). x/y are absolute screen coordinates in SNES pixel space (0..256, 0..240-ish; a small negative/over-max margin is allowed and means “aimed off-screen”, matching real hardware). buttons is a bitmask over crate::controller::scope’s TRIGGER/CURSOR/TURBO/PAUSE bits — the LIVE physical switch/button state; this project reproduces real hardware’s own edge-detection internally (crate::controller::SuperScopeState), so the frontend should pass the raw host state, not a pre-toggled value. (A packed bitmask rather than one bool per button, matching Self::set_joypad’s own convention.)

Source

pub fn set_multitap_pad(&mut self, port: usize, sub_index: usize, buttons: u16)

Feed one frame’s worth of input for Super Multitap sub-pad sub_index (0..=3) of port port (only meaningful when that port’s device is PortDevice::Multitap) — same 12-bit BYsSUDLR.... format and per-frame convention as Self::set_joypad.

Source

pub fn multitap_pad(&self, port: usize, sub_index: usize) -> u16

The current input state of Super Multitap sub-pad sub_index of port port — the read side of Self::set_multitap_pad, for the debugger overlay and TAS movie recording.

Source

pub fn peek_wram(&self, addr24: u32) -> u8

Non-intrusive read of WRAM for the test harness + debugger (does NOT advance the clock, touch open bus, or trip register side effects). I/O registers and the cart region return 0 — this is for inspecting RAM-resident test-result variables, not for emulation.

Source

pub fn poke_wram(&mut self, addr24: u32, val: u8)

Non-intrusive write of WRAM (the write counterpart to Self::peek_wram, same addressing, same “no clock/open-bus/register side effects” contract) — for rustysnes- script’s Lua emu.write. A write to an address outside WRAM’s mirrors is silently ignored (matching peek_wram’s _ => 0 read side) rather than erroring, since a script address is arbitrary user input, not a bug to surface loudly. (Cheat codes, T-81-003, use Self::set_cheats’s CPU-read intercept instead — real Game Genie/Pro Action Replay codes overwhelmingly target cartridge ROM, which this WRAM-only accessor cannot reach.)

Source

pub fn wram(&self) -> &[u8]

The full 128 KiB WRAM as a flat byte slice (linear address 0..0x1_FFFF, the same mapping Self::peek_wram’s 0x7E..=0x7F bank arm uses) — for a host embedder that needs a raw memory-map pointer (e.g. a libretro core’s RETRO_MEMORY_SYSTEM_RAM).

Source

pub fn wram_mut(&mut self) -> &mut [u8]

The mutable counterpart to Self::wram — same host-embedder use case (a libretro frontend’s memory-map API hands this pointer to RetroAchievements/cheat tooling that writes through it directly).

Source

pub fn peek(&mut self, addr24: u32) -> u8

Non-intrusive read of an arbitrary 24-bit CPU address, for the debugger overlay’s disassembly view (v0.9.0, T-81-001 PR B). Unlike CpuBus::read24, this does NOT touch the open-bus latch, does NOT check watchpoints, and does NOT trigger any I/O register’s own read side effect (VRAM/CGRAM auto-increment, NMI-flag-clear-on-read, the H/V-counter latch, …) — genuinely just peeking. Real 65C816 code only ever executes from WRAM or cart ROM/RAM space, so (mirroring Self::peek_wram’s own “not for register space” posture) this only special-cases those two regions; any other address returns 0 rather than reaching into a register’s live side effects, which is fine since real code never lives there anyway. The cart-space branch still calls into the board (some coprocessors gate their own ROM/RAM reads on internal state), but passes a neutral 0 open-bus fallback rather than the Bus’s real, live latch — this peek must never read or write that shared state.

Source

pub fn set_cheats(&mut self, patches: &[CheatPatch])

Install the currently-active cheat-code patches (v0.8.0, T-81-003), replacing any previously installed set. CpuBus::read24 checks this list on every CPU-visible read and substitutes a matching patch’s value — the same point in the pipeline real Game Genie/Pro Action Replay hardware intercepts at, which is why this is a read intercept and not a poke_wram-style direct write: those codes overwhelmingly target cartridge ROM, not WRAM, so a write-based model would silently do nothing for the vast majority of real codes. The underlying ROM/RAM byte is never modified — only what the CPU observes reading it.

Source

pub const fn set_voice_mutes(&mut self, mutes: [bool; 8])

Set the 8 per-voice audio mute toggles (v1.0.1). See [rustysnes_apu::dsp::Dsp::set_voice_mutes]’s doc for why this is a frontend/debug convenience re-synced once per real frame, not real S-DSP hardware state.

Source

pub const fn frame_ready(&self) -> bool

Whether the PPU has a finished frame ready to present.

Source

pub fn framebuffer(&self) -> &[u16]

The PPU framebuffer (256×239 15-bit BGR).

Source

pub fn save_state(&self, w: &mut SaveWriter)

Write the PPU’s own section, the APU’s own section, the DMA controller’s own section, then a "BUS0" section for WRAM + the Bus’s own timing/register state, then (if a cart is loaded) its battery SRAM + coprocessor state as a final untagged tail (a presence flag, the length-prefixed SRAM bytes, then the board’s own save_state bytes — the cart has no single section of its own since its payload is really “however many bytes the board’s own implementation writes”). The cart’s ROM/header are NOT written: the caller must reload the same ROM (Cart::load) and install it before calling Bus::load_state, the same “never embed a ROM byte” contract every coprocessor board in rustysnes-cart already follows.

Source

pub fn load_state( &mut self, r: &mut SaveReader<'_>, ) -> Result<(), SaveStateError>

The inverse of Self::save_state.

§Errors

SaveStateError on truncated/corrupt input, a section with unconsumed trailing bytes, or SaveStateError::Invalid if the save-state’s cart presence doesn’t match this Bus’s own (a save-state taken with a cart loaded can only be restored onto a Bus that already has the SAME cart’s ROM loaded — via rustysnes_cart::Cart::load — installed first; there is no ROM byte in the save-state to reconstruct it from) or if a restored SRAM image’s length doesn’t match the installed cart’s own SRAM size (a mismatched ROM).

Trait Implementations§

Source§

impl Bus for Bus

The 65C816’s view: route a 24-bit access + drive the master clock in lockstep.

Source§

fn read24(&mut self, addr24: u32) -> u8

Read a byte at a 24-bit address (no clock advance — the CPU sequences timing via Bus::advance around this call).
Source§

fn write24(&mut self, addr24: u32, val: u8)

Write a byte at a 24-bit address (no clock advance — see Bus::read24).
Source§

fn access_cycles(&self, addr24: u32) -> u32

Master clocks this access costs (ares CPU::wait): the region-variable access speed (FastROM vs SlowROM, WRAM, I/O). Defaults to the SlowROM/internal-cycle cost of 6.
Source§

fn advance(&mut self, clocks: u32)

Advance the system clock by clocks master ticks (ares CPU::step), ticking the PPU/APU/coprocessor and HDMA in lockstep. Default no-op for buses whose timebase is charged elsewhere (the SA-1 second CPU) or that don’t model timing (unit-test buses).
Source§

fn poll_nmi(&mut self) -> bool

Edge-triggered NMI poll (PPU vblank → CPU). Returns true once per high→low edge.
Source§

fn poll_irq(&mut self) -> bool

Level-sensitive IRQ poll (PPU HV-IRQ, on-cart coprocessor, APU timer). Honored only when the CPU’s I flag is clear.
Source§

impl Debug for Bus

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Bus

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl DmaBus for Bus

The DMA controller’s view: A-bus (24-bit) via the decode, B-bus via b_read/b_write.

Source§

fn read_a(&mut self, addr: u32) -> u8

Read a byte from the A-bus (24-bit CPU address). Invalid A-bus regions return open bus.
Source§

fn write_a(&mut self, addr: u32, val: u8)

Write a byte to the A-bus (24-bit CPU address). Invalid regions are dropped.
Source§

fn read_b(&mut self, addr: u8) -> u8

Read a byte from the B-bus register $2100 | addr.
Source§

fn write_b(&mut self, addr: u8, val: u8)

Write a byte to the B-bus register $2100 | addr.
Source§

fn step(&mut self, clocks: u32)

Advance the master clock by clocks during a transfer, so time-dependent state that gates the transfer — chiefly the PPU scanline that decides VRAM/CGRAM/OAM accessibility ($2118/$2119 land only in force-blank or V-blank) — tracks the transfer in lockstep, exactly as the real bus keeps scanning while a DMA runs (ares Channel::stepcpu.step). A big GP-DMA that starts late in active display and crosses into V-blank must have its V-blank portion actually reach VRAM; freezing the scanline for the whole burst would drop every write (the Star Fox framebuffer-transfer bug). Default no-op so the controller stays unit-testable against an isolated bus with no clock.
Source§

fn scanline(&self) -> u16

The PPU scanline the bus is currently scanning. On real hardware HDMA preempts a running GP-DMA at the start of every scanline; a long GP-DMA that spans scanline boundaries must therefore let those HDMA transfers interleave (Star Fox force-blanks its framebuffer DMA via an HDMA-driven $2100 write on the very lines the DMA is filling). crate::dma::Dma drives that interleave itself from inside run_gp because the bus’s own per-tick HDMA path is dormant while the bus has lent out its controller. Default u16::MAX so a clockless unit-test bus never trips the interleave.
Source§

fn visible_height(&self) -> u16

The last visible scanline HDMA services this frame (visible_height), so the in-GP-DMA interleave knows the visible-line window. Default 0 (unit-test buses run no HDMA).
Source§

fn hdma_last_line(&self) -> u16

The scanline the bus last serviced HDMA for, so the in-GP-DMA interleave resumes from the bus’s own bookkeeping and hands it back afterwards (no line runs twice, none is skipped). Default u16::MAX.
Source§

fn set_hdma_last_line(&mut self, line: u16)

Record that HDMA has now been serviced for line, syncing the bus’s bookkeeping with the interleave driven from inside run_gp. Default no-op.

Auto Trait Implementations§

§

impl Freeze for Bus

§

impl !RefUnwindSafe for Bus

§

impl Send for Bus

§

impl !Sync for Bus

§

impl Unpin for Bus

§

impl UnsafeUnpin for Bus

§

impl !UnwindSafe for Bus

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.