Category: Veronica

Veronica – RAM Board

Making our girl Turing-complete.

 

It suddenly occured to me that Veronica is missing something rather important which is needed to be called a “computer”. RAM! With all the chaos of developing EEPROM programmers and VGA boards and such, I completely neglected to add any RAM to the main system. Time to remedy that!

Luckily, the RAM board is about as simple as things get. All that’s needed is RAM chips and address-decode logic. Here’s the schematic (and Eagle file):

 

..

Nothing to it!

 

Once again, I’m using 32k DIP SRAMs here, when using a single larger one would be much easier. Why? I dunno- I just think they’re neat.

The RAM board is simply listening for all lower memory addresses, and patching into the bus when it hears them. This is dictated by the system memory map, which for Veronica currently looks like this:

  • $0000..$00ff: 6502 zero-page
  • $0100..$01ff: 6502 stack
  • $0200..$dfff: Program memory
  • $e000..$effe: Reserved
  • $efff: VGA command buffer & status register
  • $f000..$ffff: System ROM

 

One thing to note- the 6502 has a two-phase clock, with a low part and a high part. The low part is used for internal housekeeping, and RAM access is performed on the high part. The critical bit is that you must protect your RAM during the low part of the clock phase. There is no guarantee that the CPU won’t put stuff out on the bus during this time, and it may cause random memory to be overwritten. In my case, I include the phase 2 clock signal in my address decode, so that RAM cannot be activated when the clock is low.

As you might expect by now, I started by prototyping this circuit on a breadboard to make sure it worked. As usual, it’s connected using the big header on the end of my backplane that allows things to be patched into Veronica’s system bus.

 

..

Obligatory breadboard porn.

 

Sadly, all was not well. I was having difficulty reading and writing memory locations in the new RAM. After some basic debugging, the problem became clear. The address decode logic on my ROM board was actually wrong! I didn’t catch it until now because nothing else had ever tried to use lower memory addresses. Some quick hacking on the ROM board straightened that out. Luckily, all the gates needed to make the corrections were already available in the chips on the board. I was able to fix it with some judicious trace-cutting and jumper-adding.

 

..

One big advantage to etching your own boards is that making late corrections like this is quite easy. Not so for a fancy board from a fab house.

 

So, with the prototype now working on the breadboard, it was etchin’ time. I opted to try using a two-layer board this time. This was a good test-case, because it’s such a simple board. Here’s the two-layer Eagle layout I ended up with:

 

..

Purrrty

 

I documented my experience with etching a two-layer board over here. The short version is that it went well! The final result, all installed in our girl, looks something like this:

 

..

The plastic over the standoffs at the top is needed for insulation. The structural standoffs on Veronica’s boards are all connected to ground. However, the top fill layer on my two-sided board is Vcc, so it needs to be insulated. I didn’t think of this until after etching. Next time I’ll just leave a big space around the standoff hole in the Vcc plane. It’s also protecting a header on the board behind it. That header is a little too tall, and tends to touch the back of the RAM board when flexed. These little details will all get resolved at some point. Until then, plastic.

 

So, to make sure the final product is really worked as expected, I wanted to write a full RAM checking routine. That’s quite a bit more 6502 code than I’ve needed until now, so I put together a cross-development system so I can write this code conveniently on my laptop. Here’s what I ended up with:

 

; Name: romImage.s
; Author: Quinn Dunki
; Copyright: ©2012 Quinn Dunki
; License: All Rights Reserved
;
; 6052 code for Veronica's main system ROM
; For details, see http://www.quinndunki.com/blondihacks
;
; Zero Page:
;    $00..$01:	Subroutine parameter 1
;	$10..$11:	Scratch

;
; GPU commands:
;
CLEARSCR = $01
PLOTCHAR = $02
PLOTSTR = $03
FONTFGCLR = $04
FONTBGCLR = $05
CURSORXPOS = $06
CURSORYPOS = $07

.macro GPUCMD	cmdByte,paramByte
	lda		#cmdByte
	sta		$efff
	lda		#paramByte
	sta		$efff
.endmacro


;
; Function calling
;
.macro CALL16	subroutine,param16
	lda		#<param16
	sta		$00
	lda		#>param16
	sta		$01
	jsr		subroutine
.endmacro


;
; Constants:
;
GPUREG = $efff		; GPU command and status register
RAMSTART_LO = $00	; First valid RAM location, after stack
RAMSTART_HI = $02
RAMEND_LO = $01		; Last valid RAM location +1
RAMEND_HI = $e0
TESTVALUE = $42		; Bit pattern written and read to test RAM locations


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Veronica main system ROM
;
; Boot entry point: $F000
;
.org $f000

	jsr		delay		; Give GPU time to boot and achieve VGA lock
	jsr		delay
	jsr		delay

ramCheck:
	GPUCMD	CLEARSCR,$00	; Show introduction text
	GPUCMD	FONTFGCLR,$3f
	GPUCMD	FONTBGCLR,$00

	CALL16	printStr,prompt

	GPUCMD	CURSORXPOS,$00
	GPUCMD	CURSORYPOS,$01

	lda		#RAMSTART_LO		; Initialize test address
	sta		$00
	lda		#RAMSTART_HI
	sta		$01

	lda		#$00
	sta		$10			; Initialize page counters
	sta		$11			; BCD version

