dirkwhoffmann / tiara2600

A user-friendly Atari 2600 emulator for macOS
Other
3 stars 0 forks source link

Logbook no. 1 #1

Open dirkwhoffmann opened 2 months ago

dirkwhoffmann commented 2 months ago

After watching the lovely movie Atari: Game Over, I imagined how cool it would be to play the "worst game of all times" in my self-written emulator.

Bildschirmfoto 2024-09-25 um 22 09 24

Unfortunately, with the Commodore 64 and the Commodore Amiga, my emulators imitate the wrong machines. Thus, before playing the game, we must transform a Commodore computer into an Atari. As the Commodore C64 and the Atari 2600 share an almost identical CPU, it is straightforward to commence this project with the latest version of VirtualC64.

In this thread, I am going to document the first steps in transforming VirtualC64 into an Atari 2600 emulator. I've already checked in the latest VirtualC64 code and renamed some stuff. The next big step will be to remove unnecessary components. For example, the AtariC64 has no keyboard and does not support floppy drives. Thus, before adding any Atari-specific components, let's start trashing some existing code.

So, let the heavy equipment roll…

Bildschirmfoto 2024-09-25 um 22 13 28
dirkwhoffmann commented 2 months ago

After trashing lots of unneeded stuff, we can put the heavy machinery out of business for now. In the next step, let us adjust the texture size to the screen dimensions of the Atari 2600. The following image is well known to all with some knowledge about the A2600 and basically says it all:

atari-screen-setup

Changing the texture size was comparably easy as VirtualC64 utilizes two constants called Texture::width and Texture::height instead of hard-coded values. As each machine cycle (CPU cycle) corresponds to 3 color clocks of the A2600, changing Texture::width to 76 * 3 = 228 is what we need. After the adjustment, the emulator comes up like this:

Bildschirmfoto 2024-09-27 um 16 36 02

I have already added a TIA component to the code base. However, it is only a stub at the moment. It emulates nothing but the dual-phase horizontal counter, which produces the $\phi_1$ and $\phi_2$ signals used all over the place inside the TIA. Internally, my counter is a reimplementation of the jigo2600 counter found here. I did not know how to implement it better, so my code is mainly a reimplementation in my programming style. 

I have also hacked VirtualC64’s DMA debugger to ensure the counter counts.  Instead of visualizing the accesses of the C64 data bus, it now displays $\phi_1$ and $\phi_2$:

Bildschirmfoto 2024-09-27 um 16 07 54

In the image above, $\phi_1$ is drawn in red and $\phi_2$ in yellow.

dirkwhoffmann commented 1 month ago

At this juncture, it is time to say goodbye to VICII. I've already put it out of service with some other crucial components, such as the CIAs. CIA2 has been deleted altogether, whereas CIA1 calls itself RIOT now. It's just a shell, though, as all CIA-specific capabilities have been trashed, and RIOT-specific stuff is yet to come. 

After starting up, the emulator currently looks like this:

Bildschirmfoto 2024-10-01 um 12 26 56

Since VICII is no longer present, a blank texture shows up. However, you can still see the emulator's new heart beating, TIA's horizontal counter. I've also removed the old memory interface and the Commodore ROMs, which causes the CPU to hang in an infinite loop, constantly executing the break instruction. Yeah, it's sad, but that will change soon. 

In addition, I've adjusted some internal parameters, such as the proposed clocking frequency, horizontal and vertical dimensions of the viewable screen area, etc. As a result, the emulator already runs at its proper speed. As you can see in the status bar, the CPU frequency has slightly increased from about 1 MHz, the Commodore C64's native speed, to the clocking frequency of the Atari 2600. 

In the next step, let's add cartridge support to the emulator...

dirkwhoffmann commented 1 month ago

Here we go. This is what the Rom panel looked like in VirtalC64:

Bildschirmfoto 2024-10-01 um 13 20 28

In Tiara, the panel will allow the user to attach A2600 cartridges:

Bildschirmfoto 2024-10-01 um 13 20 43

Drag and drop is already working, so let's add the first cartridge…

Bildschirmfoto 2024-10-01 um 13 04 06

Voilà. PacMan is plugged in. OK, I've cheated a bit. While developing the panel, I've used Combat as my test cartridge most of the time, but that doesn't really matter.

As you can see, I've boldly exploited the efforts of the Stella team members, who have assembled an extensive database of existing Atari cartridges over time. I've integrated their database into my code so Tiara recognizes the cartridge by name. The credit for this all goes to the Stelle team, of course.

I've also peeked into the number of different bank switching schemes Stella supports and added a popup button that lets the user change the type. Tiara will only support standard 4K cartridges for now, but I've chosen a code design that allows me to quickly add other cartridge types later. In particular, the cartridge is Tiara's only internal component created dynamically (and thus accessed by a pointer); all others are pure static. In general, dynamically allocated components are a hassle when it comes to serialization. However, this approach allows me to implement other cartridge types by subclassing, which justifies the extra work at other places. VirtualC64 works similarly, except that Tiara has no expansion port class.

From here, let's move the construction zone to the emulator's memory inspector to see what's hidden inside the cartridge we've just attached...

dirkwhoffmann commented 1 month ago

Below, you will see the new memory inspector. It looks like a simplified version of the one inside VirtualC64 or vAmiga, as we do not have to consider overlayed memory blocks or different memory views that depend on the accessor. The colored bar at the top visually represents the Atari's bank map. As you can see, the first half of the 8K space the 6507 CPU can address is mapped to RAM and the two custom chips, TIA and PIA (aka RIOT). In fact, the A2600's RAM is part of PIA, which means that the first 4K is equally shared between both chips. Each chip address has multiple mirror addresses, a natural phenomenon in hardware design. It arises as soon as not all address lines are connected to a specific chip. The upper 4K of the Atari's memory is mapped to the cartridge, Combat in this case:

Bildschirmfoto 2024-10-02 um 16 48 43

The panel's lower part allows the user to select one of 64 memory banks and view its contents. The image above shows bank number 63, which marks the end of the 4K cartridge ROM. The two bytes marked in red are significant as they contain the reset vector for the CPU. The 6507 is a little-endian processor, meaning we must reverse the byte order to get the address. Consequently, the reset address is $F000. Since the 6507 only features 13 address lines, $F000 is the same as $1000. 

Let's head to the CPU inspector and see what we find there:

Bildschirmfoto 2024-10-02 um 16 48 30

If our analysis was correct, these are the first instructions of Combat.

A quick web search proves us correct. AtariAge features a complete annotated disassembly of the Combat cartridge:

 START  SEI                     ; Disable interrupts
    CLD                     ; Clear decimal bit
    LDX  #StkTop
    TXS                     ; Init Stack
    LDX  #$5D
    JSR  ClearMem           ; zero out RAM except address $A2
    LDA  #$10       ;
    STA  SWCHB+1            ; Port B data direction register and
    STA  GameOn             ; GameOn (tho not quite a valid value)...
    JSR  ClrGam     ; clear game RAM $82-$A2
dirkwhoffmann commented 1 month ago

Viewing the Combat code in the CPU inspector is one thing; executing the instructions is another. Thus, my next milestone is to make single-stepping work. I've already added the necessary memory hooks that provide the CPU with the proper data from the cartridge ROM and the hooks for accessing PIA's immense amount of 128 bytes of RAM.

Stepping through the first couple of instructions in Combat quickly leads us to the subroutine at $F032 (actually $1032 in the 8K address range):

Bildschirmfoto 2024-10-03 um 17 41 42

As the Combat disassembly tells us, we have reached a significant function: 

; Vertical sync, basic frame-start housekeeping
;
VCNTRL  INC  CLOCK            ; Master frame count timer
    STA  HMCLR            ; Clear horizontal move registers.
    LDA  #2               ; Get this ready...
    STA  WSYNC            ; for start of next line...
    STA  VBLANK           ; Start vertical blank.
    STA  WSYNC 
    STA  WSYNC            ; and do three lines
    STA  WSYNC
    STA  VSYNC            ; Now start vertical sync
    STA  WSYNC
    STA  WSYNC            ; and do three lines
    LDA  #0               ; get this ready
    STA  WSYNC
    STA  VSYNC            ; End of vertical sync pulse
    LDA  #43              ; And set VBLANK timer
    STA  TIM64T           ; with 64 clock interval.
    RTS  

