DEV Community

BibleClinger
BibleClinger

Posted on

Mini Micro and the 6502: Adding retro to the neo-retro

I couldn't help but feel something was missing in the Mini Micro environment. After all, we are encouraged to poke around and go deeper in this MiniScript-centered ecosystem, but there are practical limits to a "neo-retro virtual computer." Naturally the "virtual" part of that description means there is no hardware to discover. There is no processor for which you can write assembly code in order to squeeze out extra performance.

The Processor

That gave me an idea. Why not give the Mini Micro a virtual CPU for which we could write assembly? It wouldn't provide us with any performance benefit, but it would definitely add some immersion, and it has the potential to be a cool, educational tool.

I had done some dabbling with NES development. While some aspects of the NES are indeed a nightmare, the one thoroughly enjoyable aspect to it is writing assembly code for its 6502-based processor. The 6502 would be my processor of choice to add to the Mini Micro.

Necessary Ingredients

In order to make this happen, we need some components:

  1. A 6502 assembler
  2. A MiniScript program that can read the assembled binary file and execute it
  3. A way for the assembly program to utilize Mini Micro's features
  4. A goal

For #1, yes, I could write my own assembler in MiniScript within the Mini Micro environment, but I decided to choose ca65, at least for now. This assembler is robust, very versatile, and offers some very nice features, not the least of which is the ability to painstakingly describe your memory layout. More on this later!

For #2, that is where the bulk of my project comes into play. My intent is to write more posts on this aspect of the journey as development (hopefully) continues.

For #3, the answer is memory-mapped IO. We'll need a scheme. More on this later as well!

For #4, I'd like to be able to make some sort of Pong clone for the Mini Micro, completely in 6502 assembly.

Memory Mapping

The 1976 Second Edition 6502 manual showing an example of memory mapping convention
The 1976 Second Edition MCS6500 MICROCOMPUTER FAMILY
PROGRAMMING MANUAL provides a convention for memory mapping

The 6502 has a 16-bit address space, but it has no special way to communicate with other hardware (ie. sound, video, etc. etc..). As a result, in real systems, certain addresses were actually mapped directly to the hardware devices instead of being mapped to RAM.

Did you want to make the sound chip on your system play a tone? Perhaps you'd need to write a value to address $5000 that represents the pitch of the tone you'd like to play. Did you want to change a pixel on the video output? Perhaps you'd need to write the color to $6000, the X coordinate to $6001, and the Y coordinate to $6002. This was different in every system, and it all depended on the hardware components and how they were mapped.

Given that the Mini Micro has no real hardware, but that it has similar functionality (ie. sound and video) tied to native MiniScript objects, we can implement memory mapping to utilize this same functionality. We just need to pick a wise scheme that maps Mini Micro functionality to the assembly world in a smart and efficient manner.

Hello World: 6502 style

Let's see how far I've gotten in about two weeks. Here's a 6502 assembly program that is far from complete, but actually assembles and runs in Mini Micro:

.CODE

msg: .asciiz "Hello, world!"

reset:
ldy $4003   ; Store previous text.delimiter
ldx #$00
stx $4003   ; Disable text.delimiter by setting it to 0

loop:
lda msg, x
beq done
sta $4002   ; print single character of msg[x]
inx
jmp loop
done:
ldx #$0D
sty $4003   ; Enable previous text.delimiter by setting it to char(y)
stx $4002   ; Print the new line
brk

.segment "VECTORS"
.word reset
.word reset
.word reset
Enter fullscreen mode Exit fullscreen mode

And here we have its output when executed on the Mini Micro:

Output of 6502 "Hello World" on the Mini Micro
From 6502 assembly to the Mini Micro display: "Hello, world!"

Whew! That's a lot of effort to get a simple message printed to the screen!

But how does it work? How does reading (ie. lda, ldx, ldy) and writing (ie. sta, stx, sty) to memory affect text output?

I mapped addresses $4001 to $4003 to Mini Micro's text functionality for this test:

  • $4001: Writing a byte to this address outputs it as a number to the screen (ie. Writing 65 will print "65" to the screen)
  • $4002: Writing a byte to this address outputs it as a character to the screen (ie. Writing 65 will print "A" to the screen)
  • $4003: Reading or writing a byte to this address gets or sets respectively the value of text.delimiter -- which is the character in Mini Micro that gets printed after every call to print. By default it is the same value as char(13) in MiniScript (or '\r' in C).

The Journey

The important thing so far is that it works -- but is it sufficient for Pong?

As you can see, in this run we've managed to emulate 223 cycles (BRK isn't emulated correctly just yet, so its cycle count isn't accurate) in 0.03506 seconds. That means that the virtual CPU is working at an average speed of ~6.360Khz -- wildly underperforming the real chip that could run between 1Mhz and 3Mhz in the real world. Hopefully we'll explore performance in later updates.

Is it enough? Can we squeeze more performance out of it? Join me on the journey, and let's find out.

Do you have any good 6502 memories? Do you have ideas for this project? Let me know!

Top comments (3)

Collapse
 
sebnozzi profile image
Sebastian Nozzi • Edited

I have some questions about memory-mapping. I know nothing about all this, so bear with me ... You write a byte to addresses $4001 or $4002 result in writing a number or a character on the screen ... Does this happen immediately? When? How often? Why doesn't the CPU keep writing the same character / number stored in those addresses ...? Or is my understanding wrong? Does the system "know" when a new value has been stored on an address and "react" (?) only once? Thanks in advance!

Collapse
 
bibleclinger profile image
BibleClinger

My understanding (which is severely lacking compared to actual hardware experts) is that real hardware essentially acts as a state machine. Writing to addresses mapped to the hardware can trigger this state machine, when it's in the correct state, to begin an operation and to move along in states, so that it will complete an operation once when the correct number of write operations are performed. You can think of these addresses as hardware registers that are monitored for changes.

In terms of my implementation for this project, I have the hardware designed as plugin objects that have onRead and onWrite methods. When the plugins are registered, the addresses they want monitored for reading and the addresses they want monitored for writing, are transferred to the main program/class. Memory access triggers the correct methods for the correct hardware.

Collapse
 
sebnozzi profile image
Sebastian Nozzi

Awesome!