Inside the 76477 Space Invaders sound effect chip: digital logic implemented with I2L

The 76477 Complex Sound Generation chip (1978) provided sound effects for Space Invaders1 and many other video games. It was also a popular hobbyist chip, easy to experiment with and available at Radio Shack. I reverse-engineered the chip from die photos and found some interesting digital circuitry inside. Perhaps the most interesting is a shift register based white noise generator, useful for drums, gunshots, explosions and other similar sound effects. The chip also uses a digital mixer to combine the chip's different sound generators. An unusual feature of the chip is that it uses Integrated Injection Logic (I2L), a type of digital logic developed in the 1970s with the goal of high-density, high-speed chips. (I wrote about the chip's analog circuitry last year in this article.)

Functionality blocks inside the 76477 sound chip, indicated on the die. Die photo courtesy of Sean Riddle.

Functionality blocks inside the 76477 sound chip, indicated on the die. Die photo courtesy of Sean Riddle.

Looking under a microscope, you can see the circuitry that makes up the chip. The yellowish lines above are the metal traces that connect the circuits of the die. The reddish and greenish regions are the silicon of the chip, forming transistors and resistors. The black blobs around the edges show where tiny bond wires connected the die to the integrated circuit pins. I've outlined the analog circuits outlined in purple, while digital circuits are in cyan. The 76477 is primarily analog—most control signals are analog, the chip doesn't have digital control registers, and most sounds are generated from analog circuits—but about a third of the chip's area is digital logic.

The block diagram below shows the 76477 chip's functional elements and can be compared to the die photo above. The voltage-controlled oscillator (VCO) produced a tone whose frequency depends on the control voltage. The "super low frequency" SLF oscillator generated a triangle wave. Feeding this into the VCO generated a varying pitch, useful for bird chirps, sirens, or the warbling sound of the UFO in Space Invaders. The "one-shot" produced a pulse of a fixed length to control the length of the sound. The envelope generator made the sound more realistic by ramping its volume up at the start (attack) and down at the end (decay). The digital white noise generator was used for drums, gunshots, explosions and other similar sound effects. Finally the digital mixer combined these signals and fed them to the output amplifier.

Block diagram of the 76477 sound chip, from the datasheet. Digital inputs: triangles, resistor inputs: red, capacitor inputs: cyan, voltage inputs: violet.

Block diagram of the 76477 sound chip, from the datasheet. Digital inputs: triangles, resistor inputs: red, capacitor inputs: cyan, voltage inputs: violet.

The remainder of this article will dive into how the digital circuitry of the 76477 chip was implemented. First I'll explain Integrated Injection Logic (I2L), a short-lived logic family that was supposed to revolutionize computer chips. Next, the noise generator, control logic and the digital mixer are reverse engineered and explained.

Integrated Injection Logic

You may be familiar with TTL integrated circuits, such as the popular 7400 family. These chips are built from bipolar transistors—NPN and PNP transistors—and were fast. (Minicomputers were built from boards full of TTL chips, taking advantage of its speed.) Unfortunately, TTL gates required multiple transistors and bulky resistors, so it was difficult to fit a lot of TTL circuitry into an integrated circuit. The die photo below illustrates the complexity of a single TTL inverter. (For details on how it works, see my earlier post.)

Die photo of a TTL inverter. The inverter uses four transistors, four resistors and two diodes.

Die photo of a TTL inverter. The inverter uses four transistors, four resistors and two diodes.

A competitor to TTL was MOS; in the early 1970s, integrated circuits based on MOS transistors led to the rise of the microprocessor. Unlike TTL, MOS transistors could be densely crammed onto VLSI integrated circuits, but unfortunately, MOS was much slower than TTL. This posed a dilemma in the 1970s: TTL couldn't implement dense circuitry and MOS was too slow. The integrated circuit industry needed a technology that combined the speed of TTL with the density of MOS, and it looked like I2L was the solution. (Spoiler: I2L wasn't the wave of the future; CMOS was.)

I2L solved the problems of TTL and MOS with a clever new design. Each I2L gate was built from a single multi-collector bipolar transistor instead of the multiple transistors of TTL. Even better, the transistors could be arranged in a high-density grid. Finally, the bulky resistors of TTL were replaced by an "injector", a tiny transistor that injected the necessary current. For a final bonus, I2L was compatible with existing silicon fabrication techniques and could be built on the same chip with bipolar analog circuitry. (This was important for the 76477 sound chip, which combined analog and digital circuits on one chip.) In the late 1970s and early 1980s, I2L looked like the dream technology that would take over the integrated circuit industry. A variety of I2L chips were built, including digital watch chips and some microprocessors, as well as the 76477 sound chip.

The diagram below shows six logic gates on the 76477, implemented with I2L logic. There are two columns of gates, with injectors down the middle. Each gate consists of one transistor (inner green rectangles). Note the high density of the circuitry, without wasted space. Each I2L gate is implemented with a single transistor, compared to multiple transistors and bulky resistors for a single TTL gate (compare with the TTL photo above). Unlike TTL, which requires considerable wiring inside a gate, I2L only uses wiring between gates. (Gates are connected by a layer of metal, which appears as thick yellow lines in the die photos.)

Six I2L gates in the 76477 chip. Each gate is implemented with a single, compact transistor.

Six I2L gates in the 76477 chip. Each gate is implemented with a single, compact transistor.

I2L is a bit tricky to understand; it's like being in a crazy backwards world compared to TTL. A normal logic gate (such as a NAND gate) has a few inputs and one output. But an I2L gate has one input and multiple outputs! How can that work? The schematic below shows an I2L gate, with one input and three outputs. Normally the current from the injector (ICC) turns on the output transistor, pulling the output low. But if the input is low, the output transistor turns off and the output will be high.2 Thus, the gate inverts the input. (You can think of the injector as a pull-up resistor on the input.)

Implementation of a I2L gate. Note that it has a single input and multiple outputs. Icc is the injected current. From "Integrated Injection Logic: A Bipolar LSI Technique".

Implementation of a I2L gate. Note that it has a single input and multiple outputs. Icc is the injected current. From "Integrated Injection Logic: A Bipolar LSI Technique".

Since the circuit above has a single input, it may seem to be just an inverter. But by wiring several signals together at the input, you get an AND gate "for free": if any signal is low, it will pull the wire low, and otherwise the signal is high. This is called "wired-AND". The wired-AND input to the I2L inverter results in a NAND gate.

One problem arises with wired-AND: if you connect an output to more than one wired-AND, everything gets shorted together. The solution is to have multiple outputs from the inverter. Thus, each I2L NAND gate has a single input and multiple identical outputs. The outputs from various gates (A and B below) are connected together and fed to the input of a I2L gate, creating a NAND gate.

Diagram of a NAND gate implemented in Integrated Injection Logic (I2L). From "Integrated Injection Logic: A Bipolar LSI Technique".

Diagram of a NAND gate implemented in Integrated Injection Logic (I2L). From "Integrated Injection Logic: A Bipolar LSI Technique".

Compared to TTL, I2L is also constructed "backwards". The transistors in I2L have multiple collectors, while the transistors in TTL have multiple emitters.3 It may seem strange to think of transistors with multiple collectors, but the diagram below shows how they are constructed. Each collector has an N region (brown) with a P region (green) below for the base, and another N region at the bottom, forming an NPN transistor. The multiple collector is built by creating multiple N regions. Note that the transistor's emitter is the grounded substrate. Also note that the injector PNP transistor is just a P region, reusing the emitter and base's N and P regions; this make the injector more compact than a "full" transistor.

Die photo and cross section diagram of an I2L gate in the 76477 sound effects chip. The transistor base, collectors, and emitters are labeled along with the current injection.

Die photo and cross section diagram of an I2L gate in the 76477 sound effects chip. The transistor base, collectors, and emitters are labeled along with the current injection.

Noise generator

The 76477 generates sounds such as a gunshot, explosion, steam train, or drum by producing a burst of white noise, a hissing staticky sound. Although white noise is relatively difficult to generate with an analog circuit, it can be simulated by a digital shift register circuit.5 Linear feedback shift registers are a well-known technique, generating each new bit by combining some of the existing bits with an exclusive-or operation. With a careful design, a LFSR with an n-bit shift register can output 2^n-1 pseudorandom bits before repeating. The 76477, however, uses a nonlinear feedback shift register, a less-known technique that uses a different function to generate new bits.

The output from the noise shift register is a pseudorandom sequence of 0's and 1's, output at the shift register's clock frequency. The oscilloscope trace below shows the output sequence.

Random noise output form 76477 sound chip.

Random noise output form 76477 sound chip.

The shift register consists of 32 stages, each built from a two-latch flip flop.8 Looking at the die, we can see the shift register and determine the function that generates new bits. Bits move through the shift register as indicated by the white arrow. The feedback logic generates each new input bit from the current bits.7 The output is a pseudo-random bit that repeats after 56883 cycles.6

Die photo of the shift register white noise generator in the 76477 sound effects chip.

Die photo of the shift register white noise generator in the 76477 sound effects chip.

The noise circuitry is driven by a clock signal, either external or internal. The internal clock is produced by a ring oscillator consisting of 15 inverters wired in a loop. Because the number of inverters is odd, the input signal will be inverted after it goes through the loop and reaches the input. Thus, the circuit will continuously oscillate.

The noise shift register is driven by a clock generated from fifteen inverters forming a ring oscillator.

The noise shift register is driven by a clock generated from fifteen inverters forming a ring oscillator.

The die photo above shows the structure of the ring oscillator. Each inverter is connected to the next, as indicated by the white arrow. The last inverter is connected to the first through the "ring feedback" wire. The output from the ring oscillator is a bit unusual, due to the single-input multi-output structure of I2L. The output is tapped from the third inverter, which has a second output. It is connected to a 4-output inverter, which drives the four output inverters in parallel. This produces an output with four times the regular current, sufficient to drive the shift register.

The noise filter

The sound of the noise can be tuned for different effects such as a high-pitched gunshot or a lower-pitched steam engine. This filtering is done by the noise filter, an adjustable low-pass filter that processes the pseudo-random white noise produced by the shift register. While this circuit is analog, I'll explain it here since it's part of the noise circuitry. The filter is basically an integrator, controlled by an external resistor and capacitor. In other words, the filter converts the sharp pulses from the shift register into ramps up or down, for inputs of 1 or 0 respectively, yielding the waveform below. (A slower ramp up and down yields an output signal that changes more slowly and is biased towards low frequencies, filtering out more high frequencies.) This signal is then converted back into a digital signal, which is used as the noise signal by the rest of the chip.

Random noise output form 76477 sound chip after filtering and before conversion back to digital pulses.

Random noise output form 76477 sound chip after filtering and before conversion back to digital pulses.

On the die, the noise filter is made of NPN and PNP transistors, along with some resistors (long red squiggles). You can see that analog circuits are not as dense as the I2L transistors. The black blobs are where bond wires connect the die to the IC pins.

The noise filter circuitry is next to the shift register on the die. It is an analog circuit, built from NPN and PNP transistors.

The noise filter circuitry is next to the shift register on the die. It is an analog circuit, built from NPN and PNP transistors.

While the noise filter is controlled by an external resistor and capacitor, it is built very differently from a typical R-C filter. The schematic (below) shows that the noise filter is built largely from current mirrors. (A current mirror is a controllable current sources built from a few transistors, shown as an arrow in a circle on the schematic.) The external resistor sets the current through the current mirror reference. Under the control of the shift register output, the double current mirror (two arrows) will either sink twice the resistor current or nothing. Summing the two currents yields either a charging current or a discharging current for the capacitor, equal to the resistor current. The result is the external capacitor will be charged or discharged with a steady current, with the rate controlled by the external resistor. Finally, the capacitor voltage is converted to a clean digital output by a Schmitt trigger, and fed to the mixer. The chip uses this current mirror "trick" in multiple places and I've written about it here if the description here is too brief.

Schematic of the noise filter. This low-pass filter integrates the input signal, using multiple current mirrors (arrow symbol). The frequency response is controlled by the external resistor and capacitor.

Schematic of the noise filter. This low-pass filter integrates the input signal, using multiple current mirrors (arrow symbol). The frequency response is controlled by the external resistor and capacitor.

Miscellaneous logic

There's a block of I2L circuitry that implements miscellaneous digital logic to control the chip. It implements the envelope select logic that generates the attack and decay signals that shape the output sound.11 This logic block also processes the inhibit and reset inputs, used to produce bursts of sound. I won't go into the details of this logic; it's basically NAND gates, inverter buffers, and a T flip flop built from NAND gates. The die photo below shows the efficient, dense packing of I2L logic; each column contains two gates.

Miscellaneous logic circuits in the 76477 sound effects chip.

Miscellaneous logic circuits in the 76477 sound effects chip.

The mixer

The 76477 includes a mixer that allows any combination of the three sound sources (Voltage-Controlled Oscillator, Super-Low Frequency oscillator, and noise) to be combined to form the output. The mixer isn't an analog mixer, but just ANDs together the digital inputs. Unfortunately this makes the mixer less useful since inputs aren't combined as audio sounds, but essentially gate each other.

The three mixer input pins select which sound sources are combined to form the output.

The three mixer input pins select which sound sources are combined to form the output.

The mixer is implemented by an 8-input multiplexer, consisting of eight NAND gates feeding an output NAND gate (below).13 Each gate is enabled by one of the eight mixer select combinations (above), and passes the corresponding sound signals. Finally the 8-input NAND gate merges the eight branches to produce the output.12 For instance if A, B and C are low (selecting VCO), the top NAND gate is active, passing the VCO signal to the output. If A is high, B is low and C is high, the SLF, VCO and Noise signals are ANDed and passed to the output. If A, B and C are all high (Inhibit), the bottom gate pulls the output high. The logic is essentially a direct implementation of the table above.

Schematic of the mixer, showing three of the eight multiplexer gates.

Schematic of the mixer, showing three of the eight multiplexer gates.

The die photo below shows the mixer. The black line indicates the middle NAND gate, which on the die has a single input and a single output. The key point is that with I2L, a 6-input NAND gate is implemented by wiring together the 6 desired signals to a single gate input. This wire is indicated in blue with the 6 desired input signals marked with blue circles. Likewise, the eight NAND gate outputs (red circles) are wired together (red line) for the output. Interpreting an I2L circuit is confusing because of the conceptual reversal of inputs and outputs.

Die photo showing the digital mixer in the 76477 sound effects chip.

Die photo showing the digital mixer in the 76477 sound effects chip.

Conclusions

