Back in the first half of February, Ben Eater added parts 5 and 6 of his YouTube series on building a 6502-based homebrew computer.
The enhancements he made gave the computer something it has been sorely lacking – memory. And the reason the computer needed memory was so it could have a stack.
Up to that point, the code had been highly repetitive – watch the video to see what I mean. It’s normal in assembler code to make use of subroutines to simplify things. But what happens when you call a subroutine in 6502 assembler, using jsr, is that it stores the current location of where you are in the program (the program counter, or PC) in a reserved bit of memory – the stack. The 6502 is hard-wired to use memory locations $01FF down to $0100. The code then jumps to the subroutine and, when it encounters a rts (return from subroutine) instruction, it looks at the stack and pulls the stored PC so it knows where to go back to.
That’s a very simplified explanation of stacks and subroutines. The point is, the machine expects there to be RAM at the stack locations above. No RAM? No subroutines.
A bit too clever
Now, the thing is, I have my own 6502-based machine in mind, the Zolatron 64, and I’ve come up with an address decoding approach that is somewhat different from Ben’s. I did a post about this recently.
So, for the RAM, I decided I’d use my decoding scheme, while keeping Ben’s decoding for the ROM and 6522 VIA.
I hooked up the RAM chip, used a 74HC00 quad NAND chip for most of the decoding and connected address line A15 to the RAM’s chip enable (/CE) pin. This is also part of the decoding, but it would cause me problems, as we’ll see.
I created a Beebasm version of Ben’s code and … it didn’t work. You knew that was coming, didn’t you?
Then the world went into plague mode and my life got complicated for other reasons and the dead project just sat on a tray.
Nonetheless, I couldn’t ignore it forever. I hooked up the Arduino Mega and made a few extra connections. I wired the /CE, /WE (write enable) and /OE (output enable) pins of the RAM chip to GPIO pins on the Arduino and added a bit of code so I could check if these were in the right states at the appropriate times. This turned out to be illuminating.
I also went back to the crude version of Ben’s code – the one without subroutines – and made one modification. The letters ‘H’, ‘e’, ‘l’ and ‘l’ would all be printed the simple way. But I moved the code for the letter ‘o’ to a subroutine. Then the rest of the ‘Hello, world!’ message would once more be printed the simple way. In other words, there would be only a single call to a subroutine.
And when I ran this code, the LCD printed ‘Hello’. In other words, the code entered the subroutine (otherwise I wouldn’t have see that ‘o’), but failed at that point.
Beep! Beep! Beep!
With the multimeter in continuity mode, I checked every damn connection on that board. And sure enough, everything was connected to what it should have been connected to and nothing else. WTF!
Time to take a look at the output from the Arduino. Here’s a section that tells an interesting story.
The values you see are, (left to right): Address bus in binary; Data bus in binary; Address bus in hex; whether the 6502 R/W pin is set to read or write; Data bus in hex; then the values of three pins on the RAM – /CE, /OE and /WE. Finally, some comments adding when editing the file later.
This is a section of the output where the processor is writing a value to the 6522 VIA chip. But look at the CE, OE and WE values for the RAM chip on the last line, which is where the actual writing takes place. These pins on the RAM are all active low. With CE and WE both low in this case, we can see that the processor is also writing the value to the RAM.
This comes back to using the A15 address line to select the RAM chip. In my design, I’m assuming that everything below $8000 is RAM. So, if A15 is low, it assumes we’re talking to RAM. But in Ben’s design, the VIA is at $6000, so A15 is going to be low for that too.
During writing, this isn’t a problem – on this machine, we just end up putting some data in RAM that won’t affect anything else. Remember that I said ‘during writing’.
Stacking it up
Now let’s look at where the machine is storing address data on the stack and pulling it back out again.
Here’s where the jsr instruction happens.
The least significant byte of the subroutine address appears on the second line. Then we jump to address $01FF (the stack works from the top down). The value $80 is written to this address on the fourth line and the value $9B on the fifth line is written to the stack address $01FE (the stack pointer automatically decrements). Now we continue the program at $809B where the most significant byte of the subroutine address appears. And then we jump to the subroutine itself.
The order in which things happen seems a bit jumbled, but it’s correct and it works. The ‘o’ character gets printed.
Now let’s look at what happens when we get to the end of the subroutine.
We encounter the rts instruction. There’s a random line after that because I think the processor takes another clock cycle to perform this, but then we jump to $01FD, which is part of the stack. (We don’t jump to $01FE, I think, because the stack pointer was incremented when we wrote the second byte of the return address. I presume it automatically increments here before reading the return address.) Anyway, we then read from $01FE and $01FF to retrieve the address to return to.
Except we don’t. There are just zeroes there. In fact, there could have been anything there – whatever random values might have been formed in the RAM on power up.
That means we’re not writing values to the RAM, dammit. It’s as though it wasn’t there.
Check the voltages
Finally, a little nagging voice spoke up in the back of my head. ‘Always check the voltages first’.
I fired up the multimeter again and started probing. And whaddya know? The VCC pin on the RAM chip read 3.3V instead of 5V. Then it read 2.8V. At one point it showed around 1V before heading back up to 3.5V.
The jumper wire connecting the VCC pin’s row on the breadboard to the power rail showed a healthy and steady 5V at both ends, so it wasn’t a problem delivering power. Something was going on between that wire where it plugged into the row and the RAM chip’s pin just a few millimetres away.
Could the RAM chip be faulty? Maybe I zapped it with some static?
I checked continuity between the power jumper wire and the VCC pin and got zilch. Except it beeped ever so slightly when I pressed hard on the probes. Switching to ohmmeter mode showed a high level of resistance.
Could the breadboard be faulty? I pulled the chip from the board, turned it over and… oh no…
The VCC pin was bent double. It had never properly gone into the breadboard at all. The fact that I ever saw any voltage on it was a miracle. I replaced the chip and now everything worked fine.
Problem in store
But that’s not the end of the story.
The issue about the VIA and the RAM overlapping in the memory map is a problem.
So far, all the write operations have gone to both VIA and RAM, but only the VIA did anything with the data.
The read operations were from the stack addresses, way below the VIA’s address, which is up at $6000. So there’s no conflict there.
But what if we want to read from the VIA. Any attempt to do that will also read random data from the RAM. And that’s just not going to work.
In part 6 of his series (below), Ben adds the RAM and explains a lot of issues to do with timing – and it’s timing in a different context that I need to worry about next.
So far, I’ve run my version of the computer at a maximum speed of about 15Hz. But as Ben explains in part 7 (which I’ll discuss in a future post), there are problems when you ramp up the speed of the clock. It’s to do with the LCD being slow at accepting data, and the solution is to read from the LCD, which is able to tell you when it’s ready to receive the next byte.
Like I said, I’ll go into this in more detail in a future post. For now, I need to look at throwing away Ben’s address decoding and implementing my own for the ROM and VIA.