Al is a writer and consultant. He can be contacted at [email protected]
Many programmers consider hardware a commodityyou have a PC or embedded system thrust upon you, and you make the code fit the confines of that platform. Some developers design hardware and software, but hardware design often means assembling a microprocessor, clock, some memory, and a reset circuit. Not many of us get our hands dirty with discrete devices or logic gates anymore.
We can usually avoid this level of complexity because microprocessors are so ubiquitous that they solve many problems without requiring us to deal with intricate hardware design. However, the key term here is "many problems"there are still certain problems that don't lend themselves to solutions involving typical microprocessors.
For instance, microprocessors are still relatively slow compared to discrete logicespecially if you consider the entire system. Suppose you need to monitor half a dozen inputs for some condition. Digital logic can perform this task rapidly, limited only by the propagation delay of the gates you use. A fast microprocessor (a 100-MHz Ubicom SX, for example) might execute an instruction every 10 nanoseconds. However, the microprocessor may need 10, 20, or more cycles to perform the task. If your processing requires 20 cycles per scan, the effective speed of the system is a relatively slow 5 MHz.
Another limitation in most microprocessor designs is their single-tasking nature. With interrupts and some operating system software, microprocessors can appear to handle multiple tasks. In truth, single microprocessors can only do one task at a time unless they incorporate special hardware; for example, a UART might handle serial I/O without direct intervention from the main processor. Multiprocessor systems are possible, but anything more than a few simultaneous operations will be costly and complex.
Digital logic, on the other hand, is inherently multitasking. If you connect an AND gate to two inputs, the output is effectively computed constantly. Ten (or 100) AND gates compute 10 (or 100) outputs just as fast as one AND gate does.
Some jobs are naturals for microprocessors, others can benefit from digital logic, and some can use a combination of the two. For example, many microcontrollers have dedicated hardware to generate pulses, handle communications, and perform other tasks in parallel with the main processor.
FPGAs and CPLDs
It used to be that using discrete logic meant building giant cabinets full of integrated circuits, and semiconductor manufacturers produced large-scale integrated circuits for common functions. However, for niche circuits, you had to roll your own using smaller building blocks.
Eventually, chip makers found ways to make field-programmable ICs. Field Programmable Gate Arrays (FPGAs) contain myriad logic blocks and a programmable interconnect switch that can tie different blocks together (or connect them to I/O pins). These switches usually require programming from an external read-only memory device.
Complex Programmable Logic Devices (CPLDs) are similar to FPGAs, although their internal architecture is different. Inside CPLDs are arrays of macrocells that connect to each other and I/O through a programmable switch similar to that of an FPGA. The difference is that CPLD macrocells are simpler than FPGA logic cells, and are usually more connected. Of course, different vendors have different architectures, but from the developer's point of view, CPLDs are just simpler FPGAs. In this article, I refer to "programmable logic" when I mean a CPLD or FPGA.
Developing for programmable logic used to be a formidable task. In particular, the development tools ran on expensive workstations and often cost thousands of dollars. Designing for the hardware was also a problem because FPGAs usually require some external device to reprogram upon reset.
Many CPLDs are now based on nonvolatile memory technology. You program the chip once and it stays programmed until you decide to change the program. Better still, most major vendors provide low-cost, easy-to-use software tools that run on platforms such as Windows and Linux. FPGAs are still more complicated due to external configuration requirements. However, most modern FPGAs allow IEEE 1194.1 (also known as "JTAG") testing and programming so you can often program the chip with a printer cable from an ordinary PC.
That said, most modern devices are contained in packages that are difficult to use for development. Only a few CPLDs are available in PLCC packages. Larger devices come in surface-mount packaging. However, there are many prebuilt circuit boards that let you incorporate these chips into prototype or small-volume designs. If you are accustomed to working with 5V logic, you may have to change on this front also. Only a few CPLDs operate on 5V power; most devices now require 3.3V or less. However, since most have 5V tolerant inputs, and 3.3V is above the logic 1 threshold for 5V logic families, you can usually connect these chips to most normal TTL or CMOS logic without any problems (normal logic chips use a 2.5V or 3V threshold).
Still, when you need the speed and concurrency of logic, these devices are powerful tools. Small CPLDs might have 50 or 100 macrocells (100 macrocells are roughly equivalent to 100 flip flops and perhaps 2200 to 2300 logic gates). Large FPGAs might field the equivalent of 200,000 gates or more. They also may have special features like embedded RAM memory. With that kind of power, you can even build custom microprocessors.
To design a circuit using programmable logic, you don't need to delve into the internal details of macrocells and interconnectsat least, not for most designs. Instead, you use vendor tools to describe the circuit. One way to do this is to draw a schematic that includes logic gates, flip flops, and even custom blocks made up from other schematics.
Once the design is ready, the vendor's tool synthesizes the design. Development environments have powerful simulation tools that let you test your work before you actually commit to the physical chip. This was important back in the days when having a custom chip made meant custom masks for the manufacturing process, or burning a chip that was not reprogrammable. With quick reprogramming of CPLDs now a reality, you might not need rigorous simulation. Still, it is handy to study your circuit's behavior in a virtual environment.
Once you are satisfied, the software will fit the design into a physical device, assigning the macrocells and determining the necessary interconnects. This can be a complex process as the chip gets full. Once fitting is complete, you can predict the propagation delays of the signals on the chip and run simulations that take these delays into account.
Finally, you can program your handiwork into the IC. For an FPGA, you might directly program the chip for testing. In real life, you'd program an EEPROM device that the FPGA reads when it resets. CPLDs typically hold their programs internally. If you've been careful doing simulations, everything should work and you can move on to your next project.
Nearly everyone starting out in programmable logic thinks schematic entry is a great idea. After all, every hardware developer is accustomed to schematics. However, it turns out that most people don't have the patience to draw complex schematics that might have a quarter of a million gates. Sure, it is easy to draw a 32-bit Johnson counter, but it is still tedious and repetitive.
This tedium is why most experienced programmable logic developers rarely use schematic entry. Instead, they write descriptions of the hardware using a Hardware Description Language (HDL) such as Verilog (http://www.verilog.com/) or VHDL (http:// www.accellera.org/). These resemble programming languages, but aren't really programming languages. Instead, they describe how the hardware works. The HDL compiler infers what gates are necessary from your description. So if you define X as a 32-bit value and write X=X+1, the compiler infers the 32-bit counter for you.
In theory, anything you can create with an HDL can also be created with schematics. In practice, schematic entry is relegated to small modules or high-level overviews that connect other modules together. There are also some tools that are specifically designed for generating certain types of functions. For example, many tools can automatically create common items such as counters or latches to your specifications. There are also special tools that create state machines from a state diagram.
Using Programmable Logic
To illustrate, consider the classic quiz-show button problem. Four contestants wait for the host to ask a question. The first contestant to buzz locks out the other three. However, contestants who buzz too early are disqualified for that round. (Of course, the contestants could be race cars or products on an assembly line.) The key is fast sampling, thereby minimizing the chance of two inputs arriving simultaneously.
This is a perfect job for programmable logic. You could easily develop the logic as a schematic or via HDL. In addition, there are several ways you might use HDL to get the same result. Since I'm comfortable with logic, I decided to take a schematic-like approach, but I used Verilog. Although most of my Verilog corresponds directly to logic gates and flip flops, there are a few cases where I use a more abstract way of specifying a module.
I used a Xilinx (http://www.xilinx.com/) test board that has several switches, LEDs, and a display wired to a 95108 CPLD. Since all the CPLD pins are configurable, it does not usually matter which I/O pins are used. In this case, the I/O pins have to match the connections that already exist on the board. There are several ways to do this. You can:
- Edit a constraint file, which is a simple text file that provides commands to the synthesizer.
- Use constraints embedded in comments to set pin numbers to logical I/O points.
- Provide other commands to the synthesizer to force it to keep certain logic constructs in certain macrocells, for example.
You won't often use anything other than the pin number selections. If you don't want to edit a text file, you have two other options. Xilinx's WebPack ISE tools (which I used to develop this project) provide graphical ways to set constraints (the Constraint Editor and Chip Viewer). Finally, you can embed constraints inside Verilog files as specially coded comments (which is what I did).
Verilog looks like a programming language, but since things go on concurrently, you can't really read Verilog from top to bottom like you would, say, a C source file. Listing One is a top-level source file.
The first part of the module defines inputs and outputsthe ports that let it connect to the rest of the world. Subordinate module ports connect to the ports of other modules. Since this module is at the top level, its ports connect to the physical I/O pins on the chip.
Following the port definitions, special comments assign signals to the physical chip pins. The next section defines what you can think of as local variables. The wire keyword identifies connections between things that behave like a wire. The reg keyword specifies a variable that retains its value (these turn into flip flops in the final product). Some of the registers are multibit vectors; for example, the winner vector has four elements.
The initial statement lets you set items to a known state. The synthesizer ignores this since it is a function of the hardware. However, initializing is important when simulating. If you don't initialize items, the simulator keeps the value at the X state forever because it doesn't know if the real state is 0 or 1. In the real world, of course, this can't happenthe value is a 1 or a 0 even if you don't know which one it is.
The assign statements form the first example of things that happen concurrently. Each assignment makes a permanent connection between the two items mentioned. They don't assign the value and move on to the next statementthey constantly keep making the assignment forever. So when ready changesat any timeled will change to match immediately.
Following the assign statements are several primitive logic gates (AND/OR gates, in particular). The syntax for these primitives let you specify gates with many inputs. The first argument is the output of the gate. The subsequent arguments are the inputs: and(O,A,B); is a two-input gate, while and(O,A,B,C,D); is a four-input gate. Verilog looks like C, and indeed the "~" symbols in these lines act as inverters, flipping 1 bits to 0, and 0 bits to 1.
In addition to primitive gates, you can define modules. The show1to4 module (Listing Two) drives the LED display. This is similar to an object in an object-oriented program. The main module has to instantiate one or more copies of the module. Verilog lets you customize each instance with parameters (although I haven't used this feature here). Consider show1to4 leddisp(winner,disp);, which creates a new instance of the show1to4 module (named leddisp). The ports in this instance connect to the winner and disp signals.
Example 1(a) describes the clocking behavior of the module by using always. Each block of code in an always statement generates clocked flip flops. The simplest example toggles the ready state. Example 1(b) tells the synthesizer to arrange circuitry so that when pb has a rising edge, the ready signal is inverted.
The example code builds a flip flop with a reset so that when win0 has a rising edge, the state of winner asserts. However, if ready has a falling edge, it acts as a reset. This is one of the drawbacks to Verilog and other HDLsyou can write plenty of legal Verilog that the synthesizer can't understand, which either leads to inefficient implementation or the flat refusal of the synthesizer to create the circuit. So, in some cases, you wind up having to plan your Verilog in such a way that you know the synthesizer will find things it understands.
Listing Two shows a different way to use Verilog to describe a circuit. This particular circuit decodes the numbers 1 to 4 and shows them on a seven-segment LED. Of course, you could write logic equations for each LED segment or use a decoder. However, it is easy enough to simply describe the transformation you want in Verilog. The synthesizer will deduce your intention.
WebPack doesn't have a way to view the results of synthesis easily. However, third-party tools like Gatevision (from Concept Engineering GmbH; http://www.concept.de/) can convert the output back into human-readable form.
One Size Fits All?
Programmable logic isn't the answer to everything, and microprocessors are usually still more cost effective. However, there are some jobs where the speed and concurrency of a programmable logic device is necessary. Some problems lend themselves to a solution with a logic device and a microprocessor. In fact, on some larger devices, you can actually place a microprocessor design (usually purchased from a vendor) in the device, then use the extra space to develop the rest of your circuitry. This has many advantages because you can conquer the hard parts with logic and handle the ordinary command and control with the microprocessor. Do you need a CPU with five UARTs, three PWM generators, and four-timer capture registers? Build it with programmable logic.
Although programmable logic was once a tool for specialists, inexpensive and easy-to-use tools coupled with more powerful reprogrammable (and nonvolatile) devices have put programmable logic within reach of every embedded-system designer.
// The following synthesis comments will wrap in print, but of course, should // be on one line for compilation // This is the gameshow top-level module module top1(pb,sw,disp,led,dp); input pb; // host push button input [7:0] sw; // 3-0 are the contestant buttons output [6:0] disp; // A 7 segment display output [7:0] led; // 8 LEDs (not all used) output dp; // decimal point for 7 seg // These comments have special meaning to the synthesizer and make sure that // the correct pins get connected to the physical I/O // synthesis attribute LOC pb "P10" // synthesis attribute LOC led "P35","P36","P37","P39","P40","P41","P43","P44" // synthesis attribute LOC sw "P70","P66","P71","P72","P5","P11","P7","P6" // synthesis attribute LOC disp "P17","P14","P19","P21","P23","P18","P15" // synthesis attribute LOC dp "P24" reg ready; // are we ready? wire lock; // locked out wire swready; wire ltrig0, ltrig1, ltrig2, ltrig3; // lock out signals wire win0, win1, win2, win3; // individual win wires reg [3:0] winner; // which is the winner? reg [1:0] swin; // reg [3:0] llock; // locked out? initial ready=0; // for simulation only assign led=ready; // led7 is ready assign led=lock; // led6 means someone won assign led[3:0]=winner; // led3-0 tells who won // if a switch is active and we aren't ready, lock that switch out and(ltrig0,sw,~ready); and(ltrig1,sw,~ready); and(ltrig2,sw,~ready); and(ltrig3,sw,~ready); // if a switch is active, no one has won, we are ready, and the // switch is not locked out, then set the correct win signal and(win0,~lock,sw,ready,~llock); and(win1,~lock,sw,ready,~llock); and(win2,~lock,sw,ready,~llock); and(win3,~lock,sw,ready,~llock); // if anyone won, lock everyone out or(lock,winner,winner,winner,winner); // display on 7 segment LED show1to4 leddisp(winner,disp); // if any of the winX wires go high or ready goes low // we need to set winner[X] correctly always @(posedge win0 or negedge ready) begin if (ready==0) winner=0; else winner=1; end always @(posedge win1 or negedge ready) begin if (ready==0) winner=0; else winner=1; end always @(posedge win2 or negedge ready) begin if (ready==0) winner=0; else winner=1; end always @(posedge win3 or negedge ready) begin if (ready==0) winner=0; else winner=1; end // if ltrigX asserts, or ready goes low we need to set llock[X] correctly always @(posedge ltrig0 or negedge ready) begin if (ltrig0) llock=1; else llock=0; end always @(posedge ltrig1 or negedge ready) begin if (ltrig1) llock=1; else llock=0; end always @(posedge ltrig2 or negedge ready) begin if (ltrig2) llock=1; else llock=0; end always @(posedge ltrig3 or negedge ready) begin if (ltrig3) llock=1; else llock=0; end // when the host presses pb, it toggles the ready state always @(posedge pb) ready=~ready; endmodule
// display the number 1 to 4 on the display (other values blank display) module show1to4(D,O); input [3:0] D; output [6:0] O; reg [6:0] O; always case (D) 0: O <= 7'b0000000; 1: O <= 7'b0000110; 2: O <= 7'b1011011; 3: O <= 7'b0000000; 4: O <= 7'b1001111; 5: O <= 7'b0000000; 6: O <= 7'b0000000; 7: O <= 7'b0000000; 8: O <= 7'b1100110; 9: O <= 7'b0000000; 10: O <= 7'b0000000; 11: O <= 7'b0000000; 12: O <= 7'b0000000; 13: O <= 7'b0000000; 14: O <= 7'b0000000; 15: O <= 7'b0000000; endcase endmodule