ramCheckLoop:
	ldy		#$00		; Store and retrieve the test value
	lda		#TESTVALUE
	sta		($00),y

	lda		#$00
	lda		($00),y
	cmp		#TESTVALUE
	beq		ramCheckNextAddr
	jmp		ramCheckFail

ramCheckNextAddr:
	inc		$00			; Increment the 16-bit test address
	bne		ramCheckThresholds
	inc		$01

ramCheckThresholds:
	lda		$01
	cmp		#RAMEND_HI		; Have we reached the last address?
	bne		ramCheckPage
	lda		$00
	cmp		#RAMEND_LO
	beq		ramCheckSuccess

ramCheckPage:
	ldy		$01			; Have we advanced another K?
	dey
	dey
	tya
	lsr		a
	lsr		a
	cmp		$10
	beq		ramCheckLoop

	sta		$10
	sed					; Increment BCD version of page counter
	clc
	lda		$11
	adc		#$01
	sta		$11
	cld

	ldy		#PLOTSTR		; Update display
	sty		GPUREG
	pha
	lsr		a
	lsr		a
	lsr		a
	lsr		a
	ora		#'0'
	sta		GPUREG

	ldy		#PLOTSTR
	sty		GPUREG
	pla
	and		#$0f
	ora		#'0'
	sta		GPUREG

	lda		#PLOTSTR
	sta		GPUREG
	lda		#'K'
	sta		GPUREG

	GPUCMD	CURSORXPOS,$00
	GPUCMD	CURSORYPOS,$01
	jmp		ramCheckLoop

ramCheckSuccess:
	GPUCMD	CURSORXPOS,$00	; Print success message
	GPUCMD	CURSORYPOS,$02
	GPUCMD	FONTFGCLR,$0c

	CALL16	printStr,success

	jmp		spinlock

ramCheckFail:	; Print failure message
	GPUCMD	CURSORXPOS,$00
	GPUCMD	CURSORYPOS,$02
	GPUCMD	FONTFGCLR,$00
	GPUCMD	FONTBGCLR,$30

	CALL16	printStr,failure

	jmp		spinlock


;;;;;;;;;;;;;;;;;;;;;;;
; printStr
; Renders a null-terminated string at the current cursor location
; Args: $0000: Address of string
;
printStr:
	ldy		#$00

printStrLoop:
	lda		($00),y		; Render the next character
	beq		printStrNull
	tax
	lda		#PLOTSTR
	sta		GPUREG
	txa
	sta		GPUREG

	inc		$00			; Increment pointer
	bne		printStrLoop
	inc		$01
	jmp		printStrLoop

printStrNull:
	rts


;;;;;;;;;;;;;;;;;;;;;;;
; delay
; Sleeps for ~1 second
; Args: None
;
delay:
	pha				; Save registers
	tya
	pha
	txa
	pha

	ldy		#$ce	; Loop a bunch
delayOuter:
	ldx		#$ff
delayInner:
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	dex
	bne		delayInner
	dey
	bne		delayOuter

	pla				; Restore registers
	tax
	pla
	tay
	pla
	rts


;;;;;;;;;;;;;;;;;;;;;;;
; spinLock
; Stalls CPU indefinitely
; Args: N/A
;
spinlock:
	jmp		spinlock


;;;;;;;;;;;;;;;;;;;;;;;
; Static strings
;
prompt:
.byte		"VERONICA RAM CHECK",$00
success:
.byte		"SUCCESS",$00
failure:
.byte		"FAILED",$00

That code is for the CA65 6502 cross-assembler. I can’t say enough good things about the whole CC65 package. If you’re doing anything 6502, give this a look. The macro assembler is just peachy and does everything you could possibly need. For more information on how I’m using CA65 to develop Veronica’s ROM code, look here.

So, does it work? Let’s find out. Veronica’s ROM will now run a full RAM check on startup. It writes a known bit pattern from a register into every memory location, clears the register, then reads the memory back in. If it gets the same bit pattern back, all is assumed to be well. It counts the bytes as it goes along, so if something goes wrong, we know where. It also lets the user know how much RAM is actually available for program use. To display this onscreen, I’m using the 6502’s nifty BCD mode, which makes displaying numbers as ASCII text really easy. I apologize for the quality of these videos. I’m trying to find a reasonably-priced way to record Veronica’s video output directly, but I haven’t found one yet. You’ll have to bear with my “point-camera-at-screen” method in the meantime. Set the quality setting in the YouTube player to 720P for best results.

 

 

Here’s a test of the address decoding. I’ve changed the ram test code to continue one address past the limit of RAM. If the decode is working, then the last address will not activate the RAM board, thus nothing will be read or written successfully. The write will do nothing, and the read will get garbage from whatever noise was on the bus. That means the ram test will fail on the very last address.

Huzzah! Veronica has RAM. Good times were had by all.

Hacks Veronica