Skip to content

firmware: DbgSnoopTap records C64 bus cycles on chosen pages for the host to take - #93

Draft
kfox wants to merge 7 commits into
SensoriumEmbedded:mainfrom
kfox:firmware-snoop-tap
Draft

kfox wants to merge 7 commits into
SensoriumEmbedded:mainfrom
kfox:firmware-snoop-tap

Conversation

@kfox

@kfox kfox commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

What changed and why

A debug-only bus tap. isrPHI2 records the C64's cycles on pages the host picks into a ring holding the last 1,024 entries. Each entry carries the ARM cycle stamp, the address, the data, the DMA state, and whether a locked slice was armed. The host takes the ring over the control channel with SnoopTapToken (0x64F8).

A page flagged as a trigger freezes the ring 64 entries after the first write to it, so the cycles leading up to a crash survive until the host gets back to the port. The tap sits after the fBusSnoop slot rather than in it, so it keeps recording while Sample Watch holds the slot.

It is off by default. It is built only when DbgSnoopTap is defined in TeensyROM.h, and only in images with Fab04_FullDMACapable. tools/bench/snooptap.py is its client:

snooptap.py arm 03:5      record writes to $03xx; the first write freezes the ring
...run the program under test, which takes the port...
snooptap.py stop          print the ring and stop recording

The protocol is written up in docs/ControlComms.md under "Snoop Tap".

The tap was written to chase an intermittent crash under host DMA. It caught the 6510 doing a read-modify-write on $0318 with no DMA set up. Without a record of the cycles before the fault, that would have read as a bad DMA write.

What to look at

  • ISRs.c: the hook is the only code isrPHI2 gains, and only with DbgSnoopTap defined. With it undefined, nothing outside #ifdef DbgSnoopTap changes except the token's #define.
  • SnoopTapCommand() in SerUSBIO.ino sits in the always-allowed switch ahead of the Busy gate. It touches no state the C64 reads. Cycles the ISR counts while a reply is being sent carry into the next reply.
  • The tap adds work to a tapped cycle before the handler dispatch: a data read on a write, and a store to DMAMEM on every tapped cycle. That only matters in a DbgSnoopTap image, on the pages it is told to watch.

How it was verified

  • npm test passes, as do the bench tests (python3 -m unittest discover -s tools/bench). test_protocol.py pins the token, and the DMA state names the client prints, to the firmware's definitions. Changing either fails it.
  • Against a fake link, the client sends the right bytes and decodes a write entry and a slice mark correctly.
  • On a TR+, the tap ran on an earlier base. That run produced the $0318 capture above. The current commits have not been built with DbgSnoopTap defined, and the client has not been run against a board.

kfox added 7 commits November 6, 2026 19:50
… to take

A debug-build switch, off by default. Writes (and optionally reads) on the pages the host
names go into a 1024-entry ring with the ARM cycle, the data byte, DMA_State and whether a
locked slice is armed; each locked slice's start is marked too. SnoopTapToken sets the pages
and takes the ring over the command channel, the same pull model as SampleWatch, so it can
be interleaved with DMA commands on the one serial port.
…takes back

The tap had no client of its own, so reading it meant patching one into
whatever host program was under test. protocol.py names the token, so
test_protocol.py pins it to the firmware, and it pins the DMA state names
the printout uses to the order of enum DMA_States.
A take dropped the page cycles the ISR counted while the ring was being
sent: SnoopSkipped and SnoopTaps were zeroed after the send, so no reply
ever counted them. They now carry into the next take. The reply's flush
goes through FlushCmdChannel, since CmdChannel->flush() waits with no
deadline for a TCP peer. SnoopActive was written and never read.
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