next up previous contents
Next: Using the Logic Simulator Up: Iowa Logic Simulator User's Previous: Introduction

Subsections

The Logic Specification Language

A circuit description in the Iowa Logic Specification Language consists of 5 sections. These sections describe the circuit name, the circuit inputs, the circuit outputs, the parts used to make the circuit, and how the inputs, outputs, and parts are to be wired together. These sections must be given in the order listed.

Circuit descriptions are free format, so the indenting rules used in the following material are actually optional. The punctuation marks comma and semicolon are also optional, but their use is strongly encouraged, as they greatly improve the readability of circuit descriptions.

Comments may be included in circuit descriptions, and their use is encouraged. Blank lines may be used to delimit or separate blocks within a description. Marginal comments (along the right margin, as in assembly language) may be used, set off from the circuit description text by pairs of dashes (--). Comments within the circuit description may be bracketed with curly brackets (as in Pascal, where { and } or (* and *) mark comments).

Within a circuit description, a number of identifiers are used to name such things as gates and input or output connections. Identifiers may consist of any number of characters as long as the indentifier fits on one line, and as long as it consists of a letter followed by a sequence of letters and digits. All characters of an identifier are significant, and reserved words may not be redefined.

The Circuit Name

Every logic circuit description must begin with a declaration of the circuit name. Name declarations begin with the keyword circuit, followed by an identifier used as the circuit name. For the example circuit, which turns out to be a type D latch, the following declaration might be used:

circuit dlatch;   -- designed by D. W. Jones
By convention, the circuit designers name should be put in a comment in the circuit header, as shown above.

The Input Connections

Most logic circuits will have one or more input connections. These must be declared immediately following the circuit name using the keyword inputs, followed by a list of input names. For the example circuit, the inputs may be declared as follows:

inputs  d, c;
It would be better to put a comment on each input explaining what it is for, as in the following:
inputs
        d {data},
        c {clock};
Spelling out the appropriate mnemonic names would have eliminated the need to include these comments, but long names for the inputs or outputs can lead to hard to read simulation results (see Chapter 3), and they do not improve the readability of small circuits such as the example.

The Output Connections

To be worth simulating, a logic circuit must have output connections. These are declared immediately after the inputs, using the keyword outputs and a list of output names. For the example circuit, the following declaration would be appropriate:

outputs
        q {the stored data value},
        qbar,
        dout {echo input d},
        cout {echo input c};
Note that qbar needs no comment, since (by convention), the suffix bar is used to indicate that this is the inverse of q.

The Parts List

After the inputs and outputs are declared, any parts used in the circuit must be declared. (Trivial circuits without parts are possible, since these may consist of inputs wired directly to outputs). The parts used in a circuit are declared after the keyword parts. Each part declaration consists of a list of new part names, a colon, and the part type to be used for those names. There are a number of predefined part types, and new part types may be defined as needed using the subcircuit mechanism. The predefined part types are listed below:


simple Boolean logic gates:
		not 		 - 		inverter
		and(n) 		 - 		n input and gate
		or(n) 		 - 		n input or gate
		nand(n)		 - 		n input nand gate
		nor(n) 		 - 		n input nor gate
		xor 		 - 		exclusive or gate
		equ 		 - 		equivalence gate
 
special gates:
		tsgate 		 - 		non-inverting three-state driver
		ntsgate		 - 		inverting three-state driver
		bus 		 - 		three-state bus
		latch 		 - 		type D latch (1 bit memory)
Note that with many of the Boolean logic gates, the number of inputs is variable and must be specified as a parameter to the part type. It should also be noted that, for the purposes of logic simulation, a three-state bus is a device, even though in a real circuit, it is merely a piece of wire. For the example circuit, there are 4 two-input nand gates, and 1 not gate, so the following parts declaration would do:
parts   gater, gates, ffq, ffqbar: nand(2);
        inverter: not;
It would be better to put comments on the individual definitions as follows:
parts   { basic rs flipflop gates }
        ffq, ffqbar: nand(2);

        { gates used to convert d and c to r and s }
        gater, gates: nand(2);
        inverter: not;
Commenting could be carried further, but would probably not make the resulting circuit definition any easier to read.

The Wire List

After declaring the parts, all that is needed to finish the circuit is a description of how the parts are connected to the inputs and outputs. This is done with a wire list starting with the keyword wires and ending with the keyword end. Each entry in the wire list consists of the name of a signal source, the keyword to, and a list of names of signal destinations.

