Encoder Front Page
SRS Home | Front Page | Monthly Issue | Index
Google
Search WWW Search seattlerobotics.org

'Have you considered programable logic for your next design?'

Kenneth Maxon
kmaxon@uswest.net

SECTION 1: INTRODUCTION

The buzzwords these days in leading edge hardware are intellectual properties licensed into semi-custom ASIC's and 'fitting' has nothing to do with your blue jeans. Let's face it, most of us (the author included) don't have too much need for semi-custom ASIC's on our robots. Even now as the prices on development tools have plunged through the floor, that level of silicon integration just isn't justified. However just last week I my jaw hit the floor to hear one of my fellow robot designers complaining that he didn't have enough board space for the fifteen or so 74-HCT-XX parts on his board. We talked for a few minutes before the subject of CPLD's surfaced.

CPLDs or Complex Programmable Logic Devices are the next extension to the PLD market. They are today's technological stepping stone between a handful of discrete gates and a full custom design. What's more, the silicon manufacturers are so interested in beginning engineers down the path of brand loyalty, that one & two quantity pricing is around five dollars for surprisingly powerful devices. At the same time, entry level software development tools have evolved from multi-thousand dollar price tags to free (Public Domain) Inter Net downloads while at the same time, continuing to gain in power.

Fifteen years ago, the author started using programmable logic in his designs. A lot has changed since then with the language syntax and different flavors of implementation, however when sticking to the higher level of logic description many things have also standardized. There are countless examples of how and when in-system programmable logic can and should be used on a mobile robotics platform. This article is presented to kick start that process. Towards that end, this article is geared towards the beginner. I've tried to target the language and 'verbage' to the entry level design engineer as well.

In this article the author will review some PLD & ABLE basics, language and syntax, then lead into some AHDL language extensions, followed by a simple yet real world implementation of a PIA (Programmable Interface Adapter). To maximize the educational benefits of this article, it is heavily example laiden.


SECTION 2: Article Overview

In this article the author will present an overview and all of the pertinent information required to start designing with, and successfully implement CPLD's into the readers next design. It is the author's hopes that this article can be used to slightly elevate the level of technology used in the design and building of robots throughout the Seattle Robotics Society as well as that of robotics amateurs everywhere.

Section 1: Introduction.
Section 2: This section.
Section 3: This section introduces the reader to the basics of the ABLE language and provides many examples such as AND gates, Or gates, Inverters and other general logic operators.
Section 4: Test vectors and stimulus are introduced in this section to aid in simulating and debugging your designs.
Section 5: This section introduces some of the more common ABLE languages extensions that are used when discussing AHDL. More examples are included including implementation of an octal gate, an octal latch (74xx373), and a bi-directional octal transceiver (74xx245)..
Section 6: This section focuses on the state machine architecture of AHDL using the ABLE basics and the AHDL primitives presented in the preceding sections. After a quick discussion, a simple state machine is presented to load a byte in parallel and serially shift it out including start bit, stop bit and parity calculation.
Section 7: A change from the previous sections, this section focuses on the hardware requirements needed to get a prototype system up and running with pictorial documentation presented from some of the authors previous work.
Section 8: Specifics on the 1000 series parts and the 1016 by Lattice are given.
Section 9: The author presents the final design of a PIA (Programmable Interface Adapter) again using a 1016 part by Lattice.

NOTE: The author developed all of the code examples contained here in. If you find any mistakes please contact him to correct this online document as a service to other readers.


SECTION 3: ABLE BASICS

ABLE in it's simplest form was designed to describe combinatorial logic. In a basic ABLE script there are three types of syntax. The first type of syntax is file formatting overhead. The second type of syntax is signal definition. And the third type of basic syntax in ABLE is the equation. In this section, the author will present each of these three types of syntax:

SYNTAX 1: Formatting overhead - The file overhead in ABLE is pretty minimal. All that is required is a 'TITLE' line, an identifier at the beginning of the 'MODULE' & 'EQUATIONS' sections and an 'END' terminator.

The examples through-out this article are compatible with the Synario product line developed by Data IO. The author has chosen this content format as many of the more common CPLD manufactures provide this product for free to support their chip sets, including Lattice, Vantis and Phillips. The specific examples here were built and tested on the version of the Synario product that is provided by Lattice Semiconductor.
MODULE DEMO_1
TITLE 'this is an example of the minimal formatting overhead'

	// Syntax type 2 - variable definitions go in this section.

EQUATIONS

	// Syntax type 3 - equations go in this section.

END

The lines present in this first section (Syntax 1) add nothing to the logic description within the device itself. These lines are present merely for use by the compiler itself. Unlike all other lines in ABLE, these lines have no terminating semicolon. (';')

SYNTAX 2: Signal definitions - Just as in a C program, variable names and data types and other attributes are defined at the top of a function. The same holds true in ABLE, where the design engineer may define a signal or group of signals, fix them to a specific pin on the part and group multiple signals together to form variables of larger sizes.

Signal definitions must come between the 'MODULE' keyword and the 'EQUATIONS' Keyword in an ABLE file. Refer to the formatting section above.

A signal is defined as a signal name followed by a signal type. The signal type is either combinatorial 'COM' or a node 'NODE'. Think of combinatorial signals as logic that is run at full speed without the need for flip-flops or clocks. Inverters, Or-gates & And-gates are examples of combinatorial gates that work with combinatorial signals. Flip-flops and Tri-State latches are examples of gates that work with NODE type signals. Signals of type COM are usually assigned directly to pins of a package:

	SIGNAL_A PIN 12;
	SIGNAL_B PIN 13;
	SIGNAL_Y PIN 14;

In the source example above the following syntax could have been used alternatively. SIGNAL_A,SIGNAL_B,SIGNAL_Y PIN 12,13,14; However this is not as clear to read when debugging complex source files, and eventually will lead to mistakes so the author will stick to one pin definition per line.

Notice the ';' at the end of each line. Just like the 'C' programming language, if you forget one or add too many the compilers will either flag errors or produce undesired output. The above example defines three bit wide variables, named signal_a, signal_b & signal_y inside the CPLD and assigns them to pins 12, 13 & 14 respectively. To clarify the above example a bit further we want signal_y to represent an output pin from some combinatorial logic that we will define below (Syntax 3). To distinguish it from variable names used as input, the programmer, should explicitly define this output as a combinatorial variable by the use of the 'ISTYPE' operator:

	SIGNAL_A PIN 12;
	SIGNAL_B PIN 13;
	SIGNAL_Y PIN 13 ISTYPE 'COM';

The same convention is used for defining a pin that will be used for Bi-directional IO. For now, simply define it as 'COM' and later we'll use DOT extensions to enable it as an input some of the time while enabling the output drivers, for this pin, at other times. The compiler defaults to enabling the output drivers for any pins that have only output signals attached.

There are some specifics to be aware of when defining output pins from combinatorial logic. Many of the earlier devices PLD's PLA's, GAL's etc had inverters that could be programmed inline with their outputs to aid in logic reduction and issues of signals being active-high / active-low. To enable an inline inverter on a combinatorial pin uses the 'INVERT' ABLE keyword:

	SIGNAL_A PIN 12;
	SIGNAL_B PIN 13;
	SIGNAL_Y PIN 13 ISTYPE 'COM,INVERT';

A word of caution about the 'INVERT' keyword: If the package you are using to compile and fit your logic is device aware do not specify the 'INVERT' keyword. Instead let the compiler decide where to use active-low logic to reduce product term count where needed during the fitting phase. More latter...

Sometimes the programmer wants to define an internal variable that has inputs or outputs connected between equations(more later) ,without attachment to a pin, or require special clocking or other considerations. These variables are defined through the use of the 'NODE' keyword. Nodes are also used for flip-flops and other clocked logic. An important note, nodes are automatically removed from your logic by the compiler unless all inputs are utilized. To keep this from occurring, specify the 'KEEP' modifier in the 'ISTYPE' tag on the variable's definition line.

	SIGNAL_U NODE;
	SIGNAL_X NODE ISTYPE 'REG_D,BUFFER,KEEP';

In the lines above the first NODE would be implemented in a design like an intermediate variable which will be reduced by the complier. The second, however, specifices to the compiler that the variable name should be associated with a 'D' type flip-flop. More about flip-flops in Section 5 (below).

There are other types of node assignment to use with flip-flops, but most compilers run logic through several different transformations to build them down to the one simple registered D type flip flop. As the reader will discover later with descriptive dot operators it is possible to transform the logic your self so that you are certain how your logic is being assigned within the compiler.

In the definition of an output pin, the type defined with the 'ISTYPE' modifier can be set to 'REG_D,BUFFER,KEEP' as well, but there is a drawback to designing this way. The compiler will try to force the NODE to live with a particular output structure. As AHDL which well deal with later is quite portable, and output structures change quite a bit, it is better to leave nodes floating so that the compiler can place them where the routing can be most successful. In a moment well examine equations to show how floating nodes are connected to input and output pins.

Group: Often the designer has a group of signals that because of their number become unwieldy to work with. This happens quite commonly on computer circuit boards where bytes, words, and long words are the data types of the day. It would be convenient if there were a way to work with a number of bit wide signals as a byte, word, or other arbitrary sized data chunks. ABLE provide this functionality by grouping as follows.

	DATA_0 PIN 14;
	DATA_1 PIN 15;
	DATA_2 PIN 16;
	DATA_3 PIN 17;
	DATA_4 PIN 18;
	DATA_5 PIN 19;
	DATA_6 PIN 20;
	DATA_7 PIN 21;
	OUT_0 PIN 2 ISTYPE 'COM';
	OUT_1 PIN 3 ISTYPE 'COM';
	OUT_2 PIN 4 ISTYPE 'COM';
	OUT_3 PIN 5 ISTYPE 'COM';
	OUT_4 PIN 6 ISTYPE 'COM';
	OUT_5 PIN 7 ISTYPE 'COM';
	OUT_6 PIN 8 ISTYPE 'COM';
	OUT_7 PIN 9 ISTYPE 'COM';
	DAT = [DATA_7,DATA_6,DATA_5,DATA_4,DATA_3,DATA_2,DATA_1,DATA_0];
	OUT = [OUT_7,OUT_6,OUT_5,OUT_4,OUT_3,OUT_2,OUT_1,OUT_0];

Now when the designer wants to use all of the variables DATA_7 .. DATA_0 at the same time, they merely reference the variable DAT[7..0]. To access just the top nyble of the byte, the designer may reference DAT[7..4] or any subscript range desired. The same applies for the OUT variable, although these have the 'COM' property that specifies them as outputs.

In the example abvoe the choice of names for the variable DATA_0, DATA_1 ... DATA_7 is arbitrary. These variables could just as well have been named Peter, Paul .... & Mary, and still have been grouped into the DAT[7..0] variable above. The order of prescedence is determined by their order of apperance in the grouping structure.

SYNTAX 3: Equations - The third and final type of syntax used in ABLE is that of Equations. Equations use standard Boolean logic operators to tie together variable nodes and gates in an ABLE source file.

Equations defined in an ABLE source file must lie between the 'EQUATIONS' keyword and the 'END' keyword. Refer to formatting syntax above.

Some of the basic ABLE Equation syntax is listed below. TABLE 3.1

AND &
OR |
NOT !
XOR $
ASSIGNMENT =
CLOCKED ASSIGNMENT :=
COMPARISONS ==,<,>,<=,>=,!=
ADD +
SUBTRACT -
MULTIPLY *
DIVIDE /
SHIFT RIGHT/LEFT >>,<<
START/END BLOCK {}
START/END PREDICIENCE GROUP ()
LITTERAL ''
MORE SYNTAX WHEN,THEN,ELSE
UNKNOWN EQUATIONS TRUTH_TABLE()
STATE STUFF STATE,STATE_DIAGRAM,STATE_REGISTER,GOTO
  IF,THEN,ELSE,WITH,ENDWITH,SYNC_RESET,ASYNC_RESET
FORMATTING MODULE,TITLE,END,EQUATIONS
COMMENTS //

This list of basics may seem quite simple, but as the 'high level' entry into the CPLD programming world, these simple commands, pack a lot of power. Now let's explore how to put some of this logic to use.

Above in the section on variable definition we defined several single bit variables. (SIGNAL_A,SIGNAL_B & SIGNAL_Y). Let's examine the simplest case, an inverter. From the table above the operator for inversion is the '!' and it is implemented as follows:

	SIGNAL_Y = !SIGNAL_A;

Looks too easy doesn't it? Well remember in order for this to work, it must be combined with the variable definitions from (Syntax section #2) above and the formatting from (Syntax section #1) above as well. The combined file would look like:

MODULE INVETER
TITLE 'this example demonstrates a complete ABLE file for a simple inverter'
	SIGNAL_A PIN 12;
	SIGNAL_B PIN 13;
	SIGNAL_Y PIN 13 ISTYPE 'COM';
EQUATIONS
	SIGNAL_Y = !SIGNAL_A;
END

In the rest of the examples here in the equations section the author will omit the variable definition section defined in Syntax section #2 above and the formatting section defined in Syntax section #1 above. Remember that if you want to test any of these functions you will need to add these definitions back.

Now let's look at some of the other simple ABLE equation operators like OR & AND:

	SIGNAL_Y = SIGNAL_A | SIGNAL_B;		//TRANSLATION Y = A OR B
	SIGNAL_Y = SIGNAL_A & SIGNAL_B;		//TRANSLATION Y = A AND B
	!SIGNAL_Y = SIGNAL_A & SIGNAL_B;	//TRANSLATION Y = NOT(A OR B)
	SIGNAL_Y = !SIGNAL_A & !SIGNAL_B;	//TRANSLATION Y = (NOT A) AND (NOT B)
	WHEN((SIGNAL_A == 0) & (SIGNAL_B == 0)) THEN SIGNAL_Y = 1;  //TRASLATION SAME AS ABOVE.

Can you see where the power comes into play with such a high-level logic language such as ABLE. Merely half an hour or so and you're building chips to reduce the number of components in a system. Now let's include the signal definitions for DATA_7 .. DATA_0 and the grouped data group DAT[7..0] to see where more of the power comes from.

In the following example the author will present equations to enable an output bit, SIGNAL_Y, based on the byte wide input DATA_7 .. DATA_0 and an Active Low enable bit SIGNAL_A. Several implementations of the same equation will be shown using differing syntax. Take a few minutes to read through each one of these and appreciate the syntax that allows all of these to be identical statements. Note that when Synario evaluates an active true signal like SIGNAL_Y for a 1 condition like the examples above, all other conditions will cause the output to be 0 or de-asserted. This is an important difference between ABLE, AHDL and other more complex lower languages where the input for unspecified logic remains at the previous state instead of defaulting to 0 or de-asserted.

	SIGNAL_Y = (DATA_7 & !DATA_6 & DATA_5 & !DATA_4 &
                DATA_3 & !DATA_2 & DATA_1 & !DATA_0 & !SIGNAL_A);
	SIGNAL_Y = ((DATA_7 == 1) &
                (DATA_6 == 0) &
                (DATA_5 == 1) &
                (DATA_4 == 0) &
                (DATA_3 == 1) &
                (DATA_2 == 0) &
                (DATA_1 == 1) &
                (DATA_0 == 0) &
                (SIGNAL_A == 0));
	SIGNAL_Y = ((DAT[7..0] == [1,0,1,0,1,0,1,0]) & !SIGNAL_A);
	SIGNAL_Y = ((DAT[7..0] == ^hAA) & !SIGNAL_A);
	WHEN (DATA_7 & !DATA_6 & DATA_5 & !DATA_4 &
		DATA_3 & !DATA_2 & DATA_1 & !DATA_0 & !SIGNAL_A) THEN SIGNAL_Y = 1;
	WHEN ((DAT[7..0] == ^hAA) & (SIGNAL_A == 0)) THEN SIGNAL_Y = 1;

Okay, we've seen some pretty cool stuff, but lets take it a bit farther. The following example will make use of the rest of the signals defined in Syntax Section #2 above, specifically Out_7 .. Out_0 grouped together as Out[7..0]. The design will implement a four-position roll function defined by the following rules. SIGNAL_A & SIGNAL_B will be grouped together as a two bit number to determine how far the input byte DAT[7..0] will be rolled before being output on OUT[7..0]. The equations will also take advantage of the group operators {} to perform multiple steps grouped together.

	WHEN([SIGNAL_B,SIGNAL_A] == 0)THEN OUT[7..0] = DAT[7..0]; //SIMPLE CASE NO ROLL
	WHEN([SIGNAL_B,SIGNAL_A] == 1)THEN{OUT_0 = DATA_7;OUT[7..1] = DAT[6..0]} // ROLL BITS BY ONE
	WHEN([SIGNAL_B,SIGNAL_A] == 2)THEN{OUT[1,0] = DAT[7,6];OUT[7..2] = DAT[5..0]} // ROLL BITS BY TWO
	WHEN([SIGNAL_B,SIGNAL_A] == 3)THEN{OUT[2..0] = DAT[7..5];OUT[7..3] = DAT[4..0]} // ROLL BITS BY THREE

In the next example the author presents a code snippet that shows off the power of the truth table. Truth tables are a great way to quickly define the logic of a system when you know it's inputs and outputs without having to sit down and solve for the exact logic that describes the relationship. In the example below a nyble is decoded to drive a seven segmented led.

MODULD SEG_SEVEN
TITLE 'Driver for seven segment led'
	D3 PIN 13;
	D2 PIN 14;
	D1 PIN 15;
	D0 PIN 16;
	A PIN 17 ISTYPE 'COM';
	B PIN 18 ISTYPE 'COM';
	C PIN 19 ISTYPE 'COM';
	D PIN 20 ISTYPE 'COM';
	E PIN 21 ISTYPE 'COM';
	F PIN 22 ISTYPE 'COM';
	G PIN 23 ISTYPE 'COM';
	INPUT = [D3,D2,D1,D0]
EQUATIONS
THRUTH_TABLE(INPUT -> [A,B,C,D,E,F,G])
               0 -> [1,1,1,1,1,1,0];
               1 -> [0,1,1,0,0,0,0];
               2 -> [1,1,0,1,1,0,1];
               3 -> [1,1,1,1,0,0,1];
               4 -> [0,1,1,0,0,1,1];
               5 -> [1,0,1,1,0,1,1];
               6 -> [1,0,1,1,1,1,1];
               7 -> [1,1,1,0,0,0,0];
               8 -> [1,1,1,1,1,1,1];
               9 -> [1,1,1,1,0,1,1];
END

