Guessing things from cartridge connector signals
Both CPU and PPU offer very limited information to provide about current frame timing. Hopefully their behaviour is predictable and so more information can be revealed with help of mapper hardware inside cartridge that wathes specific signals on the cartridge connector:
Table of contents:
1) Detect power/reset state via M2
2) Detect power/reset state via CPU A0
3) Distinguish between hard reset (power button) and soft reset (reset button)
4) Detect if PPU is rendering:
5) Detect new scanline - method 1
6) Detect new scanline - method 2
7) Detect new scanline - method #3
8) Detect half of the screen (scanline 128)
9) Detect discrete vs single (nes-on-chip) console
10) Detect if CPU read cycle is opcode fetch or data read
---
1) Detect power/reset state via M2
M2 toggles permanently during normal console operation, but when RESET button is held or console has been powered just microseconds ago, CPU's /RESET line is held low which forces M2 to be in high impedance state.
Code: Select all
VCC | VCC | VCC / GND
| | | | |
C (1n) | R (4.7k) | R (10k)
| | | | |
M2---►|---+-|G)-- /RESET | M2---|<---+-|G)-- +RESET | M2--+----[ sample it ]
| | | |
R (4.7k) | C (1n) |
| | | |
GND | GND |
| |
recovered /RESET: | recovered' +RESET: | sample it few times and check
* 0 when in reset | * 1 when in reset | if it toggles (ext pull-up/down
* 1 otherwise | * 0 otherwise | migh be not neccessary)
Used by:
* most of multicarts; sometimes extra gate |G) is added to overcome slow edge transition
* MMC5 (internal implementation is unknown)
Sources:
* 1
* 2
2) Detect power/reset state via CPU A0
During normal CPU operation, A0 can be held in same state not longer than for 5 cycles
It also becomes high impedance when RESET is held. So similar reset detector like for M2.
The longest A0 can be held in one state is for example:
ASL $0001,X ;assuming that X==$FF
Code: Select all
A0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
A | $8000 | $8001 | $8002 | $0000 | $0100 | $0100 | $0100 | $8003 |
D | $1E | $01 | $00 | $00 | $00 | $00 | $00 | |
R/W | R | R | R | R | R | R | W | |
comment | ASL $0001, X | read | read | read | read | do math | write | |
| (X==$FF) | LO | HI | mem | val | operation | | |
| | arg | arg | val | again | | | |
3) Distinguish between hard reset (power button) and soft reset (reset button)
* After reset vector is executed, read some arbitrary RAM cell (N) and compare if it contains some arbitrary value (V):
It does not match V -> it was hard reset (power was cut for long enough so RAM content become corrupted)
It does match V -> it could be soft reset
Then write V to N and never change cell N
4) Detect if PPU is rendering:
Check if during 3 consecutive CPU cycles PPU /RD changed its value
5) Detect new scanline - method 1
Check if three consecutive ppu read cycles have the same PPU A13 (MMC5 uses that, but it checks whole 14 bits of PPU A)
6) Detect new scanline - method 2
Count PPU A13 edges - there are 43 edges per each scanline (32+8+2+1)
Code: Select all
| 32x | 8x | 2x | |
| (backgr)|(sprites)| (backgr)| unk |
| N A p P | N A p P | N A p P | N N |
PPU A13 | 1 1 0 0 | 1 1 0 0 | 1 1 0 0 | 1 1 |
N = nametable fetch
A = attribute table fetch
p = pattern table lo byte fetch
P = pattern table hi byte fetch
Count PPU A12 edges - depending on which pattern table is used by backgorund and sprites, there
are different number of PPU A12 edges (8 vs 34). But if oy filter edges (=do not count each edge more that once during some longer period of time) there is one of such filtered edge per whole scanline
Code: Select all
Background use $0000-$0fff
Sprites use $1000-$1fff
+-----------------------------------+
| 32x | 8x | 2x | |
| (backgr)|(sprites)| (backgr)| unk |
| N A p P | N A p P | N A p P | N N |
PPU A12 | 0 0 0 0 | 0 0 1 1 | 0 0 0 0 | 0 0 |
PPU A12f | 0 | 1 | 0 | 0 |
+-----------------------------------+
Background use $1000-$1fff
Sprites use $0000-$0fff
+-----------------------------------+
| 32x | 8x | 2x | |
| (backgr)|(sprites)| (backgr)| unk |
| N A p P | N A p P | N A p P | N N |
PPU A12 | 0 0 1 1 | 0 0 0 0 | 0 0 1 1 | 1 1 |
PPU A12f | 1 | 0 | 1 | 1 |
+-----------------------------------+
* MMC3 uses that
8) Detect half of the screen (scanline 128)
Works only if scroll-y is equal to 0:
Whenever PPU A13 transitions from 0 to 1, latch the current value of PPU A9.
latched PPU A9=0 -> first half of screen
latched PPU A9=1 -> second half of screen
Explanation:
First 16 row of tiles are located at $2000..$21ff (A9=0) in patternatble and
second 14 row of tiles is at $2200..$23ff (A9=1).
When PPU fetches nametable entry, it drives A13=1.
Code: Select all
| 32x | 8x | 2x | |
| (backgr)|(sprites)| (backgr)| unk |
| N A p P | N A p P | N A p P | N N |
PPU A13 | 1 1 0 0 | 1 1 0 0 | 1 1 0 0 | 1 1 |
9) Detect discrete vs single (nes-on-chip) console
Pull-up CPU-D7 to 1, read $4016.D7
If it is 1 - console is discrete (separate CPU, PPU and other chips)
If it is 0 - console is nes-on-chip
Explanation:
In discrete consoles, $4016.D7 is not driven by anything so if you pull it up to some constant level in cartridge connector, you will get the same value when reading $4016.
In nes-on-chip consoles, CPU data bus is disconnected from cpu data bus in cartridge connector for $0000-$401f, so reading $4016 will return current value of internal data bus (when you do it via LDA $4016, you will get 0 because the previous value on the bus was $40 because of the capacitance)
10) Detect if CPU read cycle is opcode fetch or data read
Without watching CPU carefuly from reset until this moment, you can't know that but if you want to halt CPU before next opcode fetch, wait for CPU write cycle or CPU read cycle from $2000-$401f. Very next CPU cycle will be for sure opcode fetch.