Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions docs/internals/chipset.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ specification of the modelled behaviour.
Agnus owns the beam: `vpos`/`hpos` counters advanced per colour clock, PAL
(313 lines, 227 CCK/line) and NTSC (263 lines with long/short line
alternation) geometry, the long-field flag for interlace, and VPOSR/VHPOSR.
It also owns DMACON and the display-fetch machinery: the bitplane fetch
plan for the current line is computed from DDFSTRT/DDFSTOP, the plane
count, resolution, and FMODE, producing the per-slot fetch pattern the
[arbitration model](timing) consumes. The fetch sequencer is anchored by
the DDFSTRT comparator, then each fetch block/unit uses the BPLCON0 value
visible at that block's first cycle. A mid-row BPLCON0 plane-count change
therefore cannot retroactively fetch earlier words, but it can add or
remove planes for later blocks in the same row. The sequence completes
whole fetch units: the DDF register value is first masked to the Agnus
It also owns DMACON and the display-fetch machinery: for FMODE=0 fetches
the per-line fetch table comes from the DDF sequencer flop model
(`src/chipset/ddf_sequencer.rs`; see the [arbitration model](timing) for
the flop semantics - comparator edges, stop drain through a final
modulo-applying unit, cross-line run carry, OCS/ECS rule differences).
Each fetch unit uses the BPLCON0 value the sequencer sees at that point,
so a mid-row plane-count change cannot retroactively fetch earlier words,
but it can add or remove planes for later units in the same row; word
addressing stays unit-based. The DDF register value is masked to the Agnus
revision's comparator precision (OCS keeps 4-CCK precision; ECS/AGA keep
2-CCK precision), then a DDFSTOP landing mid-unit extends the fetch through
the unit starting at-or-after it (`agnus::bitplane_fetch_blocks`; the CDTV
trademark screen's hi-res $64/$A8 window fetches 20 words per row, not the
truncated 18). In lo-res OCS, bit 2 of DDFSTRT and DDFSTOP remains visible
2-CCK precision), and a DDFSTOP landing mid-unit extends the fetch through
the unit starting at-or-after it plus the drain unit (the CDTV trademark
screen's hi-res $64/$A8 window fetches 20 words per row, not the truncated
18). In lo-res OCS, bit 2 of DDFSTRT and DDFSTOP remains visible
to the 8-CCK fetch-unit count: $34/$D4 fetches 21 words, $28/$D4 fetches
23, and $4A/$B6 fetches 15. Wide-FMODE units (16/32 CCK) use the same rule
rather than moving DDFSTRT down to an absolute grid. In
Expand Down
37 changes: 24 additions & 13 deletions docs/internals/timing.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,30 @@ order (`fixed_dma_owner_at`, `src/bus.rs`):
7. **Blitter** -- any remaining slots its schedule claims.
8. **CPU** -- whatever is left.

