- A Logic Circuit Simulation Library in C++ |
The easiest way to get started with libLCS is to start building simple circuits using the off-the-shelf logic gates provided in libLCS. All circuit elements, which have an input and an output, will be called as modules when reffered to in the context of libLCS. Since logic gates have a well defined input and output, they are also called as modules in libLCS (unlike in Verilog where logic gates are reffered to as primitives). All logic gate modules (except the NOT gate module) in libLCS are provided as class templates requiring two template parameters. The template parameter first in the order or template arguments denotes the number of inputs to the gate, and the other template parameter denotes the input to output delay of the gate. Since a NOT gate can have only one input, it is provided as a class template taking only one template parameter which represents the input to output delay of the NOT gate.
In libLCS, the cable/wire connections between modules have to be done using busses. Busses are also provided as class templates requiring one template parameter. The template parameter indicates the width (or number of lines) of the bus.
Further in this section, I will go over building a trivial circuit as an illustrative example. More complicated examples can be found in the basic examples section.
a
and b
be inputs to our circuit. Let s
be the output. Then, the boolean function for s
is:
s = ab' + a'b
Hence, to generate s
from a
and b
, we require two 2-input AND gates, one 2-input OR gate, and two NOT gates. The circuit diagram is as follows (the small circles/bubbles represent NOT gates):
Each node in the above circuit translates in to a bus in the case of libLCS. As marked in the circuit, seven single-line busses are required to build the circuit. The first step in our program is to declare these 7 single-line bus objects. The class template to generate bus classes is defined in the header file lcs/bus.h
. All classes in libLCS are defined in the namespace lcs
, and so is the bus class template lcs::Bus
. We declare the busses as follows.
lcs::Bus<1> a, b, a_, b_, p1, p2, s; // The bus names are same as // that in the diagram above.
Note the use of a template parameter of 1 in the above declaration to indicate the width of the busses being declared. After declaring the busses in the circuit, the next step is to initialise the logic gate modules in the circuit. We will first initialise the NOT gates in the circuit. The initialisation is done as follows. (The NOT gates are defined in the header file lcs/not.h
.)
lcs::Not<> ng1(a_, a), ng2(b_, b); // We donot pass any template paremeters // to the NOT gate class as we shall use // the default value of 0 for the delay // parameter.
Notice the order of arguments being passed to the NOT gate constructors. Constructors for all the off-the-shelf gates provided in libLCS require two arguments: The output bus as the first argument, and the input bus as the second argument.
Next we shall initialise the AND gate modules of our circuit. The AND gates are defined in the header file lcs/and.h
. A two input AND gate would require a two-line bus as input. However, we have declared only single-line busses. libLCS provides an overloaded operator,
with which one can concatenate two busses to form wider busses. We will make use of this concatenation operator to initialise the two 2-input AND gates required for our circuit.
lcs::And<2> ag1(p1, (a,b_)), ag2(p2, (a_,b)); // Notice the use of the template parameter of // 2 to denote a 2-input AND gate. The second // parameter is not passed to the AND gate class // as we shall make use of the default value of // zero for the delay. Also, the ',' operator is // used to concatenate single-line busses into // two-line busses.
The 2-input OR gate module is initialised in a similar manner.
lcs::Or<2> og(s, (p1,p2));
With the above OR gate initialisation, we have built our complete circuit. We now need a way to test it. For this, libLCS provides a class lcs::Tester
defined in the header file lcs/tester.h
. It is also a class template requiring one integral template parameter. A lcs::Tester
object will have to be initialised by passing one lcs::Bus
object to the constructor (This bus object should not have been connected previously to the output port of some module). The template parameter to initialise a lcs::Tester
object should be the same as the width of the bus passed as an argument to the constructor. At every clock state change, the lcs::Tester
object feeds a different value onto the lcs::Bus
object with which it was created. The values it feeds are in sequence from 0 to 2w - 1, where w is the width of the bus with which it was created. For our circuit, we will make use of the lcs::Tester
class to feed different values to the inputs a
and b
as follows.
lcs::Tester<2> tester((a,b)); // The template parameter 2 indicates the bus width of // the input bus to our circuit.
Testing our circuit would not only need feeding proper inputs, we should also monitor the output of our circuit to verify the correctness. libLCS provides a class lcs::ChangeMonitor
(defined in the header file changeMonitor.h
) for this. An object of this class should be instantiated by passing a bus object and an std::string
variable to the constructor. Then, it will monitor the bus line states and report whenever a change occurs to the line states using std::string
variable as a name for the bus. For our circuit, we will make use of two lcs::ChangeMonitor
objects: one to monitor the inputs, the other to monitor the output. This is done as follows.
lcs::ChangeMonitor<2> inputMonitor((a,b), "Input", lcs::DUMP_ON); // The template parameter 2 indicates the // bus width of the input bus to our circuit. lcs::ChangeMonitor<1> outputMonitor(s, "Output", lcs::DUMP_ON); // The template parameter 1 indicates the bus // width of the output bus to our circuit.
Note the additional parameter, lcs::DUMP_ON
, passed to the ChangeMonitor constructors. This indicates that the states of the bus lines, which are being monitored, should be dumped into a VCD file. If you do not want them to be dumped, then you can either omit the last parameter, or pass lcs::DUMP_OFF
insteas of lcs::DUMP_ON
.
After all the circuit elements are setup, we need to start the simulation. Before we start, we also need to set the time at which the simulation should stop. This is done by a call to the static function lcs::Simulation::setStopTime
. After setting the stop time, we should start the simulation by a call to the static function lcs::Simulation::start
.
The entire/complete program which simulates our circuit is as follows. It is bundeled as an example along with the latest libLCS release file.
#include <lcs/bus.h> #include <lcs/not.h> #include <lcs/and.h> #include <lcs/or.h> #include <lcs/tester.h> #include <lcs/simul.h> #include <lcs/changeMonitor.h> int main() { lcs::Bus<1> a, b, a_, b_, p1, p2, s; lcs::Not<> ng1(a_, a), ng2(b_, b); lcs::And<2> ag1(p1, (a,b_)), ag2(p2, (a_,b)); lcs::Or<2> og(s, (p1,p2)); lcs::ChangeMonitor<2> inputMonitor((a,b), "Input", lcs::DUMP_ON); lcs::ChangeMonitor<1> outputMonitor(s, "Output", lcs::DUMP_ON); lcs::Tester<2> tester((a,b)); lcs::Simulation::setStopTime(1000); lcs::Simulation::start(); return 0; }
When the above program is compiled and run, the following output will be obtained. (See this page for information on compiling a program which uses libLCS.)
At time: 0, Input: 00 At time: 0, Output: 0 At time: 200, Input: 01 At time: 200, Output: 1 At time: 300, Input: 10 At time: 400, Input: 11 At time: 400, Output: 0
We will now try to understand what the above output means. When the circuit is turned on (ie. when that the simulation starts at time 0), the object tester
sets the busses a
and b
to a value of zero each. Correspondingly, the output changes to zero from an unknown value at time 0.
The object tester
feeds sequential values at every clock state change, starting from a value of 0 . The default pulse width of the clock in libLCS is 100 system time units. Hence, a value of 0 is fed onto the input bus (a,b) at the first clock pulse occuring at 100 time units. However, since the value was 0 even before the first clock pulse, the
inputMonitor
does not display the input states at the time of 100 units. The output corresponding to a 0 input is also 0. Hence, the outputMonitor
does not display anything at the time of 100 units as the output has been 0 even before the first clock pulse.
When the input changes to a value 01 at 200 time units, the output correspondingly changes to 1. When the input changes to 10 at 300 time units, the correct output should be 1, which is the same as that before 300 time units. Hence the change monitor does not report anything about the output as the output did not change from the previous value. When the input changes to 11 at 400 time units, the output changes to 0 from a value of 1. This is reported accoridingly as the value has changed from the previous value.
Apart from the display of the simlation results on the standard output, we requested that the line states of the busses being monitored should be dumped into a VCD file. By default, the values are dumped into a file with name "dump.vcd" in the directory in which the simulation was executed. If one is wants a different name for the VCD file, they should set it using the function lcs::Simulation::setDumpFileName
. The dumped VCD file should be viewed using a VCD viewer. GTKWave is one such excellent tool which can plot the VCD wave forms. The screenshot of the GTKWave plot of the VCD file which was generated from the above simulation is as follows: