Note: Make sure that you allocate enough time to the project.
A netlist language
For this part of the assignment, you will define a netlist language (using ACL2). The language will allow you to describe combinational functions (circuits) at the gate level. You can define combinational functions, give them names, and use those functions to define other, more complex functions. Combinational functions can have any natural number of inputs, and any positive number of outputs. Here is an example of how one can define a 2-bit adder.
(defconst *defs1*
'((maj (a b c)
(d)
((0 (and a b))
(1 (and a c))
(2 (and b c))
(d (or 0 1 2))))
(fa (a b cin)
(s cout)
((s (xor a b cin))
(cout (maj a b cin))))
(adder-2-bit (a1 a0 b1 b0 cin)
(cout c1 c0)
(((c0 0) (fa a0 b0 cin))
((c1 cout) (fa a1 b1 0))))))
Above we defined maj, a function that has three inputs, a, b, and c, and one output, d. Wire 0 corresponds to a AND b, wire 1 to a AND c, wire 2 to b AND c, and the output wire d corresponds to the OR of wires 0, 1, and 2. This is the majority function we have already seen. We then define a full adder, fa, which has the inputs a, b, and cin and the outputs s and cout using the function maj, just defined. Note that the functions and, or, and xor are built-in and can have 2 or more inputs, but functions that are defined can only have a fixed number of inputs and outputs (specified at definition time). The 2-bit adder function shows how to assign values to wires when using functions with multiple outputs.
Here is the specification for the syntax of the language. It will probably help to look at the above example as you go through it. The constant *defs1* is an example of a net definition list.
Note that we avoid cycles by requiring that functions can only call functions that have already been defined.
The above specification allows for various implementations. When you have a design decision to make, try to make a decision that makes sense, e.g., I do not explicitly prohibit multiple net definitions with the same name in a net definition list, but, obviously, one should not allow this. Document such design decisions in your code.
We now define the semantics of the netlist language. Consider the following example.
(net-val *defs1*
'(((out d1 d0)
(adder-2-bit e1 e0 a1 a0 in)))
'(out d1 d0)
'((e1 . 1) (e0 . 0)
(a1 . 1) (a0 . 1)
(in . 1)))
It returns
(1 1 0)
Notice that bits, wire values, are either 1 or 0 (instead of t or nil). The function net-val evaluates the netlist expression, its second argument, given the definitions in its first argument, given values to the inputs specified in the fourth argument and returns the values in the wires identified in the third argument. So, this is a function that evaluates netlist expressions, given some definitions and input values. In more detail, we evaluate the adder-2-bit function on inputs e1, e0, a1, a0, and in (notice that the names here differ from the names of the inputs of adder-2-bit). The result is bound to wires out, d1, and d0 and since these are the wires identified as output wires, the result is output.
How to evaluate the builtin functions and, or, xor, and not should be obvious. The builtin function wire assigns the value of the input wire to the output wire, so you can think of it as an assignment operator. The built-in function const creates a constant by assigning the sequence of bits (0s and 1s) that are its arguments to the output wires.
Here is what you have to hand in.
Notes:
For extra credit, you can do the following.
Design the ALU using the Altera toolkit.