Skip to content

rtl: hold address/data after AS through S0 - #89

Open
codewiz wants to merge 10 commits into
captain-amygdala:mainfrom
codewiz:bus-hold
Open

rtl: hold address/data after AS through S0#89
codewiz wants to merge 10 commits into
captain-amygdala:mainfrom
codewiz:bus-hold

Conversation

@codewiz

@codewiz codewiz commented May 20, 2026

Copy link
Copy Markdown

Adds a 68000-style address/data hold after AS by holding the latch OEs and write-data buffer through S0 (one half-c7m of hold past the AS rising edge that ends S7). Intended for Zorro II cards that need address and data valid past AS to latch correctly, e.g. GVP RAM.

Stacked on #87: until #87 merges this diff also shows the arbitration commit; review/merge #87 first.

Build/test infra:

  • quartus_docker_install.sh / quartus_docker_run.sh: install and run Quartus Lite 20.1.1.720 via the stock raetro/quartus:base image.
  • build_svfs.sh: build EPM240_pistorm.svf.gz and EPM570_pistorm.svf.gz.
  • verify_verilog.sh: run rtl/pistorm_tb.v under Icarus Verilog.

codewiz and others added 5 commits May 16, 2026 02:33
The stock RTL declares M68K_BR_n and M68K_BGACK_n as input ports but
never samples them, and ties M68K_BG_n high — so the CPLD ignores
bus-master requests from Zorro II devices like the A590 SCSI controller
during DMA. This patch implements the standard 68000 BR/BG/BGACK
handshake:

  - Two-flop synchronizers on M68K_BR_n and M68K_BGACK_n
  - A 3-state arbitration FSM (IDLE -> GRANTING -> RELEASED -> IDLE)
    sharing the c200m clock domain with the existing CPU FSM
  - Tri-state hand-off: M68K_FC, M68K_AS_n, M68K_UDS_n, M68K_LDS_n,
    M68K_RW are now inout, driven through a bus_owned flag so the
    PiStorm cleanly releases the bus when BGACK is asserted by the
    requester
  - The CPU FSM stays parked at S1 while the arbiter is not in IDLE,
    matching the real 68k's behavior (no new cycles are started
    between BG-out and BGACK-in)
  - Internal weak pullups on BR_n / BGACK_n to backstop the
    motherboard's external pullups; the long CPLD-to-CPU-socket trace
    picks up DMA noise otherwise, which previously caused intermittent
    lockups on fast-RAM accesses during heavy disk activity
  - set_false_path entries in the SDC for the new async inputs
  - rtl/pistorm_tb.v: an Icarus Verilog testbench covering eight
    scenarios — normal cycle, BR while idle, queued op during release,
    BR asserted mid-cycle, BR pulsed (sync filter), BGACK delayed,
    two grants back-to-back, BR + queued op race

Also moves the `ipl` reg declaration earlier in pistorm.v so open
source simulators (iverilog) accept declaration-before-use; Quartus
was lenient about this but iverilog is not.

Tested on PiStorm Classic (EPM240) + Amiga A500 + A590 sidecar:
  devtest scsi.device 0 -i 8192 -l 1000 -m zorro   (A590 fast RAM via DMA)
  devtest scsi.device 0 -i 8192 -l 1000 -m chip    (chip RAM via DMA)
  devtest scsi.device 0 -i 8192 -l 1000            (no -m, works as before)
  Destructive read/write modes also pass — devtest compares read-back
  data, so the bus is verified correct both with and without DMA.

Resources: ~86 / 570 LEs on EPM570T100C5 (15 %), 88 / 240 LEs on
EPM240T100C5 (37 %).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restructure the FSM end-of-cycle so AS/DS deassert on the stock S7
schedule but the address-latch OEs and the write-data buffer stay
driven through to S0 (one half-c7m of hold after AS). S0 then releases
the OEs and waits for op_req and arb_state == ARB_IDLE on c7m_falling,
so the deassertion is deterministically clock-aligned and the arbiter
window is well defined. S1 collapses to a transitional c7m_rising into
S2.

This is the 68000-style address/data hold after AS that some Zorro II
cards (e.g. GVP RAM) need and that the stock PiStorm gave as zero. The
inter-cycle gap grows from 0.5 to 1.5 c7m, putting the cycle at the
spec 4-clock 68k bus cycle.

Quartus STA on PI_CLK at 200 MHz: this commit's slow-corner worst-case
setup slack is within fitter noise of pristine main (no arbitration);
the change is timing-neutral. Hold and pulse-width slack are positive
at both corners.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add quartus_docker_install.sh (one-time Quartus install into
~/intelFPGA_lite, MAX II/V device family only) and quartus_docker_run.sh
(wraps the stock raetro/quartus:base image with -e HOME / PATH /
LD_PRELOAD, the LD_PRELOAD being the tcmalloc fix for Quartus 20.1's
libsys_cpt udev double-free). The build flow consumes them via
../quartus_docker_run.sh from rtl/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Build the EPM240 and EPM570 bitstreams via a throwaway pistorm_build
Quartus revision so the tracked pistorm.qpf/qsf are never mutated.
Outputs gzipped SVFs as rtl/{EPM240,EPM570}_pistorm.svf.gz.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Compile and run rtl/pistorm_tb.v with Icarus Verilog and exit non-zero
if the testbench does not report PASSED. No Quartus or Docker required;
used as a fast pre-build sanity check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@codewiz