In the late 1970s, I2L was heralded as the technology of the future, combining the speed of TTL integrated circuits with the density of MOS.9 I2L reached its peak with the production of 16-bit microprocessors by Fairchild and Texas Instruments in the 1970s and 1980s.14 Fairchild's I2L Microflame processors lived up to their name, running so hot that some chips were packaged on beryllium oxide, a toxic ceramic that conducts heat better than most metals. Unfortunately for I2L, CMOS turned out to be the winning technology. CMOS's extremely low power consumption and scalability allowed CMOS chips to hold exponentially more circuitry (as described by Moore's Law) making modern microprocessors possible. Thus, the processor you're using now is built from CMOS and I2L is a historical footnote.

The 76477 sound chip is an unusual combination of analog and digital circuitry, using I2L for the digital logic. Since the sound chip was primarily controlled by resistors, capacitors and voltages, it was difficult to control with a microprocessor. As a result, the 76477 sound chip was soon overshadowed by digital sound chips, such as the AY-3-8910 and the 76489 that could easily be interfaced to a microprocessor.15 Nevertheless, the 76477 chip was popular with hobbyists as it was easy to experiment with and readily available at Radio Shack. Although long obsolete, the 76477 still lives on in the occasional retro project and (inexplicably) an iPhone app.

The 76477 sound chip was popular with hobbyists and sold at Radio Shack. Photo courtesy of Bill Lewis.

The 76477 sound chip was popular with hobbyists and sold at Radio Shack. Photo courtesy of Bill Lewis.

I announce my latest blog posts on Twitter, so follow me at kenshirriff. I also have an RSS feed. Thanks to Sean Riddle for the die photos.

Notes and references

  1. The Space Invaders schematics show that the video game used seven different circuits to create its different sounds. The 76477 generated the "UFO" sound, while other sounds (saucer hit, explosion, missile, invader hit, etc.) were mostly generated by collections of op amps. 

  2. In the I2L schematic as shown, the output will be floating when not pulled to ground. But in use, the output will be connected to another gate, and its injector current will pull the output high. 

  3. If you're familiar with TTL circuitry, you may know that it uses transistors with multiple emitters. The multiple-collector transistors used by I2L are constructed in essentially the same way on die, except the role of the emitter and the collector are swapped. 

  4. When looking at the 76477 die, a collector and a base are almost identical, which I didn't expect. Fortunately, there is a subtle difference that lets you distinguish them: both have a circle inside a square, but for some reason the base's circle touches the square while the collector's circle is centered. 

  5. Because the output of the shift register is pulses at a fixed clock frequency, the noise isn't "genuine" white noise, which has a flat frequency spectrum and is more random. However, the shift register output is a reasonable approximation below the clock frequency. Some discussion is here

  6. The pseudo-random noise output from the shift register repeats after 56883 cycles. This is much worse than you could get from a 32-bit LFSR (a cycle of length 2^32-1), so they could have used a much smaller shift register. Perhaps the nonlinear terms help initialize the shift register; a linear-feedback shift register can get stuck in the all-zeros state. 

  7. The nonlinear feedback function is NOT ((r2 XOR r30) OR (r2 AND r26 AND r27 AND r28 AND r29 AND r30)). I verified that the chip's output matches this formula. Interestingly, the Mame emulator's code for the 76477 uses a similar, but not identical noise algorithm. 

  8. One puzzling feature of the shift register is that there is no wiring between the stages! How do bits get from one stage to the next? Did the chip have another layer of wiring that wasn't in the photos? Was there some sort of hidden connection? Eventually I noticed that there wasn't an isolation ring between the stages—a silicon barrier that separated most I2L circuits. Without this isolation ring, an "invisible" PNP transistor exists between the stages, apparently allowing one stage to flip the next stage to the right value. Each shift-register stage is constructed from two NAND-gate latches. When the clock is low, the first latch is forced into an indeterminate state. When the clock goes high, the latch ends up as 0 or 1 based on the bias it receives from the previous stage through the "invisible" PNP transistors. Thus, the latch becomes edge-sensitive since it will change right on the clock's rising edge. I found a paper ("Injection-Coupled Synchronous Logic", 1978) that describes a similar technique for an I2L shift register9, so I think this is the right explanation, even though the circuit seems a bit sketchy. 

  9. See the 1976 article "Integrated Injection Logic: A Bipolar LSI Technique"for an overview of I2L (pdf). The most thorough reference I've found on I2L is the book Integrated Injection Logic (1980), a collection of dozens of articles on I2L.  

  10. The die has a multi-transistor circuit connecting the env1 pin to the shift register. It looks like pulling the env1 input above 5 volts should reset the shift register. Perhaps this is an undocumented feature for testing. However, I couldn't make this work on an actual chip, so it's a bit mysterious. 

  11. Strangely, the envelope control logic has separate logic to generate the attack and decay signals, even though they are simply complements of each other. I wonder if the designers originally planned a more complex envelope, perhaps attack, sustain, and decay. 

  12. The mixer implementation can be viewed as AND gates feeding an OR gate (rather than NAND gates feeding a NAND gate). This follows from De Morgan's Law

  13. The mixer implementation is more complex than it needed to be for no apparent reason. An easier approach would be to have each mixer select input control one of the three signals with a simple NAND gate. Instead the mixer options are ordered apparently randomly, requiring the complex multiplexer. 

  14. I'll give a brief summary of I2L microprocessors. Fairchild's Microflame architecture series started with the 16-bit F9440 processor in 1977. (The Microflame processors used Fairchild's "Isoplanar Integrated Injection Logic" technology (I3L); presumably this was one better than regular I2L.) The Fairchild F9445 microprocessor (1979) implemented the architecture of the Data General Nova minicomputer. In 1980 the US Air Force came up with a standard 16-bit processor architecture called MIL-STD-1750A and multiple companies built microprocessors meeting this standard. Fairchild's I2L entry in this market was the F9450. Meanwhile, Texas Instruments produced the 4-bit SBP0400 bit-slice processor (1975) with 1660 gates. This was followed by I2L processors in its 16-bit 9900 family: the SBP9900 with 6034 gates (1979) and improved SBP9989 with 5000 gates (1981). I2L processors were used in the niche market of radiation-resistant processors for space applications. For example, the TI SBR9000 (1985), a successor to the 9900. One radiation-resistant I2L microprocessor was the TI SBR9000 

  15. The 76477 has a few design decisions that don't make sense to me. One is the complex, suboptimal shift register configuration for the noise generator. Another is the digital mixer configuration that makes the mixer circuit unnecessarily complex. Finally, the envelope control logic seems like it was designed for more complex envelopes than than the chip actually implemented. I don't like to criticize chip designs, figuring the designers must have known what they were doing, but I have to wonder about the 76477. 

Using an FPGA to generate raw VGA video:FizzBuzz with animation

This blog post shows how you can generate a video signal with an FPGA, using the FizzBuzz problem as an example. Creating video with an FPGA was easier than I expected, simpler than my previous serial-line FizzBuzz on an FPGA. I got a bit carried away with the project and added animation, rainbow text and giant bouncing words to the display.

FizzBuzz from an FPGA board. The board generates raw VGA video output with the results animated, along with the words "Fizz" and "Buzz" that bounce around the screen.

FizzBuzz from an FPGA board. The board generates raw VGA video output with the results animated, along with the words "Fizz" and "Buzz" that bounce around the screen.

If you're not familiar with the "FizzBuzz test", the problem is to write a program that prints the numbers from 1 to 100, except multiples of 3 are replaced with the word "Fizz", multiples of 5 with "Buzz" and multiples of both with "FizzBuzz". Since FizzBuzz can be implemented in a few lines of code, it can be used as an interview question to weed out people who can't program at all. But it's much more of a challenge on an FPGA.

An FPGA (Field-Programmable Gate Array) is an interesting chip that you can program to implement arbitrary digital logic. This lets you build a complex digital circuit without wiring up individual gates and flip flops. It's like having a custom chip that can be anything from a logic analyzer to a microprocessor to a video generator. For this project, I used the Mojo FPGA board (below).

The Mojo FPGA board. The Spartan-6 FPGA chip dominates the board.

The Mojo FPGA board. The Spartan-6 FPGA chip dominates the board.

Generating the VGA signals

There's a learning curve to an FPGA, since you're designing circuits, not writing software that runs on a processor. But if you can blink five LEDs with an FPGA, you're most of the way to creating a VGA video signal. The VGA video format is a lot simpler than I expected: just three signals for the pixels (red, green and blue), and two signals for horizontal sync and vertical sync.

The basic idea is to use two counters: one to count pixels horizontally and one to count lines vertically. At each spot on the screen, the desired pixel color is generated from these coordinates. In addition, the horizontal and vertical sync signals are produced when the counters are at the right positions. I used the basic 640x480 VGA screen resolution2 which requires counting to 800 and 525.111 Horizontally, there are 800 pixels for each line: 640 visible image pixels, followed by 16 blank pixels, 96 pixels of horizontal sync and 48 more blank pixels. (There are historical reasons for these strange numbers.) Meanwhile, the vertical counter must count out 525 lines: 480 image lines, 10 blank lines, 2 lines of vertical sync and 33 more blank lines.

Putting this all together, I created a vga module (source) to generate the VGA signals. This code is in Verilog (a standard language for FPGAs); I won't explain Verilog thoroughly, but hopefully enough to show how it works. The code below implements the x and y counters. The first line indicates action is taken on the positive edge of each (50 MHz) clock signal. The next line toggles clk25 each clock, creating the 25 MHz signal we'll use for the pixel clock. (One confusing thing is that <= indicates assignment, not comparison.) The code increments the x counter from 0 to 799. At the end of each line, y is incremented, running from 0 to 524. Thus, this code generates the necessary pixel and line counters.

always @(posedge clk) begin
  clk25 <= ~clk25;
  if (clk25 == 1) begin
    if (x < 799) begin
      x <= x + 1;
    end else begin
      x <= 0;
      if (y < 524) begin
    y <= y + 1;
      end else begin
    y <= 0;
      end
    end
  end
end

While Verilog code looks like a standard programming language, its effects are very different. This code doesn't generate instructions that are executed sequentially by a processor, but instead causes circuitry to be instantiated in the FPGA chip. It creates registers from flip flops for clk25, x and y. Binary adders are generated to increment x and y. The if statements turn into logic gate comparators controlling the registers. All this circuitry runs in parallel, triggered by the 50 MHz clock. To understand FPGAs, you need to get out of the sequential program mindset and think of the underlying circuits.

Getting back to the vga module, the horizontal and vertical sync signals are generated from the x and y counters by the code below. In addition, a valid flag indicates the 640x480 region where a video signal should be generated; the screen must be blank outside this region. As before, these Verilog statements are generating logic gates to test the conditions, not creating code.

assign hsync = x < (640 + 16) || x >= (640 + 16 + 96);
assign vsync = y < (480 + 10) || y >= (480 + 10 + 2);
assign valid = (x < 640) && (y < 480);

The "useful" part of the VGA signal is the red, green, and blue pixel signals that control what appears on the screen. To test everything out, I wrote a few lines to turn r, g and b on for various regions of the screen, blanking them all outside the visible (`valid`) area.3 (The question mark is the ternary conditional operator, as in Java)

if (valid) begin
  rval = (x < 120 || x > 320) ? 1 : 0;
  gval = (y < 240 || y > 360) ? 1 : 0;
  bval = (x > 500 && (y < 120 || y > 300)) ? 1 : 0;
end else begin
  rval = 0;
  gval = 0;
  bval = 0;
end

I ran the code on my FPGA board and nothing happened—the monitor stayed black and I wondered what went wrong. Fortunately, after a couple seconds4 the monitor completed its normal startup cycle and displayed the output. I was pleasantly surprised that VGA output worked the first time, even if the output was just arbitrary blocks of color.5

My first VGA program produced random color blocks on the screen. Not very meaningful, but it showed that everything worked.

My first VGA program produced random color blocks on the screen. Not very meaningful, but it showed that everything worked.

Putting characters on the screen

The next step was to display text characters on the screen. I implemented a character generation module to provides the pixels for an 8x8 character. Rather than include the full ASCII character set, I only used the characters necessary for FizzBuzz: 0 through 9, "B", "F", "i", "u", "z" and blank. Conveniently, this worked out to 16 character values, fitting into a 4-bit input.

Thus, the module takes a 4-bit character code and a 3-bit row number (for the 8 lines of each character) and outputs 8 pixels for that row of the character. The code (excerpt below, full code here) is simply a big case statement to output the appropriate bits.6 This code essentially compiles into a ROM, which is implemented in the FPGA by lookup tables.

case ({char, rownum})
  7'b0000000: pixels = 8'b01111100; //  XXXXX  
  7'b0000001: pixels = 8'b11000110; // XX   XX 
  7'b0000010: pixels = 8'b11001110; // XX  XXX 
  7'b0000011: pixels = 8'b11011110; // XX XXXX 
  7'b0000100: pixels = 8'b11110110; // XXXX XX 
  7'b0000101: pixels = 8'b11100110; // XXX  XX 
  7'b0000110: pixels = 8'b01111100; //  XXXXX  
  7'b0000111: pixels = 8'b00000000; //

  7'b0001000: pixels = 8'b00110000; //   XX    
  7'b0001001: pixels = 8'b01110000; //  XXX    
  7'b0001010: pixels = 8'b00110000; //   XX    
  7'b0001011: pixels = 8'b00110000; //   XX    
  7'b0001100: pixels = 8'b00110000; //   XX    
  7'b0001101: pixels = 8'b00110000; //   XX    
  7'b0001110: pixels = 8'b11111100; // XXXXXX  
...

I updated the top-level program to use the low bits of the X pixel position for the character and pixel index, and the low bits of the Y pixel position for the row index. The results can be seen below; I've magnified the text in the red box so you can see the characters.

My first implementation of text. (Zoomed region in red.) Due to a bug, all the characters are backwards.

My first implementation of text. (Zoomed region in red.) Due to a bug, all the characters are backwards.

Oops, I implemented the character generator with bit 7 on the left, while the pixel index values have bit 7 on the right, so the characters were displayed backwards. But a quick fix got the characters to display correctly.

Generating a line of FizzBuzz

Once I could display characters, I needed to provide the right characters for the FizzBuzz output. The algorithm is the same as my previous FizzBuzz program, so I'll just give the highlights here.

Converting the numbers from 1 to 100 into characters is trivial on a microprocessor, but more difficult with digital logic since there's no built-in divide operation; dividing by 10 and 100 requires many logic gates. My solution was to use a binary-coded decimal (BCD) counter, using a separate 4-bit counter for each digit.

The next challenge was testing if the number was divisible by 3 or 5. As with division, the modulo operation is easy on a microprocessor, but hard with digital logic. Instead of computing modulo values, I used counters for the value modulo 3 and the value modulo 5. The value modulo 3, for instance, simply counts 0, 1, 2, 0, 1, 2, ... (See the footnote for other approaches.7)

A FizzBuzz output line can be up to 8 characters long. The `fizzbuzz` module (source) outputs the appropriate eight 4-bit characters as a 32-bit variable line. (The normal way to generate video would be to store all the screen's characters or pixels into video RAM, but I decided to generate everything dynamically.) An if statement (excerpt below) updates the bits of line to return "Fizz", "Buzz", "FizzBuzz" or the number as appropriate.

if (mod3 == 0 && mod5 != 0) begin
  // Fizz
  line[3:0] <= CHAR_F;
  line[7:4] <= CHAR_I;
  line[11:8] <= CHAR_Z;
  line[15:12] <= CHAR_Z;
end else if (mod3 != 0 && mod5 == 0) begin
  // Buzz
  line[3:0] <= CHAR_B;
  line[7:4] <= CHAR_U;
  line[11:8] <= CHAR_Z;
  line[15:12] <= CHAR_Z;
...

The FizzBuzz module needed a signal to increment the counts to the next number so I modified the vga module to indicate the start of a new line and used this to move to the next number. When I tried my code, I got very strange output with alien symbols; the photo below shows a detail.

Changing the character every row yields mysterious symbols, not the desired characters.

Changing the character every row yields mysterious symbols, not the desired characters.

The bug was that I was moving to the next FizzBuzz value after every line of pixels instead of waiting 8 lines to draw a full character. Thus, each displayed character consisted of slices from 8 different characters. Incrementing the FizzBuzz counter every 8 lines instead of every line fixed this problem, as you can see below. (Debugging VGA code is much easier than other FPGA things, since problems are visible on the screen. You don't need to poke around with an oscilloscope trying to figure out what went wrong.)

The FizzBuzz output, as displayed on a VGA monitor.

The FizzBuzz output, as displayed on a VGA monitor.

At this point I had the FizzBuzz output appearing on the screen, but the static display was kind of boring. Changing the foreground and background color of each row was easy—just using some bits of the Y value for the red, green and blue colors. This yielded colored text with a 1980s PC aesthetic.

With the foreground and background colors based on the line, the text is more interesting.

With the foreground and background colors based on the line, the text is more interesting.

Next, I added some basic animation. The first step was to add an output from the vga module to indicate when the screen was redrawn, a new field every 60th of a second. I used this to change the colors dynamically. (Tip: don't flash the screen color every 60th of a second unless you want a headache; use a counter and change it less often.)

Trying out different graphical effects is fun and addictive, since you get immediate feedback. I decided to have the "Fizz" and "Buzz" lines slide across the screen with a rainbow color trail (inspired by Nyan Cat). To do this, I changed the character's start position based on a counter. For the rainbow color effect, I selected the color based on the row number (so each row of the character could have a different color) and added the rainbow trail.

A closeup of the final output.

A closeup of the final output.

The final effect I added was giant words "Fizz" and "Buzz" that bounced around the screen. The bouncing effect was based on a bouncing invisible box (inspired by FPGA Pong) that held the word.8 A variable dx keeps track of the X direction and dy keeps track of the Y direction. On each new screen (i.e. 60 times a second), the box's X and Y coordinates are incremented or decremented based on the direction variables. If the box reaches the right or left edges, dx is toggled. Similarly, dy is toggled if the box reaches the top or bottom. The big text is then drawn inside the box using another instantiation of the character generator described earlier. The word is enlarged by a factor of 8 by dropping the three low-order bits from the coordinate. You're probably tired of Verilog code by now so I won't show the code here, but it's on github. The final result is shown in the video clip below.

Hardware details

For this project, I used the Mojo V3 FPGA board development board (below), which was designed to be an easy-to-use starter board. It uses an FPGA chip from Xilinx's Spartan 6 family. Although the Mojo uses one of the smallest Spartan 6 chips, the chip still contains over 9000 logic cells and 11,000 flip flops, so it can do a lot. (For other options, see this list of cheap FPGA boards.)

Interfacing the FPGA board to VGA is almost trivial: just three 270&ohm; resistors. The perf board is just to attach the cable to a header.

Interfacing the FPGA board to VGA is almost trivial: just three 270Ω resistors. The perf board is just to attach the cable to a header.

If you want to use VGA, it's easier to use a development board that includes a VGA connector. But if your board lacks a connector (like the Mojo), adding VGA is straightforward. Simply put 270Ω resistors between the FPGA's red, green and blue output pins and the VGA connection. The horizontal and vertical sync can be wired directly from the FPGA.9

I used Xilinx's development environment (called ISE) to write and synthesize the Verilog code. (For details on writing code and getting it onto the FPGA board, see my previous FPGA article.) To specify which physical FPGA pins to use, I added lines to the mojo.ucf configuration file. This maps the red output pin_r to pin 50 on the board, and so forth.

NET "pin_r" LOC = P50 | IOSTANDARD = LVTTL;
NET "pin_g" LOC = P40 | IOSTANDARD = LVTTL;
NET "pin_b" LOC = P34 | IOSTANDARD = LVTTL;
NET "pin_hsync" LOC = P32 | IOSTANDARD = LVTTL;
NET "pin_vsync" LOC = P29 | IOSTANDARD = LVTTL;

Driving VGA from an FPGA is common, so you can find lots of similar projects on the web such as FPGA Pong, 24-bit color using a DAC chip, the Basys 3 board and displaying an image. If you want a schematic, see this page. The book Programming FPGAs has a whole chapter on VGA.

Understanding the VGA format

The VGA video format may seem a bit strange, but looking at the history of television and the CRT (cathode ray tube) provides context. In a CRT, a beam of electrons scans across the screen, lighting up the phosphor coating to produce an image. Scanning happens in a raster pattern: the beam scans across the screen left-to-right, and then a horizontal sync pulse causes the beam to rapidly return to the left during the horizontal retrace. The process repeats line-by-line until the bottom of the screen, when a vertical sync pulse triggers vertical retrace and the beam returns to the top. During the horizontal and vertical retrace, the beam is blanked so retrace doesn't draw lines on the screen. The diagram below illustrates the raster scan pattern.

Raster scan pattern for a CRT. (Wikipedia)

Raster scan pattern for a CRT. (Wikipedia)

These characteristics carried over to VGA, resulting in the horizontal and vertical sync pulses, and the long intervals during which the video must be blanked.

In 1953, the NTSC standard for color television was introduced.10 VGA, however, is much simpler than NTSC since it uses five wires for the sync and color signals, instead of cramming everything into a single signal with a complex encoding. One strange feature of NTSC that carries over to VGA is the screen refresh rate isn't the claimed 60 Hertz, but actually 59.94 Hertz.11

The oscilloscope trace below shows the VGA video signals across two lines. The horizontal sync pulses (yellow) indicate the start of each line. The brief red, green and blue pulses near the start of the line are white pixels from a FizzBuzz number. The red signal near the middle of the line is the floating red word "Buzz".

Oscilloscope trace of VGA signals, showing red, green, blue and horizontal sync.

Oscilloscope trace of VGA signals, showing red, green, blue and horizontal sync.

Conclusion

Driving a VGA monitor from an FPGA was much easier than I expected, but I certainly wouldn't get hired if I encountered FizzBuzz as an FPGA interview question! If you're interested in FPGAs, I highly recommend playing around with video output. It's not much harder than blinking an LED and much more rewarding. Creating video output is also much more fun than debugging with an oscilloscope—you get immediate visual feedback and even if things go wrong, they are often entertaining.

Follow me on Twitter or RSS to find out about my latest blog posts. My FPGA code is on github.

Notes and references

  1. The VGA signal is supposed to have a 25.175 MHz pixel clock. Instead, I divided the Mojo board's 50 MHz clock by 2 to get a 25 MHz clock. The VGA standard says that the pixel clock must be 25.175 MHz ± 0.5%. A clock rate of 25 MHz is off by 0.7% so technically is a bit off from the spec. However, monitors are designed to handle signals generated by cheap graphics cards, so they will usually manage to display whatever you throw at them. 

  2. The detailed timings for dozens of standard video resolutions can be found here. Page 17 has the 640x480 60 Hz resolution I used. 

  3. When I forgot to blank the pixels outside the valid screen area, the monitor still managed to display an image, but it was very dim because the monitor got confused about what voltage represented "dark". Just a tip in case you find your display mysteriously darkened. 

  4. It's kind of amazing if you think about what an LCD monitor needs to do in order to display a VGA image designed for a CRT. The monitor has to examine the incoming signals to "guess" what video resolution a computer is sending to it. Then it needs to resample the video signal to match the resolution of the LCD panel. Finally, it sends the new pixel data to the LCD. 

  5. For some reason, the output was shifted down about 25 pixels. It worked fine on a different monitor, so I'm not sure if was just something with my monitor's alignment or if I had a bug. I could adjust this by making the vertical sync pulse 25 rows later. 

  6. It would be tedious to type all the code for character generation, so I wrote a Python program to create the Verilog code from a font file. The original font is here

  7. After my last FPGA FizzBuzz, people suggested some other approaches so I tried them out on the VGA project. With modulo counters (my original approach), the entire project used 276 LUTs (lookup tables) and 277 flip flops. One suggestion was to use ring counters (i.e. one shifted bit) instead of binary counters for the modulo counters. This used 277 LUTs and 281 flip flops, so slightly worse. Another suggestion was to add the digits and take the modulo 3 value. The logic to add and perform modulo brought the count to 305 LUTs, much worse. Adding the modulo 3 values (instead of digits) brought it down to 289 LUTs, still worse. 

  8. The big floating words are essentially sprites—bitmaps that can be arbitrarily positioned on the screen. Sprites were popular with video games and computers in the 1970s and early 1980s such as Pacman, Atari and Commodore 64. Sprites let slow processors perform animation; instead of moving all the pixels around in memory, the processor would just update the sprite coordinates. The top-level code (source) ties together all the pieces and combines everything onto the screen: the text, the rainbow trail, the giant "Fizz" (in a rainbow pattern) and the giant "Buzz" (in red). 

  9. To connect to the VGA monitor, I cut a VGA cable in half and connected its wires to the FPGA board, reusing the cable from my project to read monitor data using I2C. Don't take this approach—the tiny wires inside are difficult to solder and break easily. Instead, just use a VGA connector. The interface to VGA is simply three 270Ω resistors, to get the right voltages for the red, green and blue signals. You can use more resistors to get more colors, essentially building 2-bit or more A/D converters. 

  10. NTSC was a remarkably clever way to introduce color, while remaining compatible with black-and-white televisions. However, it is very hard to understand. I was scared off from video by the talk of encoding color with phase and quadrature and a color subcarrier synced to a color burst signal. Because NTSC combines the color and sync signals into a single broadcast signal, the encoding is much more complex than VGA. 

  11. While monitors say they have a 60 Hz refresh rate, the actual refresh rate is 59.94 Hz, a strange frequency dating back to the origins of color television. A screen refresh frequency of 60 Hz was desirable to cancel out power line interference. However, to prevent the color carrier frequency from interfering with the audio frequency, various frequencies had to be adjusted, resulting in the screen refresh frequency of 59.94 Hertz. It almost seems like numerology, but the frequency choices are explained in detail in this video and in Wikipedia

A 1970s disk drive that wouldn't seek: getting our Xerox Alto running again

Our vintage Xerox Alto has been running reliably for months, but a couple weeks ago the disk drive malfunctioned and the heads stopped moving. With a drive that wouldn't seek, our Alto wouldn't work.3 After extensive debugging and studying the drive's complex head movement control system, we discovered that the problem had a trivial fix. This blog post discusses our adventures debugging the Alto's Diablo hard drive and how we got it to work again.

The Alto was a revolutionary computer designed at Xerox PARC in 1973 to investigate personal computing. It introduced the GUI, high-resolution bitmapped displays, Ethernet, the optical mouse and laser printers to the world. The Alto I've been restoring came from YCombinator; the restoration team includes Marc Verdiell (curiousmarc on YouTube), Carl Claunch and Luca Severini.

The Xerox Alto's disk drive is the unit below the keyboard. The cabinet under the drive holds the computer itself.

The Xerox Alto's disk drive is the unit below the keyboard. The cabinet under the drive holds the computer itself.

For storage, the Alto used a removable 14" hard disk cartridge that held just 2.5 megabytes. (A user might have multiple cartridges for different purposes, similar to floppies a decade later.) This model 2315 cartridge was invented by IBM in 1964 and became an industry standard, used in minicomputers by HP, DEC, Wang and many other companies. The photo below shows how a disk cartridge is inserted into the Diablo drive. (The drive has been pulled out from the cabinet and its cover removed to show its internal mechanisms.)

A disk cartridge is inserted into the Alto's drive. The drive has been pulled out and the cover removed, revealing its internals.

A disk cartridge is inserted into the Alto's drive. The drive has been pulled out and the cover removed, revealing its internals.

Each disk cartridge contains a single platter. The drive has two heads, one for each side of the platter, and the heads seek (move back and forth) in unison. Each side of the disk contains 203 tracks at a density of 100 tracks per inch (.254mm spacing), so the heads need to be positioned with very high accuracy. The heads float 70 microinches (1.8 µm) above the disk surface on a cushion of air, so any contamination on the disk surface can cause a head crash, causing the head to contact the surface and scrape up the oxide layer.

Opening a disk cartridge reveals the single hard disk platter. The disk isn't scratched; it's just the lighting.

Opening a disk cartridge reveals the single hard disk platter. The disk isn't scratched; it's just the lighting.

Our disastrous adventure started when we tried to help out another Alto owner whose disk drive suffered a head crash.1 (Because of the problems this drive has caused, it will be called the cursed drive, although diabolical fits too.) Replacing the heads in the cursed drive should have taken an hour or so, but became much more complex. I'll describe the full saga of the cursed drive in another post, but to make a long story short we installed new heads that immediately crashed so badly that the head arms were physically bent. After installing another set of heads and fixing various other issues the cursed drive finally seemed to work, so we connected it to our Alto.2 Boot almost worked, except any disk in the cursed drive got hopelessly corrupted. To make things worse, our previously-working drive started seeking erratically and then stopped seeking entirely. We suspected an electrical problem with the cursed drive had damaged the Alto's interface board or the good drive's circuitry. This was rather distressing since now we couldn't use our Alto.3

At this point, I should explain a bit about the Diablo drive and the complex mechanism it uses for seeking. The seek circuitry has two purposes. First, when the Alto wants to read from a particular track, the drive must seek, moving the heads to the desired track as fast as possible. Then, the heads must be held perfectly steady over the track. (Keep in mind that a track is only 0.007 inches (.18mm) wide.) Instead of a stepper motor, the drive moves the heads with a DC motor controlled as a servo. To make seeks faster, the motor runs at four different speeds, accelerating quickly and then slowing as it approaches the desired track. Once the head reaches the desired track, the servo mechanism constantly adjusts the head positioner motor to keep the head centered over the track.

The Diablo drive's circuitry pulls up for repair. The drive has three circuit boards on the left and three on the right.

The Diablo drive's circuitry pulls up for repair. The drive has three circuit boards on the left and three on the right.

The seek logic is implemented by the three circuit boards on the right.4 These boards mostly use simple DTL (Diode Transistor Logic) gates, integrated circuits from the 1960s that predated TTL. The innermost board receives the desired track number from the Alto. The next board computes the difference between the drive's current track and the desired track and determines how fast to move the head. Finally, the rightmost board is the analog board that drives the head positioner motor as well as processing head position signals from the transducer. 5 In a modern system, the seek logic could be compactly implemented with a microcontroller. But in the 1970s, controlling the heads took three boards full of integrated circuits.

The photo below shows the disk heads and the head position transducer, a key component of the seek circuitry.6 The heads are in the foreground, two barely-visible white ceramic circles on flat metal arms. The head positioner motor (hidden underneath) moves the heads in and out to the appropriate track. The head position transducer, the green disk in the photo below, provides electronic feedback on the head position. The yellow pointer and the scale on the transducer show the track number visually.

The green head positioner transducer provides feedback to the head servo mechanism. The pointer and dial indicate what track the heads are on.

The green head positioner transducer provides feedback to the head servo mechanism. The pointer and dial indicate what track the heads are on.

The transducer generates two "quadrature" signals 90° apart, with one pulse per track.7 The disk drive counts these pulses to determine the current track number. By looking at the phase of the two signals, the drive can determine the direction of head movement.8 The video below shows the two transducer outputs displayed in X-Y mode on an oscilloscope. As the head is (manually) moved, the dot rotates 360° on the screen for each track. The direction of rotation indicates which way the head is moving. When the head is aligned over the track, the dot is at the top of the screen. Thus, the transducer outputs show the direction of head motion, the number of tracks moved, and alignment over the track.

Getting back to our disk drive that had problems seeking, we did some testing and determined that seeking had totally failed. The drive did not seek when requested by the Alto or Carl's FPGA-based disk controller. The drive didn't return the head to track 0 when the disk was unloaded. It didn't even hold the head in place over a track. This let us know that the problem was not with the signals from the Alto but something inside the drive.

We figured the complex seek control circuitry must have malfunctioned, so our strategy was to swap the three seek board with boards from a spare drive. Then we could replace boards individually until we found which board had the problem. Much to our surprise, the problem still remained even after we swapped the boards.

An oscilloscope trace shows signals in the malfunctioning disk drive. The motor control signal (yellow) causes the motor to be driven with +15V and -15V (pink), but nothing shows up in the current-sensing resistor (green). Xerox Alto oscilloscope-bad.jpg

An oscilloscope trace shows signals in the malfunctioning disk drive. The motor control signal (yellow) causes the motor to be driven with +15V and -15V (pink), but nothing shows up in the current-sensing resistor (green). Xerox Alto oscilloscope-bad.jpg

Next, we checked out the drive's seek signals with an oscilloscope (above). We found that the seek circuitry was generating a motor control signal (yellow) and the motor driver board was sending +15V or -15V to the head positioner motor (pink). Although these signals weren't really what we expected to see, with full voltage to the motor, the heads should have been moving back and forth rapidly instead of remaining stationary. Also, nothing was showing up across the current-sensing resistor (green).9

The head-seek motor is driven through a large current-sensing resistor (left). (A disk cable or terminator is attached to the connector on the right.)

The head-seek motor is driven through a large current-sensing resistor (left). (A disk cable or terminator is attached to the connector on the right.)

Although the seek circuitry was complex, the actual motor wiring was fairly simple. The motor received up to +/- 15V from a driver board, and was connected to ground through a large (10W) 0.2Ω current sensing resistor (above). A bypass capacitor across the motor (below) filtered out noise. We suspected a failure of the current-sensing resistor, the bypass capacitor, or the motor itself, so we tested these components. A multimeter verified the resistor hadn't burnt out. A LCR meter showed the capacitor had the right capacitance. We powered the motor directly from a power supply and the heads moved back and forth smoothly. This was a puzzle: all the components tested fine and we had measured voltage from the motor driver board, so why was nothing moving?

The head positioning motor moves the heads back and forth. Drive wires (yellow) are bolted to the motor. A bypass capacitor (black) is connected across the motor.

The head positioning motor moves the heads back and forth. Drive wires (yellow) are bolted to the motor. A bypass capacitor (black) is connected across the motor.

At this point, Carl noticed that one of the wires on the motor was loose. He tightened the nut and the seek problems were immediately solved. After all our investigation, the problem with our drive was simply a loose wire that prevented power from getting to the motor. Vibration must have slowly loosened the nut until the drive quit working. Apparently it was just coincidence that the problem happened when we had the cursed drive connected.

Conclusion

It was a bit anticlimactic to find a simple loose wire after all our investigation of the seek circuitry. But we were happy to have our drive back in operation, so we could use our Alto again. We still have to diagnose the problem with the cursed drive, but hopefully we're getting closer; I plan to write another blog post once we get that problem solved.

My full set of Alto posts is here. Follow me on Twitter or RSS to find out about my latest blog posts.

Notes and references

  1. Since the head flies at high speed above the disk surface, any particles on the disk can cause the head to crash into the disk surface, scratching the disk and clogging up the head with oxide. Usually the heads can be removed and cleaned. After reinstalling the heads, they need to be realigned with a special alignment pack so they are properly positioned over the tracks. 

  2. The disk drives have two connectors on the back, so multiple drives can be daisy-chained together. This lets you have a two-drive Alto configuration, for instance. A terminator is connected to the last disk in the chain. Thus, the Alto was connected to the working drive in the Alto cabinet, which was then connected to the cursed drive. 

  3. Our Alto wasn't totally dead without a disk drive, since we could boot over Ethernet using my Ethernet gateway. However, without a working disk drive the Alto was very limited. 

  4. The three boards on the left of the drive aren't relevant for this repair, but I'll describe them for completeness. The leftmost board (J10) has the analog read/write circuitry that drives the heads. (You can see a wire from the upper left corner of the board going to the heads.) The next board (J9) controls the spindle drive motor, lowers the heads onto the disk after loading, and detects sector marks. The inner board on the left (J8) counts the sectors on the disk. It also generates the 5V supply and has an oscillator to drive the head position transducer. 

  5. The Disk drive maintenance manual includes schematics and a detailed description of the drive's operation. 

  6. Modern disk drives position the heads based on a servo track written on the disk, a technology developed in 1971 that provided better positioning accuracy. The Diablo drive on the other hand, used older technology where position feedback was part of the drive. 

  7. The head position rotary transducer uses a special transformer to generate the position signals. A 50 kHz carrier signal is fed into the transducer. This signal is modulated based on the head position to yield two signals, the quadrature signals 90° out of phase. The transducer has two parts: a rotary member that receives the carrier signal, and a stationary member that provides the two output signals. I haven't disassembled the transducer, but based on similar rotary transducers, I believe the transducer is built from zig-zag windings etched into circular printed circuit boards in the transducer. The zig-zags are closely spaced around the transducer disk, with their spacing matching one track's rotation of the transducer disk. The two output windings have the same spacing, but are offset one quarter of a zig-zag, i.e. 90°. As the transducer rotates, the input winding will line up alternately in phase and opposite phase with the output winding, yielding a positive and then negative output, once per track. The other output winding behaves similarly, but 90° out of phase. 

  8. A mechanical mouse uses a similar quadrature technique to determine the direction of motion. A mechanical mouse typically uses optical encoders rather than the disk drive's transformer encoders. 

  9. We used an oscilloscope to examine the seek circuitry on a working drive, and found very complex, almost chaotic signals showing the constant adjustments of the servo circuitry to keep the head aligned.

    A working disk drive shows the complex signals in the servo mechanism. The input signal (blue) triggers variations in the motor control signal (yellow). The motor voltage (pink) is constantly adjusted so the motor current (green) tracks the control signal.

    A working disk drive shows the complex signals in the servo mechanism. The input signal (blue) triggers variations in the motor control signal (yellow). The motor voltage (pink) is constantly adjusted so the motor current (green) tracks the control signal.

    The signals from the transducer are processed, combined, and differentiated to generate spikes (blue) as the head moves. This input is filtered to form the motor control signal (yellow). The voltage driving the head motor (pink) is constantly adjusted so the velocity signal (green, from the current-sense resistor) is proportional to the control signal (yellow). 

Reading a VGA monitor's configuration data with I2C and a PocketBeagle

Have you ever wondered how your computer knows all the characteristics of your monitor— the supported resolutions, the model, and even the serial number? Most monitors use a system called DDC to communicate this information to the computer.1 This information is transmitted using the I2C communication protocol—a protocol also popular for connecting hobbyist devices. In this post, I look inside a VGA monitor cable, use a tiny PocketBeagle (a single-board computer in the BeagleBone family) to read the I2C data from an LCD monitor, and then analyze this data.

Inside a VGA cable. The cable is more complex than I expected, with multiple layers of shields. The green, red, white (sync) and blue wires are thicker and have their own shielding.

Inside a VGA cable. The cable is more complex than I expected, with multiple layers of shields. The green, red, white (sync) and blue wires are thicker and have their own shielding.

To connect to the monitor, I cut a VGA cable in half and figured out which wire goes to which pin.3 The wire (above) is constructed in an interesting way, more complicated than I expected. The red, green, blue and horizontal sync signals are transmitted over coaxial-like cables formed by wrapping a wire a spiral of thin copper wires for shielding.2 The remaining signals travel over thinner plain wires. Several strands of string form the structural center of the VGA cable, and the ten internal wires are wrapped in a foil shield and woven outer shield.

The VGA connector consists of 3 rows of 5 pins.
Pins are simply numbered left-to-right with 1 through 5 in the first row, 6-10 in the second, and 11-15 in the third.
(Click image for a closeup.)

The VGA connector consists of 3 rows of 5 pins. Pins are simply numbered left-to-right with 1 through 5 in the first row, 6-10 in the second, and 11-15 in the third. (Click image for a closeup.)

The photo above shows the male VGA connector on each end of the cable. The function assigned to each pin is shown in the table below. The I2C clock (SCL) and data (SDA) are the important pins for this project. The wire colors are not standardized; they refer to my VGA cable and may be different for a different cable.

PinFunctionWire color
1RedRed coax
2GreenGreen coax
3BlueBlue coax
4ReservedShield
5GroundBlack
6Red GroundShield
7Green GroundShield
8Blue GroundShield
95VYellow
10GroundWhite
11ReservedShield
12SDAGreen
13HSyncWhite coax
14VSyncBrown
15SCLRed

The 5 volt wire in the cable has a clever purpose. This wire allows the computer to power the EEPROM chip that provides the configuration data. Thus, the computer can query the display's characteristics even if the display is turned off or even unplugged from the wall.

Reading the configuration data

To read the data over I2C, I used the PocketBeagle, a tiny Linux computer that I had handy. (You could use a different system that supports I2C, such as the Raspberry Pi, Beaglebone or Arduino.) I simply connected the I2C clock (SCL), data (SDA) and ground wires from the VGA cable to the PocketBeagle's I2C pins as shown below.

Connecting a VGA cable to the PocketBeagle allows the configuration data to be read over I2C. The black wire is ground, the green wire is I2C data (SDA) and the red wire is I2C clock (SCL).

Connecting a VGA cable to the PocketBeagle allows the configuration data to be read over I2C. The black wire is ground, the green wire is I2C data (SDA) and the red wire is I2C clock (SCL).

Simple Linux commands let me access I2C. First, I probed the I2C bus to see what devices were present, using the i2cdetect command. (Many devices can be connected to an I2C bus, each assigned a different address.) The output below shows that devices 30, 37, 4a, 4b and 50 responded on I2C bus 1. Device 50 is the relevant I2C device, assigned to the configuration information. Device 37 is DDC/CI, allowing monitor settings to be controlled by the computer, but I'll ignore it for this post. Devices 30, 4a, and 4b are a mystery to me so leave a comment if you know what they are.

$ i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 -- -- -- -- -- -- 37 -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- 4a 4b -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Next, I used the i2cdump command to read 128 bytes from device 50's registers, providing the raw VGA information. The hex values are on the left and ASCII is on the right.

$ i2cdump -y -r 0-127 1 0x50 b
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 ff ff ff ff ff ff 00 04 69 fa 22 01 01 01 01    ........?i?"????
10: 12 19 01 03 1e 30 1b 78 ea 3d 25 a3 59 51 a0 25    ?????0?x?=%?YQ?%
20: 0f 50 54 bf ef 00 71 4f 81 80 81 40 95 00 a9 40    ?PT??.qO???@?.?@
30: b3 00 d1 c0 01 01 02 3a 80 18 71 38 2d 40 58 2c    ?.?????:??q8-@X,
40: 45 00 dd 0c 11 00 00 1e 00 00 00 fd 00 32 4c 1e    E.???..?...?.2L?
50: 53 11 00 0a 20 20 20 20 20 20 00 00 00 fc 00 56    S?.?      ...?.V
60: 45 32 32 38 0a 20 20 20 20 20 20 20 00 00 00 ff    E228?       ....
70: 00 46 34 4c 4d 51 53 31 32 38 35 34 36 0a 00 bb    .F4LMQS128546?.?

Understanding the monitor's EDID data

The configuration data is encoded in the EDID (Extended Display Identification Data) format, so it's not immediately obvious what the data means. But the format is well-documented, so it's not too hard to figure out. For instance, the first 8 bytes 00 ff ff ff ff ff ff 00 are the header. The next two bytes 04 69 encode three 5-bit characters for the manufacturer ID, in this case "ACI" - Asus Computer International. (The data format uses a lot of annoying bit manipulations like these to make the data compact.) Near the end of the output, the ASCII strings "VE228" and "F4LMQS128546" are clearly visible; these are the monitor's model number and serial number.

I made a simple Python program to decode the data, giving the following results:

Header:
  Manufacturer: ACI
  Product code: 8954
  Week: 18
  Year: 2008
  Edid version 1, revision 3
  Analog input
  Levels: +0.7/-.03
  Blank-to-black setup (pedestal) expected
  Separate sync supported
  Composite sync supported
  Sync on green supported
  Horizontal screen size: 48cm
  Vertical screen size: 27cm
  Display gamma: 2.200
  DPMS standby supported
  DPMS suspend supported
  DPMS active-off supported
  Display type (analog): RGB color
  Preferred timing mode in descriptor block 1
Chromaticity coordinates:  r: (0.637, 0.351), g: (0.319, 0.626), b: (0.145, 0.061), w: (0.313, 0.329)
Established timings:
  720x400 @ 70 Hz
  640x480 @ 60 Hz
  640x480 @ 67 Hz
  640x480 @ 72 Hz
  640x480 @ 75 Hz
  800x600 @ 56 Hz
  800x600 @ 60 Hz
  800x600 @ 72 Hz
  800x600 @ 75 Hz
  832x624 @ 75 Hz
  1024x768 @ 60 Hz
  1024x768 @ 72 Hz
  1024x768 @ 75 Hz
  1280x1024 @ 75 Hz
Standard timing information:
  X res: 1152, aspect 4:3, Y res (derived): 864), vertical frequency: 75
  X res: 1280, aspect 5:4, Y res (derived): 1024), vertical frequency: 60
  X res: 1280, aspect 4:3, Y res (derived): 960), vertical frequency: 60
  X res: 1440, aspect 16:10, Y res (derived): 900), vertical frequency: 60
  X res: 1600, aspect 4:3, Y res (derived): 1200), vertical frequency: 60
  X res: 1680, aspect 16:10, Y res (derived): 1050), vertical frequency: 60
  X res: 1920, aspect 16:9, Y res (derived): 1080), vertical frequency: 60