The following example shows how the use of grouping can ease the implementation of chip select logic. The author will use some notation (specifically '.X.') that will not be presented until a later section in this article (#4). This notation allows unspecified logic to be used as a Dont Care reference.

MODULE CHIP_SEL
TITLE 'USED TO EXPRESS CHIP SELECTS'
	A0 PIN 17;
	A13 PIN 18;
	A14 PIN 19;
	A15 PIN 20;
	CHIP_SEL_1 PIN 21 ISTYPE 'COM';
	CHIP_SEL_2 PIN 22 ISTYPE 'COM';
	CHIP_SEL_3 PIN 23 ISTYPE 'COM';
	CHIP_SEL_4 PIN 24 ISTYPE 'COM';
	ADDRESS[A15,A14,A13,.X.,.X.,.X.,.X.,.X.,.X.,.X.,.X.,.X.,.X.,.X.,.X.,A0];
	ACTIVE = 0;
	INACTIVE = 1;
EQUATIONS
	WHEN(ADDRESS <^h8000) THEN CHIP_SEL_1="ACTIVE" ELSE CHIP_SEL_1="INACTIVE;" WHEN((ADDRESS> ^h7FFF) & (ADDRESS <= ^h8000)) THEN CHIP_SEL_2="ACTIVE" ELSE CHIP_SEL_2="INACTIVE;" WHEN((ADDRESS> ^h8000) & (ADDRESS <= ^h8001)) THEN CHIP_SEL_3="ACTIVE" ELSE CHIP_SEL_3="INACTIVE;" WHEN(ADDRESS> ^hA000) THEN CHIP_SEL_4 = ACTIVE ELSE CHIP_SEL_4 = INACTIVE;
//NOTE: the following line could replace the previous line in specifying the same logic...
//The logic is not as readable, but a lot more compact.  Once compiled both generate the same output.
	CHIP_SEL_4 = !(ADDRESS > ^hA000);
END

Observe the use of the '^h' operator above to indicate the use of hexadecimal numbers. Also observe the definition of the temporary variables 'ACTIVE' and 'INACTIVE'. These make the code easier to read since the chip selects in the above example are active low. (ACTIVE = 0) Another important observance that is rather hidden in the code that often causes problems when specifying logic with don't care (.X.) operations comes when evaluating the address that will enable, for example, CHIP_SEL_2. These addresses all in the range of all even addresses greater than or equal to 0x8000 and less than 0xA000. The same holds true for the effects of CHIP_SELECT_3 with odd addresses. The reader should take a minute to review the code above until they understand this multiple enable situation. This will go along way to understanding where common errors are introduced in logic systems.

As you can see the scalability of the logic is extremely large, and unlike all those basic logic classes back in college faced with the undaunted task of hundreds of wires on a bread board, the keyboard can correct a mistake in seconds flat. Later well look at how these new CPLD chips offer in system programmability to quickly fix little mistakes (typo's) but first lets continue exploring the power of equations and start to cross the barrier between pure ABLE and the slightly empowered statements that make up the AHDL language.


SECTION 4: Test Vector Definitions & Simulation

One of the powerful operations available through the use of a high-level logic language like AHDL is the ability to generate test vectors. Test vectors allow the user to 'simulate,' a design before it is programmed into hardware. This section will deviate from the design aspect of the article to cover design simulation and we'll pick back up with the design criteria in Section 5 later.

Simulation of a design becomes paramount when the hardware must work correctly the first time. Imagine a CPLD that as part of its design controls an H-bridge configured power mosfet array. Sure you can rely on the programmer to get it right, but when designing for failure, (and let's face it processor crash during development) wouldn't it be easier use external hardware to prevent a burn up?

Reading through the article up to this point, the reader can see how easy the logic to implement such a task will be. However, you've got the hardware wired up on a proto-board and you're about to apply power. Wouldn't it be nice to know that you are not going to blow up the left or right side of the H-bridge before you actually cycle the power? This is where Test Vectors and Simulation come into play.

Defining test vectors and simulating them is a breeze. Test vector statements come in two flavors. First a statement that defines the symbols to display when running the simulation and secondly, the inputs to apply along with the expected outputs from the simulation:

Following along at the end of your ABLE / AHDL file between the last equation and the 'END' statement is where the compiler will expect to find your test vectors and stimulus. Let's look at the two parts of the vector definition and the stimulus definition separately.

Vector Definition Input

The syntax for a vector definition is pretty straightforward. The statement is started with the Test_Vectors declaration and followed by a formatted list of symbols to display during the simulation. All symbols listed in square brackets to be displayed as input signals during the simulation must come before the '->' directive. Groups of signals may be entered for display as well. The following example shows the test vector definition used to set up the AND gate example form Section 3 above.

Test_Vectors([SIGNAL_A,SIGNAL_B] -> [SIGNAL_Y])

In the example above the variables SIGNAL_A AND SIGNAL_B will be used for the inputs to the equations defined above in your AHDL file.

Vector Definition Output

Test Vector outputs are defined in the same line as the inputs. Referring to the example above under test vector inputs, the reader can see that the variable 'SIGNAL_Y' has been defined to be the output in the simulation. Outputs can be listed singly or a variable representing a number of grouped signals can be expressed to display output as an 8-bit variable or other.

Vector Definition Stimulus

The Power of logic simulation comes from the specification of stimulus to the simulator. Each line of defined stimulus creates one or more lines in the output simulation file. Stimulus lines follow the same format as the vector definition lines presented above. A digital input representing the input for each signal defined in the vector definition must be present as well as an expected digital output for each signal defined in the vector stimulus. Often, the designer does not know what the output will be, or does not care. There are some specifics to help define these.

Table 4.1

LOGICAL ONE 1
LOGICAL ZERO 0
HIGH IMPEDANCE .Z.
DON'T CARE .X.
CLOCK LOW-HIGH-LOW .C.
CLOCK LOW-HIGH .U.
CLOCK HIGH-LOW .D.
REGISTER PERELOAD .P.
looping @ REPEATE X{}

Above, Table 4.1 defines the inputs and output stimulus more commonly used in testing programmable logic designs. 0 & 1 are self-explanatory when used as inputs. When used in the output section of a stimulus line, the designer is telling the compiler to compare these 'expected' values against whatever was actually calculated and flags an error if a discrepancy occurs. The .X. input or output specifies a 'don't' care state. Commonly used to specify inputs to parts of logic in a design when testing others. When used as an output .X. tells the compiler to fill in the blank. The .Z. specifies a signal that is in a high-impedance state for either inputs or outputs. This can be important when simulating signals that have internally programmed pull up resistors on their pins. Finally, the .C. input is used when simulating state machines and nodes that need to experience a high going edge sensitve pulse. Depending on the amount of cascaded logic attached, the .C. -input stimuli can create multiple lines of output. (Ripple) Finally, the repeat directive can be used to simulate 1000 iterations of a function, like clocking a state machine or counter. The syntax for @ REPEATE X{stimuli}; requires all of the stimuli to lie within the curly braces and the X representing the number of times to repeat the enclosed lines.

The example below shows how to fully test an 'AND' gate such as that defined in Section 3. The first two lines define a specific output that is 'expected' to be found by the simulation, while the last two lines specify a don't care state that the compiler will fill in upon running the equations through the simulation.

MODULE _AND_
TITLE 'THIS FILE DEMONSTRATES IMPLEMENTATION OF AN AND GATE WITH ASSOCIATED TEST VECTORS'
	SIGNAL_A PIN 12;
	SIGNAL_B PIN 13;
	SIGNAL_Y PIN 13 ISTYPE 'COM';
EQUATIONS
	SIGNAL_Y = SIGNAL_A & SIGNAL_B;
Test_Vectors([SIGNAL_A,SIGNAL_B] -> [SIGNAL_Y])
"four tests for the logic of an and gate
@ REPEATE 2 {
	[0,0]->[0];
	[0,1]->[0];
	[1,0]->[.X.];
	[1,1]->[.X.]; };
END

The example above shows an extremely simple case, however as with everything else in the ABLE/AHDL language, the test cases are scaleable and can be setup iterativly to generate complex testing automatically. Two output formats can be generated. The first of these is a table format, which simply verifies each line of the input stimulus against the corresponding 'expected' output. The second is a graphical representation of the output, which can be configured to display waveforms, and groped variables as busses. Figure 4.1 below depicts the wave form output below for the previous example being run twice.

FIGURE 4.1

Make no mistake the wave form timing model simulator provided with this package is quite powerfull. Figure 4.2 (below) from one of the authors previous projects demonstrates some of the bus grouping features available. For more information on this tool refer to the online documentation and the instruction PDF file available from the Lattice web site.

FIGURE 4.2

All of the tools required to simulate equations using the logic stimulus that the designer provides is included within the Lattice tools. It's a bit strange, as the functions to simulate designs within the Synario product do not become available (as a matter of fact, they're not even there) until after the designer defines vectors and test stimulus with their design.


SECTION 5: AHDL EXTENSIONS PART I (*.DOT OPERATORS)

In the last section the article deviated from the design aspect a bit to present testing and simulation. In this section the author will bring us back on track with a review of the first AHDL operators, DOT operators.

Nodes provide the power required to handle more complex time dependant logic. With nodes come the ability to design register and complex state machines. However, before we can fully use nodes, the author will present some of the AHDL extensions to the ABLE language required to fully understand their implementation.

One of the more powerful language extensions that was fully defined and standardized under AHDL is the cross platform equation .DOT extensions. These are summarized in Table 5.1 (below). Additionally, Figure 5.1, (below), provides a visual representation to help create a mental image of one possible layout.

TABLE 5.1

*.PIN Used to access the signal present on a physical pin outside all buffers and drivers.
*.OE Output Enable, used in conjunction with the tri-state function.
*.D Data input to a REG_D type flip-flop.
*.Q Q output from a Reg_D type flip-flop.
*.CLK Clock input to a Reg_D type flip-flop.
*.AR Asynchronous Reset input to a Reg_D type flip-flop.
*.AP Asynchronous Preset input to a Reg_D type flip-flop.

FIGURE 5.1**

This may look pretty complex, but as well explore later (Section 7) the designer does not need a full understanding of the layout of the entire CPLD, only the basics of product terms, registers and output routing are required to implement designs of moderate complexity.

The examples presented here-in focus on 'D' type-registered flip-flops. Language extensions and definitions exist and are available within the software package that define other flip-flop types, 'JK', 'T', 'SR'. A review back to one's college based digital logic basics should bring to the forefront of memory the fact that any kind of flip-flop may be reduced down to discrete logic. Using the 'D' type flip flop as a base it is easy to add the additional logic to create a 'JK' or other type flip-flop without depending on the definitions within the compiler if one so chooses, Figure 5.2 (below):

FIGURE 5.2**

FIGURE 5.3**

If the reader so chooses, REG_JK, REG_T and REG_SR are defined within the package. With these the reader accesses member functions by the *.J, *.K, *.T, *.S & *.R .DOT operators. All of the other dot operators are still valid with these types of flip-flops as well.

As in the previous section, the above *.DOT operators will be demonstrated through a series of examples. Starting with the *.OE dot operator the following example will show how to enable the tri-state functionality of any output pin. We are going to make an invisible octal gate using DAT[7..0], OUT[7..0] and SIGNAL_A from the previous section. The octal gate will follow these rules. (When SIGNAL_A = 1 the inputs DAT[7..0] will drive the outputs OUT[7..0], however when SIGNAL_A = 0 the outputs OUT[7..0] will be high impedance.

	OUT[7..0] = DAT[7..0];
	OUT[7..0].OE = [SIGNAL_A, SIGNAL_A, SIGNAL_A, SIGNAL_A,
					SIGNAL_A, SIGNAL_A, SIGNAL_A, SIGNAL_A]

In the next example let's look at what it takes to change our octal gate into an octal transparent latch. Let's model these equations to look like the popular 74xx373 series. To do this well need to add some more data definitions and it's been strung on long enough in this article, for convenience, let's re-present all of the data definitions as well as the full file spec.

To make this work we'll need to access the data extensions that access the members of the D-Type flip-flops that we'll be using for the nodes to store the byte. First of all, we'll use the *.D as the input to each of the flip-flops. Then we'll access the *.CLK on all of the flip-flops to effect a parallel load of our byte wide register. Next we'll use the *.Q output from the eight flip-flops to send the data to the output port. Finally we'll use the *.OE dot extension on the output pins to control the Tri-State functionality of the output pins.

NOTE: THE PIN NUMBERS IN THIS EXAMPLE DO NOT MATCH THOSE IN A REAL PART, AND SHOULD BE MODIFIED TO MATCH THE PART THAT YOU CHOOSE TO FIT YOUR DESIGN TO.

MODULE oct_latch
TITLE 'demonstration of 74xx373 octal transparent latch'
// First define the inputs
	CK PIN 12;
	ENB PIN 13;
	DATA_0 PIN 14;
	DATA_1 PIN 15;
	DATA_2 PIN 16;
	DATA_3 PIN 17;
	DATA_4 PIN 18;
	DATA_5 PIN 19;
	DATA_6 PIN 20;
	DATA_7 PIN 21;
// Then define the outputs
	OUT_0 PIN 2 ISTYPE 'COM';
	OUT_1 PIN 3 ISTYPE 'COM';
	OUT_2 PIN 4 ISTYPE 'COM';
	OUT_3 PIN 5 ISTYPE 'COM';
	OUT_4 PIN 6 ISTYPE 'COM';
	OUT_5 PIN 7 ISTYPE 'COM';
	OUT_6 PIN 8 ISTYPE 'COM';
	OUT_7 PIN 9 ISTYPE 'COM';
// Next define the nodes
	NODE_7..NODE_0 NODE ISTYPE 'REG_D,BUFFER,KEEP';
// Now define data groupings to make things easier to work with
	DAT = [DATA_7,DATA_6,DATA_5,DATA_4,DATA_3,DATA_2,DATA_1,DATA_0];
	OUT = [OUT_7,OUT_6,OUT_5,OUT_4,OUT_3,OUT_2,OUT_1,OUT_0];
	NODES = [NODE_7,NODE_6,NODE_5,NODE_4,NODE_3,NODE_2,NODE_1,NODE_0];
EQUATIONS
	NODES[7..0].D = DAT[7..0];
	NODES[7..0].CLK = [CK,CK,CK,CK,CK,CK,CK,CK];
	OUT[7..0] = NODES[7..0].Q;
	OUT[7..0].OE = [!ENB,!ENB,!ENB,!ENB,!ENB,!ENB,!ENB,!ENB];
// REMEMBER ACTIVE LOW ENABLE REQUIRES !ENB
END

Let's side track a moment to look at counters. The following example generates an up / down counter that counts only when enabled. When the counter is greater than 7 an output is set. When the counter reaches 11, it is reset to 0. When the counter is counting down and reaches zero it will wrap around to 16 Note: that counters can also be defined by using the := syntax, (Counter_var := Counter_var + 1) which will cause the variable Counter_var to increment every time Counter_var.clk experiences a high going leading edge. However the author chooses to present notation using the *.Q and *.D Dot operators as in more complex cases they ten to help clarify the intended operation.

MODULE count_up
TITLE 'sample counter'
	ENB PIN 12;
	INC_DEC PIN 13;
	CK PIN 24;
	COUNT_0 NODE ISTYPE 'REG_D,KEEP,BUFFER';
	COUNT_1 NODE ISTYPE 'REG_D,KEEP,BUFFER';
	COUNT_2 NODE ISTYPE 'REG_D,KEEP,BUFFER';
	COUNT_3 NODE ISTYPE 'REG_D,KEEP,BUFFER';
	SIG_OUTPUT PIN 1 ISTYPE 'COM';
	COUNTER = [COUNT_3,COUNT_2,COUNT_1,COUNT_0];
EQUATIONS
	WHEN(INC_DEC == 1) THEN COUNTER[3..0].D = (COUNTER[3..0].Q + 1);
	WHEN(INC_DEC == 0) THEN COUNTER[3..0].D = (COUNTER[3..0].Q - 1);
	WHEN(ENB == 1) THEN COUNTER[3..0].CLK = [CK,CK,CK,CK]
		ELSE COUNTER[3..0].CLK = [0,0,0,0];
	WHEN(COUNTER[3..0].Q >= 7) THEN SIG_OUTPUT = 1;
	WHEN(COUNTER[3..0].Q >= 11) THEN COUNTER[3..0].AR = [1,1,1,1];
END

Are you getting the idea here? Let's look at one more example here to finish up our look at the dot operators. That operator is *.PIN, and it is used when pins need to be Bi-directional. Remember that Bi-directional pins require tri-state control as well. To use a pin as Bi-directional simply refer to the pin name as name.pin when referencing it as an input and make sure name.oe = 0. To use it as an output, reference it by name without any extension.

In this example we'll build a 74xx245 Bi-directional bus buffer.

MODULE Bi_Dir
TITLE 'demonstration of 74xx245 octal transparent reg.'
// First define the inputs
	DIR PIN 12;
	ENB PIN 13;
	DATA_0 PIN 14 ISTYPE 'COM';
	DATA_1 PIN 15 ISTYPE 'COM';
	DATA_2 PIN 16 ISTYPE 'COM';
	DATA_3 PIN 17 ISTYPE 'COM';
	DATA_4 PIN 18 ISTYPE 'COM';
	DATA_5 PIN 19 ISTYPE 'COM';
	DATA_6 PIN 20 ISTYPE 'COM';
	DATA_7 PIN 21 ISTYPE 'COM';
// Then define the outputs
	DATA_10 PIN 2 ISTYPE 'COM';
	DATA_11 PIN 3 ISTYPE 'COM';
	DATA_12 PIN 4 ISTYPE 'COM';
	DATA_13 PIN 5 ISTYPE 'COM';
	DATA_14 PIN 6 ISTYPE 'COM';
	DATA_15 PIN 7 ISTYPE 'COM';
	DATA_16 PIN 8 ISTYPE 'COM';
	DATA_17 PIN 9 ISTYPE 'COM';
// Now define data groupings to make things easier to work with
	DAT = [DATA_7,DATA_6,DATA_5,DATA_4,DATA_3,DATA_2,DATA_1,DATA_0];
	DAT2 = [DATA_17,DATA_16,DATA_15,DATA_14,DATA_13,DATA_12,DATA_11,DATA_10];
EQUATIONS
	WHEN((ENB == 0) & (DIR == 1))THEN{DAT2[7..0] = DAT[7..0].PIN;
							DAT2[7..0].OE = [1,1,1,1,1,1,1,1];};
	WHEN((ENB == 0) & (DIR == 0))THEN{DAT[7..0] = DAT2[7..0].PIN;
							DAT[7..0].OE = [1,1,1,1,1,1,1,1];};
END

SECTION 6: AHDL EXTENSIONS PART II (STATE MACHINES)

One of the last and most powerful extensions to be standardized into the AHDL language in it's face lift from the ABLE days implements a powerful state machine context. One of the features of the Synario package is in parsing and compiling state machines. If a give state machine has a binary multiple number of states given by 2^N then the corresponding state machine will only require N flip-flops to implement. Other wise the state machine will still be realized in N+1 flip-flops.

State machines have the following requirements. First define enough nodes to hold the number of states for the state machine based on the equations in the preceding paragraph. Then group the nodes together in a variable. Define a State Name. Define the clock source for the states, and define the actions to take place in each state as well as the transition conditions that take place between states. In the first example, a simple parallel shift register is constructed.

MODULE Bi_Dir
TITLE 'demonstration of 74xx245 octal transparent reg.'
// First define the inputs
	CK PIN 24; 	//THIS PIN LINES UP WITH ONE OF THE CLOCK LINES ON THE PART
	ENB PIN 12;
	DATA_0 PIN 14;
	DATA_1 PIN 15;
	DATA_2 PIN 16;
	DATA_3 PIN 17;
	DATA_4 PIN 18;
	DATA_5 PIN 19;
	DATA_6 PIN 20;
	DATA_7 PIN 21;
// Then define the nodes
	NODE_0 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_1 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_2 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_3 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_4 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_5 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_6 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	NODE_7 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	ST0 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	ST1 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	ST2 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	ST3 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	COUNT_NODE_0 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	COUNT_NODE_1 NODE ISTYPE 'REG_D,BUFFER,KEEP';
	COUNT_NODE_2 NODE ISTYPE 'REG_D,BUFFER,KEEP';

// Then define the outputs
	SERIAL_OUT PIN 2 ISTYPE 'COM';
// Now define data groupings to make things easier to work with
	DAT = [DATA_7,DATA_6,DATA_5,DATA_4,DATA_3,DATA_2,DATA_1,DATA_0];
	LATCH = [NODE_7,NODE_6,NODE_5,NODE_4,NODE_3,NODE_2,NODE_1,NODE_0];
	ST_REG = [ST3,ST2,ST1,ST0];
	PARITY_COUNT = [COUNT_NODE_2,COUNT_NODE_1,COUNT_NODE_0];
EQUATIONS
	LATCH[7..0].D = DAT[7..0];
	LATCH[7..0].CLK = [ENB,ENB,ENB,ENB,ENB,ENB,ENB,ENB];
	PARITY_COUNT[2..0].D = (PARITY_COUNT[2..0].Q + 1)
	ST_REG.CLK = [CK,CK,CK,CK];

STATE_DIAGRAM ST_REG;

STATE 0:
	SERIAL_OUT = 0;	// AUTOMAGICALLY START WHEN NEW BYTE IS WRITTEN
	IF(ENB == 1) THEN 1
ELSE 0;
STATE 1:
	SERIAL_OUT = 1;	// START BIT
	PARITY_COUNT[2..0].AR = [1,1,1];	//RESET THE PARITY COUNTER
	GOTO 2;
STATE 2:
	SERIAL_OUT = NODE_0.Q;
	IF(NODE_0 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 3;
STATE 3:
	SERIAL_OUT = NODE_1.Q;
	IF(NODE_1 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 4;
STATE 4:
	SERIAL_OUT = NODE_2.Q;
	IF(NODE_2 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 5;
STATE 5:
	SERIAL_OUT = NODE_3.Q;
	IF(NODE_3 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 6;
STATE 6:
	SERIAL_OUT = NODE_4.Q;
	IF(NODE_4 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 7;
STATE 7:
	SERIAL_OUT = NODE_5.Q;
	IF(NODE_5 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 8;
STATE 8:
	SERIAL_OUT = NODE_6.Q;
	IF(NODE_6 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 9;
STATE 9:
	SERIAL_OUT = NODE_7.Q;
	IF(NODE_7 == 1) THEN PARITY_COUNT[2..0].CLK = [1,1,1];
	GOTO 10;
STATE 10:
	IF(PARITY_COUNT[0].Q == 1) THEN SERIAL_OUT = 1 ELSE SERIAL_OUT = 0;
	GOTO 11;
STATE 11:
	SERIAL_OUT = 1;	// STOP BIT
	GOTO 0;
STATE 12:
	GOTO 0;
STATE 13:
	GOTO 0;
STATE 14:
	GOTO 0;
STATE 15:
	GOTO 0;
END

There are other more elegant ways to write the equations to implement a serial shift register however the state machine presented above is an easy to read and fast to develop solution. It also makes the calculation of parity a distributed task where the overhead to do the actual calculation is spread out over the states used to send each individual bit.

In the example above state 12 - 15 are defined specifically for use during power up. Instead of including a state machine reset equation involving the *.AR and *.AP feature of the state machine it was simply easier for the sake of this example to ensure that any invalid state had a properly defined exit. This is a good practice to get into to aid in recovery from abnormal operation duing debugging and odd power transients.

In the next example the author introduces another helpful state machine four counting grey code. This snippet assumes that an x-bit variable COUNTER has already been defined. This example will also introduce the with statement. With is used to specify transition logic to be applied 'between' state. It's usage follows along the line of: If x then goto y with {statements to do on the way}.

STATE 0:
	ERROR_OUTPUT = 0;
	WHEN (ENC_INPUT_BITS[1,0] == [0,0]) THEN GOTO 0
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [0,1]) THEN GOTO 1
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q + 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [1,1]) THEN GOTO 3
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q - 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [1,0]) THEN GOTO ERROR;
STATE 1:
	ERROR_OUTPUT = 0;
	WHEN (ENC_INPUT_BITS[1,0] == [0,1]) THEN GOTO 1
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [0,0]) THEN GOTO 0
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q - 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [1,0]) THEN GOTO 2
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q + 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [1,1]) THEN GOTO ERROR;
STATE 2:
	ERROR_OUTPUT = 0;
	WHEN (ENC_INPUT_BITS[1,0] == [1,0]) THEN GOTO 2
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [0,1]) THEN GOTO 1
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q - 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [1,1]) THEN GOTO 3
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q + 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [0,0]) THEN GOTO ERROR;
STATE 3:
	ERROR_OUTPUT = 0;
	WHEN (ENC_INPUT_BITS[1,0] == [1,1]) THEN GOTO 0
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [1,0]) THEN GOTO 2
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q - 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [0,0]) THEN GOTO 0
                 WITH {COUNTER[X..0].D = COUNTER[X..0].Q + 1; COUNTER[X..0].CLK = [1,1,.....,1]}
	ELSE WHEN (ENC_INPUT_BITS[1,0] == [0,1]) THEN GOTO ERROR;
