Quantcast
Channel: Quinn Dunki – Blondihacks
Viewing all articles
Browse latest Browse all 10

Veronica – F18A Baby Steps

$
0
0

One step back, two steps forward.

 

If you’ve been following this Put-An-F18A-In-Veronica drama, you know that we had some very light early successes, followed by an emotional rollercoaster of tragic failures and heroic repairs (for certain values of “heroic”).

It is high on my priority list to never repair that transceiver again, so it would behoove me to figure out what is causing that thing to burn out repeatedly. Normally at this stage I would start a long, possibly patronizing diatribe about how to debug things by starting at one end of the chain and working your way down, challenging assumptions at each stage. However, when your failure mode is “thing blows up and you have to play roulette with a soldering iron to maybe get it unblowed up” that scientifically rigorous approach loses a lot of appeal. Situations like this are where it is sometimes okay to take more of a shotgun approach to problem-solving. That is, fix everything you think might be contributing to the problem and hope you found the cause. This can lead to some superstitious behavior, as we’ll see, but it’s a decent enough place to start.

Many of my kind commenters had suggestions on previous posts for possible causes of the problem, and I took all those to heart. The most common hypothesis among myself and others is the quality of my power rails. I’ve been lazy on this F18A project by using my bench supply to power the breadboard directly. This started because I wasn’t certain if Veronica’s built-in power supply had enough current capacity to power the computer and the new F18A experiments on the breadboard. I also like using the bench supply because it gives you some warning if you have a short, in the form of a spike in current consumption on the built-in ammeter. However, that ammeter trick didn’t save me either of the times that the transceiver went quietly into that good night, so perhaps said trick is less helpful with modern components. Big old 5V TTL stuff can take an over-current condition for several seconds, so you have time to see the problem on an ammeter and shut things down. That said, I don’t know that it was any form of short causing this problem in any case. At the end of the day, I don’t for sure, but cleaning up my power can’t hurt and seems like it should have a good shot at helping.

Now that I have some experience with the F18A, I know that it draws around 200mA, and I’m comfortable Veronica’s 7805-based power supply can handle that just fine. Veronica’s supply has good filtering on it, and the backplane has the power rails on it, so it’s a simple matter to route power to the breadboard from there (instead of the other way around, as I was doing).

It doesn’t look like much, but those red and black wires running from the backplane harness to the breadboard’s power rails may be the most significant thing I’ve done so far on this F18A project.

 

The next thing I decided to do was eliminate possible sources of noise. I doubt noise would fry anything, but while we’re in the mode of cleaning up my bad habits, why not? I had two main sources of noise here- floating TTL inputs, and lack of decoupling capacitors on any of the chips. I generally don’t bother with that stuff while on the breadboard, because you can generally get away without tying up those loose ends. It’s important to decouple power at each chip and floating TTL is a definite no-no for robust final designs, but for screwing around on the breadboard it’s not generally a problem. However, we’re shotgunning here, so brace for splash damage.

 

A liberal sprinkling of 100nF decoupling caps on the power rails near each chip, all unused TTL inputs pulled high, and you’d almost think I know what I’m doing.

 

I also took this opportunity to verify every single connection on the board. It’s possible I had some lingering short I didn’t know about. Furthermore, Sprocket H.G. Shopcat really really loves poking around on my electronics bench, and she has a tendency to mess around with my breadboards. Sure enough, she had pulled a couple of wires out. Electronics prototyping with a cat in the house is like playing on Advanced Mode. You think you’re good at this stuff? Well how about if a malevolent entity randomly yanks on things when you’re not looking! Now how good are you, hotshot?

The final effort I’ll be undertaking is removing the F18A any time I need to flash Veronica’s EEPROM. I know from experience that Veronica’s on-board EEPROM programmer (driven by an AVR programming header) tends to drive the system bus pretty hard. Current consumption goes up 20mA during programming, so something on the bus is insufficiently isolated. It’s never caused a problem for Veronica herself, but I’m taking no more chances with the F18A. This will result in a time-consuming and possibly pointless superstition of removing and replacing the device on the breadboard a lot, but I need that peace of mind right now (I could use a piece of mind also, for that matter).

After all that work, no news would be good news. I should see my old test code turning the border white at startup.

White border! We’re back in business.

 