Any input to the circuit is a valid signal source, as is the output connection of any previously declared part. The special signal sources high and low are also provided for constant inputs. The valid signal destinations include the outputs of the circuit and any unused inputs of a previously declared part. The following table lists the forms of the input and output connections of each of the types of gates supported by the logic simulator:

part name type of part inputs outputs
a not a.in a.out
b and(n) b.in(1) b.out
  or(n) to  
  nand(n) b.in(n)  
  nor(n)    
c xor c.in(1) c.out
  equ c.in(2)  
d tsgate d.control d.out
  ntsgate d.data  
  latch    
e bus e.in e.out
Thus, if b is the name of a 3 input nor gate, it will have inputs b.in(1), b.in(2), and b.in(3), and it will have an output named b.out. The example circuit performs exactly the same function as the built-in latch part type. The wire list for the example circuit could be given in the following form, but appropriate comments and blank lines (as shown in the next section) would make it easier to read:
wires
        d to gater.in(1), dout, inverter.in;
        inverter.out to gates.in(1);
        c to gater.in(2), gates.in(2), cout;
        gater.out to ffq.in(1);
        gates.out to ffqbar.in(1);
        ffq.out to q, ffqbar.in(2);
        ffqbar.out to qbar, ffq.in(2);
end.

A Complete Example

Combining the heading, input/output lists, parts list and wire list from the previous sections gives the following description for the example circuit:

circuit dlatch;   -- designed by D. W. Jones
inputs
        d {data},
        c {clock};
outputs
        q {the stored data value},
        qbar,
        dout {echo input d},
        cout {echo input c};
parts
        { basic rs flipflop gates }
        ffq, ffqbar: nand(2);

        { gates used to convert d and c to r and s }
        gater, gates: nand(2);
        inverter: not;
wires
        { distribution of inputs to rs conversion gates }
        d to gater.in(1), dout, inverter.in;
        inverter.out to gates.in(1);
        c to gater.in(2), gates.in(2), cout;

        { transfer of r and s values to basic rs flipflop }
        gater.out to ffq.in(1);
        gates.out to ffqbar.in(1);

        { basic rs flipflop }
        ffq.out to q, ffqbar.in(2);
        ffqbar.out to qbar, ffq.in(2);
end.
In the following sections, it will be assumed that this circuit description is stored in file dlatch (the convention of using the circuit name as the file name is strongly encouraged).

Unlike most computer programming languages, the order of the statements in the wire list has no effect on the meaning of the circuit description; nonetheless, careful ordering and grouping of wires is important to human readers. In this example, the wire list has been broken up into functional sections, each headed with a comment.

Advanced Features

The Iowa Logic Specification Language has a number of features which allow it to be used to specify large and complex systems of logic gates. Primary among these are a subcircuit mechanism and an array mechanism. The former allows a circuit to be defined once and used many times, while the latter allows input or output connections from a circuit to consist not of individual wires but of named arrays of wires.

In some applications, it is necessary to simulate not only the logical behavior of a circuit, but also the time that the circuit takes to act on changes in its inputs. The Iowa Logic Specification Language provides complete support for specification of such time delays, and the associated simulator accurately simulates them. If time delays are not specified, default delays are assumed that are typical of breadboard circuit construction with TTL gates.

Subcircuits

Large digital systems frequently consist of many logical subsystems, and the Iowa Logic Specification Language reflects this by allowing the declaration and use of subcircuits. Any circuit described in the Iowa Logic Specification Language may be used as a subcircuit of some larger circuit. Subcircuits of a circuit may be declared immediately after the circuit name, before the declarations of inputs and outputs. A subcircuit declaration consists of exactly the same parts as any other circuit declaration; it starts with a name, followed by an input list, an output list, a parts list, and a wire list.

When a circuit is used as a subcircuit, its name becomes a new gate type, and any number of parts of this type may be declared. For example, if the circuit defined in the previous sections is used as a subcircuit, any number of parts of type dlatch may be declared, as in the following example:

parts
        bit1, bit2, bit3, bit4: dlatch;
This declares the parts bit1 through bit4 to be instances of the circuit dlatch. These instances are completely independent of each other; all that they have in common is that they are described by the same circuit description.

Connections between an instance of a subcircuit and its user are made through the inputs and outputs of the subcircuit. For example, given the above definitions, it would be legal to connect bit1 through bit4 into a shift register as follows:

wires
        bit1.q to bit2.d;
        bit2.q to bit3.d;
        bit3.q to bit4.d;
This relies on the fact that each instance of dlatch has an output named q and an input named d. All of the inputs of a subcircuit must be connected for that subcircuit to function correctly; for example, bit1.c and bit1.d must be connected to make the subcircuit instance bit1 work. Some subcircuits may have inputs which are not needed in a particular application; these should be connected to the global inputs high or low, as appropriate. The outputs of bit1 are bit1.q, bit1.qbar, bit1.cout, and bit1.dout. It is not necessary to use all of the outputs of a subcircuit.

In many ways, subcircuits are similar to procedures in a conventional programming language. Thus, it is correct to talk about the main circuit and the subcircuits it uses. The inputs and outputs of a subcircuit are similar to procedure parameters, since they allow communication between the user of a subcircuit and the subcircuit. The Iowa Logic Specification Language has scope rules similar to those of Pascal.[*] If subcircuit X is declared within subcircuit Y, then X may only be used in Y and in other subcircuits declared in Y. That is, X is considered to be a local subcircuit of Y.

Text Inclusion from Multiple Source Files

The Iowa Logic Specification Language provides a text inclusion statement as an alternative to physically including the text of a subcircuit in the definition of a circuit which uses it. The text inclusion statement consists of the keyword use followed by the name (or pathname) of the file where the desired subcircuit definition is to be found. Use statements are only allowed in places where a subcircuit definition would be legal. For example, to use the circuit dlatch as a subcircuit, assuming it is stored in a file with the same name, all that would be needed is the following statement:

use dlatch
The use statement textually inserts the contents of the named file into the original source file as the logic simulator reads it. The file referenced by a use statement may contain a sequence of subcircuit or constant definitions, and each subcircuit in the referenced file may contain local subcircuits and other definitions.

Files referenced by use statements may contain other use statements, but such nesting should be limited to 3 levels beyond the original source file (the logic simulator can have no more than 4 open input files at any time). Circuits with deeply nested use statements can frequently be fixed by putting all of the use statements in the main circuit, in effect, making all of the used subcircuits global. Note that use statements do not introduce any scope rules!

Try to avoid multiple use statements referencing the same file, since this will result in a waste of memory and time when the logic simulator processes a circuit. If two subcircuits both require definitions from the same file of definitions, put a use statement for that file global to both subcircuits instead of two use statements, one local to each subcircuit.

An Example with Subcircuits

The examples given above suggest the construction of a shift register made of type D latches. The complete specification of this circuit would have the following structure:

circuit dregister;   -- designed by D. W. Jones

    circuit dlatch;
    inputs
            d {data},
            c {clock};
    outputs
            q {the stored data value},
            qbar,
            ... rest of definition from Section 2.6 ...
    end;    -- dlatch

-- start of dregister
inputs
        i { the data to shift into the register },
        c { the clock signal to shift the register };
outputs
        o1, o2, o3, o4 { output the 4 bits of the register };
parts
        invert: not { used to invert the clock };
        bit1, bit2, bit3, bit4: dlatch { storage for bits };
wires
        { wires for the data flowing through the register }
        i to bit1.d;
        bit1.q to o1, bit2.d;
        bit2.q to o2, bit3.d;
        bit3.q to o3, bit4.d;
        bit4.q to o4;

        { wires for the clock signals of the register }
        c to bit1.c, bit3.c, invert.in;
        invert.out to bit2.c, bit4.c;
end.    -- dregister
In this example, note that there is no conflict between the input c of the circuit dregister and the input c of the subcircuit dlatch. Within the subcircuit, the name c is the input to that subcircuit; outside the subcircuit, c is the input to the entire circuit, and bit1.c through bit4.c are the inputs to the instances of the subcircuit. Within the subcircuit, c is a source of data; outside the subcircuit, bit1.c through bit4.c are destinations.

As with the previous example, commenting and indenting have been used here to help the reader find logically related wires and to set off the subcircuit from the main circuit. Note the comment at the end of each circuit and subcircuit which gives the circuit or subcircuit name. Also note that a comment is included after the end of the subcircuit definition to help the reader find the start of the main circuit.

This example of the use of subcircuits could be written in more compact form with the aid of the use statement. Assuming that the circuit dlatch is stored in a file with that name, the file defining the shift register would have the following form:

circuit dregister;   -- designed by D. W. Jones

     use dlatch;