The arbiter consults the owner several times per colour clock, so the
bitplane decision (step 5) memoizes its line-invariant part: the effective
DDF window, FMODE fetch cadence, and per-plane fetch-order mask live in a
`BitplaneSlotPlan` keyed on exactly the register inputs that feed them
(`BitplaneSlotKey`, `src/bus.rs`) and are recomputed only when a register
write or a write-delay expiry changes the key. The vpos-dependent gates
(vertical display window, DDFSTRT write miss) are still evaluated live, so
the memoization cannot change behaviour. Once a line reaches DDFSTRT, the
arbiter keeps the fetch sequence anchored there but evaluates BPLCON0 at
each fetch block's first cycle. A later BPLCON0 plane-count increase does
not claim slots for earlier blocks or advance newly enabled plane pointers
for those words, but it can claim the matching slots and start advancing
those pointers on later blocks of the same row.
For FMODE=0 fetches (OCS/ECS, and AGA with a 1-word fetch quantum) the
bitplane decision (step 5) is driven by the Agnus DDF sequencer flop model
(`src/chipset/ddf_sequencer.rs`, walked per line by `src/bus/ddf_line.rs`):
DDFSTRT and DDFSTOP are comparator EDGES that set/clear flip-flops, not a
value range. A stop request drains through one final fetch unit (which
applies the modulos per plane), the hardwired window ($18/$D8, HARDDIS
relaxes the stop to $E0) gates starts and forces stops, and the flop state
carries across line boundaries. Missed comparators therefore produce the
hardware's "old stop" behaviours: a rewritten-too-late DDFSTOP lets the run
continue to the hardware-stop drain, a DDFSTRT match past $D8 starts a run
that wraps through horizontal blanking into the next line, and an
early-blanked DDFSTRT ($10 with SHW still down) never starts on OCS. The
per-line fetch table is rebuilt when DDFSTRT/DDFSTOP/BPLCON0/DMACON/DIW
writes land (DDF writes commit to the comparators four colour clocks after
the write slot; an old DDFSTOP still fires on its commit clock, an old
DDFSTRT does not - vAmiga's sequencer semantics, hardware-verified in
aggregate by the vAmigaTS Agnus/DDF/DDF/oldhwstop1-4 A500 photos). A
mid-row BPLCON0 change switches the fetch-unit slot layout from its commit
clock; word addressing is unit-based, so late-enabled planes keep their
word positions and earlier words stay zero.
Wide-FMODE (quantum > 1) fetches keep the memoized value-window plan: the
effective DDF window, fetch cadence, and per-plane fetch-order mask live in
a `BitplaneSlotPlan` keyed on the register inputs (`BitplaneSlotKey`,
`src/bus.rs`).
Wide-FMODE lo-res slots are packed into the first eight CCKs of each
16/32-CCK fetch unit; the rest of the unit remains available to later
arbitration priorities.
Expand Down
25 changes: 25 additions & 0 deletions src/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ pub struct CapturedBitplaneRow {
pub nplanes: usize,
pub words_per_row: usize,
pub planes: [Vec<u16>; 8],
/// Colour clock of the row's first fetch-unit boundary when the DDF
/// sequencer's run diverges from the register-derived window (missed
/// stops draining to the hardware stop, late starts). None when the
/// register-derived geometry already matches (and for wide-FMODE rows).
pub fetch_origin_cck: Option<u16>,
}

