Skip to main content

Counter Submodule

The counter module is responsible for generating x & y addresses to read image pixels or characters from the framebuffers, as-well-as generating the active low hsync & vsync signals for the VGA signal.

VGA Protocol Timings

Taken from tinyvga.com. A VGA signal works using 5 wires, three for the color channels, one for the active-low hsync signal, and one for the active-low vsync signal. The producer varies the voltage values on the color channels in the range of 0-0.7V. The mode, i.e. pixel resolution and refresh frequency, depends on the timing of the two sync signals. The consuming monitor therefore first determines the mode of the data that it receives and will then sample the color channels once per pixel accordingly.

The pixel clock for our chosen mode should be 25.175MHz, but a 25MHz clock works just fine. The people that built the Gigatron also had the same experience, using a clock with a slightly different frequency.

Each line consists of 640 pixels and a short "porch" period at the end, where no pixel data should be sent. The "porch" period (~send pixels x) includes a sync pulse hsync which has a strictly shorter duration.
Since our image pixels are 4 monitor pixels wide, we divide all the pixel values by 4 in the table below. The x counter is then compared against these values to drive the send pixels x and hsync signals.

Analogously, each frame consists of 525 lines, of which 480 are visible. After the visible part, there is a short period where no pixel data should be sent. Located in this ~send pixels y period, there is the sync pulse vsync which has a strictly shorter duration. Since our virtual pixels are 4 monitor pixels high, we divide all the line amounts by 4 in the table below. The y counter is then compared against these values to drive the send pixels y and vsync signals.

Horizontal timing (line)

Polarity of horizontal sync pulse is negative.

Scanline partPixelsVirtual PixelsTime [µs]
Visible area64016025.422045680238
Front porch1640.63555114200596
Sync pulse96243.8133068520357
Back porch48121.9066534260179
Whole line80020031.777557100298

Vertical timing (frame)

Polarity of vertical sync pulse is negative.

Frame partLinesVirtual LinesTime [ms]
Visible area48012015.253227408143
Front porch10~20.31777557100298
Sync pulse20.50.063555114200596
Back porch33~81.0486593843098
Whole frame525~13116.683217477656

Implementation

The 25 MHz clock drives a couple of chained 161 counters, which results in several clock outputs that have successively halved frequencies.

Since our image's virtual pixels are 4 monitor pixels wide, the x address should be incremented on each 4th rising clock edge, therefore the x output starts at the 3rd output pin of the first counter. For the same reason, the y address should be incremented on each 4th line that was completed. For this we use the "Y divider counter" whose 2nd output pin is connected to the actual y counters.

The x and y addresses are fed into several comparators. These are used to compute the signals needed for the VGA protocol. We list the comparators and their effect below. The counter module also outputs what we call the "physical y" address, which counts the actual pixel lines. This is needed for our full resolution text mode.

A line ends at x = 200. However, in order for the image mode switching to work properly (and not switch one row too late), we already need to increment y to the next row before the mode selection byte is read. For this reason, the y counter is incremented when send pixels x is unset (at x=160). Theoretically, this interferes with the proper timing of vsync, but our monitor seems to tolerate this deviation.

Comparators

Here, set means applying high voltage and unset means applying low voltage.

  • X=160 ⇾ unset send pixels x

  • X=164 ⇾ unset hsync

  • X=188 ⇾ set hsync

  • X=200 ⇾ set send pixels x

  • Y=120 ⇾ unset send pixels y

  • Y=122 ⇾ unset vsync

  • Y=131 ⇾ set send pixels y

Note that, according to the protocol, vsync would have to be set at Y=122.5 again. We tried Y=123, but that deviated too much and the monitor did not accept the signal.
So we save one comparator and set vsync using the 2nd output of the "Y divider" counter, which is the same as counting Y in 0.5 increments.

Schematics

Counter submodule schematics

In the Build

A photo of the counter module