Praxis in Practice

A Furries Fantastic Devblog!

Praxis in Practice Praxis in Practice

I dove headfirst into Super Nintendo Programming, Here is what I learned

Share Tweet

/* Without ever having touched Assembly before, I took a deep-dive into the SNES landscape, and learned valuable lessons about Communities and Buffers. */

According to the Git history of my SNES folder, I started fiddling with the black magic that is 65c816 Assembly around 3 weeks ago. You might be wondering why I'd start playing around with SNES software in 2018? I'd say because I intend to turn SNES Software into a project at CODE University, however, that'd only be half of the truth. In reality. I picked the SNES because of nostalgia; I grew up with this console, and learning some basics on how it functioned, thanks to the YouTube Channel appropriately named Retro Game Mechanics Explained, made me curious as to what I could do with the hardware.

A Super Nintendo with the game Donkey Kong Country 2 (License CC0)

Going into it, the first few pieces of documentation I found dated at least a decade back. Even the very first tutorial I looked at - Writing Your First SNES Program, by bazz - was written in 2007. This also means that most of the code examples I looked at were using an older toolchain. I personally decided to go with the libSFX Framework made by Optiroc to assemble my code, while using bsnes-plus for emulation.

But enough talking about the context, I promised to tell you about the wonderful things I learned. So here we go!

Community is key

Starting something like this might seem daunting, in fact, it felt pretty daunting to me when I first started out. I didn't know anything about accumulators and data banks. Yes, I am just going to leave them unexplained here, look them up if you don't know what they are, that's part of the fun ;). Anyhow, in order to not give up within the first few days of my dive, I decided to join a part of the SNES Development community. The easiest I could find was the SNES Game Dev Discord Server, totalling around 60 people when I initially joined. That's a very small community, but it's surprisingly vibrant for an almost 30 years old game console.

Joining this server, and just reading the cryptic messages people are sending each other intrigued me. I wanted to understand what they're talking about. At the same time, having this rather small community at hand gave me a place where I could ask my silly uninformed questions without fearing widespread rumours about my incompetence. As the last point on the community part of things, this also offered me a place to ask for specific documentation, or I guess even code-snippets (although I haven't done that yet).

So whatever you might attempt, whether that'll be programming on a SNES, getting the hang of WebAssmebly in Rust or just starting out with programming, chances are that there is a Discord Server or Subreddit for you! And probably other spaces, too. Just keep your eyes open and try to find something.

Buffers are neat

According to the Wikipedia Article on Buffers, a buffer is "a region of a physical memory storage used to temporarily store data while it is being moved from one place to another." I couldn't have formulated that any better, and it only took roughly 187 contributors to write that article. Let me explain to you how I learned the importance of buffers.

My goal was simple. On the SNES, there is something called the Color Generator RAM (CGRAM). The first (0th) register of that RAM sets the screen color. This color is 15bit, 5 red, 5 green and 5 blue. Let's get a little bit more terminology out of the way: 8bit are famously called a byte, 16bit are called a word, I'll use these terms in this paragraph, but I wanted to make sure we're all on the same page here. The Processor of the SNES usually operates in 8bit mode, this means that in order to change a color in CGRAM, you need to write to the CGRAM register twice.

I'll quickly show you a snippet of the code I ran every frame, if you don't understand assembly, don't worry. This is also code that doesn't work for my goal, as I will explain below:

    stz     $2121   ; storing a 0 byte (00000000) - this address is
                    ; the address we write to on CGRAM
                    ; Basically, it tells CGRAM that we want to 
                    ; change or read the 0th color.

    lda     $213b   ; Reading from CGRAM
    rol             ; Rotating the accumulator left
    sta     $0001   ; Saving the accumulator in an 
                    ; arbitrary place in RAM

    lda     $213b   ; Reading the second byte of CGRAM 
                    ; (as you remember: it's a word!)
    rol
    sta     $0002   ; saving that value in a different place, 
                    ; we don't want to overwrite!

    stz     $2121   ; Since we read from CGRAM, 
                    ; this register moved up, we reset it.
                    ; Now we want to write!

    lda     $0001   ; we load the first value
    add     #$00    ; we add 0 (this also adds the carry flag 
                    ; of the processor) basically, if we 
                    ; rotate a byte of the value 
                    ; %10000000 to the left we kind of "overflow"
                    ; the byte, as the 1 got rotated outside
                    ; the byte. This means we store the bit in the
                    ; carry flag of the processor.
                    ; This is supposed to make sure the bit doesn't
                    ; get lost, as we rotate a word, not byte!

    sta     $2122   ; this is the register we write to in 
                    ; order to save to CGRAM

    lda     $0002   ; we don't need to add to 0 here, 
                    ; as we automatically rotate the carry flag
                    ; of the first byte into the second!
    sta     $2122

If you've never before interacted with assembly and understood any part of this, I am really proud of you! Anyhow, let's talk about what this technically does: We load the word from CGRAM, rotate each byte, and then save it in (W)RAM (-> Working RAM). We then reset the index of the CGRAM, load our rotated values and save them to CGRAM.

This already uses a buffer! $0001-$0002 is the buffer here; However: this also doesn't work!

Why doesn't this work? Well, do you remember how I told you that the CGRAM works with 15bit colors? How many bits were a word again? 16bit. So, essentially, we load a 15bit value, handle it as a word, and then save it back into a 15bit space. We lose a bit whenever we try to save the 16th bit (technically 15th bit, as we start counting at 0). I eventually figured this out after 3 days, when looking very closely at the CGRAM during the run of the program.

I shared my findings with the community I told you about before and got some very good and important advice:

Always use a WRAM buffer for CGRAM and OAM!

Optiroc

(FYI, OAM means Object Attributed Memory, and it's something I should talk about another time!)

And this brings me back around to the beginning of the blog post. If you remember, Optiroc was the creator of the libSFX development environment I talked about above. That's how small the community is.

If I made you hungry for more, check out the Super Famicom Development Wiki as the first stop in your fantastic journey.


Receive Updates

ATOM

Contacts