Descriptor 1: Detailed timing descriptor:
  Pixel clock: 148500kHz
  Horizontal active pixels: 1920
  Horizontal blanking pixels: 280
  Vertical active lines: 1080
  Vertical blanking lines: 45
  Horizontal front porch pixels: 88
  Horizontal sync pulse pixels: 44
  Vertical front porch lines: 4
  Vertical sync pulse lines: 5
  Horizontal image size: 477mm
  Vertical image size: 268mm
  Horizontal border pixels: 0
  Vertical border lines: 0
  Digital separate sync
  VSync serration
  Positive horizontal sync polarity
Descriptor 2: Display range limits
  Minimum vertical field rate 50Hz
  Maximum vertical field rate 76Hz
  Minimum horizontal field rate 30Hz
  Maximum horizontal field rate 83Hz
  Maximum pixel clock rate: 170Mhz
  Default GTF
Descriptor 3: Display name VE228
Descriptor 4: Display serial number F4LMQS128546

As you can see, the EDID format crams a lot of configuration information into 128 bytes. The output starts off with some basic data about the monitor's characteristics and inputs. The VGA standard doesn't nail down as many things as you'd hope. For instance the sync signals can be provided on one wire (separate), two wires (composite), or on the green wire. The output above shows my monitor supports all three sync types.