The function tries to write into various custom registers, which obviously has no effect yet. Currently, TIA and PIA are (nearly) empty shells, ignoring all register accesses.

Interestingly, hitting the Run button does not crash the CPU. The Atari will accelerate to its native speed at about 1.2 MHz and execute Combat, ignoring all custom register reads and writes. I expected the CPU to stall a few instructions later, but the game seems insensitive to what it reads from the registers. This would rarely happen in the case of the Commodore C64 or the Amiga.

This leads us to the next natural milestone: Adding the yet-missing VSYNC, VBLANK, and WSYNC logic. 

dirkwhoffmann commented 1 month ago

I've started implementing WSYNC support. This register plays a crucial role in A2600 programming, as it allows a program to easily align with the video beam. When a value is written into this register, TIA pulls down the CPU’s RDY line until the end of the current scanline. With RDY low, the CPU will halt on the next memory read and only continue when the line goes up again. 

Debugging internal signals of an emulator is challenging, so I’ve added some visual support. In particular, I’ve taken VirtualC64’s bus debugger and turned it into a two-dimensional logic analyzer with four channels:

Bildschirmfoto 2024-10-04 um 16 41 07

When enabled, the logic analyzer records the values of the selected signals and superimposes the emulator texture with a debug texture. With the setting in the image above, the emulator visualizes Phi1 and Phi2, the two signals we are already familiar with.

Bildschirmfoto 2024-10-04 um 16 41 10

The following image was taken with channel 2 enabled. The channel probes the RDY line explained above:

Bildschirmfoto 2024-10-04 um 16 41 27

The image nicely illustrates the result of writing into WSYNC. When the CPU writes into this register (which happens where a colored debug line begins), the RDY line is lowered and released only at the beginning of the next scanline. Right now, the whole scene flickers like crazy due to the missing emulation of the vertical sync registers. The Combat code is basically running without proper frame sync at the moment. 

dirkwhoffmann commented 1 month ago

In my previous post, I mentioned Tiara's logic analyzer, which is essentially a 2D overlay superimposed on the emulator texture. In the meantime, I realized that Tiara would benefit from a "real" logic analyzer, allowing the user to view signal traces of certain scanlines. Here is how it looks: 

Bildschirmfoto 2024-10-13 um 19 15 28

It's already pretty neat. The user can visualize up to four signals simultaneously and view a signal trace for all scanlines that have been drawn in the current frame. The user may also set a beam trap, which stops the emulator at a specified (y,x) position. 

I've also implemented rudimentary support of the VSYNC and VBLANK registers. However, Combat is not a good test case for debugging this functionality as it is unnecessarily complex compared to Tiara's early development state. At this juncture, it is time to switch to more elementary programs. Don't worry; we'll return to Combat at a later point in time.

Luckily, the 8bitworkshop provides several example programs that perfectly suit our purpose. Their first test program is vsync which produces the following image when run on a genuine A2600:

Bildschirmfoto 2024-10-13 um 18 59 16

Tiara has no graphics capabilities yet, but we can still employ the program to test vertical sync. So, let's plug in the cartridge and configure some signals in the logic analyzer. 

Bildschirmfoto 2024-10-13 um 19 18 20

Enabling the 2D overlay, we get the following image: 

Bildschirmfoto 2024-10-13 um 19 17 54

This already looks good. The VSYNC signal is set in the upper area for a short period. After that, a couple of VBLANK lines follow. Then comes the visible drawing area, followed by the overscan area at the bottom. The RDY line is visualized in yellow. As you can see, the test program writes into WSYNC in each scanline, which pulls the RDY line low and pauses the CPU (visualized by a yellow dot). At the beginning of the following line, RDY is automatically released by TIA, waking up the CPU. 

The time has come to add some elementary pixel drawing capabilities to TIA to get a colored canvas. Stay tuned...