Open dirkwhoffmann opened 1 month ago
After peeking into the (well-structured) Jigo2600 code, adding playfield support was a piece of cake:
I've basically reimplemented 1:1 what I saw in Jigo2600 in my personal programming style, and the code just worked. As my contribution was close to zero, all credit goes to the Jigo2600 team. Well done, Jigo team!
The next challenge for Tira is to pass the Fine Positioning test from the 8bitworkshop. The program draws a colored background and moves Player 0 smoothly from left to right. A stand-still looks like this:
Again, by boldly copying some more Jigo code, I've made it work somehow:
Somehow, because the player sprite has hiccups 🙈. In regular intervals, the sprite jumps at the wrong position in some frames. This calls for a timing problem, so the time has come not only to get the register values right but also the cycles in which the values change. Although larger portions of my TIA implementation originate from Jigo2600, Tiara's timing still differs. This is because Tiara utilizes the CPU core Peddle, which interacts differently with TIA than Jigo's CPU. Ergo: Time has come to start the first debugging task: To find out the timing differences between Jigo2600 and Tiara that cause the test case to stutter.
Time to track down that bug. The interesting scanline is line 36 in Jigo (or line 39 in Tiara's current numbering scheme). In this line, the test program writes into HMOVE, which is where the fun begins.
Let's start with something simple and see if the main horizontal counter works as expected. Besides others, the counter is utilized to generate the phi1 and phi2 signals that are used all over the place inside TIA. Inserting some print statements into Jigo's source code produces the following output:
0:36:10 phi1 = 1 phi2 = 0
0:36:11 phi1 = 0 phi2 = 0
0:36:12 phi1 = 0 phi2 = 1
0:36:13 phi1 = 0 phi2 = 0
0:36:14 phi1 = 1 phi2 = 0
0:36:15 phi1 = 0 phi2 = 0
0:36:16 phi1 = 0 phi2 = 1
0:36:17 phi1 = 0 phi2 = 0
0:36:18 phi1 = 1 phi2 = 0
0:36:19 phi1 = 0 phi2 = 0
0:36:20 phi1 = 0 phi2 = 1
The prefix numbers follow the scheme frame:scanline:colorclock
.
In Tiara, we can invoke the logic analyzer to examine the signals:
Let's check some more signals, namely SEC, SECL, and HMC:
1:36:10 SEC = 0 SECL = 0
1:36:11 SEC = 0 SECL = 0
1:36:12 SEC = 1 SECL = 1
1:36:13 SEC = 1 SECL = 1
1:36:14 SEC = 1 SECL = 1
1:36:15 SEC = 1 SECL = 1
1:36:16 HMC = 1
1:36:16 SEC = 0 SECL = 1
1:36:17 SEC = 0 SECL = 1
1:36:18 SEC = 0 SECL = 1
1:36:19 SEC = 0 SECL = 1
1:36:20 HMC = 2
1:36:20 SEC = 0 SECL = 1
1:36:21 SEC = 0 SECL = 1
1:36:22 SEC = 0 SECL = 1
1:36:23 SEC = 0 SECL = 1
1:36:24 HMC = 3
In Tiara, we get:
Here we go: There is a timing issue on Tiara's HMC, which increments four color clocks too early.
Fixing the issue was actually easy, as it turned out that Tiara updated SEC and HMC in the wrong order. Swapping the corresponding code segments makes the sprite move smoothly to the right 😎.
In the meantime, I've added experimental PIA support and the ability to detect joystick movement. Hence, it's time to try a somewhat more complex test case from the 8bitworkshop: collision.a26
In this test, a ball bounces between the top and bottom of the screen. In addition, some Indiana-Jones-like guy can be moved around with the joystick. The player can catch the ball by pressing the button.
Everything is all right in the first few seconds, i.e., the ball starts dropping. However, before reaching the player, it stops falling and starts trembling in the air.
This bug took me some time to track down, but I finally got it. As so often, the root cause was rather trivial. Here is the relevant code from the test case:
; Check for collisions
lda #%01000000
bit CXP0FB ; collision between player 0 and ball?
bne PlayerCollision
lda #%10000000
bit CXBLPF ; collision between playfield and ball?
bne PlayfieldCollision
The program checks for collisions by reading from CXP0FB
and CXBLPF
. From early on, I had the suspicion that the bug was related to collision checking, so I played safe and let Tiara always return 0 from the collision registers. Because this didn't help, I left the collision hypothesis behind for a while (which is why it took me so long to finally track the bug down).
Here is the thing: My peek function is set up straightforwardly like this:
switch (TIARegister(addr)) {
case TIA_COLUP0: return colup0;
case TIA_COLUP1: return colup1;
case TIA_COLUPF: return colupf;
case TIA_COLUBK: return colubk;
case TIA_CTRLPF: return ctrlpf;
case CXBLPF: return …
…
Constant CXBLPF
is mapped to 0x36 as defined here:
// Readable
TIA_CXM0P, ///< 30 11...... read collision M0-P1, M0-P0 (Bit 7,6
TIA_CXM1P, ///< 31 11...... read collision M1-P0, M1-P1
TIA_CXP0FB, ///< 32 11...... read collision P0-PF, P0-BL
TIA_CXP1FB, ///< 33 11...... read collision P1-PF, P1-BL
TIA_CXM0FB, ///< 34 11...... read collision M0-PF, M0-BL
TIA_CXM1FB, ///< 35 11...... read collision M1-PF, M1-BL
TIA_CXBLPF, ///< 36 1....... read collision BL-PF, unused
TIA_CXPPMM, ///< 37 11...... read collision P0-P1, M0-M1
This means that Tiara returns the correct value from TIA_CXBLPF
if the value is read from 0x36. However, the Atari's actual memory layout appears like this:
0000-002C TIA Write
0000-000D TIA Read (sometimes mirrored at 0030-003D)
Bummer! The readable TIA registers overlap with the writable registers. This is, of course, well-known to all Atari 2600 programmers, but I have never written any Atari 2600 myself in the past. Hence, I am still a newbie here and completely missed that point.
My current code was designed to make writable registers readable, allowing the debugger to display internal values. When written to, address 0x06 maps to TIA_COLUP0
, the color register of player 0. Hence, when reading TIA_CXBLPF
from address 0x06, Tiara returned the player color as collision bits. Thus, collisions could be triggered by changing the player's color, which is what actually happened.
Luckily, this issue is easy to fix. Returning the value from the proper register makes the test case work… except for catching the ball with the joystick button, which has not been implemented yet.
Here is another test case from the 8bitworkshop, displaying a big 48-pixel sprite:
Nope, this doesn’t look right in Tiara (yet):
This thread continues #1 which documented the first steps of converting VirtualC64 into an Atari 2600. After some basic groundwork, one of the first implemented Atari features was the VBLANK logic, which plays an essential role in VCS programming. Unlike more sophisticated machines such as the Commodore 64 or the Commodore Amiga, the VCS cannot generate a VSYNC signal by itself. The programmer is responsible for issuing these signals at the proper scanlines.
The test program discussed at the end of #1 did exactly that. It had a minimal kernel with a simple VSYNC logic built in. To produce some visible output, the program additionally switched the background color from time to time. Since Tiara had no graphics capabilities, we could only track some signals in the virtual logic analyzer.
This has changed now. In the meantime, I have added rudimentary drawing capabilities and support for the HB signal (horizontal blank), which makes the test program work. Here is how it looks in Tiara:
Register timing is likely all wrong, but that doesn't matter much for the simple test cases we currently run. We'll focus on timing at a later point.
As this all looks pretty well, let's turn to the 8bit workshop's next test case, playfield.a26. This one generates a simple playfield by writing into TIA registers PF0, PF1, and PF2. A real A2600 would produce the following output:
Tiara doesn't know anything about playfields yet, so let's change that…