Now, that last concession about pulling the F18A off the board on every EEPROM flash is not a light one. When doing low-level programming without a lot of sophisticated tools like this, you tend to iterate a lot. Like, really a lot. Iteration is one of the most powerful forms of debugging in the absence of high level tools like simulators and steppers. You make a tiny change, see what effect it had, form a hypothesis, make another tiny change to test that hypothesis, and so on. Pulling a device on and off the board on every iteration is going to slow me down a lot, and I’m also concerned about the wear on the device and breadboard. I’ve ordered a 40-pin ZIF socket, and I’m going to install that on my breadboard to make this easier. Until it arrives, we’ll press on. Slow iteration time has one advantage- it forces you to spend more time thinking about your code before hitting Build And Run. I’m reminded of my early days in C programming. I first learned C during a time when the best computer I had access to was an Apple IIgs. Compiling a project of any decent size on that machine took around 20 mins (on a 2.8MHz 65816). With a 20-minute build time, you learn to spend more time running code in your head before testing it out, and you don’t make changes lightly. Oh, and that build time doesn’t count the additional 10 minutes it takes to get back to where you were if your code crashes, because crashes on 1980s computers bring everything down. That means reboot, restart the OS, reload your editor, load your code, and then start to debug. Kids today- you don’t know!

Tapping into my old IIgs roots, I did a lot of simulating code on paper at this stage of the project. That’s pretty easy with 6502 assembly. You can run through a block of code, noting what’s in each register and relevant memory location as you go. Every bug you catch this way saves you an iteration, and potentially a lot of time in a situation like mine. In this case, it also saves wear-and-tear on the F18A headers (which are not designed for hundreds of insertion cycles).

Speaking of code, let’s look at some. Now that the hardware is working again, I can start building up some first principles of the new F18A-based graphics engine for Veronica.

There are two types of memory writing on the F18A (and 9918A on which it is based)- registers and VRAM. Writing to registers is easy. We demonstrated that right away by changing the border color. I decided to formalize that a bit with a macro:

 

; Write to an F18A register. High 5 bits of regNum must be 10000
.macro F18AREGW regNum,dataByte
	lda		#dataByte
	sta		F18AREG1
	lda		#regNum
	sta		F18AREG1
.endmacro

 

If that syntax is unfamiliar to you, recall that Veronica’s tool chain for ROM code development is based on ca65, the assembler that underpins the excellent cc65 package. Unfortunately cc65 is no longer maintained, but there’s nothing platform specific in it, so it still works great for me, even six years after it was orphaned.

The second type of memory writing we need to handle is VRAM writes. Recall that the 9918A has a very narrow pipe to the CPU. There’s no shared memory, only an 8-bit virtual register that you can write to. There are effectively two registers, because of the unusual MODE bit, as we’ve discussed previously. Writing to a location in VRAM consists of pushing the two bytes of the address into one register, then pushing your data byte into the other register. This puts in the device into a special “VRAM writing mode”. All subsequent pushes to the register are treated as additional data bytes, and the address in VRAM is auto-incremented on each write. Any subsequent write to the “first” register will break the device out of this mode and you can go back to writing to the internal registers if you wish. It’s all a bit confusing, I know. The price we pay for a very easy hardware interface is a slightly awkward software interface. As it has always been, so it shall always be. The 9918A also has some special rules about the values of high bits on the things you are writing that help it keep track of whether you’re trying to start a new block with an address byte pair, or appending to a previous block with new data bytes.

I sat down to write a VRAM block writer, and since I needed to minimize iteration time, I was going to need to simulate it at length on paper. I ended up writing it on an airplane, thousands of miles from Veronica’s hardware. Now that’s a great way to test your assembly programming mettle. Can you code something away from the hardware entirely, and have it work the first time? Spoiler: no, I can’t. Here’s the code I wrote on the plane, anyway:

 

;;;;;;;;;;;;;;;;;;;;;;;
; graphicsVramWriteBlock
; PARAM1 : Destination VRAM address in F18A, high 2 bits must be 01
; PARAM2 : Source address in main RAM, must be page-aligned
; PARAM3 : Number of bytes to copy
;
; Write data to F18A's VRAM
;
; Trashes PARAM2
;
graphicsVramWriteBlock:
	SAVE_AXY

	; Set up F18A address register
	lda PARAM1_L
	sta F18AREG1
	lda PARAM1_H
	sta F18AREG1

	ldy #0
	ldx PARAM3_H