codewiz commented May 20, 2026

Copy link
Copy Markdown
Author

SVF files compiled from this PR using build_svfs.sh.

EPM240_pistorm.svf.gz
EPM570_pistorm.svf.gz

Sof far I have only tested the EPM240 variant on an A500 Rev 5 with:

  • A590 + 2MB RAM + ZuluSCSI Blaster: Can DMA in chipmem and zorro mem, passes RAM tests and devtest
  • GVP HC+8 Series II + 2MB onboard + Zorro II adapter: RAM tests pass (didn't pass without the bus hold), but devtest fails and hangs the machine after several iterations

Please help test different configurations and report here. I used AmigaTestKit to test RAM access and devtest for I/O and DMA:

devtest omniscsi.device 0 -iii 65536 -l 1000 -m zorro -v

Add -d for read-write (potentially DESTRUCTIVE!) tests. Please also test -m chip.

@codewiz

codewiz commented May 20, 2026

Copy link
Copy Markdown
Author

Quartus STA: PI_CLK setup slack at 200 MHz (worst-case / TNS, ns)

Config EPM240 Slow EPM240 Fast EPM570 Slow EPM570 Fast
main (no arb, no hold) -0.908 / -7.262 +0.131 -0.992 / -3.774 -0.002 / -0.002
z2-dma (arb only, #87) -1.608 / -21.964 +0.121 -1.405 / -12.900 -0.002 / -0.002
bus-hold (this PR) -0.939 / -9.174 +0.117 -0.995 / -13.678 -0.002 / -0.002

Slow corner = 1200 mV, 85 C; Fast = 1200 mV, 0 C. Hold and pulse-width slacks are positive across all configs and corners.

Compared to pristine main, #87 adds ~0.4-0.7 ns of slow-corner setup violation and roughly 3x the TNS. This PR is timing-neutral vs main: EPM240 TNS drops from z2-dma's -22 ns back down to -9 ns (close to main's -7), and EPM570 matches z2-dma on worst slack.

Numbers come from quartus_sta against pistorm_build for both families, with PI_CLK constrained at 200 MHz via pistorm.sdc. Same fitter results as the three-way comparison from earlier (numbers are reproducible across runs).

Full timing analysis (HTML)

bus-hold-timing-reports.tar.gz

codewiz and others added 2 commits May 27, 2026 15:29
…ups)

pistorm.v: reformat with 4-space indent and section banners, group port
list by function, add copyright header. One functional change: move the
E-counter and M68K_E generation from @(negedge c7m) into
@(posedge c200m) if (c7m_falling), making the whole design single-clock
on c200m and removing the standalone `wire c7m = c7m_sync[2]` net.

pistorm.sdc: drop Intel/Quartus boilerplate; replace the per-path
set_false_path pairs between PI_CLK and M68K_CLK/C1/C3 with one
set_clock_groups -asynchronous; cut CLK_SEL false_path; extend the async
input list with M68K_RESET_n and M68K_HALT_n, and the async output list
with PI_RESET.

Verified with iverilog testbench, Quartus 20.1.1 Lite STA on EPM240T100C5
and EPM570T100C5 (fast corner clean, slow corner negative slacks fall on
the same paths reported on main), and hardware testing on an A3000 with
AmigaTestKit memory tests plus Boing demo concurrent DMA - no regression.

Co-Authored-By: Claude Schwarz <claude.schwarz@gmail.com>
@codewiz

codewiz commented May 27, 2026

Copy link
Copy Markdown
Author

Firmware built from this PR after Claude's improvements:
EPM240_pistorm.svf.gz
EPM570_pistorm.svf.gz

Timing report for the above:
bus-hold-timing-reports.tar.gz

The EPM240 was stress-tested on my A500 with:

  • A590 + 2MB RAM + ZuluSCSI Blaster
  • GVP HC+8 Series II + 2MB onboard + Zorro II adapter

Both configurations passed hours of AmigaTestKit memory tests in all regions.

DMA works fine on the A590 in all regions, while the GVP still occasionally fails compares in devtest's destructive tests, but previously it was just hanging, so it's an improvement. I suspect these rare failures are caused by an unrelated hardware quirk with my setup, but I couldn't confirm it by testing without the PiStorm.

Another user independently tested the EPM240 firmware on an A2000 with a MaxV 5M240Z. The EPM570 firmware remains untested.

codewiz added 3 commits June 12, 2026 02:20
Rework S0 so all decisions happen on c7m_falling. With an op queued
and the bus owned, switch the external latches directly to the next op
without releasing OE: the address bus never floats between
back-to-back cycles, and the new address and RW are driven through S1,
giving half a c7m of setup before AS asserts in S2 - the same sequence
as a real 68000 (address valid from S1). When idle or arbitration is
pending, release the bus; total address/data hold after AS grows to
one full c7m (~140 ns), which Amigas with Buster (e.g. A2000) need to
latch the address cleanly.

bus_idle now also requires LTCH_A_OE_n released, so the arbiter cannot
grant while the address latches still drive the bus.

Also drop two comments that restated the code (e_counter / M68K_E).
The firmware dropped TXN early in S4 (via a 3-tick delay shifter) so
the Pi could overlap the next op with the cycle tail. But the Pi's
register writes pulse the external latch LEs combinationally: once TXN
is low, the Pi can rewrite the address/write-data latches during
S5-S7, changing the bus mid-cycle while AS/DS are still asserted. A
real 68000 holds address and data until the cycle ends.

Keep TXN asserted until the S7->S0 edge, so the Pi's earliest latch
reload (poll + GPIO latency) lands in S0, between the old address's
hold past AS and the new address's setup through S1. Costs some
op-to-op overlap; correctness first. Removes the delay shifter.
DTACK only promises read data by the end of S6 (a real 68000 latches
on the S6->S7 falling edge); slaves with buffered data paths can
deliver well after asserting DTACK. Closing the read latch at S5 entry
missed late-arriving data. Close at S6 entry instead, half a c7m more
margin.

Two later capture points were tried and rejected: closing on the
synchronized S6->S7 edge corrupts chipmem (the c7m synchronizer plus
output delay land the physical close 15-25 ns after the real edge,
past Agnus' slot turnover; the machine does not boot), and a c200m
tick-counted close just before the real edge did not help the GVP
either. This depends on the previous commit: TXN must not drop before
the latch closes, or the Pi could read it while still transparent.

Validated on A500: boots, A590 passes devtest -i 8192 -l 1000 on
zorro/chip/fast under load, GVP -m chip passes under load. GVP
-m zorro (DPRC hidden DMA to onboard RAM) still fails under load on
all our builds; scoped to not be read-latch timing, no BR involved.
@codewiz

codewiz commented Jun 12, 2026

Copy link
Copy Markdown
Author

Just pushed a large update tested thoroughly on my A500 R5 with an A590 and a GVP HC+8 Series II.

Please test it with different Amiga variants and Zorro cards.

Binaries

Changes relative to previous binaries

  1. Never float the bus between cycles + S1 address setup: back-to-back cycles switch the address latches directly to the next op without tri-stating; address and RW are driven half a c7m before AS asserts, like a real 68000.

  2. Hold PI_TXN_IN_PROGRESS until S7->S0: the Pi can no longer rewrite the external latches mid-cycle (previously possible from S4, changing address/data while AS/DS were still asserted).

  3. Read latch closes at S6 entry instead of S5: +70 ns for slaves whose data trails DTACK (e.g. behind Buster's buffers); later capture points were tried and rejected (chipmem corruption at the synchronized S6->S7 edge).

All three fixes have been implemented by Claude Opus in response to test failures and to align the PiStorm timings to the official 68000 bus cycle diagrams.

Timing report

Test results

  • A500 R5
    • AmigaTestKit long-running memory test 512KB chipmem: PASS
    • AmigaTestKit long-running memory test 2MB GVP onboard RAM: PASS
    • AmigaTestKit long-running memory test 2MB A590 onboard RAM: PASS
    • A590: devtest omniscsi.device 0 -iii 8192 -l 1024 -d -m chip + Boing demo: PASS
    • A590: devtest omniscsi.device 0 -iii 8192 -l 1024 -d -m zorro + Boing demo: PASS
    • GVP: devtest omniscsi.device 0 -iii 8192 -l 1024 -d -m chip + Boing demo: PASS
    • GVP: devtest omniscsi.device 0 -iii 8192 -l 1024 -d -m zorro + Boing demo: FAIL with compare errors

The last failure happens every time if the Boing demo is running in the background, rarely without it. It means that CPU activity, even to other regions, can disrupt the DPRC hidden DMA somehow.

While this remaining issue proves that there's still a critical difference between the PiStorm timing and a real 68000, this firmware behaves substantially better than anything else I have tested before.

@codewiz
codewiz marked this pull request as ready for review June 12, 2026 01:52
@codewiz

codewiz commented Jun 12, 2026

Copy link
Copy Markdown
Author

Bus timing diagram of the current release.

bus-cycle-read

NOTE: the first S0 cycle has several "3-stated" signals because it's at the start of the simulation. The second S0 cycle should be representative.

Generated by this script (requires python3-matplotlib):
plot_bus_cycle.py

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