Skip to content

Add early OE cutoff for CPLD bus data output enable#37

Open
BrentRector wants to merge 1 commit intoa2fpga:mainfrom
BrentRector:upstream-oe-cutoff
Open

Add early OE cutoff for CPLD bus data output enable#37
BrentRector wants to merge 1 commit intoa2fpga:mainfrom
BrentRector:upstream-oe-cutoff

Conversation

@BrentRector
Copy link
Copy Markdown

@BrentRector BrentRector commented Feb 23, 2026

Summary

Files: boards/a2n20v2/hdl/bus/apple_bus.sv, boards/a2n20v2-Enhanced/hdl/bus/apple_bus.sv

The CDC denoise pipeline (2-stage sync + 3-bit debounce + FIFO) delays phi0 by ~6 clk_logic
cycles (~100ns at 54 MHz). The CPLD bus data output enable (a2_bridge_bus_d_oe_n_o) is purely
combinational, tracking data_out_en_i. When cards deassert rd_en_o at the real phi0 falling
edge, the CPLD OE doesn't release until the CDC-delayed phi0 drop reaches the FPGA — creating
~100ns of bus contention where the CPLD continues driving the Apple II data bus while the 6502
is already in its next cycle. A real slot card's 74LS245 releases within ~10-20ns of phi0
dropping.

This ~100ns overlap is especially harmful during I/O write cycles ($C0xx) that originate from
expansion ROM ($C800-$CFFF) instruction fetches. The Apple II motherboard's I/O decode logic
responds with timing sensitive to bus contention. Binary search via ROM patching confirmed: I/O
writes from C8 space break disk loading; main RAM writes from C8 space work fine.

Why registering OE doesn't work

Hardware-tested: registering a2_bridge_bus_d_oe_n_o causes Apple II resets. The 1-cycle
delay extends CPLD drive INTO phi1, creating bus contention with the 6502 that is worse than
the ~100ns overlap.

The fix

Use the existing phase counter (phase_cycles_r) to force-release OE before the CDC-delayed
phi0 drop. Real phi0 drops at approximately PHASE_COUNT - CDC_DELAY (= 20) counts. The
cutoff fires at cycle 22 (OE_MARGIN=2), ~37ns after the estimated real phi0 drop.
IO_WRITE_DATA triggers at cycle 10 and completes by cycle 13, so all card data is latched
well before cutoff.

This reduces the contention window from ~100ns to ~37ns. The margin parameters (OE_MARGIN)
can be tuned if needed: increase to 3-4 if cards fail to respond, decrease to 1 if more
aggressive cutoff is needed.

Only applies to CPLD-based boards (a2n20v2, a2n20v2-Enhanced). The a2mega board has direct bus
access and is not affected.

How Discovered

Found during Apple Pascal 1.3 boot testing with Videx VideoTerm (slot 3) and SSC (slot 2)
emulated cards. Both perform I/O writes during FINIT routines from C8 expansion ROM. Pascal
boot hung deterministically at the disk load phase. The root cause was isolated through ROM
patch binary search: replacing C8-space I/O writes with main RAM writes eliminated the hang.

The bug is latent: the ~100ns OE extension exists on every bus read cycle, but only causes
failures when multiple cards with C8 expansion ROM are active. With only the SSC (the only
upstream card with C8 ROM), the timing margin is sufficient. Adding any second C8-capable
card increases bus activity and OE transitions, narrowing the margin past the failure
threshold. Physical Apple II cards with C8 ROMs sharing the CPLD bus would also be affected.