graphicsVramWriteBlockLoop:
	lda (PARAM2_L),y
	sta F18AREG0
	iny
	cpy PARAM3_L
	beq graphicsVramWriteBlockLoopLowDone

graphicsVramWriteBlockLoopCont:
	cpy #0
	bne graphicsVramWriteBlockLoop

	; Finished 256 byte page, so wrap to next one
	dex
	inc PARAM2_H	; Advance source pointer in RAM to next page
	jmp graphicsVramWriteBlockLoop

graphicsVramWriteBlockLoopLowDone:
	; Low byte of counter matched
	cpx #0		; If high counter is finished, so are we
	bne graphicsVramWriteBlockLoopCont

graphicsVramWriteBlockDone:
	RESTORE_AXY
	rts

 

The basic idea is that the input block is required to be page-aligned (that’s 256-byte-aligned in 6502-speak) which makes the code much easier to write. I simulated this case on paper using every edge case I could think of (partial blocks less than one page, partial blocks more than one page, exactly-one-page blocks, blocks that are even multiples of pages, etc) and it seemed to do the right thing (write thing?) in all cases. I was pretty confident as I deplaned that it should work.

Before we can test it, however, we need to get the F18A into a known state. I decided to start with text mode, since I’m driving towards rewriting Veronica’s ROM monitor for this new graphics system.

 

;;;;;;;;;;;;;;;;;;;;;;;
; graphicsModeText
;
; Begins text rendering mode
;
graphicsModeText:
	SAVE_AX

	F18AREGW $80,%00000000	; Set M3 to 0 (dumb, but necessary)
	F18AREGW $81,%01010000	; Select Text mode (with M1,M2)
	F18AREGW $87,$f0		    ; Set white on black text color

	RESTORE_AX
	rts

 

The rendering mode on the 9918A is set with three bits. Inexplicably, two of those bits live in one register, and one bit lives in another register. Things must have been tight on the chip, and they were jamming stuff in wherever they could. Anyways, that’s why you see all the weird gymnastics in that code, just to enable text mode. That code gave me this:

The screen clearly changed modes, so that’s a good sign. I had also set the frame buffer to $0000 in VRAM (not shown in that code), so in theory we’re seeing uninitialized memory here.

 

For giggles, I decided to give my VRAM block writer a try and use it to load some font data (which should those garbage blocks to recognizable characters). The screen did this:

It’s garbage, but something happened.

 

So, it doesn’t seem like my VRAM block writer is working, despite what Deplaning Quinn thought. Next I tried pointing the frame buffer to $0800, which is a default place that the manual suggests using for various things.

 

Now this is super interesting. We can see what looks like a distorted character set. I think I need to take a step back, though. There are too many variables changing at once here.

 

Now we get to a tricky place. The 9918A’s renderer uses a whole series of data structures that are indirections for various things. This is very desirable in a 2D renderer, because indirection means things can be very dynamic. For example, rather than hardcoding a font in ROM as many 1980s computers did, the chip has a lookup table that holds the font characters, and to place a character on the screen, you place a pointer into that table in the frame buffer. Similarly, the character definitions are not pixel matrices, but rather matrices of pointers into the color table. All these tables themselves are also specified by pointers into VRAM, so you can move them around, or switch quickly between them at will. Even the frame buffer itself is a pointer, so it’s trivial to double-buffer, or even triple buffer (VRAM space permitting). Vertical scrolling is also straightforward, just by moving the frame buffer pointer. The only real downside to all this indirection is that it means a lot of moving parts all have to be configured perfectly, or nothing works. That’s a problem for the intrepid bare-metal ROM programmer, who doesn’t even know if she can write to VRAM correctly yet. How do we set up all these tables and pointers to other tables if we can’t trust our code? If it doesn’t work on the first try (which it won’t) how do we debug this? In a situation like this, you have to find a baby step. One thing you can do that will have a predictable outcome. I struggled with this for a while, before it hit me- the F18A’s title screen has text on it, so that text is coming from a font defined somewhere. If I can deduce where, I should be able to modify the characters used in that font and verify my VRAM writes are working. That’s a baby step!