STATE ERROR:
	ERROR_OUTPUT = 1;
	GOTO ERROR;

SECTION 7: Interfacing to the hardware...

So the software (firmware/syntax) end of things looks to be straight forward and easy to use, but what about the hardware? Some of these packages look pretty complex to set up and use, but don't let that stop you because in this section, the author, will show exactly how easy it can be to work with these parts.

From the hardware standpoint the hardest part to deal with can be the package foot print. Many of the entry level parts use a 44 pin PLCC package that most people prototyping or bread boarding just don't want to work with. Let's start by looking at an extremely helpful device that can alleviate much of the headache potential in dealing with this problem. A few companies now make PLCC to wire wrap adapter sockets, like the one pictured below (Figure 7.1). Note also, that the left corner of the socket has a diagonal corner "cut" off. This is to help denote the orientation of the part. Since most of these parts are square care should be taken when pushing the parts into the socket, as a part that is forced will go in with the wrong orientation. (Figure 7.3) shows how the same socket will appear from the bottom when used in a wire wrapped proto-type board. Figure 7.2 provides a close up of this typical 44 pin socket in on a wire wrap board. Later the author will present a quick review of many of the different packages styles available to the CPLD user.

FIGURE 7.1

FIGURE 7.1

FIGURE 7.3

FIGURE 7.4

