RAMBO-1 Mapper Investigation

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

Moderators: B00daW, Moderators

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Wed Feb 12, 2020 12:03 pm

I am simulating it more accurately now and I am seeing something interesting. It sure looks like my test is still screwed up but I thought I would share in case it triggers any ideas for you. The green channel inverts each time I start a new scanline. I am probably off by a scanline, either that or they somehow are reloading right before the IRQ, which doesn't really make sense.
zoom.png
overview.png

Edit:
I had not implemented pre-render scanline 261 correctly. It now makes more sense:
tek00024.png


Edit 2:
Focusing on this example, where it writes $04 to $C000:

Code: Select all

Scanline | PA12 | PPUCycle | Event
---------+------+----------+---------------
241      |   1  | 28       | NMI
258      |   0  | 261      | $C000=$BC
258      |   0  | 279      | $C001=$00
258      |   0  | 291      | $E001=$00
188      |   1  | 295      | IRQ
188      |   0  | 304      | $E000=$00
189      |   0  | 44       | $E001=$00
189      |   0  | 62       | $C000=$04
193      |   0  | 288      | IRQ
193      |   0  | 297      | $E000=$00
194      |   0  | 133      | $E001=$00
194      |   0  | 151      | $C000=$17
217      |   1  | 303      | IRQ
217      |   0  | 312      | $E000=$00
241      |   1  | 24       | NMI
I bumped this write later and later until it didn't use the new value. I found this:

Code: Select all

                       _______     _______     __
PA12 __________________|     |_____|     |_____|
      ^                 ^     ^
      |                 |     +----After first falling edge PA12 (NOT using new value)
      |                 +----------After first rising edge PA12 (still using new value)
      +----------------------------PPU cyc 62 (uses new value - original example)
I ensured that there was always a small delay after changing PA12 to avoid any potential race condition. I.e. PA12 was definitely high, followed by a delay, before writing to $C000 in the middle test result.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Wed Feb 12, 2020 3:13 pm

Scopeshots:
orig.png
after first rising edge.png
after first falling edge.png

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Thu Feb 13, 2020 4:49 pm

Latest text description, untested, but now at least considering this behavior where you can use a new reload value right away. It is not narrowed down to the IRQ being ACKed enabling this ability, that is still unknown what exactly opens the doors to make that immediately writable -- I just know that the door closes after the first falling edge of PA12. In addition, I do not know if the falling edge of PA12 itself triggers it or if it is actually M2 edge triggered with PA12 as an input. Lots to be carefully thought about and narrowed still, but we have made a step in the right direction finally.

Of the 4 main bullets in the M2 falling edge, there are conflicting statements there. I do not yet know which precedence or hierarchy these go in. It is now harder to avoid having an 8-bit comparison happening, but I think it is still valid for that to be a goal. I changed the 8-bit counter to an up-counter which is a step in the wrong direction from that goal. Lots more work to do here.

On M2 falling edge:
  • If PA12 = 0:
    • If 4-bit counter < 16: 4-bit counter++.
    • else keep the same value.
  • If PA12 = 1:
    • If 4-bit counter = 16: Clock the 8-bit counter. (see below)
    • Always reset 4-bit counter to 0.
  • If writing to $C001 (reset):
    • Clear 8-bit counter
    • Set 4-bit counter to 17.
  • If writing to $C000 (reload value):
    • If 8-bit counter = 0: Also reload 8-bit counter with new value being written to $C000
    • Do not reset the 4-bit counter, but do count it as an M2 cycle for that counter.

On 8-bit counter clock:
  • If value = $C000:
    • If IRQ is enabled, trigger IRQ (wait 1 more cycle before doing that though, regardless of PA12 next cycle...)
    • Always clear 8-bit counter.
  • Else value++.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Fri Feb 14, 2020 2:42 pm

New drawing, definite progress being made understanding this thing.

