Solving Combinational Logic Circuits using plcLib

We previously used a 3-input inclusive OR gate, based on a logical function block, as part of a simple alarm application. Simpler instruction list style coding was used to build basic logical functions including inverters and logic gates with normal or inverted inputs. This article looks in more detail at plclib implementation options related to combinational logic.

Combinational logic refers to digital circuitry, where Boolean Algebra is used to calculate the current output (or outputs), based on the current input values. The output at any time depends only on the present input values, hence, combinational logic does not have ‘memory’. This contrasts with sequential logic, circuits, such as latches/bistables, and more complex circuits such as counters and shift registers, whose behaviour depends not only on the current inputs, but also on the previous ‘history’ of the system.

A key difference between these two types of circuit, when looking at gate-level circuit diagrams, is the absence or presence of feedback connections. These are always present in sequential logic, and are always absent in combinational logic. (It does of course need to be the correct type of feedback, in the right place, for it to become a useful sequential logic circuit!)

A logic diagram may be used to graphically show the layout and interconnections of a combinational logic circuit. These typically have inputs at the left, output(s) at the right, and one or more logic gates in between, with signal flow moving via interconnections, going from left to right. A simple logic diagram is shown below.

A simple logic diagram of a combinational logic circuit.
Figure 1. A logic diagram based on the Boolean expression Y0 = (X0 . X1) + (X2 . X3).

(Parentheses are optional in the above Boolean expression, given that AND has higher priority than OR.)

Logic gates were historically implemented using discrete and low cost logic ICs, such as the venerable 74XX TTL (Transistor Transistor Logic) or 40XX CMOS (Complementary Metal Oxide Semiconductor) IC families. Newer designs make use of programmable logic ICs, such as FPGAs (Field Programmable Gate Arrays) or ASICs (Application Specific Integrated Circuits). However, it is also possible to use a microcontroller, plus a small amount of PLC style coding, to implement simple combinational logic systems, as will be described here.

Whether or not it is a good idea to use a microcontroller, is likely to depend on several factors. If you can say ‘yes’ to the following statements, then it is certainly worth considering: –

  • You are already using a microcontroller in your project so there is no additional expense.
  • You have the required number of unused microcontroller pins, or the logic functions are ‘internal’ so do not require direct I/O connections.
  • The slower propagation delays associated with a software-based solution are not an issue (likely to be measured in tens of microseconds rather than a few nanoseconds).
  • Your application is insensitive to momentary glitches, which could potentially be caused by asynchronous changes to inputs, or intermediate results produced as the network is repeatedly solved.
  • The current operational speed of the system is adequate, and the required spare processor bandwidth is available for the additional functionality.
  • Your application is non-blocking, so there is nothing in your existing code that could prevent the logic functionality from running repeatedly (such as time delays or a paused thread/task).

The plcLib library offers four main approaches, each of which will be briefly discussed in following sections.

  1. Low level ‘Instruction List’ style coding.
  2. Using variables to hold intermediate results, with these acting as named ‘nodes’.
  3. Stack-based storage of intermediate results in complex circuits.
  4. Function block-based logic.

‘Instruction List’ style coding

Instruction list style coding of a simple logic gate, typically starts with a digital input command, followed by one or more single bit logical commands, which is then terminated by a digital output command. For example, a two input AND gate with inputs X0 and X1 and output Y0, may be created ‘on the fly’ by using din(X0), followed by andBit(X1) and then dout(Y0). A summary of 2-input gates with normal (rather than inverted) outputs, is given in Figure 2.

Simple logic gates with normal outputs, shown both in ladder diagram form and also as logic symbols.
Figure 2. Simple logic gates with normal outputs, shown both in ladder diagram form and also as logic symbols (Source: Logic > AndOrXorNot).

Note that an xorBit command is available, so there is no need to implement the exclusive-or command from first principles!

Behind the scenes, the scanValue variable enables communication of results from one command to the next, but only in the same rung of the ladder diagram. Hence it is possible to extend the number of inputs for the above AND, OR and XOR commands, simply adding additional single bit logical commands, as shown in Listing 1.

Listing 1. A 3-input AND gate, created using repeated single bit AND commands.

Inverting Outputs

It is also possible to create NAND, NOR and XNOR gates, simply by replacing the final digital output command with its inverted equivalent – doutNot. A summary of 2-input gates with inverted outputs, is given in Figure 3.

Two-input logic gates with inverted outputs, shown both in ladder diagram form and also as logic symbols.
Figure 3. Two-input logic gates with inverted outputs, shown both in ladder diagram form and also as logic symbols (Source: Logic > NandNorXnor).

Once again, there is no need to implement the exclusive-nor command from first principles. Just follow the previously seen xorBit command with doutNot.

Inverting Input(s)

It may sometimes be necessary to invert an input, prior to it be fed into the logic gate, as shown in the examples of Figure 4.

Simple logic gates with a single inverted input, shown both in ladder diagram form and also as logic symbols.
Figure 4. Simple logic gates with a single inverted input, shown both in ladder diagram form and also as logic symbols (Source: Logic > InvertedInputLogic).

Diagrammatic representation may be simplified by replacing the Not gate by an inversion symbol on the input of the logic gate, although coding is unaltered, as shown in Listing 2.

Listing 2. Performing logic operations with an inverted input (Source: Logic > InvertedInputLogic).

Inverting Inputs and Outputs

Simplification of complex combinational logic circuits may be possible, if the ‘equivalence’ of certain logic circuits is recognised.

According to De Morgan’s Theorem, an AND gate may be transformed to an OR gate by first inverting each input of the AND gate, and then also inverting the output. An equivalent process transforms an OR gate into and AND gate. The following example tests both rules.

Listing 3. Testing De Morgan’s Theorem by converting AND to OR and OR to AND (Source: Logic > DeMorgansTheoremAndOr).

Another way to think of this is that a positive logic AND gate is equivalent to a negative logic OR gate, and vice versa.

It is also possible to use the same approach (inverting all inputs and the output) to convert a NAND gate to a NOR, or a NOR gate to a NAND, as shown below.

Listing 4. Using De Morgan’s Theorem to convert NAND to NOR and NOR to NAND (Source: Logic > DeMorgansTheoremNandNor).

Notice in the above listing that adding another output inversion to a NAND or NOR gate creates a double inversion, which cancels out.

Finally, be aware that De Morgan’s theorem and related logic gate conversations will also work with gates having more than two inputs.

The Need for Temporary Storage

The above instruction list coding approach is useful and flexible when implementing individual logic gates. However, it struggles with more complex circuits – in its simplest form at least. Consider again the circuit of Figure 1, which implements the Boolean expression Y0 = (X0 . X1) + (X2 . X3). This is redrawn below as a ladder diagram.

A combinational logic circuit with two parallel branches, represented in ladder diagram form.
Figure 5. A combinational logic circuit with two parallel branches, represented in ladder diagram form.

The standard coding approach is to start at the upper left, implementing one rung at a time – hence moving repeatedly from left to right and progressing steadily from top to bottom. However, having ANDed X0 and X1, this result will be lost if we continue directly to AND X2 and X3, as the scanValue global variable is cleared at the start of each rung! A method is needed to temporarily store the result of (X0 . X1), before continuing to calculate (X2 . X3), then ORing these two results and finally outputting to Y0. Two approaches are available, as will be discussed next.

Storing Temporary Results in Variables

The first approach is to use variables, or auxiliaries, to hold any intermediate results. These may then be used as inputs to subsequent commands. The following JavaScript sketch demonstrates the approach used to solve the previously seen combinational logic circuit of Figure 1 and the equivalent ladder diagram of Figure 5.

Listing 5. Using a variable to hold temporary logic calculation results (Source: Variables> ComplexLogic).

It is also possible to use variables in a more extensive way, by reading all inputs into variables at the start of the scan loop, storing all intermediate results in temporary variables and finally updating any outputs at the end of the scan loop. Doing so avoids the possibility of the same input or output being updated more than once in a single scan cycle. However it fails to ensure that all inputs or outputs are ‘consistent’ at any given instant, given that each input or output bit is read or updated, respectively, at a slightly different instant of time.

Using a Stack for Temporary Storage

The plcLib library adds the ability to create and use a software-based stack for temporary storage and retrieval of single-bit values. This capability may be combined with block logic commands, to simplify the solution of complex networks based on Boolean algebra – but without the need to create individual user variables for temporary storage, as discussed previously.

A stack is a special area of memory which may be used for temporary data storage and retrieval. Information is stored by being pushed onto the stack and is later retrieved by popping it from the stack. The most recently stored information is always the first to be removed, so the stack acts as a last-in, first-out (LIFO) store, as shown below.

Using a stack as a last-in first-out data store.
Figure 6. A stack is a last-in first-out data store, and may also be used to solve complex logic circuits.

A useful analogy is a pile of trays in a self-service cafeteria, where each tray represents a single piece of information. Storing data is equivalent to adding a new tray to the top of the pile, which causes the ‘stack’ of trays to grow higher. Conversely, information is retrieved by removing a tray. The most recently added tray will always be at the top, and the oldest at the bottom.

The first step when writing a stack-based sketch is to create a stack object. For example, the JavaScript command stack1 = new Stack(); creates a stack called stack1, capable of holding up to 32 single-bit values. These may be added or removed from the stack by using the push or pop methods of the previously created stack object. There is no limit to the number of stacks which may be created, other than the available memory.

The ability to store temporary calculation results on the stack may be used to simplify the solution of complex logic networks. Options are available to combine parallel or series branches by using block-based logical AND and OR operations.

A ladder logic network such as that previously seen in Figure 5, may be solved by using a three step process. The first is to calculate the upper branch, then save (or push) this intermediate result on the stack. The second branch may then be solved in the normal way, hence leaving its result in the scanValue variable. The third and final step is to combine the current and previous results, by using the orBlock method of the stack object, which also removes (or pops) the previous result from the stack. The following JavaScript sketch demonstrates the approach.

Listing 6. Performing a logical OR of two parallel branches using Block OR instruction (Source: Stack > OrBlock).

A similar technique may be applied with series connections of switch groups, such as that shown below, which may be combined using an andBlock command.

A Block-AND operation may be used to solve a complex network consisting of series elements.
Figure 7. A Block-AND operation may be used to solve a complex network consisting of series elements.

The associated sketch is shown below. This first calculates the result of the switch group at the left, which is stored as an intermediate result on the stack. The right hand block is then solved and combined with the earlier result by using the andBlock method of the stack object.

Listing 7. Logical AND of two series switch groups using a Block AND instruction (Source: Stack > AndBlock).

Using Logical Function Blocks

A range of logical function blocks are available in plcLib, which implement the behaviour of common logic gates. These may be visualised using function block diagrams, as shown in the following illustration.

A simple function block diagram with interconnected rectangular function blocks.
Figure 8. A simple function block diagram with interconnected rectangular function blocks.

The above diagram has obvious similarities to the original logic diagram of Figure 1, which implemented the Boolean expression Y0 = (X0 . X1) + (X2 . X3). An equivalent JavaScript-based sketch is shown below.

Listing 8. A combinational-logic circuit consisting of a pair of 2-input AND gates, followed by an inclusive-OR (Source: FB >NetworkAndOr).

The above sketch begins with the creation of three function block variables, each of which are used to hold the result of their associated logical function block command. The commands themselves are placed inside the main program loop and accept firstly the name of their associated variable, followed by up to four input parameters.

The compulsory use of data variables is a key feature of logical function blocks, which contrasts with their optional use with instruction list programming. In effect, each node of the related function block diagram, is automatically associated with the related function block output. Note that plcLib-based function block diagrams may optionally be annotated with these variable names, as was demonstrated in the preceding article on Block Diagram-based System Development.

Logical Function block commands are available for all major logic gate types, as shown below.

  • AND – andFB
  • OR – orFB
  • Not – notFB
  • NAND – nandFB
  • NOR – norFB
  • XOR – xorFB
  • XNOR – xnorFB

Please see the ‘FB’ folder of the live system for examples of each command.

Final Thoughts

This article has demonstrated four main approaches to the solution of combinational logic circuits, based on the plcLib library.

  1. Low level ‘Instruction List’ coding, including the use of inverted inputs and/or outputs.
  2. Using variables to hold ‘named’ intermediate results.
  3. Stack-based storage of intermediate, combined with block logic commands.
  4. Function block-based logic with automated use of variables at each associated output node.

To some extent, the method used is a matter of personal preference, although each approach has its own pros and cons. The speed and simplicity of instruction list style coding, based on unnamed circuit nodes, contrasts with the more highly structured approach of logical function blocks. The use of variables to hold named nodes may be viewed as a half-way house between the two extremes. A stack-based solution is perhaps the least used of all approaches, although potentially useful when solving networks based on series or parallel branches.

The use of a microcontroller to implement logic functions may be applicable in certain situations, particularly where this is part of the implementation of a larger system. Recall that a checklist of potential reasons to use a microcontroller was considered earlier.

It is important to remember some of the inherent limitations of this approach, including the lower speed of operation, compared to dedicated silicon-based logic.

A further issue is the asynchronous relationship between external signals (which can change at any instant) and the sequential reading of each individual digital input, as part of the internal operation of the scan loop of the plcLib software. This problem can be minimised, although not eliminated, by a structured approach to system design, which includes: –

  • Reading all inputs, once only, at the start.
  • Performing all operations and calculations on previously inputted variables.
  • Updating outputs at the end.

Despite these limitations, many applications will operate without problems, particularly if the operating speed of the scan loop is significantly faster than the rate at which inputs change. For example, the previously considered burglar alarm circuit is relatively insensitive to the precise timing of the alarm signal, or the order in which sensor inputs occur. The same can be said of many systems designed to operate at ‘human’ speeds.

Nevertheless, it is important to carefully all possible input scenarios and ensure that a potentially unlikely set of circumstances cannot cause incorrect operation. If the scan cycle is running thousands of times a second, then even the most unlikely of situations will eventually occur!


Posted

in

,

by

Tags: