I find it quite interesting that – for my computer at least – RAM isn’t quite as vital as I thought it was.
Don’t get me wrong: of course I know it is. Computers aren’t nearly as useful without it. But my misconception up until now has always been that RAM is inextricably tied to all the peripherals, and without it you can run nothing else.
But actually: for my computer, the RAM is just another device on the bus, along with all the others. The CPU can read from it or write to it, just like it can the 6522 (see part four) or the other peripherals I’m planning to add later.
Which is why I’ve not needed to add any RAM until now – but I reckon I have enough of a handle on how the CPU communicates with devices to be able to add it, and debug potential problems.
As I did earlier with the ROM board, I’ve taken the RC2014 schematic of the RAM board, screengrabbed it, and drawn my modifications over the top.
I already have the /OE (output-enable) and /WE (write-enable) lines on the backplane, because I’ve chosen to implement address decoding on its own board rather than implement it on each board. To connect /OE and /WE I add the blue and green wires respectively. All I’m missing is the /CS (chip-select) line to select the RAM chip as the “active” device.
Fortunately that’s trivial here: the RAM should be active for any address in the lowest 32K (in the RC2014 it’s in the upper 32K). We check for that just by looking at A15, which will be low for any memory access in this range. And as the chip-select line is active-low, that just means that all I need to do is connect /CS directly to A15 (that’s the red line in the diagram). Easy!
It’s noteworthy that I can get a 32K SRAM device for a pound or two, when the BBC micro (and other computers of the era) had to do complicated things involving many smaller RAM chips, plus the associated “selection-logic” circuitry. The single chip I’m using here is doing the same job as around a third of the BBC micro motherboard, for a fraction of that cost. And as it’s SRAM rather than DRAM, I don’t have to worry about running the circuit at a minimum speed to “refresh” the RAM contents.
If you’ve ever done any diagnostics on a computer (whether that’s an antique 8-bit computer or a more modern PC) you might have seen that there are a number of ways of comprehensively testing RAM. You can write sequential numbers in and read them back, write particular bit patterns, or just random data. In addition to writing to locations, and then reading the value straight back out again, you can also write values into RAM and check that no other addresses in RAM have been inadvertently set.
These are all great ways of soak-testing your computer, and I fully intend to implement them eventually, but … this is a bit much at the moment! Partly because it’s a lot of work, but mostly because doing that degree of RAM testing requires working RAM to begin with!
To write code that has any more complex requirements than the A, X and Y registers means that there must be RAM at our disposal to work in. And if I want to implement any subroutines (because the code will get more complicated than a few simple stores and loads) then I’ll need a stack – which means I need RAM.
So for now, I’m going to implement some simple code that writes one or two ‘test’ bytes to a particular location, and then reads them back. If that passes, then we assume we have some RAM and can move on (eventually) to things more comprehensive. But if even the simple tests fail, then we assume we have no RAM.
I’ll use two LEDs attached to the 6522 to indicate success or failure – if they flash together then the test failed. If they flash alternately, then the test passed.
via_base = &8000 via_portb = via_base via_ddrb = via_base + 2 zp_memtestlocation = &80 ; location in RAM we're going to test ORG &8000 EQUS "This 16K isn't being used yet. Just placeholder for the moment." ORG &C000 .start_here sei:cld lda #&FF:sta via_ddrb ; set all portb as outputs ldx #&00:stx via_portb ; set all portb pins low ; very primitive RAM test starts here ... lda #0 sta zp_memtestlocation ; store 0 in RAM lda #&FF ; trash A, just to be certain lda zp_memtestlocation ; now load A back from RAM cmp #0 ; is it 0? bne simpleramtestfailed ; no lda #&55 sta zp_memtestlocation ; store &55 in RAM lda #&FF ; trash A, just to be certain lda zp_memtestlocation ; now load A back from RAM cmp #&55 ; is it &55? bne simpleramtestfailed ; no ; if we get here, then the simple tests passed. ; So from this point, we'll assume we have RAM. .simpleramtestpassed ; for pass, we'll alternate even and odd pins on port B ldx #&aa:stx via_portb ; %10101010 ldx #&55:stx via_portb ; %01010101 jmp simpleramtestpassed .simpleramtestfailed ; for failure, we'll toggle ALL pins on port B ldx #&FF:stx via_portb ; %11111111 ldx #&00:stx via_portb ; %00000000 jmp simpleramtestfailed ORG &FFFA equw start_here ; NMI address equw start_here ; RESET address equw start_here ; IRQ address
And … it works! Well, eventually. After a lot of staring at my board with a USB microscope, to see what insignificant spec of solder was earthing one of my address lines. It’s interesting how fragile the bus is, really. Just one tiny bridge, and the whole thing fails. But at least as it’s on separate boards, it becomes easier to track-down faults. And the address decoder was very useful for hinting which address line was always being held low.
Now it works, I’ve decided to throw caution to the wind, fly on the ragged edge and laugh in the face of fear … I’ve upped the clock speed from 1Hz to a colossal 5Hz. Yep, I think you’ll agree that such a monumental speed increase is living a bit on the wild side. Fortunately it’s still slow enough that I can see what the address bus is doing. But honestly: now that my code is getting a bit longer, I’m getting bored waiting for it to run!