The monitor then provides a long list of supported resolutions, which is how your computer knows what the monitor supports. The "detailed timing descriptor" provides more information on signal voltage levels and timings. The timing of VGA signals contains some strange features (e.g. blanking and "front porch") inherited from obsolete CRT (Cathode Ray Tube) displays. The values in the configuration provide the information necessary for the computer's graphics board to synthesize a proper VGA signal that the monitor can understand.

The CIE chromaticity coordinates provided by the monitor are interesting, but need a bit of background to understand. A CIE chromaticity diagram (below), shows all the colors in the real world. (Brightness is factored out, so grays and browns don't appear.) Individual wavelengths of light (i.e. the spectrum) curve around the outside of the diagram. The colors inside the curve are combinations of the pure spectral colors, with white in the middle.

CIE diagram showing the color gamut and white point of my monitor.

CIE diagram showing the color gamut and white point of my monitor.

A display, however, generates its colors by combining red, green, and blue. The result is that a display can only show the colors inside the triangle above with red, green, and blue at the corners. A display doesn't generate the light wavelengths necessary to display colors outside the triangle. Like most monitors, this monitor can only show a surprisingly small fraction of the possible colors. (A wide-gamut display uses different phosphors to expand the triangle and get more vivid colors.) The triangle vertices and white point4 in the diagram above come from the x,y chromaticity coordinates in the configuration data.

You might wonder how you can see the whole CIE diagram on your display if only the colors inside the triangle can be displayed. The answer is the diagram "cheats"—the colors are scaled to fit into RGB values, so you're not seeing the exact colors but just an approximate representation. If you look at the spectrum through a prism, for instance, the colors will be more intense than what you see in the CIE diagram.

Inside I2C

The I2C protocol (Inter-Integrated Circuit) was invented in 1982 by Philips Semiconductor to connect a CPU to peripheral chips inside televisions. It's now a popular protocol for many purposes, including connecting sensors, small LED displays, and other devices to microcontrollers. Many I2C products are available from Adafruit and Sparkfun for instance.

The I2C protocol provides a simple, medium-speed way to connect multiple devices on a bus using just two wires—one for a clock and one for data. I2C is a serial protocol, but it differs from serial protocols like RS-232 in a couple ways. First, I2C is synchronous (using a clock), unlike RS-232 which is asynchronous (no clock). Second, I2C is a bus and can connect dozens of devices, while RS-232 connects two devices.5

The oscilloscope trace below shows what an I2C communication with the monitor looks like on the wire. The top line (cyan) shows the clock. Note that the clock only runs while data is transmitted.) The yellow line is the binary data. At the bottom, the oscilloscope decoded the data (green). In this trace, register number 0x26 is being read from device 0x50. The I2C protocol is rather peculiar since a read is performed by doing a write followed by a read. That is, to read a byte, the master first does a write to the device of the desired register number: the master first sends 0x50 (the device ID) the write flag bit (indicated with "W:50"), and 0x26 (the register number, ASCII "&"). Then, the master does a read; it sends 0x50 and the read flag bit ("R:50"). The device responds with the value in the register, 0x71 (ASCII "q").6