While you can theoretically put all these tables anywhere you want in VRAM, the 9918A’s technical manual does have some examples that seem to be reasonable defaults. I took a guess that the F18A might be using them. I tried enabling text mode without setting the location of the text buffer, or the character font. They would remain at their defaults. If I understand how the F18A title screen works, the screen should remain unchanged, and I can then try modifying a character in the font.

Ah, now that is interesting! We’ve learned two things here.

 

Notice that the title screen was still there, but the characters all look clipped. This is because the text mode uses 6×8 characters, but the basic graphics mode uses 8×8 tiles. So the title screen is actually a graphics screen, not a text screen. Now recall earlier when I pointed the frame buffer at $800, I saw what looked like a character set. This character set, in fact. Perhaps I can write a single byte to VRAM at $800, and modify the top line of the 0th character. I decided it would be a good idea to write a helper function to write a single byte to VRAM, instead of the complex block one above.

 

;;;;;;;;;;;;;;;;;;;;;;;
; graphicsVramWriteByte
; PARAM1 : Destination VRAM address in F18A, high 2 bits must be 01
; X : Byte to write
;
; Write a single byte to F18A's VRAM
;
graphicsVramWriteByte:
	SAVE_AX

	; Set up F18A address register
	lda PARAM1_L
	sta F18AREG1
	lda PARAM1_H
	sta F18AREG1

	; Write the byte
	stx F18AREG0

	RESTORE_AX
	rts

 

Using that, I can now try this little hack:

 

.macro CALL16	subroutine,param16
	lda		#<param16
	sta		PARAM1_L
	lda		#>param16
	sta		PARAM1_H
	jsr		subroutine
.endmacro

  ldx #$fc
  CALL16 graphicsVramWriteByte,$0800

  
  
  

 

The screen now did this:

 

WHOA. Now we’re getting somewhere.

 

Clearly I succeeded in setting the top row of pixels in the 0th character to 1s. Also, by dumb luck, the background of the title screen was made up of character 0. There was no guarantee that would be the case, although it was likely because programmers love zeroes.

If I’m right about my write, then I should be able to make little rectangles by doing this:

 

  ldx #$fc
  CALL16 graphicsVramWriteByte,$0800
	ldx #$84
	CALL16 graphicsVramWriteByte,$0801
	ldx #$84
	CALL16 graphicsVramWriteByte,$0802
	ldx #$84
	CALL16 graphicsVramWriteByte,$0803
	ldx #$fc
	CALL16 graphicsVramWriteByte,$0804

 

What effect did that have?

 

Result!

 

That was a substantial victory! It proves that I can write a byte to VRAM on the F18A, which is a huge milestone. I still have to debug my block writer, and get the whole network of indirection tables up and running, but now it seems like I can trust the basics, and my hardware all seems to be working correctly. I can also state that I have spent a whole day iterating on this software and didn’t blow up the bus transceiver on my F18A. That’s a great sign that perhaps one of the changes I made was effective.

If you decide to go down a rabbit hole with the 9918A’s documentation (which you can find on archive.org) note that they use weird names for all the concepts we’ve been discussing. The frame buffer is a “name table”. The sprite/character bitmaps are a “pattern generator” and so on. It can be hard-going understanding the concepts with these weird terms, but it’s important to remember the historical context here. In the early days of graphics hardware, nobody had agreed yet on what all this stuff should be called. Every designer had their own set of vaguely similar abstractions for how to represent the data structures needed in a 2D tile rendering engine. Arguably Texas Instruments chose the weirdest possible names, and inexplicably numbered their bits and busses backwards (0 is the most significant bit in all cases). I’ll tend to use more modern and accepted terms for these concepts in my posts, but if you read the docs, be prepared to see a frame buffer referred to as a “name table”.

Anyway, I think this is a great victory to end on for now. Assuming the finicky bus transceiver continues to hold up, I’ll continue pressing forward with the software layers needed to bring up this device. Stay tuned for that!

And hey- if you’re enjoying this series, maybe buy me a beer once a month over on Patreon. The patrons of Blondihacks are what is keeping this whole enterprise going. Thanks to all of you who already support me!

 

 

 


Viewing all articles
Browse latest Browse all 10

Trending Articles