-- start of dregister
inputs
        ... rest of definition as given above ...

end.    -- dregister

Arrays and Iterators

The Iowa Logic Specification Language allows inputs, outputs or parts to be declared as arrays. This is done by declaring a named range of allowed subscripts with a range declaration, and then using the range identifier as a subscript on the input, output, or part definition. Declarations of ranges and named constants may appear anywhere a subcircuit declaration is allowed, that is, between the circuit header and the input keyword, or in a file included by a use statement. The outputs o1 through o4 and the parts bit1 through bit4 used in the dregister example in the previous section would much more appropriately have been declared as arrays. The following example illustrates this:

range   nibble = 1 .. 4;
inputs  i, c;
outputs o(nibble);
parts
        invert: not;
        bit(nibble): dlatch;
In this example, the output o is a 4 bit wide output array, with subscripts ranging from 1 to 4. Similarly, bit is a part array.

Arrays of inputs, outputs, and parts may be interconnected one wire at a time, explicit iterators may be used, or implicit iterators may be used. In the context of the above definition, it would be legal to make individual connections as follows:

wires
        i to bit(1).d;
        bit(1).q to o(1), bit(2).d;
        bit(2).q to o(2), bit(3).d;
        bit(3).q to o(3), bit(4).d;
        bit(4).q to o(4);
If such an interconnection pattern is desired, a much more direct expression of the pattern can be made with an iterator.

Iteration in the Iowa Logic Specification Language is done with a for statement; for statements control the iteration of a wire list, and they may appear anywhere in the body of a wire list. This implies that for statements may be nested. The following example illustrates the use of a for statement equivalent to the above:

wires
        i to bit(1).d;
        for j in first(nibble)..last(nibble)-1 do
            bit(j).q to o(j), bit(j+1).d;
        endfor;
        bit(4).q to o(4);
Note that the for statement declares an identifier, j in this example. The range of values over which this identifier is iterated is given by the expression between the keywords in and do.[*] The order in which values are assigned to j is not specified and has no effect on the meaning of the result.

The identifier declared by a for statement must not have been declared as a local identifier of the current circuit or of an enclosing for statement. The identifier may override an identifier of the same name declared in an outer block, and two for statements in the same block may use the same identifier if neither encloses the other.

Implicit iteration may be applied to wiring outputs of one subcircuit to inputs of another. It is applicable whenever the source pin and destination pin of a wire list entry are arrays of the same dimension. For example, if byte is a global range, perhaps declared as 8 bits, the following would be legal:

inputs  a(byte);
outputs b(byte);
wires
        a to b;
This single wire list entry actually implies the following for loop:
wires
        for i in byte do
            a(i) to b(i);
        endfor;

The expression giving the range over which a for loop iterates may be the name of a range or an explicit statement of the upper and lower bounds of the range. Although numerical bounds may be used, it is usually preferable to state the bounds in symbolic terms. The predicates first, last, and size are provided in order to simplify the construction of new ranges from old. These extract the lower bound of a range, the upper bound of a range, and the size of a range. For example, the following would be a legal iterator equivalent to the one used in the array version of dregister:

for i in first(nibble) .. first(nibble) + size(nibble) - 2 do
For the example declaration of nibble, this iterates over the range 1..3. Note that would have been legal to use the explicit range 1..3 in this context, but that this would make it hard to change the size of the register. A simple change to one range declaration is much easier than a search through the code for all related range expressions.

Constant declarations allow commonly used constants and the values of commonly used expressions to be named. The keywords range, integer, real, boolean and time introduce constant declarations for constants of the associated types. As already mentioned in the context of range constants, these may be put anywhere a subcircuit declaration is allowed. The following example illustrates a typical use of constant declarations:

range   word = 0 .. 15;
integer wordsize = size(word);
integer doublewordsize = 2 * wordsize;
range   doubleword = 0 .. doublewordsize - 1;
The types of expressions follow from the types of their components in obvious ways. The time type obeys the usual rules for time intervals; thus, times may be added or subtracted, times may not be multiplied, and the ratio of two times is a real number, not a time.

The identifiers created by range and constant declarations obey all of the hierarchic scope rules of the specification language. Thus, it is legal, and in fact, common, to declare a range in the main circuit and then use that range in subcircuits. This provides a major abstraction facility, since it allows circuits to be interconnected without knowledge of the actual numbers of wires needed to interconnect them.

Conditional Part and Wire lists

In some contexts, a subcircuit may need to expand differently depending on the context of the expansion; for example, additional gates may be needed if the number of elements in an array is greater than some threshold. In this case, conditional part and wire lists can be added. The notation used for this is based on the familiar if then else endif constructs of programming languages. Note that an if construct must always end with the keyword endif. Within an if construct, the phrase else if is allowed for testing secondary Boolean conditions; the phrase else if does not introduce a new if construct, it only introduces an alternate then clause.

For example, consider a circuit for adding two numbers. This might use the global range word to describe the structure of a word. Suppose that a simple adder, with ripple carry, is preferable when there are fewer than K bits in a word, but that fancy carry anticipation logic is desired for larger word sizes. A generic adder circuit could include conditional sections in its part and wire lists so that it selects one of the two implementations depending on the word size. This might be done as follows:

parts
        if size(word) < K then { use ripple carry }
                adders(word): fulladder;
        else { use carry anticipation }
                adders(word): addanticipate;
                lookahead(1 .. size(word)/4): anticipator;
        endif;
wires
        if size(word) < 1 then { something is wrong }
                error;
        else if size(word) < K then { use ripple carry }
                ...
        else { use carry anticipation }
                ...
        endif;
end { adder };

Note that the conditional structure of the parts list will frequently mirror that of the wire list. This is because each change in the parts list frequently requires a corresponding change in the wire list. It is legal to declare parts with the same name in both the then and else clauses, but this can be very confusing unless the parts correspond in some way. In the above example, the part adders has two possible definitions depending on the size of a word. This would be sensible if the circuit addanticipate is similar to fuladder in both function and input/output pin naming conventions. For example, addanticipate might have the same pin names as fulladder for the two addends, the sum, and the carry in and out, but it might have additional input and output pins for communication with the anticipation units.

The Boolean expression controlling an if statement must be composed using the usual relational and Boolean operators; these include >, <, =, <=, >= and <> (not equal), as well as & (and), | (or) and \ (not), with parentheses allowed to control the order of evaluation.

Parameterized Circuits

It is sometimes useful to make generic subcircuits which can be parameterized when they are used in a parts list. For example, the predefined gates are parameterized with the number of input pins they allow, and they have an optional time delay as a parameter. Subcircuits may have a formal parameter list following the circuit name. This may include formal parameters of types integer, real, time, range or circuit. The following example illustrates this:

circuit generic( circuit x; integer n );
inputs  i, c;
outputs o(1..n);
parts   bit(1..n): x;
        invert: not;
wires
        i to bit(1).d;
        for j in 1 .. n-1 do
            bit(j).q to o(j), bit(j+1).d;
        endfor;
        bit(n).q to o(n);

        c to invert.in;
        for j in 1 .. n / 2 do
            c to bit(2 * j - 1).c;
            invert.out to bit(2 * j).c;
        endfor;
end;
This example will only work if the subcircuit passed as parameter x has inputs called c and d and an output called q. The subcircuit dlatch meets these criteria, so it would be legal to include a: generic(dlatch,4) in a parts list where generic and dlatch are visible. As a result of this, a would have exactly the same behavior as an instance of circuit dregister given previously.

The combination of parameterized circuits with conditional part and wire lists allows recursive circuit descriptions. For example, an n input decoder with k auxiliary enable inputs can be described either as an inverter and a pair of k + 1 input and gates, in the case where n = 1, or as an inverter and a pair of n - 1 input decoders with k + 1 enable inputs, in the case where n > 1.

Time Delays

For most purposes, a logic description such as is given above will be sufficient, but there are times when it is desirable to simulate not only the static interconnection of a circuit, but also the timing characteristics of that circuit. The Iowa Logic Specification Language allows timing characteristics to be specified for both logic gates and wires. If timing characteristics are not given, the logic simulator will assume defaults typical of kluge card or breadboard implementations of TTL circuits (gates are given a nominal propagation delay 10ns (nanoseconds), and wires are given delays between 0.5ns and 1.5ns, corresponding to wires from about 15mm to 45mm long).

If the default gate propagation delay is not appropriate, an explicit delay can be specified as an additional parameter on the part type. This is illustrated in the following example:

parts
        a: not;
        b: not(1.5 * ns);
        c: nand(2);
        d: nand(2, 2 * ns);
In this example, gates a and c have the default delay, while gates b and d are relatively fast gates, with delays of 1.5 and 2 nanoseconds.

Delays are expressions or constants of type time. These are usually constructed by multiplying or dividing a predefined time constant by a factor, as in the above examples. Times may be added or subtracted to yield new times, but they may not be multiplied, and division of two times yields a real number, not a time. The predefined constants of type time are:

s - second  
ms - millisecond (ms = s/1000)
us - microsecond (us = ms/1000)
ns - nanosecond (ns = us/1000)
The symbol u in the abbreviation of microseconds is used to approximate the shape of the Greek letter $\mu$ (mu) which is a standard abbreviation for the prefix micro.

It should be noted that, during the simulation of a circuit, the propagation delay specified for a gate is modified by a small random factor each time it is used. Thus, a gate with a nominal 10ns delay will have actual delays ranging from 9.5ns to 10.5ns. This jitter in the propagation delay results in slightly unpredictable results for some circuits, but it is necessary to make flipflops decay realistically to one or the other of their stable states after being simultaneously set and reset.

If the default signal delay along a wire is not appropriate, a specific delay can be given as a parameter on the to keyword used in the wire list part of the circuit description, as shown in the following example.

wires
        a.out to(15 * ns) c.in(1);
        a.out to(1 * ns) c.in(2), b.in;
Here, note that all of the destination connections listed on any particular to clause have the same delay, so a separate to clause must be used for each different delay which is specified. In the above example, a.out is connected to three different inputs, using two different delays.

If subcircuits are used, delays may be specified in both the connections to an instance of a subcircuit, and in the connections from the inputs and outputs of the subcircuit to its components. The Iowa Logic Simulator will correctly sum these delays in order to determine the total time taken by a signal as it travels from one component to another.

Some circuits require periodic clocks for their operation; these may be constructed from an inverter with its output fed back into its input. The period of the clock will be twice the delay of the inverter, so a clock with a period of 1$\mu$s may be constructed from an inverter with a delay of 500ns; the same effect may be obtained by using an inverter with a shorter delay and putting extra delay in the wire connecting the output of the inverter back to its input. Note that the technology used to construct a gate determines its delay, and thus, that real gates have nothing analogous to a delay parameter. Furthermore, long wires used to achieve long delays are impractical: a 500ns delay would require about 150 meters of wire, or perhaps 50 meters of coaxial cable. Thus, the periodic clocks used in real systems usually rely on more complex electronic solutions.

Formalities

The Iowa Logic Specification Language includes the following reserved words:

boolean         for             range
circuit         if              real
do              inputs          then
else            integer         time
end             mod             to
endfor          outputs         use
endif           parts           wires
In addition to these words, the identifier in appears to be a keyword in the context of the third lexeme of a for statement, but not elsewhere. The identifier tally may be placed before the keyword circuit in the heading of the main circuit to request a tally of all parts and subcircuits used in building that circuit. No predefined meaning is ascribed to tally in any other context, and tally may be freely redefined.

The following identifiers are predefined as input or output pins of predefined gates. With the exception of in, they have no meaning elsewhere, and their redefinition will not have any effect on their meaning in the context of the predefined gates or in the context of the for statement.

in              control         data            out

The following identifiers are the names of predefined global inputs to the circuit; they may be redefined, but such redefinition will mask their predefined meaning.

high            low

The following identifiers are predefined constants; they may be redefined, but such redefinition will mask their predefined meaning. Of these, true and false have Boolean values, the others are time units.

true            s               ms
false           ns              us

The following identifiers are names of predefined gates; they may be redefined, but such redefinition will mask their predefined meaning.

and             latch           not             tsgate
bus             nand            ntsgate         xor
equ             nor             or
Note that and, or, and not are not legal operators in Boolean expressions, but that the symbols &, | and \ are used in expressions. The and, nand, or and nor gates have a required parameter, the number of inputs; all gates other than the bus have a second optional parameter giving the time delay of the gate.

The following identifiers are names of predefined functions which may be used within expressions; they may be redefined, but such redefinition will mask their predefined meaning. All but odd take a single range expression as an argument; odd takes an integer argument.

first           last            size            odd

Predefined identifiers are considered to be declared globally to the main circuit. Local declarations within any circuit or subcircuit may redefine identifiers declared outside that circuit or subcircuit. It is illegal to redefine an identifier which was locally defined within a circuit or subcircuit. Although the identifier declared by a for statement has a scope limited to the for statement body, it may not have the same name as any locally declared identifier within the same circuit or subcircuit, and it may not have the same name as the identifier declared by any enclosing for statement. It is legal for two for statements in the same wire list to declare the same identifier as long as the two are not nested.

