Guessing things from cartridge connector signals

Discuss hardware-related topics, such as development cartridges, CopyNES, PowerPak, EPROMs, or whatever.

Moderator: Moderators

Post Reply
User avatar
krzysiobal
Posts: 1108
Joined: Sun Jun 12, 2011 12:06 pm
Location: Poland
Contact:

Guessing things from cartridge connector signals

Post by krzysiobal »

I would like to make new page on wiki, if you have any new tricks or comments to ones described below, don't hesitate to add them:

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 |           |       |       |
Or maybe there exist longer one?


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
7) Detect new scanline - method #3
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 |
Source: Source

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.
Image My website: http://krzysiobal.com | Image My NES/FC flashcart: http://krzysiocart.com
Fiskbit
Site Admin
Posts: 1068
Joined: Sat Nov 18, 2017 9:15 pm

Re: Guessing things from cartridge connector signals

Post by Fiskbit »

krzysiobal wrote: Fri Nov 29, 2024 12:02 pm 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 |           |       |       |
Or maybe there exist longer one?
With DMA, it should be able to stay even for much more than 5 cycles. A 9 cycle sequence with DMC DMA during OAM DMA should be possible using something like STA ($01),Y where the pointer points at $4014 and Y is 0:

Read $01 for low pointer byte
----Even A0's start here-----
Read $02 for high pointer byte
Read $4014 for index adjustment
Write $4014 (put)
Read even PC address (get) [DMA halt]
Read even PC address (put) [DMA align/dummy]
Read even DMC address (get) [DMC DMA read]
Read even PC address (put) [OAM DMA align]
Read $xx00 (get) [OAM DMA read]
Write $2004 (put) [OAM DMA write]
----Even A0's end here-----
Read $xx01 (get) [OAM DMA read]

With a looping 1 byte DMC sample or with the implicit stop DMC DMA bug on some G CPUs and all H CPUs, it should be possible to stay even for as much as 14 cycles:

Write $4015 (get)
Read STA ($01),Y opcode (put)
Read STA ($01),Y operand (get)
Read $01 for low pointer byte (put)
-----Even A0's start here-----
Read $02 for high pointer byte (get) [DMC DMA halt]
Read $02 (put) [DMC DMA dummy]
Read even DMC address (get) [DMC DMA read]
Read $02 (put) [DMC DMA halt]
Read $02 (get) [DMC DMA dummy]
Read $02 (put) [DMC DMA alignment]
Read even DMC address (get) [DMC DMA read]
Read $02 for high pointer byte (put)
Read $4014 for index adjustment (get)
Write $4014 (put)
Read even PC (get) [OAM DMA halt]
Read even PC (put) [OAM DMA alignment]
Read $xx00 (get) [OAM DMA read]
Write $2004 (put) [OAM DMA write]
-----Even A0's stop here-----
Read $xx01 (get) [OAM DMA read]

I wouldn't be surprised if there's a way to make this a little longer, but this is what came to mind for now. You should refer to https://www.nesdev.org/wiki/DMA for the specific timings of these DMA cases.
Post Reply