The work I did before without toggling PA12 within each scanline turns out is still very valid information. It is not necessary to toggle it for the info I was looking for. It takes 1 M2 cycle with PA12 high to detect entering the sprite area of the scanline, and 16 M2 cycles with PA12 low to detect exiting it the sprite area of the scanline. So, filling it in with PA12s all high vs. toggling doesn't end up entirely invalidating that information.

I found that the unlocking of the reload is not anything triggered by writing to a register. The concept in the 2nd state machine in the diagram shows that it will constantly be loading the value from $C000 into the 8-bit counter each M2 cycle until that state becomes locked. This can explain the ability to "directly" write the 8-bit counter in this condition.

Note that in this diagram, the counters and state machines have a few references to each other. Since everything is clocked from M2, this means that the existing state resulting from the previous M2 cycle is used for this reference -- not the next state resulting from the present M2 cycle being considered. It gets me every time. Therein explains the "next cycle" / "1 cycle late" stuff we have known about with IRQ, and just demonstrated in the previous scopeshots with the locking of the 8-bit reload 1 cycle later than we might expect, etc.
state_machine.png


Edit:

I am not sure if this explains the +1 scanline anymore, I still have to think about that versus my older observations.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Mon Feb 17, 2020 9:53 pm

Just a quick update,

I updated the A/B/C1/C2 simulator from before with the 3 state machine / 2 counter idea from the previous diagram I shared. I don't understand how yet but it does usually get the extra scanline correct when doing the A/B/C1/C2 test. I have at least 2 A/B/C1/C2 combinations that do not match the chip. For example, 10/10/15/15 gets an extra scanline before the IRQ on the chip but not in the simulator. I understand that the A/B/C1/C2 test is not realistic and might not even make much sense, but if the diagram matches the chip, it should give the same answer, so the fact that it doesn't means it is valuable. Whatever I am doing in that test gets it to hiccup in a different way.

Also, one detail I did not share about the scope shots. In those tests, when I got to the sprite area, I toggled PA12 for each M2 cycle. So that is not exactly a perfect test but I thought I should clarify that. So, PA12 rose the first time, followed by an M2 cycle, then PA12 fell, followed by another M2 cycle. And it was that 2nd M2 cycle where, if it was a write to $C000, was no longer allowed to take immediate effect. I think there is still something there not captured in my diagram because the way I drew it, it gets locked following that first M2, which is good. But if $C000 was written at the first M2, the 8-bit counter would not get reloaded until the 2nd M2, which is expressly forbidden by the statement "reload 8-bit counter if 8-bit reload unlocked". Almost as if the unlock needs to jump through another state before becoming truly locked... I will try adding an additional state there to the simulator tomorrow and see if it makes any improvement.


Edit:
Alternatively, I could try making the 8-bit counter's reload asynchronous/transparent (i.e. not waiting for M2).

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Wed Feb 19, 2020 4:08 pm

I got sort of stuck the way I was looking at this with the A/B/C1/C2 test. The problem with that test is that it runs through many loops before giving a result, so anything within the whole loop could propagate to the result, making it very difficult to debug. So I started a new type of test that does a 1-shot from power on. The challenge here (or possibly benefit?, see below) is that you start with a random state.

1-shot test:

Power on: PA12 = 0, wait 3.6msec
- Write 00 to E000 (IRQ Ack / Disable)
- Write 01 to C000 (Reload value)
- Write 00 to C001 (Set IRQ mode to scanline counter mode)
- Write 00 to E001 (IRQ enable)
- Goto [X]
[X]
- PA12 = 0
- Do A1 dummy reads
- Goto [Z]
[Y]
- PA12 = 0
- Do A2 dummy reads
- Goto [Z]
[Z]
- PA12 = 1
- Do B dummy reads
- Loop to [Y] 20 times (i.e. trigger 20 scanlines)
- After 20 times, stop test, examine waveform triggered on scope.


Results from these values:
A1, A2, B, result
20 20 10 3
20 20 1 3
16 16 10 3
15 15 10 Never
14 14 10 Never
12 16 10 3
11 16 10 3
1 16 10 3

Result=3 means IRQ went low on the 3rd time I got to [Z]. Since this is a power-on test, the internal state machines may come up in any random state. I find that in some cases I get a 2 on at least some of these tests. However, I found no combination that made it ALWAYS 2. It proves that writing to all 4 registers E000, C000, C001, E001 in itself is not enough to completely initialize all of the scanline state machines into known states.

I modified the test as follows:
Insert 514 dummy reads before the register writes. No change.

I then removed that modification and did a different modification as follows:
Before writing to any of the registers, set PA12 = 1, delay, do a dummy read, set PA12 = 0, delay, then proceed with the normal test. Always the result is 2 scanlines with this modification. To me, this change represents entering sprite region, so it suggests that the 8-bit counter reload can only be unlocked from within sprite region. I made this change on the state diagram.

I then added to this test an additional 20 dummy reads like this:
Before writing to any of the registers, set PA12 = 1, delay, do a dummy read, set PA12 = 0, delay, [20 DUMMY READS], then proceed with the normal test.
I still get result = 2 scanlines when doing this, even though this test will have returned to background region before writing to the registers. So 8-bit reload remains unlocked even when entering background region. We already knew and are again confirming that PA12 must be 1 (i.e. re-enter sprite region) in order to lock out the 8-bit reload.

Since there are now 2 inter-dependencies between the sprite/background state machine and the lock/unlock state machine, I merged them into 1. They are still functionally the same, just providing 2 different ways of looking at it. This is another incremental progress but not finished and not tested enough yet.
merged.jpg

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Fri Feb 21, 2020 10:55 am

I am running into a lot of dead ends with this lately. I thought I would put together a list of knowns and unknowns:

What is considered a scanline:
- There is an internal counter that counts scanlines. It is considered a scanline if it steps this counter.
- It is very likely but not exactly proven that the counter is clocked from M2.
- PA12 = 1 indicates a sprite tile fetch, PA12 = 0 background tile or nametable fetch.
- PA12 = 1 theoretically triggers sprite area detection right away, since this spot marks the last clock where a write to $C000 can take effect for the next IRQ.
- Approx. 16 consecutive M2 clocks with PA12 = 0 is theoretically triggers background area detection.
- It is not known if the scanline counter steps on the transition from sprite mode to background mode, or the transition from background mode to sprite mode. It is presumably happening at, or 1 cycle after, one of those 2 points.

Initialization:
- "Inconsistent initial state" refers to usually an extra scanline before the first IRQ, but sometimes not an extra scanline.
- PA12 must be high during an M2 clock for the scanline counter to get into a consistent initial state (no extra scanline before the first IRQ).
- PA12 high during M2 clock can be the very first M2 clock at power on, before writing to any registers.
- PA12 high and back low without clocking M2 does not affect the inconsistency; M2 clock is required.
- PA12 never high but 514 M2 cycles before writing to all registers C000, C001, E000, E001 before proceeding does not consistently initialize.
- An additional 20 M2 cycles with PA12 low after writing to the registers has no effect on IRQ, it remains consistent with no more or fewer scanlines. (This represents a transition back to background mode.)

Reload:
- The power-on value of C000 is always FF.
- The value written to C000 is loaded separately into an internal counter because IRQs can loop with the period specified by C000 without rewriting C000.
- Normally, IRQ will occur in the C000 value +1 scanlines, and in some cases +2 when writing to the registers normally. This is confused by the fact of the prerender scanline on real hardware triggering an extra count.
- When clearing the IRQ but not writing to C000, IRQs happen with a C000 +1 period. This period is 256 from power on if C000 has not ever been written yet.
- If C000 is written at the time of IRQ, it may be written at any time up to and including the first M2 clock with PA12 = 1 to take effect on the next IRQ.
- If C000 is written after the first M2 clock with PA12 = 1, the next IRQ will still use the previous C000 value, and then subsequent IRQs will use the new value written here.
- Automatic reload from the C000 value, triggered when the internal counter reaches 0.
- It is not known if the reload happens repeatedly for every M2 cycle for a period of time, or if it happens just at just 1 specific point in time.
- If value zero causes immediate reload, there must also be a state machine latching reloadability because C000 remains writable per above statements.
- If value zero causes reload specifically at transition from background to sprite, a separate state machine tracking this may not be necessary.

IRQ:
- IRQ can only go low on the next M2 clock, regardless of PA12, after an M2 clock with PA12 = 1.
- IRQ can only go high by writing to $E000 (IRQ disable).
- Writing to $E001 does not cause IRQ to go high.
- If IRQ is looking for value zero in the internal scanline counter, it is complicated by the fact that enabling IRQ before a value gets reloaded does not cause immediate IRQ to go low. Therefore, reload and the IRQ decision can't both happen at the same time, i.e. transition from background to sprite modes. Unless there is a separate state machine involved, or counting 2 triggers to the IRQ before really doing an IRQ low, etc. So that gets overly-complicated there.
- Clearing the IRQ has no apparent effect on scanline counting. If you let it auto-reload and just keep clearing and reenabling IRQ, it does not matter when or where you clear. The actual period of the scanline causing IRQ remains the same regardless if you left IRQ low for a few extra scanlines before resetting it.

Writing to $C001
- This is known to have some sort of reset characteristic.
- It seems that the present number of scanlines being counted can be aborted when writing to this, and forcing IRQ to happen earlier or later than originally planned.
- It is not known if there are blacked-out areas where this register can't be written to cause a reset.
- It is not known if this register directly reloads the internal 8-bit counter, clears the internal counter causing it to naturally have a reload later, or operates in some other way, possibly with its own state machine.
- It is not know if writing to this register affects the count-to-16 for background area detection.
- $C001 write can't fully initialize the scanline counter (see initialization section).


Edit: Updated C000 power-on value is FF and updated that the natural scanline IRQ period is +1 of the C000 value.
Last edited by Ben Boldt on Mon Feb 24, 2020 1:43 pm, edited 1 time in total.

FrankWDoom
Posts: 236
Joined: Mon Jan 23, 2012 11:27 pm

Re: RAMBO-1 Mapper Investigation

Post by FrankWDoom » Fri Feb 21, 2020 1:26 pm

Most of this is over my head but it's fascinating to follow along anyway. Thanks for putting in the work.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Fri Feb 21, 2020 1:36 pm

FrankWDoom wrote:
Fri Feb 21, 2020 1:26 pm
Most of this is over my head but it's fascinating to follow along anyway. Thanks for putting in the work.
Thanks, Believe me, it is over my head too...

I found something interesting. I made my test do a write instead of a dummy read specifically at the exact cycle that makes IRQ go low. If I write to $C001(reset/scanline mode) or $E000(disable IRQ) for this specific cycle, it DOES prevent IRQ going low, even though IRQ doesn't care about PA12 in that cycle. Writing to these addresses does not prevent IRQ low:

E001 (enable IRQ), C001 (reload, also with larger reload value), A001 (the missing register), A000, 8001, 6000, 4001, didn't test any others.


Edit:
Scopeshots:

Normal scanline IRQ: Writing FF to $0000 (FF lasts a while due to data bus going Hi-Z after write)
write FF to 0000 - normal IRQ.png

Write FF to $C001 in the same cycle where IRQ would have happened:
write FF to C001 - prevents IRQ.png

Edit 2:
I got a little excited for a second there, actually writing FF to C001 put it into M2 cycle counter mode instead of scanline mode, that explains where M2 went low at an unusual spot in that screenshot. When writing $FE instead, it went low after C000+1 BG->sprite transitions, no surprises there.


Edit 3:
This is actually a really great discovery because it makes us think about what gets triggered on the rising edge of M2, and what gets triggered on the falling edge of M2. I had not thought about it at that level yet, I was only considering everything happening at the same time, once per cycle.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Fri Feb 21, 2020 6:42 pm