Test Plan

  • Apple ][+ hardware test with all emulated cards: SSC, Videx, Mockingboard, SuperSprite
  • Apple Pascal 1.3 boots 100% (was deterministic hang before fix)
  • Original Videx VideoTerm Demo application runs correctly
  • No regression in card bus response timing
  • a2n20v2-Enhanced board test (identical fix applied, not hardware-tested yet)

Problem:
The A2FPGA emulates multiple cards through a single A2Bridge CPLD
(XC9572XL). When any emulated card responds to a bus read, the signal
a2_bridge_bus_d_oe_n_o tells the CPLD to drive data onto the Apple II
bus. This signal is purely combinational, tracking data_out_en_i:

    assign a2_bridge_bus_d_oe_n_o = ~(data_out_en_i & BUS_DATA_OUT_ENABLE);

The CDC denoise pipeline (2-stage synchronizer + 3-bit debounce + FIFO)
delays phi0 by ~6 clk_logic cycles (~100ns at 54 MHz). When cards
deassert rd_en_o at the real phi0 falling edge, the CPLD OE doesn't
release until the CDC-delayed phi0 drop reaches the FPGA ~100ns later.
This creates ~100ns of bus contention where the CPLD continues driving
the Apple II data bus while the 6502 is already presenting the next
address. A real slot card's 74LS245 releases within ~10-20ns of phi0
dropping.

This ~100ns overlap is especially harmful during I/O write cycles
($C0xx) that originate from expansion ROM ($C800-$CFFF) instruction
fetches. The Apple II motherboard's I/O decode logic (GAL/PAL) responds
to $C0xx with timing that is sensitive to bus contention. ROM patch
testing confirmed: I/O writes from C8 space break disk loading; main
RAM writes from C8 space work fine.

How discovered:
This bug was found during Apple Pascal 1.3 boot testing with emulated
Videx VideoTerm (slot 3) and SSC (slot 2) cards. Both cards perform I/O
writes during their FINIT routines executed from C8 expansion ROM.
Pascal boot hung deterministically at the disk load phase — the SSC and
Videx FINIT I/O writes from C8 space created bus contention that
corrupted subsequent Disk II I/O cycles.

The bug is latent: the ~100ns OE extension exists on every bus read
cycle, but only causes failures when multiple cards with C8 expansion
ROM are active. With only the SSC (the only upstream card with C8 ROM),
the timing margin is sufficient. Adding any second C8-capable card
(emulated or physical) increases bus activity and OE transitions,
narrowing the margin past the failure threshold. The same issue would
affect physical Apple II cards with C8 ROMs sharing the CPLD bus.

Why registering OE doesn't work: Hardware-tested — registering
a2_bridge_bus_d_oe_n_o causes system crashes (Apple II resets). The
1-cycle delay extends CPLD drive INTO phi1, creating bus contention with
the 6502 that is worse than the ~100ns overlap.

Fix:
Use the existing phase counter (phase_cycles_r) to force-release OE
before the CDC-delayed phi0 drop. The phase counter resets on each
CDC-delayed phi edge. Real phi0 drops at approximately PHASE_COUNT -
CDC_DELAY (= 20) counts. The cutoff fires at cycle 22 (OE_MARGIN=2),
approximately 37ns after the estimated real phi0 drop. IO_WRITE_DATA
triggers at cycle 10 and completes by cycle 13, so all card data is
latched to the bridge well before cutoff.

This reduces the contention window from ~100ns to ~37ns, more closely
matching real slot card behavior. The 37ns post-phi0 margin is
conservative — the 6502 latches data before phi0 drops, so the data
hold time is already satisfied.

Only applies to CPLD-based boards (a2n20v2, a2n20v2-Enhanced). The
a2mega board has direct bus access (no CPLD bridge) and is not affected.

If OE_MARGIN=2 is too aggressive (cards fail to respond), it can be
increased to 3 or 4. If too conservative, it can be reduced to 1.

Testing:
Verified on Apple ][+ hardware (A2N20v2 board) with emulated cards in
slots 2 (SSC), 3 (Videx), 4 (Mockingboard), and 7 (SuperSprite).
Apple Pascal 1.3 boots 100% with all emulated cards enabled. Original
Videx VideoTerm Demo application runs correctly. Pascal applications
run without errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant