Close

Thoughts on Supporting Horizontal Smooth Scrolling

A project log for VDC-II

Commodore 8568-inspired (and mostly compatible) video core for driving VGA-type displays.

samuel-a-falvo-iiSamuel A. Falvo II 04/19/2020 at 21:412 Comments

Ever wondered why the C128's VDC has such a strange way of supporting smooth scrolling?  I did, and I'm sure I'm still wrong, but I think I've gotten pretty close to the truth.  Especially as my goal with VDC-II is to maintain backward compatibility where it makes sense.

What follows is a brain-dump, me rubber-ducking with myself, on this very topic.  Enjoy!


While working on the VDC-II's generalized sync generator circuit, I realized that I needed to think about how smooth scrolling works.  This project log aims to record my current thoughts about how the 8568 implements this feature.  DISCLAIMER!  This information is hypothetical, even speculative in nature, based on the documentation found in the C128 Programmer's Reference Guide and on observations of how VICE and the Z64K emulators behave when I twiddle VDC register bits.  I still don't have actual hardware to test with.

Let's focus on horizontal smooth scrolling, since it's actually the *harder* of two axes to consider.

It all started when I started to focus on the horizontal sync position register (R2).  It became clear to me that the horizontal total register (R0) is the reload value of a down-counter.  The HSYNC pulse down-counter is reloaded (with the lower 4 bits of R3) when the horizontal total down-counter reaches 0, while also also reloading the down counter with the value in R0.  (In case you're wondering, the HSYNC pulse is asserted as long as the horizontal sync down-counter remains non-zero.)  When the value of the horizontal total down-counter is equal to the value in R2, *another* down-counter is reloaded with the horizontal displayed value (R1).  As long as the horizontal displayed down-counter remains non-zero, a horizontal display enable signal remains asserted.  This is how the VDC knows where the playfield appears on the screen, and how it can assert its borders.

The following timing diagram helps illustrate what I've discussed above.  Signals in all-caps are signals you'd expect to find exposed to another circuit; lower-case signals are implementation details to the sync generator circuit.


This is sufficient to generate, for example, a solid block on a display.  However, there's more that must be considered when looking at supporting horizontal smooth scrolling.

The counter values above are in units of characters, not in pixels.  Within each character column, there are some number (configurable via the high half of R22) of pixels, with 8 pixels being maximum.  This character dot counter is also a down-counter, as far as I can tell from the available documentation.  Thus, we expect to find timing similar to this (assuming 6 pixels per character cell):

(If you've ever wondered why the VDC keeps asking you to subtract 1 from things here, and add 1 to things there, this is why.  This is also why you must reprogram the sync generation registers whenever you change the number of pixels in a character.)

In order to support smooth-scrolling, however, we need yet another counter.  This one is programmable from the lower half of R25.  When this counter reaches zero, then we know to reload the pixel shift register.  Based on how the character data is laid out in the VDC documentation, the shifter always draws its video data from the most-significant bit of the bitmap byte; in other words, bit 7 is always shifted out, then bit 6, etc. for as many bits as is configured to exist in a character cell.

I think the original VDC would have also used shifter_load to trigger a memory fetch for the next character as well.  It'd involve three fetches:

  1. Fetch the next character matrix byte.
  2. Fetch the next attribute matrix byte (if attributes are enabled).
  3. Fetch the character font byte for the character fetched.

Assuming these fetches take one dot-clock to complete, that implies that the minimum character width is 3 pixels if attributes are enabled; 2 otherwise.

Since the VDC-II is intended to work with modern memory architectures, which strongly favors streaming over random access, it will work a bit differently.  The assertion of the HSYNC pulse will trigger a sequence like the following:

  1. Fetch the next HDISPLAY character matrix bytes into a holding buffer.  (Bad-line only.)
  2. Fetch the next HDISPLAY attribute matrix bytes into a parallel holding buffer.  (Bad-line only.)
  3. For each byte fetched into the character holding buffer, fetch the character font byte for the named character into a line buffer.

It's regrettable that I'll need a bad-line mechanism, but that is a constraint put on me by external factors that I have no control over.

Getting back to the smooth-scrolling support, remember that characters can have fewer pixels displayed than exist in the font data.  Here's a hypothetical situation that we find in the Commodore 128 Programmer's Reference Guide: 5 pixels per character but an 8 pixel character cell width.  We might want to have another display enable signal that operates on a character column basis.  Alternatively, and I think this is most reasonable, command pulses that zeros the shift register (which has the same effect as a dedicated enable).

According to the VDC docs, if your character cell is wider than the number of pixels in a character, then to smooth scroll, you not only adjust the smooth-scroll register, but also (strangely) the horizontal character display total register as well (bottom half of R22).  In the example above, R25 would be set to 4 (which is why pix_ctr is 4 after restarting the character boundary), but for some reason, you want to set the character total register to 1.  Why is that?

It's never explained in the PRG; but, the reason appears to be that the character display total and horizontal smooth-scroll registers are both synchronized against the character total register reaching zero.  At the start of a character cell, the character total counter (yup!  Another counter!) is set to the horizontal character displayed value (R22), and counts down.  When that counter reaches zero, we negate the character display enable/zero the shift register.

So, in this bit of rambling, I think I've satisfactorily figured out how to implement horizontal smooth scrolling in a manner compatible with the original VDC chip, and explained why the registers behave as they do.  It's definitely not how I would have designed the hardware, but at least I now have an understanding of how to implement more of the VDC-II's logic.


Discussions

Martin Goodwell wrote 10/15/2023 at 18:36 point

Hi,

thanks for digging into the details and trying to find out more about the inner workings of the VDC-Chip.

I mentioned this project and this blog post in particular in this YouTube video: https://youtu.be/NLUrxx0t2HU

Hopefully all of the community's efforts can shed even more light on the VDC-Chip.

  Are you sure? yes | no

Samuel A. Falvo II wrote 10/15/2023 at 20:55 point

Hi Martin, thanks for the mention.  Here's hoping we can shed some more light about this chip, and maybe even improve it in future development revisions.  It's been several years since I have looked at this project; however, I have not forgotten about it.  It is an ambition of mine to enhance the VDC in exactly the same sort of way as the F18 did for the TMS9918, including support for color palettes, higher resolutions, and even (eventually!) sprites.  But, my professional work has sapped me of much of my creative energy, so this project is on hold until I can either get the time again.  (That said, if someone wants to submit a patch, I'd be happy to review and commit it to the repo for the benefit of others.)

  Are you sure? yes | no