It looks like PA12 is checked on falling edge of M2:
rising edge.png
falling edge.png

IRQ rises on rising edge M2 (due to disable IRQ register write):
irq_rise.png

IRQ falls on falling edge M2 due to scanline interrupt:
irq_fall.png


Known edges so far:

Falling Edge M2 triggered:
  • PA12 high to enter sprite region
  • IRQ going low
Rising edge M2 triggered:
  • Register writes
  • IRQ going high due to register write
Unknown edge M2 triggered:
  • PA12 low to increment 4-bit counter (I think that's testable with a bit of work, i.e. set PA12 high momentarily for all rising edges of M2 and see if it makes any difference, also low for all rising edges to be complete)
  • lock vs. unlock reload (if that is an actual independent function as I have described it -- that one is still only a theory)
  • clocking of the 8-bit counter
  • reloading of the 8-bit counter


Edit:
I did the test with PA12 always high for every M2 rising edge. It works the same, but hard to show a scope shot of it. So, PA12 is only ever looked at during falling edge M2.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Mon Feb 24, 2020 1:52 pm

I found today that the power-on-reset value of C000 appears to always be FF, I have found no exception to this. If I just totally skip writing to C000 (i.e. replace it with a dummy read in my test), I will get an IRQ at the 256th scanline detected, and looping naturally, I get another IRQ at 512th scanline detected. This might be able to explain some pesky immediate IRQs that I have been working around in my simulator.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Tue Feb 25, 2020 4:45 pm

Diagram updates.

I was able to correct a corner case I found in the sprite-to-background transition. The chip must be checking PA12=0 to make this transition. Normally, the 4-bit counter requires PA12 = 0 in order to increment. So, PA12 must remain 0 for 15 cycles to make the 4-bit counter = 15. Then the sprite-to-background transition happens on the next clock. I found that if the next clock is with PA12=1, it will not make the transition, indicated by no IRQ ever happening when looping a test in this fashion. Kind of a known fact, it takes 16 cycles to count as a scanline... But anyway, that's how I found it and why that condition got added. That is the part that does the 16th count.

It is better but I still have a known corner case where the chip gives +2 scanlines and the simulator gives +1 scanline, when looping similar to the A/B/C1/C2 test. I modified that test to be a 1-shot doing only 2 big loops through, so I can capture the whole thing from turn-on with the chip and with the simulator and compare. In this particular test that fails, I get IRQ low correctly, then returning high correctly, then the 2nd IRQ low is too early in the simulator. So, some path or condition is not captured for that to be possible. Possibly one of the register writes changes states or counter values.

Also, I do not yet understand what random initial state could cause the +2 when I do not give it the initial M2 cycle with PA12 high. They could be related. I have been doing all testing with that added to make things consistent.
states.jpg
counters.jpg


Here is the text/code version which might be more readable depending what you are used to. This is an actual copy-paste from my simulator -- this chunk of code runs for each CPU cycle.

Code: Select all

            string result = logCycleCount + "," + get_state_string("M2=0");

            spriteState prev_sprite_state;
            reloadState prev_reload_state;
            irqState prev_irq_state;
            decimal prev_counter_8_bit;
            decimal prev_counter_4_bit;

            // *****************************
            // * First, do Rising Edge M2. *
            // *****************************

            // Keep track of previous state:
            prev_sprite_state = sprite_state;
            prev_reload_state = reload_state;
            prev_irq_state = irq_state;
            prev_counter_8_bit = counter_8_bit;
            prev_counter_4_bit = counter_4_bit;

            // Update Reload Lock state:
            switch (prev_reload_state)
            {
                case reloadState.RELOAD_UNLOCKED:
                    
                    break;
                case reloadState.RELOAD_LOCKED:
                    if (true==writeC001)
                    {
                        reload_state = reloadState.RELOAD_UNLOCKED;
                    }
                    break;
            }

            // Update IRQ state:
            switch (prev_irq_state)
            {
                case irqState.IRQ_HIGH:
                    
                    break;
                case irqState.IRQ_LOW:
                    if( false == checkBoxIRQEnabled.Checked )
                    {
                        irq_state = irqState.IRQ_HIGH;
                        checkBoxIRQAsserted.Checked = false;
                    }
                    break;
            }


            result += get_state_string("\r\n,M2=1");

            // *****************************
            // * Next, do Falling Edge M2. *
            // *****************************

            // Keep track of previous state:
            prev_sprite_state = sprite_state;
            prev_reload_state = reload_state;
            prev_irq_state = irq_state;
            prev_counter_8_bit = counter_8_bit;
            prev_counter_4_bit = counter_4_bit;

            // Update Sprite/Background region:
            switch (prev_sprite_state)
            {
                case spriteState.IN_BACKGROUND_REGION:
                    if (true == checkBoxPA12.Checked)
                    {
                        sprite_state = spriteState.IN_SPRITE_REGION;
                    }
                    break;
                case spriteState.IN_SPRITE_REGION:
                    if ((false == checkBoxPA12.Checked) && (15 == prev_counter_4_bit))  // Found that PA12 must = 0 here to go back to BG region.
                    {
                        sprite_state = spriteState.IN_BACKGROUND_REGION;
                    }
                    break;
            }

            // Update Reload Lock state:
            switch (prev_reload_state)
            {
                case reloadState.RELOAD_UNLOCKED:
                    if ((spriteState.IN_BACKGROUND_REGION == prev_sprite_state) && (true == checkBoxPA12.Checked))
                    {
                        reload_state = reloadState.RELOAD_LOCKED;
                    }
                    break;
                case reloadState.RELOAD_LOCKED:
                    if ((spriteState.IN_SPRITE_REGION == prev_sprite_state) && (0 == prev_counter_8_bit))
                    {
                        reload_state = reloadState.RELOAD_UNLOCKED;
                    }
                    break;
            }

            // Update 4-bit:
            if ((spriteState.IN_SPRITE_REGION == prev_sprite_state) && (15 != prev_counter_4_bit) && (false == checkBoxPA12.Checked))
            {
                counter_4_bit = prev_counter_4_bit + 1;
            }
            else if ((true == checkBoxPA12.Checked))
            {
                counter_4_bit = 0;
            }

            // Update 8-bit:
            if (reloadState.RELOAD_UNLOCKED == prev_reload_state)
            {
                counter_8_bit = numericUpDownReload.Value;
            }
            else if ((spriteState.IN_BACKGROUND_REGION == prev_sprite_state) && (true == checkBoxPA12.Checked) && (reloadState.RELOAD_LOCKED == prev_reload_state))
            {
                counter_8_bit = prev_counter_8_bit - 1;
            }

            // Update IRQ state:
            switch (prev_irq_state)
            {
                case irqState.IRQ_HIGH:
                    // Known fact: PA12 does not matter at the moment that IRQ goes low.
                    if ((true == checkBoxIRQEnabled.Checked) && (0 == prev_counter_8_bit))
                    {
                        irq_state = irqState.IRQ_LOW;
                        checkBoxIRQAsserted.Checked = true;
                    }
                    break;
                case irqState.IRQ_LOW:

                    break;
            }

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Wed Feb 26, 2020 6:16 pm

You know it's getting good when I fire up Visio, I no longer know of a test condition that fails:
scanline_states.png
4-Bit counter (M2 falling edge):
(Basically: In sprite state, counts to 15 clocks before switching to background state on the 16th clock.)
  • INC if "Sprite" && (4-bit != 15) && (PA12 = 0)
  • CLEAR if (PA12 = 1)
8-bit counter (M2 falling edge):
(Basically: Happens at the transition from background to sprite.)
  • RELOAD if "Background" && (PA12 = 1) && "Reset Inactive" && "Reload"
  • DEC if "Background" && (PA12 = 1) && "Reset Inactive" && "Don't Reload"

This FINALLY captures the very counter-intuitive fact that we get an extra scanline when there are too many M2 cycles with PA12 = 0. It is having to do with sending a reset. Way back from early on, we knew that the number of M2 cycles with PA12 low, during which the reset was sent, had to be WITHIN a range, or else we would get the extra scanline. Too many or too few would give an extra scanline. The way I have captured that is to give the reset its own separate state machine.

I can't say I have a great understanding of this. Here is my take of what is going on based on the data dumped out from the simulation.

Say that we are in sprite state, PA12 = 0 for some cycles, but less than 16 of them, so we're still in sprite state. Then we send the reset command, which latches us into reset active state. From this point we can do 1 of 2 things:
  • Lots more cycles with PA12 = 0. This will return us to background state. Then the next time that we do a cycle with PA12 = 1, we will return to sprite state, but it will not decrement the 8-bit counter because it is still stuck in reset.
  • We have a cycle with PA12 = 1 before exiting sprite state. This clears the reset state EARLIER than the first option, so it is able to decrement the 8-bit counter sooner.
So, there are LOTS of conditions on the transition to "IRQ triggered". Some of these are likely artifacts of tweaking and may not need to be there, or possibly causing undiscovered problems. I need to do some more testing and come up with a plan to retest as I pull things out that might not need to be there. Also, interestingly, my reset active to inactive transition suggests that it may look at PA12 on the rising edge of M2 for that transition. Not sure how I feel about that.

User avatar
Ben Boldt
Posts: 557
Joined: Tue Mar 22, 2016 8:27 pm
Location: Minnesota, USA

Re: RAMBO-1 Mapper Investigation

Post by Ben Boldt » Thu Feb 27, 2020 5:03 pm

A couple updates.
  • Transitioning to reset inactive state does not care about PA12. I came up with no simulation where checking PA12 for this made any difference. However, this transition does have to be on falling edge M2. It could be possible to rearrange things or change dependencies in such a way to use rising edge for this. But no clear way to do that.
  • Triggering IRQ does not care if it is in sprite state. Getting into "don't reload" causes transition to sprite state anyway, so they are kind of checking the same thing. I found no case where it was necessary to double-check it. They are very similar but I do have at least 1 case where "don't reload" state works properly and using "sprite" state does not work correctly.
scanline_states_v2.png

I did some tests where I modified my writes to the real chip to have the address = 0x0000 on all rising edges of M2 and 0x0000 on all falling edges of M2. I did this on a per-register basis. For example, I would test with $C000 rising edge only, with all register writes on both edges, and see if this differed from normal operation. I did all 8 tests this way.

I got confusing results from that testing. I found that everything worked normally when I had the register addresses set only during the falling edge of M2, for each and every register. This contradicts the known fact that /IRQ goes high on the rising edge of M2 when writing to $E000. I also found that when I wrote with register addresses set only during the rising edge, $C000 could work normally, and the other 3 registers could also work normally but they each additionally magically caused the $C000 value to become $00. By switching the address to $0000 for the falling edge, it looks like I am somehow confusing the chip and making it think it is a $C000 write... It puts the $00 value that I wrote to the other register into the $C000 register. I tried writing a different value to the other register, and sure enough, that value got put into $C000. Changing things a bit so that the address bus is $FFFF instead of $0000 for these tests, funny enough gives me an extra scanline so that might be triggering a magic write to C001... Not sure I am getting much value from these tests. I thought I could narrow down the clock edge where each write is happening but there's too much interference to draw good conclusions from it.


Edit:
I think this diagram can describe the scanline counter accurately now. It might still be possible that I have redundant parts or parts that could be simplified by combining them. Or doing a slightly different sequence resulting in different edges of M2 being used, etc. I think the next thing to do is go full circle back to CPU cycle counter mode and see how it could be sharing parts, especially focusing on transitions between the cycle count and scanline count modes.

Post Reply