The first of the four pictures (above) show the actual wire wrap socket as you buy it off the shelf from any of a number of sources. (Try Allied at www.allied.avnet.com) The second and third images show a board, hand wrapped by the author, several years back, using just such a socket. The real gotch-ya with these sockets, like any other wire wrap project, is keeping the pin numbers straight when flipping the board over to wire wrap from the bottom side. (Figrue 7.5 below) Remember that the pin numbers go counter clockwise when the board is upside down. The final picture shows a non-wire wrapped adapter socket being used in a proto-type situation on a quick turn two-layer circuit board.

FIGURE 7.5 - (Top View)

Now that we have the part in a socket and the socket stuffed into a piece of proto-board all that is required to finish up our connections are a set of ISP (in system programming) header pins. If you have an external programming board and choose to program the parts before inserting them into the socket, then the ISP programming header pins are not needed. This can be useful when laying out high density surface mount boards where space is at a premium and there isn't any room for the header, but on most prototypes adding an extra 8 header pins is just a matter of moving a few parts around before the reader begins to wire wrap. The header pins are laid out in order to match the end of the download cable. The download cable can be constructed from a single IC and a couple of discrete components. Figure 7.6 (below) shows the layout of the components required in the download cable. Once built this download cable attaches between the header on your PC and the header pins on your board. There is one other piece required, a decoupling capacitor on the /ISPEN signal on your board. This small 0.1uF cap helps to keep the part from going into programming mode while the download cable is not attached. Figure 7.7 shows the pin-out required for the connection from the parallel port adapter to the PCB.

FIGURE 7.6**

FIGURE 7.7**

(Figure 7.8) (below) shows from the schematic side what is required to hook up the In system programmable header and capacitor to implement a Lattice Semiconductor ISPLSI1016E component into your design.

Look closely at J28, there you will see the ISP header that brings all of the signals onto the board. Note: that pin #5 has been removed. A keyed programming cable is used with these header pins to keep the device from being plugged together backwards. This is helpful as the download cable derives it's power from this header. I.E. power is fed back up the connector to the IC in the cable. This prevents the need to pull any power from the PC's parallel port and provides a small amount of isolation.

U14 is the ISP part itself. This is a ISPLSI1016E-100LJ part. The reader may recognize it from the author's 68332-board design. In this implementation, it is being used to implement a simple PIA (Programmable Interface Adapter). As the final example the author will present the source code required to configure the part to implement this functionality.

FIGURE 7.8


SECTION 8: Specifics on CPLD parts.

The following section takes a look at one CPLD part in particular. This part is the ISPLSI1016E-100LJ by Lattice Semiconductor. The free software that can be downloaded from Lattice's web site fully supports this device and it packs quite a bit of power into it's small 44 pin PLCC package. When the reader is ready to layout a board, and really needs to reduce the size of things, this part is also available in a fine pitch surface mount TQFP package.

The number of registers within the part commonly defines CPLD's. This is a good measure of the power of a CPLD, but it does not tell the whole story. Also of importance are the number of product terms, global routing arrays, input routing pools, output routing pools, and of course the tools to support all of this. Below, the author has attached the data sheet for the 1016 part from Lattice. Also, an overview data sheet of the entire 1000 series family is included.


1016_Data_Sheet.PDF

1000_Series_Archatecture.PDF

If the reader takes some time to review the PDF data sheet on the 1000 series family of product several of the enhancements to the PLD product that make it a CPLD become obvious. Specifically, features like the output routing pool that allow the registers/logic to be separated from a particular pin demonstrate their power. To the user, this means that the logic may be written without assigning particular pins, and letting the compiler take care of this feature for you lending the fitters within the product optimize the route for either speed for fit. Much like routing a PCB, in the routing of (or fitting of) a CPLD, optimizing for speed or size are often inversely proportional. The output routing pool sits between the registers/logic, and the pins them self, so that differing pins may (at the cost of speed) be hooked up to alternate logic. This is a powerful feature as it allows signals to be moved between pins after the board (PCB) layout has been finished.

Figure 8.1**

The graphics in Figure 8.1 - 8.3 come directly from the Lattice data sheet (PDF) provided above.

Another complex feature within the CPLD is the global routing pool. To imagine this, the reader can imagine a huge array of routes that bring every input signal in the chip into a large grid within the center of the part. Around this giant array sits all of the logic AND/OR product term arrays and registers. The product terms are hooked up to this giant global routing pool by the compiler. Borrowed directly from the Data sheet Figrue 8.2 (below) graphically depicts a partial view of the global routing pool, the product term array(condensed), and the generic logic blocks.

Figure 8.2**

The next major block in a CPLD is the generic logic block. These blocks contain 4 flip flops and the product term array and logical or array. These parts are represented in Figure 8.2 above. For readability reasons the number of AND blocks and OR blocks have been reduced from 19x19 to one representative part.

To put the power of some of these chips into perspective, the 1016 that this article focuses on, which is a relatively small part has 16 generic logic blocks each contain the 4x logic and product term array discussed above. For further details refer to the PDF data sheet provided above.

The last block within the CPLD itself is the IO cell. This cell is attached to a physical pin on the outside of the part. The inputs to this cell are two fold, both the output routing pool, and direct inputs from the global logic pool as well. The outputs from this Cell are also two fold, feedback to the Global Routing Pool and also the pin on the chip. Notice in Figure 8.3 below, the architecture of the 1000 series parts offers an input latch. This is an incredibly powerful feature, being able to latch an input to hold, then use the pin as an output and later using the previously latched input.

Figure 8.3**

This is where the real power of the AHDL language comes from. The user does not need to know the specifics of the timing of each individual gate, or even which ones will be used, as long as the worst case latency is calculated. The software provides this worst case latency calculation for you so that you can stay concentrated on the logic design itself. This is what makes AHDL such a powerful 'high level' logic synthesis language. It is somewhat hardware independent and the user does not require an full knowledge of the target silicon past it's general architecture and pin layout.

Because of it's importance I'll re-paraphrase the paragraph above:

!!!THE USER NEED NOT FULLY UNDERSTAND ALL OF THE DETAILS OF THE HARDWARE OR TIMING SPECIFICS WITHIN THE CHIP TO MAKE USE OF IT!!!

Although the author has not covered it in this article, the Synario package also offers a full schematic entry program as well. Entered from the project manager, logic may be described by drawing representative CAD blocks and hooking them together. This is a powerful editing tool for conveying the functionality of a design, as graphical presentation means work better with beginners. The author has specifically steered away from this means of data entry for two reasons, Speed and Speed. The first of this is design entry. It is not uncommon to sit down and write a complex AHDL script in an hour or so followed by hooking up and debugging the hardware all in an afternoon. Using the schematic entry tool takes considerably longer. Secondly, Speed, making changes to AHDL modules defined by equations with bit wide variable defined into bytes, words etc, and be modified with a few keystrokes from the keyboard, where modifying a schematic to change pin numbers or move byte wide variables can take hours. Commonly if a schematic is required to document a design the reader would (in the authors opinion) be better off to write the script from the AHDL equation stand point, debug and get the hardware up and running, finally when the design has been proven, tested and re-tested, go back and generate the schematic from the finished equations.

CPLD SIZING:

CPLD's come in a variety of sizes in both packaging and logic densities. Packaging is an important aspect in the design choice when using CPLD's with mobile robotics. Of primary importance, foot print real-estate issues generally dominate the list. The package selector (below) outlines many of the common package footprints available for their product line. As the ever increasing pin count indicates, logic densities within the packages have been growing steadily for years.


Package Selector - PDF
The growing trend in package size (with respect to the number of pins) discussed in the paragraph above has been equally matched by a proportional match increase in logic density. Lattice and others offer many powerful product lines each with their own unique sets of features. From products powerful enough for development of processor platforms all the way down to simple combinatorial logic such as that discussed in this article, technology is available, today to handle the case. Below I've included the data sheets for the 1000 series product line. Lattice offers several other product lines (as do other CPLD manufactures) that offer features such as ram arrays, Fifo's, Dual ports, and all manner of other logic, register functionality needed to implement even the most complex design. For more information beyond the scope of this article the author recommends visiting the respective companies web sites. There taking the three - five minutes required to register in order to access the data storage area and software download, the reader will have access to all the software required to get up and running with their first designs.

If some of these fine pitch surface mount packages look a little daunting don't fret. All of the 1000 series are available in PLCC for which socket adapters exist. If you need still more logic and pin count only available in other packages, again help is on the way. In an up coming encoder article the author will present a method for soldering up fine pitch surface mount parts in a toaster oven. Sound too good to be true? Wait till you see it and be as astounded, as the author was when he learned in his own kitchen.


SECTION 9: Final Design, PIA implementation in a CPLD.

This section will wrap up all of the concepts presented by the author by presented a completed design for a PIA (Programmable Interface Adapter). A schematic layout showing the connections of this part is depicted above at the end of Section 7 - Figure 7.7.

The PIA has on one side an interface to 8 bits of a microprocessor data bus, control signals and some timing signals. Some of these timing signals are not used in this example. On the other side are two eight bit ports used for interfacing the board to external devices. The actual implementation that the author originally targeted this design into brought these sixteen signals out to header pins where they could be accessed to move signals between boards. These could then be used to interface to any other digital IO device.

What makes a PIA programmable is the ability to change the bits of each port between being an input port or an output port under software control. To achive this programmability a two bit register 'CONTROL_REGISTER' is set up within the CPLD. When ever the processor system writes to memory location CS7 + 2, the two low order data bits are coppied into this register. Bit 0 controls port a and bit 1 controls port b. Setting either to a value of '1' causes the corresponding port to be defined as an output, enableing it's output drivers. As a convenience to the user, a read from CS7 + 2 puts the contents back onto the data bus. When the user writes to CS7 + 0 the contents of the data bus is coppied into an internal register (OUT_REG1). The Q outputs from this register are connected directly PORT_A. They are, however, only output if the corresponding output enable is driven from the control register as discussed previously. PORT_B functions similarly. The reading function is multiplexed depending on the address being read. PORT_A is read when address = CS7 + 0, PORT_B is read when address = CS7 + 1, and finally, the control register is read when address = CS7 + 2. A read from Port_A or Port_B while the corresponding control register is set to an 'output' state will read back the value last written to the port.

Note: The only thing that is not straight foreward is the assignemnt of the output enables for DATA[7..0].OE. These are assigned to a special function pin called global enable 0. Even though the equations to generate this output enable are contained within the logic of the chip, the output from these equations had to be run out one generic IO pin on the chip, and back in through the GLOBAL LOGIC ENABLE 0 pin. One limitation of the chip is that there are only two internal Output Enables (Signal_name.OE) available within the chip, and the author had already used both of them in this design. See PORTA.OE & PORTB.OE in the file below. One way around this is to use one or both of the two other dedicated global enable pins. The drawback to this is that these signals must come from outside the chip. The number of output enables is a function of the number of logic blocks available on the 1000 series parts. Different manufactures handle this in different ways.

MODULE pia_cpld
TITLE 'this file describes the PIA function'
// DEFINE INPUTS
    GLB_ENB PIN 2;
    DATA8 PIN 15;
    DATA9 PIN 16;
    DATA10 PIN 17;
    DATA11 PIN 18;
    DATA12 PIN 19;
    DATA13 PIN 20;
    DATA14 PIN 21;
    DATA15 PIN 22;
    R_W PIN 25;
    ADDR0 PIN 26;
    ADDR1 PIN 27;
    L_CS7 PIN 28;
    TPUCH0 PIN 31;
// DEFINE OUTPUTS
    GLOBAL_ENABLE PIN 32 ISTYPE 'COM';
    PA0 PIN 37 ISTYPE 'COM';
    PA1 PIN 38 ISTYPE 'COM';
    PA2 PIN 39 ISTYPE 'COM';
    PA3 PIN 40 ISTYPE 'COM';
    PA4 PIN 41 ISTYPE 'COM';
    PA5 PIN 42 ISTYPE 'COM';
    PA6 PIN 43 ISTYPE 'COM';
    PA7 PIN 44 ISTYPE 'COM';
    PB0 PIN 3 ISTYPE 'COM';
    PB1 PIN 4 ISTYPE 'COM';
    PB2 PIN 5 ISTYPE 'COM';
    PB3 PIN 6 ISTYPE 'COM';
    PB4 PIN 7 ISTYPE 'COM';
    PB5 PIN 8 ISTYPE 'COM';
    PB6 PIN 9 ISTYPE 'COM';
    PB7 PIN 10 ISTYPE 'COM';
//DEFINE NODES
    REG0 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    REG1 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT1 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT2 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT3 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT4 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT5 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT6 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT7 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT8 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT9 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT10 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT11 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT12 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT13 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT14 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT15 NODE ISTYPE 'REG_D,BUFFER,KEEP';
    OUT16 NODE ISTYPE 'REG_D,BUFFER,KEEP';
//TEMPORARY NODES 'KEEP' NOT DEFINED SO THE COMPILER WILL REPLACE LATER
    TN1 NODE;
    TN2 NODE;
//DEFINE GROUPS
    DATA = [DATA15,DATA14,DATA13,DATA12,DATA11,DATA10,DATA9,DATA8];
    CONTROL_REGISTER = [REG1,REG0];
    OUT_REG1 = [OUT8,OUT7,OUT6,OUT5,OUT4,OUT3,OUT2,OUT1];
    OUT_REG2 = [OUT16,OUT15,OUT14,OUT13,OUT12,OUT11,OUT10,OUT9];
    PORTA = [PA7,PA6,PA5,PA4,PA3,PA2,PA1,PA0];
    PORTB = [PB7,PB6,PB5,PB4,PB3,PB2,PB1,PB0];
    ADDRESS = [ADDR1,ADDR0];
//DEFINE CONSTANTS THAT MAY BE USED
    H,L,X,Z,C,U = 1,0,.X.,.Z.,.C.,.U.;
//COMPILER DIRECTIVES
    PLSI PROPERTY 'ISP ON';
EQUATIONS
    CONTROL_REGISTER[1..0].D = DATA[1..0].PIN;
    CONTROL_REGISTER[1..0].CLK = [(!L_CS7 & (ADDRESS == 2) & !R_W),
                                  (!L_CS7 & (ADDRESS == 2) & !R_W)];
    OUT_REG1[7..0].D = DATA[7..0].PIN;
    OUT_REG2[7..0].D = DATA[7..0].PIN;
    TN1 = (!L_CS7 & (ADDRESS == 0) & !R_W);
    OUT_REG1[7..0].CLK = [TN1,TN1,TN1,TN1,TN1,TN1,TN1,TN1];
    TN2 = (!L_CS7 & (ADDRESS == 1) & !R_W);
    OUT_REG2[7..0].CLK = [TN2,TN2,TN2,TN2,TN2,TN2,TN2,TN2];
    PORTA[7..0] = OUT_REG1[7..0].Q;
    PORTB[7..0] = OUT_REG2[7..0].Q;
    PORTA[7..0].OE = [REG0.Q,REG0.Q,REG0.Q,REG0.Q,
                      REG0.Q,REG0.Q,REG0.Q,REG0.Q];
    PORTB[7..0].OE = [REG1.Q,REG1.Q,REG1.Q,REG1.Q,
                      REG1.Q,REG1.Q,REG1.Q,REG1.Q];
    WHEN(ADDRESS == 0)THEN DATA[7..0] = PORTA.PIN;
    WHEN(ADDRESS == 1)THEN DATA[7..0] = PORTB.PIN;
    WHEN(ADDRESS == 2)THEN DATA[1..0] = CONTROL_REGISTER[1..0].Q;
//IN THE LINES BELOW IT IS IMPORTANT TO REFER TO THE SCHEMATIC AND
//NOTE THAT GLOBAL_ENABLE IS CONNECTED TO GLB_ENB.  THIS IS DONE
//FOR REASONS OF REDUCED OUTPUT ENABLES
    WHEN(!L_CS7 & R_W) THEN GLOBAL_ENABLE = 1;
    DATA[7..0].OE = [GLB_ENB,GLB_ENB,GLB_ENB,GLB_ENB,
                     GLB_ENB,GLB_ENB,GLB_ENB,GLB_ENB];
END

The AHDL file above provides a logical and efficient way to describe the implementation of a PIA's functionality. The picture below, (Figure 9.1) is a digital image of the hardware used to implement this scheme. The reader can look for and identify, the ISP programming header, the decoupling cap, the part with associated adapter socket, and the header pins used to interface all of the digital IO to other PCB's.

FIGURE 9.1


NOTE: Images makred with the (**) were obtained from Lattice documentation, available for downlod through the internet.

It is the sincere hope of the author that this provides enough information to the user to jump-start their entry into the programmable logic world. Hopefully the examples provided contain enough information to extend that basic kick-start to a fully working design in just an afternoon's time.

Contact the author at: (kmaxon@uswest.net)

Come visit me and share the state of robotics in my corner of the world. Kenneth's Web Page

The board used in this article is available... Max's Little Robot Shop

Drinking Diet Coke - N - Gettin' it done.... Kenneth