Expressions may be composed using the following operators, listed in order of decreasing precedence:

(x) f(x) \ - nested parentheses, functions, Boolean not
** - integer exponentiate
* / mod & - multiply, divide, remainder, Boolean and
+ - | - add, subtract, Boolean or
.. < <= = <> >= > - range construction, comparison
The logic specification language recognizes expressions of type integer, real, time, Boolean, and range. Constants and formal parameters of these types may be declared using the corresponding keywords. In addition, the keyword circuit may be used to declare formal parameters of that type.

The following Extended BNF grammar describes the Iowa Logic Specification Language. The notation used here is traditional: angle brackets surround nonterminal symbols, square brackets surround optional constructs, curly braces surround constructs that may be repeated more than one time, and a vertical bar is used to separate alternatives. When one of these metasymbols also occurs as a symbol in the language, it is quoted. Lexical details are not covered by this grammar. There are two top-level constructs, files containing a main circuit, and files intended to be included by a use directive; these are defined first.

<main file> ::=
        [ tally ] <circuit declaration> [ . ]

<included file> ::=
        <subcircuit and constant declarations> [ . ]

<circuit declaration> ::=
        circuit <name> [ <formal parameter list> ] [ ; ]

        [ <subcircuit and constant declarations> [ ; ] ]

        [ inputs
                <identifier list> [ ; ] ]
        outputs
                <identifier list> [ ; ]
        [ parts
                <part list> ]
        wires
                <wire list>
        end

<formal parameter list> ::=
        ( <formal declaration> { [ ; ] <formal declaration> } )

<formal declaration> ::=
        <formal keyword> <identifier list>

<formal keyword> ::=
        boolean | integer | real | time | range | circuit

<identifier list> ::=
        <identifier> { [ , ] <identifier> }

<subcircuit and constant declarations> ::=
        <declaration> { [ ; ] <declaration> }

<declaration> ::=
        <constant declaration> | <use directive> | <circuit declaration>

<constant declaration> ::=
        <constant keyword> { <identifier> = <expression> }

<constant keyword> ::=
        boolean | integer | real | time | range

<use directive> ::=
        use <file name>

<part list> ::=
        { <part declaration> | <part list if> }

<part declaration> ::=
        <part names> : <part type> [ ; ]

<part names> ::=
        <part name> { [ , ] <part name> }

<part name> ::=
        <identifier> [ ( <expression> ) ]

<part type> ::=
        <identifier> [ ( <actual parameter list> ) ]

<actual parameter list> ::=
        <expression> { [ , ] <expression> }

<part list if> ::=
        if <expression> then
            <part list>
        [ else if <expression> then
            <part list> ]
        [ else
            <part list> ]
        endif [ ; ]

<wire list> ::=
        { <wire list element> | <wire list if> | <wire list for> }

<wire list element> ::=
        <pin> to [ ( <expression> ) ] <pin> { [ , ] <pin> } [ ; ]

<pin> ::=
        <identifier> [ ( <expression> ) ]
             [ . <identifier> [ ( <expression> ) ] ]

<wire list if> ::=
        if <expression> then
            <wire list>
        [ else if <expression> then
            <wire list> ]
        [ else
            <wire list> ]
        endif [ ; ]

<wire list for> ::=
        for <identifier> in <expression> do
            <wire list>
        endfor [ ; ]

<expression> ::=
        <simple expression> [ <relop> <simple expression> ]

<relop> ::=
        < | <= | <> | = | > | >= | ..

<simple expression> ::=
        [ + | - ] <term> { <addop> <term> }

<addop> ::=
        + | - | "|"

<term> ::=
        <factor> { <mulop> <factor> }

<mulop> ::=
        * | / | mod | &

<factor> ::=
        <fact> [ ** <fact> ]

<fact> ::=
        <identifer> | <function> | <number> | \ <fact> | ( <expression> )

<function> ::=
        <identifier> ( <expression> )

This grammar is ambiguous. One of the major problems is the keyword sequence else if; the parser is greedy, and as a result, it cannot parse an if construct at the start of a list of items in an else clause. The next version of the language will correct this problem by introducing the keyword elseif.


next up previous contents
Next: Using the Logic Simulator Up: Iowa Logic Simulator User's Previous: Introduction
author-address