Now that the V9958 video board is shipping on Tindie, and I've gotten fresh stock of all the options for the base board, I've been working on getting a basic text mode console supported by the standard firmware. It took me a while to get used to the way the V9958 works, but thanks to the pretty great Yamaha documentation that's still available online (link for V9938, the V9958 documentation just lists the changes) it wasn't too difficult to make something work.
Here's a shot of it running Jeff Tranter's adventure, with the unmodified code that runs on the board without video connected.
(Sadly I don't currently have a CRT to run it on, but I'm working on that!)
Getting this working in the firmware in a way that was compatible with the current revision 1 board presented two challenges - firstly, I had to write the code for the console, including taking care of scrolling and tracking cursor position etc, and secondly I had to somehow find a way to get the code to fit in the severely-limited 16KB of ROM (a good chunk of which is taken up by the Kermit routines already).
The first challenge was writing the code. I'd already done some examples and proof-of-concept / demo code for the V9958, including a naive text-mode console demo that basically redrew the whole screen during the vertical blanking interval.
While this worked, it made the VBLANK take so long that there wasn't time for the computer to do anything else! Obviously it was fine as a demo (and let me figure out the basics of the V9958's TEXT2 mode) but for something general purpose that would go into the firmware a better way was needed.
The result of this work was the second text-mode console demo, which is much more efficient, and has a VBLANK that runs in a timely fashion. The general approach here is:
- Have an in-RAM circular buffer with the current screen contents
- Have two pages in video RAM, one of which is always current
- When printing a character, it goes directly to the active VRAM page for display, as well as the RAM buffer
With this scheme, the VBLANK handler usually does nothing much (except handle the blinking cursor). When the time comes to scroll the screen, non-interrupt code does the following:
- Increments the RAM buffer start pointer by a line length, and clears the last line
- Copies the RAM buffer to the non-active VRAM page
- Sets a dirty flag
With that done, next time there's a VBLANK, the handler just flips the pages (by setting the appropriate V9958 register with the new active page start), which again takes very little time.
Obviously it's slightly more involved (handling backspace characters and so on) but that's the basic gist of it, and I have to say I'm pretty happy with it. It works really well, scrolls nicely with no visual tearing or other artefacts, and means that there's never a long-running interrupt handler starving the rest of the system. The use of two VRAM pages means you never see a half-scrolled screen.
I did play around with using the V9958s blitter commands (which are available in text modes, unlike on the 9938) but the timing was a nightmare, and the fact that the commands haven't really been updated (and so one has to translate coordinates to their graphics mode equivalents, for example) made it not worth the effort. I may yet revisit this in the future, but as I say, it works and I'm happy with how it turned out.
The next challenge was making it fit in the ROMs. The 16KB ROMs I (short-sightedly) used for the revision one boards were already pretty full, and I definitely didn't want to lose any of the existing functionality - the idea here is to make a new minor revision of the firmware that is a drop in replacement, but also detects and uses the V9958 if fitted.
I played around with a few different ideas, and eventually settled on splitting the firmware into two stages - stage one would handle the basic machine initialisation (including the V9958) and provide the resident ROM code and TRAP handlers (both TRAP 14 which is rosco_m68k basic IO and TRAP 15 which provides Easy68k compatibility). Stage two would take care of loading user programs (via Kermit at the moment, though with this split it can be easily replaced with code to load via SD card, for example, which we're already working towards).
How does this save any ROM space? That's the clever bit - stage 2 is compressed (with a simple Zip-based algorithm). Once the basic machine is initialised, stage one unzips stage two into RAM, and runs it from there.
The Zip algorithm doesn't give super high compression ratios, and is optimised for size in the decompression code (otherwise the advantage of using compression would be lost), but it's good enough to fit everything that was previously in the firmware, plus the V9958 code and a text-mode font (which currently isn't, but could easily be, compressed too - at the moment it just lives alongside the V9958 code itself, which has to be in stage one so it's always available in ROM).
There's still some scope for saving a few bytes (which I'm hoping to use to provide a similar "pluggable" interface for the UART, to allow it to auto-detect the coming-soon MC68681 expansion board) but overall it works quite nicely, and is available in the current development branch for the new firmware (I'll try to remember to update here once it's actually released!)