#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -884,6 +889,20 @@ pub struct Bus {
/// on each hit while keeping the `&self` owner-selection call graph intact.
#[serde(skip)]
bitplane_slot_plan_cache: BitplaneSlotPlanCache,
/// Bitplane DDF sequencer flop state at the start of the current line
/// (see src/bus/ddf_line.rs); carried across lines by the flop walk.
ddf_seq_line_initial: std::cell::Cell<crate::chipset::ddf_sequencer::DdfState>,
/// DDFSTRT/DDFSTOP values as of the start of the current line (mid-line
/// rewrites replay through `ddf_seq_writes`).
ddf_seq_line_start_regs: std::cell::Cell<(u16, u16)>,
/// (bmapen, bplcon0) as of the start of the current line; used only when
/// mid-line DMACON/BPLCON0 writes are in the log.
ddf_seq_line_start_ctl: std::cell::Cell<(bool, u16)>,
/// Register writes that reached the sequencer during the current line.
ddf_seq_writes: std::cell::RefCell<Vec<ddf_line::DdfSeqWrite>>,
/// The current line's walked fetch table (rebuilt on demand).
#[serde(skip)]
ddf_seq_line: std::cell::RefCell<Option<ddf_line::DdfSeqLine>>,
bus_accounting: BusAccounting,
/// Latches once BEAMCON0.DUAL (A2024/UHRES) is first seen set, so the
/// "not emulated" warning is logged a single time, not per write.
Expand Down Expand Up @@ -1993,6 +2012,11 @@ impl Bus {
bitplane_bplcon0_delay: None,
bitplane_ddfstart_miss: None,
bitplane_slot_plan_cache: BitplaneSlotPlanCache::new(),
ddf_seq_line_initial: std::cell::Cell::new(Default::default()),
ddf_seq_line_start_regs: std::cell::Cell::new((0, 0)),
ddf_seq_line_start_ctl: std::cell::Cell::new((false, 0)),
ddf_seq_writes: std::cell::RefCell::new(Vec::new()),
ddf_seq_line: std::cell::RefCell::new(None),
bus_accounting: BusAccounting::from_env(),
uhres_dual_warned: false,
dbg_ext_cck_x100: external_access_cck_x100_setting(),
Expand Down Expand Up @@ -6809,6 +6833,7 @@ fn palette_event_sequences_equivalent(a: &[BeamRegisterWrite], b: &[BeamRegister

mod collisions;
mod custom_regs;
mod ddf_line;
mod dma_slots;
mod frame_capture;

Expand Down
30 changes: 30 additions & 0 deletions src/bus/custom_regs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ impl Bus {
);
}
self.denise.diwstrt = val;
self.ddf_seq_invalidate_line();
self.ocs_same_line_diw_start_blocked_vpos = None;
// ECS DIWHIGH only supplies the window MSBs when it is written
// *after* DIWSTRT/DIWSTOP (HRM p.306). A later DIWSTRT/DIWSTOP
Expand Down Expand Up @@ -382,6 +383,7 @@ impl Bus {
);
}
self.denise.diwstop = val;
self.ddf_seq_invalidate_line();
self.denise.diwhigh_written = false;
if self.ocs_same_line_diw_start_blocked_vpos == Some(self.agnus.vpos)
&& !display_window_contains_vpos(
Expand Down Expand Up @@ -415,8 +417,14 @@ impl Bus {
val
);
}
let previous = self.denise.ddfstrt;
self.denise.ddfstrt = val;
self.record_ddfstrt_write_match_miss(val);
self.ddf_seq_record_ddf_write(
super::ddf_line::DdfSeqWriteKind::Ddfstrt(val),
previous,
4,
);
false
}
0x094 => {
Expand All @@ -429,7 +437,13 @@ impl Bus {
val
);
}
let previous = self.denise.ddfstop;
self.denise.ddfstop = val;
self.ddf_seq_record_ddf_write(
super::ddf_line::DdfSeqWriteKind::Ddfstop(val),
previous,
4,
);
false
}
0x080 => {
Expand Down Expand Up @@ -487,6 +501,19 @@ impl Bus {
}
if self.agnus.dmacon != previous {
self.record_bitplane_dmacon_write(previous);
let en = DMACON_DMAEN | DMACON_BPLEN;
let was = previous & en == en;
let is = self.agnus.dmacon & en == en;
if was != is {
self.ddf_seq_record_write(
if is {
super::ddf_line::DdfSeqWriteKind::BmapenSet
} else {
super::ddf_line::DdfSeqWriteKind::BmapenClr
},
2,
);
}
}
false
}
Expand Down Expand Up @@ -798,6 +825,7 @@ impl Bus {
0x1DC => {
if self.blitter_ecs_registers_enabled() {
self.agnus.write_beamcon0(val);
self.ddf_seq_invalidate_line();
self.refresh_paula_audio_min_period();
if val & BEAMCON0_DUAL != 0 && !self.uhres_dual_warned {
log::warn!(
Expand Down Expand Up @@ -938,6 +966,7 @@ impl Bus {
self.agnus.set_ersy(val & 0x0002 != 0);
if self.denise.bplcon0 != previous {
self.record_bitplane_bplcon0_write(previous);
self.ddf_seq_record_bplcon0_write(self.denise.bplcon0, previous, 3);
}
false
}
Expand Down Expand Up @@ -1099,6 +1128,7 @@ impl Bus {
if self.denise_ecs_registers() {
self.denise.diwhigh = val;
self.denise.diwhigh_written = true;
self.ddf_seq_invalidate_line();
}
false
}
Expand Down
Loading
Loading