I2C trace: clock (SCL) in cyan and data (SDA) in yellow. Green shows decoded data. Oscilloscope was set to 20&micro;s/division and 2V/division.

I2C trace: clock (SCL) in cyan and data (SDA) in yellow. Green shows decoded data. Oscilloscope was set to 20µs/division and 2V/division.

Devices on the I2C bus can only pull a line low; pull-up resistors keeps the lines high by default.7 As a result, the traces above drop low sharply, but climb back up slowly.8 Even though the transitions look sloppy, the I2C bus worked fine. I couldn't find a source to tell me if VGA monitors included pull-up resistors, or if I needed to add them externally. However, I measured voltage on the lines coming from the monitor and everything worked without external resistors, so there must be pull-up resistors inside the monitor.

Conclusion

The VGA specification includes a data link that allows a computer to learn about a monitor and configure it appropriately. It is straightforward to read this configuration data using the I2C protocol and a board with an I2C port. While VGA is mostly obsolete now, the same data protocol is used with DVI and HDMI displays. My goal in reading the monitor's config data was so I could use the timing data in an FPGA to generate a VGA video signal. (That project is yet to come.) Follow me on Twitter or RSS to find out about my latest blog posts.

Notes and references

  1. DDC (Display Data Channel) is used by VGA, DVI and HDMI connections, which transmit the data over two I2C pins. The data it sends is in the EDID (Extended Display Identification Data) format. Everything changed with the DisplayPort interface. It transmits configuration data over a differential AUX channel using the DisplayID format, which extends EDID to supports newer features such as 3D displays. Thus, the techniques I describe in this article should work with DVI or HDMI interfaces, but won't work with DisplayPort. 

  2. Looking at other VGA cables on the web, most VGA cables don't have the fourth coax for horizontal sync that my cable does. So my cable seems a bit unusual. 

  3. Instead of cutting a VGA cable in half, I could have simply plugged a cable into a VGA connector, but that's less interesting. 

  4. The white point for my monitor matches a standard called D65

  5. For more information on I2C, good explanations are on SparkFun and Wikipedia

  6. I'm leaving out some of the complications of I2C. For example, the master generates the clock, but the device can do "clock stretching" by holding the clock low until it is ready. Also, the device sends an ACK bit after each request. The device address is 7 bits, while the data is 8 bits. See protocol documentation for details. 

  7. Using a pull-up resistor on the I2C bus avoids the risk of short circuits. If, alternatively, devices could actively pull the line high, it would be a problem if one device tried to pull a line high at the same time another pulled it low. 

  8. The oscilloscope traces show exponential R-C charging curves when the line is pulled high. This is due to the wire capacitance being charged through the pull-up resistor. The signals only reach about 3V, making them suitable for the PocketBeagle's 3.3V inputs. (If you try this with a different monitor, check the voltage levels to avoid damaging the PocketBeagle's inputs.)