DEV Community

BibleClinger
BibleClinger

Posted on

Game Tank 65C02 RESET Pitfalls

I love the 6500 family of processors. Unfortunately, there aren't many modern systems out there that are equipped with a 6502 or any of its descendants. Enter the Game Tank. It's a new, 8-bit console (equipped with two Western Digital 65C02's) that is, as of the date of this writing, supposed to be brought into production soon this year. "New" and "8-bit" aren't usually heard in the same sentence in the year 2026, but here we are, and I'm completely here for it.

Writing 6502 assembly code can be incredibly difficult, even though the chip is a joy to write for -- at least as far as assembly languages go. The Game Tank does sport a C SDK, a Rust SDK, and even a BASIC SDK if you're into any of those languages. While these can be useful, it is my opinion that any developer that works with 6500 processors should write at least one project in assembly in order to better appreciate what is really going on under the hood. It's also probably going to be mandatory to write at least some of your code in assembly if you want to squeeze out every optimization that you can.

Thankfully, there's lot of information and documentation out there. For programming the 6502 you can visit the 6502.org web site. If you're looking for assemblers, there's the well-documented ca65 assembler. Regarding specifics to the Game Tank itself, there is the Game Tank wiki.

Getting Started

Last year I made an attempt at writing a simple program that bounced a rectangle around on the screen of the Game Tank Emulator.

Bouncing rectangle

It's not exactly the game of the century, but as a simple demo to see if I understood how the system worked, it was sufficient.

The Pitfalls

Between getting this demo working last year, and now trying slowly to make a Pong variant, I realized how many things can go wrong.

I present you some common pitfalls in initialization routines for the Game Tank.

Initializing Stack RAM in a Routine

.proc RESET
  ...
  ldx #$ff        ; We init the stack
  txs

  jsr ZeroAllRAM  ; We init RAM to 0
  ...
.endproc
Enter fullscreen mode Exit fullscreen mode

A classic 6502 error.

You wrote a RAM initialization routine to set all RAM values in the range $00-$1FFF to 0 so as not to be surprised by random values. Your intentions are good, but the stack lies in the $0100-$01FF range, and jsr and rts both rely on this very stack. If you initialized this range to 0 in your routine, then you just clobbered your return address, and you're probably scratching your head wondering why you somehow rtsed indirectly into your IRQ handler.

You can fix this in a number of ways. The easiest might be to just zero RAM directly in the RESET handler without using a separate routine.

Using RAM Before Initial RAM Banking

The Game Tank can bank RAM, which is a bit novel to me. Which bank is used at startup should be considered random. It is recommended that you initialize this by writing to the Banking Register at $2005 early on in your RESET routine.

Let's say you remember to bank, but you do it this way:

BANKING_REGISTER = $2005
.proc RESET
   ldx #$ff              ; We initialize the stack
   txs                   
   jsr initValues        ; We initialize our game variables
   stz BANKING_REGISTER  ; We bank ram
   ...
.endproc
Enter fullscreen mode Exit fullscreen mode

The bug is that you initialized the stack pointer and initialized your game variables before you banked RAM. Assuming that the Game Tank happens to boot with RAM Bank 0 active, you're fine; the other 3/4ths of the time, your RAM, including your stack, will appear uninitialized or even corrupted. This goes from absolute chaos to an easy fix if you can identify that you simply got the order wrong.

The solution is to set up RAM banking almost immediately -- certainly before stack initialization and variable initialization.

Using a Subroutine to Bank RAM

Let's say you have some important game variables in RAM Bank 0, and some more important game variables in RAM Bank 1. You want to initialize all of them.

BANKING_REGISTER = $2005
.proc RESET
   stz BANKING_REGISTER  ; RAM Bank 0
   ldx #$ff
   txs                   ; We initialize the stack
   jsr initValuesBank0   ; Initialize Bank 0 variables
   jsr ChangeBank1       ; We want to swap to bank 1
   jsr initValuesBank1   ; Initialize Bank 1 variables
   ...
.endproc

.proc ChangeBank1
   lda #$01000000
   sta BANKING_REGISTER  ; RAM Bank 1
   rts
.endproc
Enter fullscreen mode Exit fullscreen mode

The problem here is that by swapping to Bank 1, you have completely pulled the stack out from underneath you. Bank 1's stack almost certainly doesn't point to the correct return location, so we never return from ChangeBank1. Code execution goes somewhere into the unknown.

The solution might be to avoid RAM banking entirely if you don't really need it. (Note: you should still initialize the banking register!) It has a layer of complexity to it that really must be taken into account in order to make it useful.

If you absolutely want to use RAM banking, then the solution might be to redefine ChangeBank1 as a macro, instead of a subroutine to which to jsr. Don't forget to change back to your main RAM bank immediately afterwards!

Conclusion

The 6500 family of processors can be fun to program, but being that you'll likely need to touch assembly, a lot can go wrong. It's worth coming up with a reusable, RESET routine that does all of the initial housekeeping.

Perhaps the next article will be about the things that can go wrong with attempting to use the Game Tank's Blitter...

Top comments (0)