Beamhacking: Loose Ends
In this series I've covered a useful subset of specifics on the TRS-80 Model 3 and its graphics. Many things were left until the end because either they weren't directly relevant or, more importantly, not fully understood. Now let's speak of a few other things that are known and look at what mysteries remain.
The Model 3 can switch between two different character sets of 64 characters in the 192 to 255 range. Turns out you can switch between them without any side effects. Timed carefully, all the characters can be shown on screen at once:
Simple, but fun. Check out the demo program.
Careful programming gives us 192 pixels vertically rather than the nominal 48. That's because there really are 192 scan lines with 4 of them required to make a single large pixel. Now, back then it was quite common for displays to be interlaced. Could there be 384 vertical lines of resolution up for grabs?
Is the monitor built in to the Model 3 interlaced? No, it is not. The Model 1 monitor is based on a TV. Is it interlaced? No, it is not. What if you hook it to a TV? Not on the ones I tried.
When I plugged my Model 1 into a video capture card it captured the video in an interlaced fashion. Hooked up in that fashion I got a whopping 384 lines of vertical resolution. Way cool, but to me it doesn't count unless it can be shown on an ordinary TV. Ideally one that was plausibly available when Model 3's were in their heyday. Here's a still from my video capture.
The top and bottom lines are the program's two attempts at a smooth line as there's no way to determine even and odd fields. The one at the top came out wrong in this case and the bottom one is nice and smooth at 384. The middle line is the same slope but doesn't switch between fields and only has 192 line resolution. The second HELLO at the bottom is display every second frame thus it is faint and does look as if only every second line is displayed — furthur confirmation that interlacing is in effect.
Yes, all the characters can be made twice as wide. Counter-productive if you're trying to increase the effective resolution but the mode has its uses. Why
when you really want to
Besides, is there anything cooler than switching video modes part way down the screen? Imagine having part of the screen a graphics display with maybe a double wide scrolling message at the bottom. Easy stuff with all the syncing and timing routines at my disposal. I gave it a whirl and things got weird.
Worked great except when it switched into 32 character mode the graphics were shifted a bit to the right. At least it drifted back into position after a couple rows. Going back into 64 character mode caused a shift to the left. Some of the characters were shifted so far left they got drawn before the beam had a chance to get back to the start of the line. That gives you the overlapping, curved lines in the bottom left.
And that was one of the more successful tests. Even worse distortions happened depending on when the switch was made. And if the switch happened too quickly the display became entirely scrambled making me fear for the life of the machine's analog hardware. Other times the program would go out of sync with the beam as the video decided to skip a few cycles and draw the display a bit quicker.
It was one of the first beam synchronization effects I tried. I decided to back away slowly and return to simpler things. Later on when I got the best possible synchronization worked out it seemed like a good time to verify the emulator's results in double wide mode. A demo would surely follow. Instead, things got even stranger.
My reaction: HUH?!?! Now, this didn't happen every time. But often when I switched into double wide mode before running my synchronization test pattern the display would look corrupt. That should have been a line of "0123456789" repeating which a few "1", "2", "3" markers helping denote the 10's places. Instead only the first "1" in the lower row made it out and the rest of the line filled up starting from character 0xF3 on up. On top of that, the display apparently started to use fewer cycles per screen. Here's the loop that tried to put up the numbers.
ld a,'0' ld c,'0' ld ix,15360+64 ld b,64 numlp: cp '9'+1 jr nz,nocarry ld a,'0' inc c ld (ix+64),c nocarry: ld (ix),a inc a inc ix djnz numlp
There is a simple explanation for what happened here. Look what we get if the first byte of the "ld (ix),a" instruction has a bit flipped and changes from 0xDD to 0x9D. The 0x9D is a single byte instruction "sbc a,l". The rest of the 3 byte "ld (ix),a" would be another couple instructions. In all, the following sequence would replace it.
nocarry: sbc a,l ld (hl),a nop
The loop draws '0' through '9' are as expected (remember that only the even digits are visible). Then we hit "ld (ix+64),c" which draws the '1' on the second row. However, for unknown reasons this write to video memory causes a bus conflict. At this point the "ld (ix),a" instruction is corrupted as described above. Due to earlier code, HL is loaded with 0x3C3F. Since the carry is clear at this point the "sbc a,l" changes A from 0x30 to 0xF1 because 0x30 - 0x3F is 0xF1. And we don't write the '0' out as was planned which is why the white block remains where the '0' was meant to be.
The "ld (hl),a" stores A on screen but the address of HL is odd and the result is not visible. Afterwards the loop continues on writing the values we see from 0xF3 on up. It never again writes to the second row because A register will not equal 0x3A.
The theory fits the facts so well it must be correct. But why did that one instruction get corrupted? Beats me, but it is not unreasonable to suspect a problem as the previous instruction did access video memory and the fault is related to being in a different video mode. There's never a problem in normal, single-wide mode. I also did a few attempts to drop into BASIC and gather more information. But I ran into machine lockups which points in the direction of similar instruction zappings going on.
UPDATE: Indeed, what I just described is exactly what is happening. It is because of a bug in Model 3 double-wide mode.
Those lockups have held back investigation. Given the odd things happening I was starting to worry about damaging the hardware. I want to make my Model 3 do cool things, not kill it! There are some ideas to follow up on here what with trying to get the fault to happen consistently and changing things a bit to prove the instruction corruption theory. One has to wonder why these problems don't happen on the TRS-80 more often. My program is writing to odd video addresses in double-wide mode which is not something normal programs would do. Or perhaps the fact that double-wide mode was practically never used is explanation enough. At any rate the mystery remains unsolved. But as soon as it is you can bet I'll get the emulator to operate the same way.
For the record, the story is a little better on the Model 1. It did not exhibit any of the screen skewing when switching between video modes. But there was some kind of timing variance that I didn't figure out how to make happen programattically. Using a program that let you switch modes based on key presses I could thrash a bit and get into a mode where the apparent number of cycles per screen dropped by one. After that point there was a known procedure to get it back into a stable configuration and it flipped easily between the synced and "slipping" versions. In short, less weird but still weird.
The oddities with double-wide mode may seem a bit strange. They're not such a surprise when you look closer at the video hardware. Double-wide mode isn't something tacked on to the end which doubles the size of each character. Instead it halves the driving video clock frequency which makes everything stretch. And then doubles the frequency again at a few key points to have it skip every second character and make sure the end of line and end of screen signals go off at the right time. In other words, it's entirely reasonable to expect the screen timing to go off for a bit when switching modes.
The End?No way! I will get my emulator to support all the weird things that can happen on the Model 3. And surely there will be more graphics demos. And I'd love to see others join this mad game. Please contact me at the e-mail address below if you're at all interested, would like to hear more or would like to try a few things like this yourself. All you need is right here:
- An emulator to try out code. Necessary if you don't have a real TRS-80, still very helpful if you do.
- A Z-80 assembler with cycle counting built in.
- Code to get in sync with the beam.
- Code to waste cycles easily.
- Handy table of Z-80 instruction timings when you're inner looping.
- A video timing map.
- Brief primer on blanking.
George Phillips, August 13, 2009, gp2000 -at- shaw.ca