- A Logic Circuit Simulation Library in C++ |
In the Verilog hardware description language, modules are omni present and are required to perform a simulation, however simple the system be. libLCS does not require that a user has to define a module. As presented in the getting started section, one can use the off-the-shelf logic gates to build his/her system and perform a simulation. However, such an approach is suitable only for building small and simple systems. As the system grows in size and complexity, building it using the off-the-shelf logic gates and modules gets very laborious and error prone. One should follow a Top-Down design approach and build modules which consist of other simpler sub modules. Sometimes, as the system gets complex, simulating/testing the algorithm becomes more important than the actual gate-level circuit. For this, one would need to build modules which are functional equivalents of the actual gate-level circuit (Such a simulation is called as data flow modelling in Verilog). libLCS provides ways to build functional modules, as well as build modules consisting of other sub modules.
Using libLCS, one can build two different kinds of modules: 1. Block-Level Modules and 2. Functional Modules. Block-level modules are ones which are internally a connected ensemble of other modules. They serve merely as an encapsulation of existsing modules into a larger module. Building block-level modules does not require any special care as one only needs to initialise the sub-modules (and connect them up suitably) in the constructor of the module class. These sub-modules will have to be destroyed in the destructor of the module class. Examples of building one's own block-level modules can be found here. I shall not discuss building of block level modules in much more detail. Further in this section, I shall only discuss building functional modules in detail.
Functional modules are ones which do not internally have other sub-modules. They merely read the input busses lines and set the output bus lines accordingly. They have to be built with proper care, following certain rules. To get a quick picture on how a functional module differs from a block-level module, compare the block-level realisation and, the functional realisation examples of a 1-bit full-adder module. In this section, I shall discuss the concepts involved in building one's own functional modules. The first rule for implementing a functional module class is to derive it from the class lcs::Module
.
Every module, functional or block-level, will have a set of input busses, and set of output busses. In case of functional modules, the module implementer should ensure that the input busses drive the module. For this, the input busses to a functional module should be of the class lcs::InputBus
. Busses of class lcs::InputBus
allow registration by a module to be driven by the bus. Getting driven by a bus is equivalent to being notified of one of the following three possible line events which can occur on the bus lines.
In libLCS, the above events are provided as enumerations under the enumerated type lcs::LineEvent
. Module implementers should register the module with the input busses so that the module will be notified of the line events occuring on input busses as relevant. For example, an AND gate module should register to be notified of a line state change event on any of its input lines. Similarly, a positive-edge triggered D-flipflop module should register to be notifed of an occurance of a line positive edge event on its clock input. Registering to be notified of line events is done through the function lcs::InputBus::notify
. The prototype of this function is as follows.
void lcs::InputBus::notify(lcs::Module *mod, const lcs::LineEvent &event, const int &portId, const int &line = -1) throw (lcs::OutOfRangeException<int>);
A call to lcs::InputBus::notify
should be made in the constructor of the functional module class. The first argument should be the pointer to the module object being registered with the bus. In a module class constructor, it should be the this
argument. The second argument should be the line event which the bus should notify the module of. The third argument is the integer id of the port to which the bus is connected to. The last argument indicates the index of the line in the bus, occurrance of an event on which the module is seeking to be notified of. If a negative value is passed as the last argument, all the lines in the bus will notify the module of line the event whenever it occurs. If a value beyond the allowed line index for the bus is passed, then an lcs::OutOfRangeException<int>
is thrown.
When a module ceases to exist in the simulation space, it cannot be notified by the input busses with which it has registered. Hence, in the destructor of the module class, the module should de-register itself with all input busses from being driven by them. This is done by calling the function lcs::InputBus::stopNotification
for all the input busses. The parameters to this function should exactly be the same, and in the same order, as they were when the module was registering itself to be notified by the bus using the function lcs::InputBus::notify
.
NOTE: Objects of the class lcs::InputBus
are readonly busses. There is no way one can set the lines states of these busses. It has been designed this way as the module to which these objects are input busses have no business to set their line states. In Verilog, this feature is incorporated by restricting module inputs to be of type wire
only.
A module processes the input signals (which are available on input bus lines) and sets the output bus lines accordingly. This requires that the output busses be of a type which allows setting the bus lines. Such a bus class is available in in libLCS as lcs::Bus
. Hence, all output busses of functional modules should be of type lcs::Bus
.
[NOTE: Assignment delays can be associated with busses of type lcs::Bus
. Read more on this here.]
lcs::Module::onStateChange
, lcs::Module::onPosEdge
, lcs::Module::onNegEdge
As mentioned above, all functional modules should be a derivative of the class lcs::Module
. Apart from ensuring that the modules register with the input busses to be notified of input line events, module implementers should override the virtual member functions of the class lcs::Module
as neccessary. For example, an AND gate module should override the lcs::Module::onStateChange
function, while a positive edge triggered D-flipflop module should override the function lcs::Module::onPosEdge
. These are the functions using which the input busses will notify the module of the line events. These functions are the main work horses of the module classes. When notified of a line event, they should take the action corresponding to the event which triggered them. Typically, such action involves processing the line states of the input busses and setting the output bus states accordingly.