3-Bit Discrete RTL Counter

Interactive 3-bit counter using RTL-logic, built from raw transistors and resistors.

Introduction

In this project, we explore computing in its purest form. By foregoing the abstractions offered by microchips, integrated circuits and even logic gates, we develop an intuition and appreciation for the simplest building block underpinning modern computing - the transistor. Given that modern computers offer millions, sometimes billions of transistors inside a single chip, we will focus our efforts here on a simpler logic device with a much lower transistor count. The 3-bit counter is the perfect candidate. It has, as we will see, a relatively low transistor count, yet still possesses basic logical structures present in the most modern of computers. In this project, we will start with an overview and block diagram of a 3-bit counter, explore the bipolar junction transistor (BJT), build and simulate a digital circuit, and finally design, assemble and test the circuit on a printed circuit board (PCB).

The 3-bit Counter

A 3-bit counter is a simple digital circuit made up of three flip-flop circuits (one per bit) that steps through every binary value from 000 to 111 (0–7 in decimal) and then wraps back around to 000. Each clock pulse advances the count by one, with the least significant bit toggling every pulse and higher bits toggling only when the bit below rolls over. This gives a neat, orderly binary sequence, which we will visualise via three LEDs for the binary output, and a seven-segment display for decimal output.

Functional block diagram
Figure 2.1 - Functional block diagram.
The functional block diagram in Figure 2.1 illustrates the flow of data between high-level logic blocks in the circuit. Most notable are the three D flip-flops (one each for \(S_{0}\), \(S_{1}\) and \(S_{2}\)), next-state combinational logic blocks and the binary and 7-segment decoder. To drive the decimal output via the 7-segment display, we need a 7-segment decoder logic block to drive the necessary LED segments for each input number. Lastly, a clock pulse is provided to the three flip-flops to synchronise the system. We will explore how each of these blocks work in the following section.

Resistor Transistor Logic - Crossing from Digital to Analog

You are probably already familiar with the primitive logic functions, NOT, AND, OR, XOR and their negations. In the simplest case these logic functions transform two binary inputs A and B into a binary output, Y, corresponding to the given function. For example, Figure 3.1 shows the NAND gate alongside its truth table. The NAND gate is surprisingly versatile and will be of prime importance in the following digital design section.

NAND gate

\[ \begin{array}{cc|c} A & B & Y = \overline{A \,\cdot\, B} \\ \hline 0 & 0 & 1 \\ 0 & 1 & 1 \\ 1 & 0 & 1 \\ 1 & 1 & 0 \end{array} \]

Figure 3.1 — NAND gate.

These logic gates are powerful and allow the construction of more complex digital building blocks, eventually all the way to a full CPU. However, how can we build logic gates in the real world? The symbol above is a 'black-box', abstracting details away from the actual implementation. Surprisingly, logic gates can be implemented in various ways, for example, water valves, relays, Minecraft redstone and of course, transistors. There are various logic families which use transistor logic. For this project, we have selected resistor–transistor logic (RTL) due to its simplicty, from both an implementation and educational standpoint.

Resistor–Transistor Logic (RTL) emerged in the early 1960s as one of the first practical digital families built from discrete components. By combining simple resistors for pull-up bias and NPN transistors for pull-down switching, RTL enabled the construction of basic logic. This lead to transistor counts far lower than earlier relay or vacuum-tube systems. Widely used in early computer prototypes and minicomputers, RTL laid the groundwork for more advanced families (DTL, TTL) by demonstrating how discrete components could reliably implement Boolean functions at kilohertz-scale clock rates. Interestingly, RTL was used for the guidance computers of the Apollo missions!

RTL NAND simulation with A=0, B=0
Figure 3.2 — RTL NAND gate: A = 5V, B = 0V => Y = 5V
RTL NAND simulation with A=1, B=1
Figure 3.3 — RTL NAND gate: A = 5V, B = 5V => Y ~= 0V

RTL is remarkably simple, the only components required are NPN bipolar junction transistors (BJTs) and resistors. In each gate, NPN transistors act as pull-down switches, while resistors provide pull-up bias. It is important that we operate all BJTs in their 'saturation' mode, where they act like a switch than a current amplifier. We spare the details, but one should appreciate the analog electronics going on here. For logic, we use a 0-5 V voltage scale, with [0-1] V meaning '0' (LOW) and [3-5] V meaning a '1' (HIGH). In general, AND functions are implemented with BJTs in series, and OR functions with BJTs in parallel. In fact, the natural logic formed by these gates is NAND and NOR, respectively. To save on components (additional NOT gates), we restrict ourselves to these three logic functions. (Aside: one can actually show that any other logic function can be built purely from NAND gates). After experimentation with noise immunity, rise time and (critically) fan-out, 1 kΩ pull-ups were standardised for the collector resistance, whilst 10 kΩ would be used on the gate-inputs to isolate transistor bases and limit drive current. Fan-out refers to the phenomenon where the logical tolerances of a logic gate degrade as it drives an increasing number of inputs. For example, if we connect the output of an RTL NAND gate to the inputs of 6 downstream gates, each will load some base current through the collector resistor of the previous stage. A voltage divider expression was used to derive a function for the loss in ability of driving a logic HIGH as fan-out increased. As 3 V was deemed tolerable to push the BJTs into saturation to pull down to a 'healthy' LOW, a rough fan-out limit of 6-8 was calculated and simulatied in JScircuit. Figure 3.2 and 3.3 show a 2-input RTL NAND gate simulation in JScircuit. Based on the input switch configurations in either figure, one can see that this behaviour is consistent with the NAND gate in Figure 3.1. Note that the output produces a clean 5 V when the inputs are LOW, however this voltage will sag when the output drives a gate downstream. Additionally, the output cannot reach a true 0 V due to the V_CE(sat) of the transistors.

With our physical gate implementations ready to go, we can proceed with building digital logic blocks.

Designing Digital Logic Blocks

At the heart of the 3-bit counter are three fundamental digital logic blocks: the D flip-flops that store each bit, the next-state toggle logic that determines when each flip-flop should change state, and the seven-segment decoder network that converts the binary count into signals for the decimal display. Each of these blocks must operate reliably using only discrete RTL components, so we must carefully balance speed, noise immunity, and component count while keeping the entire design simple enough for hand-assembly.

4.1 D Flip‑Flop

Flip-flops are the fundamental digital circuit responsible for volatile memory. They are the key to how computers can store data. For this project, we have 3-bit in our counter, which means we mean to store 3 state bits. Each flip-flop can store 1 bit, so we will need 3 of them. The key to computer memory is feedback and the ability to settle into stable states. Whenever feedback is involved, digital circuits will generally be non-combinational - i.e. they possess memory. The D flip-flop is an example of a sequential circuit. In order to construct the D flip-flop, we must first consider the D latch, shown in Figure 4.1.1.

D-latch digital circuit

\[ \begin{array}{c c c|c} \mathrm{CLK} & D & Q_{\text{new}} \\ \hline 0 & 0 & Q_{\text{old}} \\ 0 & 1 & Q_{\text{old}} \\ 1 & 0 & 0 \\ 1 & 1 & 1 \end{array} \]

Figure 4.1.1 — D-latch digital circuit and truth table.

One may consider each case of the output Q and trace the signals back through the cross-coupled NAND gates to convince oneself of the truth table in Figure 4.1. Effectively, the latch is 'transparent' to the data signal (D) when CLK is HIGH, and 'opaque' when it is LOW. However, imagine the is a glitch in the data signal during the clock HIGH period. In this case, this glitch will be passed to the output Q. This is undesirable. We would like the circuit to sample D instantenously at the rising edge of the clock. Doing so results in the D flip-flop.

The D flip‑flop (DFF) is implemented as two D-latches, master and slave. On the falling edge of the clock, the master latch closes and the slave latch opens, capturing the input effectively instantenously and securely storing it. In order for this to work on the rising edge of the clock, we must feed the inverse (NOT) of CLK into the slave's clock input. The final D flip-flop is shown, in Figure 4.1.2, using D-latch blocks.

D flkip-flop digital circuit
Figure 4.1.2 — D flip-flop digital circuit, showing master and slave D-latches.
Given our RTL implementation, we can easily build 3 DFFs using BJTs and resistors. Next we consider how to drive the data inputs.

4.2 Next‑State Logic

Now we can store 3-bit numbers, but how do we get the circuit to count? The answer is to design combinational logic blocks which drive the data inputs of the DFFs. Every rising edge of the clock, the state of the counter must update. Thus, our counter is in fact a finite state machine (FSM). There are 8 total states, corresponding to the range of a 3-bit number, i.e. the integers 0 (000) through 7 (111). On each rising clock edge, the FSM must transition from state n to state n + 1. This seems easy, however our state is binary encoded with 3 bits. We will call them \(S_0\), \(S_1\) and \(S_2\) from least to most significant. Given this, the decision of when each bit must toggle is non-trivial. In a simple sequential circuit such as a shift-register, this logic is simple - the output bit of one DFF (i.e. \(Q_n\)) simply flows into the input of the next (\(D_{n+1}\)). In this section, we develop the combinational logic blocks required to drive the data inputs of each DFF.

\[ \begin{array}{c|ccc|ccc} \text{Decimal} & S_2^{n} & S_1^{n} & S_0^{n} & S_2^{n+1} & S_1^{n+1} & S_0^{n+1} \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 1\\ 1 & 0 & 0 & 1 & 0 & 1 & 0\\ 2 & 0 & 1 & 0 & 0 & 1 & 1\\ 3 & 0 & 1 & 1 & 1 & 0 & 0\\ 4 & 1 & 0 & 0 & 1 & 0 & 1\\ 5 & 1 & 0 & 1 & 1 & 1 & 0\\ 6 & 1 & 1 & 0 & 1 & 1 & 1\\ 7 & 1 & 1 & 1 & 0 & 0 & 0 \end{array} \]

Figure 4.2.1 — 3-bit binary count next-state transition table.

Let us first analyse how binary counting works, from one 3-bit number to the next. See Figure 4.2.1. Looking down the columns for each \(S_n\) we see the \(S_0\) simply toggles on each transition between '0' and '1'. \(S_1\) toggles in a similar way, exceot it toggles on every 2nd transition. Cosistent with this pattern, \(S_2\) toggles on every 4th transition. From this, we can already see that the input for the \(S_0\) DFF, i.e. \(S_{0}^{\,n}\) is simply the inverse (NOT) of the output \(Q_1\), or \(S_{0}^{\,n+1}\)! Following the standard sum-of-products (SOP) boolean algebra simplication procedure, we prove this obvious result in Figure 4.2.2.

\[ \begin{aligned} S_{0}^{\,n+1} &= \overline{S_2^{\,n}}\,\overline{S_1^{\,n}}\,\overline{S_0^{\,n}} + \overline{S_2^{\,n}}\,S_1^{\,n}\,\overline{S_0^{\,n}} + S_2^{\,n}\,\overline{S_1^{\,n}}\,\overline{S_0^{\,n}} + S_2^{\,n}\,S_1^{\,n}\,\overline{S_0^{\,n}} \\[6pt] &= \overline{S_0^{\,n}} \Bigl(\overline{S_2^{\,n}}\,\overline{S_1^{\,n}} + \overline{S_2^{\,n}}\,S_1^{\,n} + S_2^{\,n}\,\overline{S_1^{\,n}} + S_2^{\,n}\,S_1^{\,n}\Bigr) \\[6pt] &= \overline{S_0^{\,n}} \Bigl(\overline{S_2^{\,n}}\Bigl(\overline{S_1^{\,n}} + S_1^{\,n}\Bigr) + S_2^{\,n}\Bigl(\overline{S_1^{\,n}} + S_1^{\,n}\Bigr)\Bigr) \\[6pt] &= \overline{S_0^{\,n}} \Bigl(\overline{S_2^{\,n}} \cdot 1 + S_2^{\,n} \cdot 1\Bigr) \\[6pt] &= \overline{S_0^{\,n}} \Bigl(\overline{S_2^{\,n}} + S_2^{\,n}\Bigr) \\[6pt] &= \overline{S_0^{\,n}} \cdot 1 \\[6pt] &= \overline{S_0^{\,n}} \end{aligned} \]

Figure 4.2.2 — Boolean algebra simplification of the SOP expression for \(S_{0}^{\,n+1}\).

Thus, the next-state logic block for the \(S_0\) DFF (\(NSL_0\)) is simply a NOT gate.

The next-state logic for \(S_1\) is more complex. Let us first consider how this works in the familiar decimal system. When any column in a decimal number, i.e. units, tens, hundreds, etc. reaches '9', the next increment will overflow that column and cause a carry of '1' to be added to the next column. This is simpler in binary as the maximum number in any binary-place is '1'. Thus, glancing back to the state-transition table in Figure 4.1.1, we see that \(S_1\) should only toggle on the next rising-edge if \(S_0\) is currently a '1'. Note that this toggling functionality would make the logic trivial if we were using a T flip-flop. Alternatively, following the standard boolean arithmetic SOP procedure, we see that \(S_1\) is only a '1' on the next edge if it is \(S_1 = 0\) AND \(S_0 = 1\), OR if \(S_1 = 1\) AND \(S_0 = 0\). Again, we can see this algebraically in Figure 4.2.3.

Thus, the next-state logic for the \(S_1\) DFF (\(NSL_1\)) is the XOR of \(S_0^{\,n}\) and \(S_{1}^{\,n}\). This simple expression is nice, but recall we can only implement NAND, NOT and NOR with RTL logic. For simplicity, we aim for just NAND and NOT. Hence, we cannot directly implement the XOR function. To develop a 'NAND-friendly' expression, we need to 'unsimplify' using a special result called De Morgan's Theorem, which states the following

\[ \begin{alignedat}{2} \overline{A + B} &= \overline{A}\,\overline{B}, &\quad \overline{A\,B} &= \overline{A} + \overline{B} \end{alignedat} \]

In simple words, this theorem states that we may 'push' negations through a logic function (from input to output, or vice versa), so long as we change ORs to ANDs and ANDs to ORs. Now we 'NAND-friendly' simplify \(S_{1}^{\,n+1}\) in Figure 4.2.4.

\[ \begin{aligned} S_{1}^{\,n+1} &= \overline{S_1^{\,n}}S_0^{\,n} + S_1^{\,n}\overline{S_0^{\,n}} \\[6pt] &= \overline{\overline{\overline{S_1^{\,n}}S_0^{\,n}}} + \overline{\overline{S_1^{\,n}\overline{S_0^{\,n}}}} \\[6pt] &= \overline{\overline{\overline{S_1^{\,n}}S_0^{\,n}} \cdot \overline{S_1^{\,n}\overline{S_0^{\,n}}}} \\[6pt] \end{aligned} \]

Figure 4.2.4 — NAND-friendly simplification of the SOP expression for \(S_{1}^{\,n+1}\).

The expression above uses 5 gates, 2 NOT and 3 NAND. As \(\overline{S_0^{\,n}}\) and \(\overline{S_1^{\,n}}\) will already be available from the \(\overline{Q}\) outputs of the DFFs, the \(S_1\) next-state logic block really only consumes 3 NAND gates. Solid!

The next-state logic block for \(S_2\) is left as an exercise to the reader, and has the final expression given in Figure 4.2.5.

S_2 next-state logic NAND circuit.

\[ \begin{aligned} S_{2}^{\,n+1} &= \overline{\overline{\overline{S_{2}^{\,n}}S_{1}^{\,n}S_{0}^{\,n}} \cdot \overline{S_{2}^{\,n}\overline{S_{1}^{\,n}S_{0}^{\,n}}}}\\[6pt] \end{aligned} \]

Figure 4.2.5 — Simplified SOP expression for \(S_{2}^{\,n+1}\) and logic circuit implementation.

This expression uses 4 NAND gates. Note that two of these gates are 3-input NAND gates. This is easily achieved by adding a third input resistor and BJT in series to the other two in Figure 3.2.

4.3 7‑Segment Decoder

The final combinational logic block converts the 3-bit binary count into segment signals for the LED decimal display. Each of the seven segments corresponds to an LED inside the physical 7-segment display depicted in Figure 4.3.1. Later, we will need to tweak our 7-segment decoder to actually drive the the LEDs inside the common-cathode 7-seg display we will use. For now, a '1' or HIGH output will suffice in our analysis to turn a segment on.

7-segment digit patterns
Figure 4.3.1 — 7-segment digit patterns.

To gain an intuitive understanding for the logic we must design, let us think about the problem mathematically. Effectively, we are trying to implement a function of 3 variables, to 7 variables - i.e. 3 inputs (\(S_2, S_1, S_0\)), to 7 outputs (\(a, b, c, d, e, f, g\)). Additionally, the input and output variables live inside the binary field, \(\mathbb{F}_2\). This a fancy algebraic way of saying our binary variables, like \(S_1\), can only take on the values of '0' and '1', and may be operated on by any of our logical function, NAND, NOT, etc. Mathematically, the 7-segment decoder function, \(f\), is correctly represented with the notation in Figure 4.3.2. The image shows the functional representation of our desired combinational logic function. The remainder of this subsection will investigate the logic inside.

\[ \begin{aligned} &f:\;\mathbb{F}_2^3 \;\longrightarrow\;\mathbb{F}_2^7, \quad (S_2,S_1,S_0)\;\mapsto\;(a,b,c,d,e,f,g) \end{aligned} \]

7-segment function
Figure 4.3.2 – 7-segment decoder function representation.

In order to implement this function, we need to repose the problem as 7 independent functions of the 3 inputs, then analyse the truth table and SOP expression for each. For example \(f_{a}:\;\mathbb{F}_2^3 \;\longrightarrow\;\mathbb{F}_2, \quad (S_2,S_1,S_0)\; \mapsto\;a\) represents the single output segment \(a\) as a function of the 3 input bits. We will provide full derivations of the simplified functions for \(a\) and \(b\), leaving \(c, d, e, f\) and \(g\) as exercises to the reader.

Firstly, we write out the truth table for the 7-seg decoder function by analysing the digit patterns in Figure 4.3.1.

\[ \begin{array}{ccc|ccccccc} S_{2} & S_{1} & S_{0} & a & b & c & d & e & f & g \\ \hline 0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 1 & 0 \\ % 0 0 & 0 & 1 & 0 & 1 & 1 & 0 & 0 & 0 & 0 \\ % 1 0 & 1 & 0 & 1 & 1 & 0 & 1 & 1 & 0 & 1 \\ % 2 0 & 1 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 1 \\ % 3 1 & 0 & 0 & 0 & 1 & 1 & 0 & 0 & 1 & 1 \\ % 4 1 & 0 & 1 & 1 & 0 & 1 & 1 & 0 & 1 & 1 \\ % 5 1 & 1 & 0 & 1 & 0 & 1 & 1 & 1 & 1 & 1 \\ % 6 1 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 % 7 \end{array} \]

Figure 4.3.3 — 7-segment decoder truth table.

Next, we derive and simplify the SOP expressions for \(a\) and \(b\). In this subsection, we will use Karnaugh maps for simplification.

For segment \(a\), we wish to find minimised SOP expression \(a = f_{a}\left(S_0, S_1, S_2\right)\). As there are less than five variables, we may use a Karnaugh map to find the minimised SOP expression. Karnaugh maps provide an easy graphical way to find minimised boolean expressions for binary functions of 4 or less variables. In the Karnaugh map in Figure 4.3.4, we use Gray code to enumerate the rows and columns, with the ability to pair \(S_0\) and \(S_1\) across the columns. Then, for a minimised SOP expression, we circle all the '1' in as few as possible rectangles, which must contain a power of two '1's. Confusingly, the map must be thought of as toroidal (i.e. a donut) to catch the 'wrap-around' simplifications.

'a' segment k-map

\[ \begin{aligned} \implies a &= f_{a}\left(S_0, S_1, S_2\right) = S_1 + S_{2}S_{0} + \overline{S_{2}}\,\overline{S_{1}} \end{aligned} \]

Figure 4.3.4 — Segment \(a\) Karnaugh map, followed by minimised SOP expression.

This process is repeated for segment \(b\). The relevant Karnaugh map and minimised SOP expression for \(f_{b}\left(S_0, S_1, S_2\right)\) is shown in Figure 4.3.5.

'a' segment k-map

\[ \begin{aligned} \implies b &= f_{b}\left(S_0, S_1, S_2\right) = \overline{S_2} + S_{1}S_{0} + \overline{S_{1}}\,\overline{S_{0}} \end{aligned} \]

Figure 4.3.5 — Segment \(b\) Karnaugh map, followed by minimised SOP expression.

After repeating for segments \(c, d, e, f\) and \(g\), we need to think about the implementation of the decoder. Which logic circuits are we going to put inside the box in Figure 4.3.2 to achieve the desired behaviour. The groundwork is already complete in our minimised SOP expressions above. For each of the decoder's 7 outputs, \(a, b, c, d, e, f, g\), we now have a logic function in terms of the 3 inputs \(S_0, S_1, S_2\). Hence, we can implement the decoder as 7 independent sub-circuits, with each one constructed from simply reading off the SOP expressions above. However, recall that in our RTL implementation, we prefer NAND/NOT logic. As such, we will first manipulate the SOP expressions for \(f_{a}\) to \(f_{g}\) to be NAND-friendly. For \(a\) and \(b\) these expressions are given in Figures 4.3.6 and 4.3.7, respectively, along with logic circuit. Note that \(S_0, S_1, S_2\) and \(\overline{S_0}, \overline{S_1}, \overline{S_2}\) are available directly from the output of the flip-flops.

'a' segment k-map

\[ \begin{aligned} a &= f_{a}\left(S_0, S_1, S_2\right) = \overline{\overline{S_1} \cdot \overline{S_{2}S_{1}} \cdot \overline{\overline{S_2}\,\overline{S_1}}} \\ \end{aligned} \]

Figure 4.3.6 — NAND-friendly SOP expression and circuit realisation for \(f_{a}\).
'a' segment k-map

\[ \begin{aligned} b &= f_{b}\left(S_0, S_1, S_2\right) = \overline{S_2 \cdot \overline{S_{1}S_{0}} \cdot \overline{\overline{S_1}\,\overline{S_0}}} \end{aligned} \]

Figure 4.3.7 — NAND-friendly SOP expression and circuit realisation for \(f_{b}\).

This process is repeated for segments \(c, d, e, f\) and \(g\).

With this complete, we simply package the sub-circuits for \(f_{a}\) to \(f_{g}\) into the 7-segment decoder block, connecting the appropriate inputs and outputs. This is shown in Figure 4.3.8.

7-segment decoder functional block diagram
Figure 4.3.8 — 7-segment decoder functional block diagram.

Lastly, to reduce gate count, and hence components, we may re-use gate outputs which appear in other expressions. For example, the \(\overline{S_{2}S_{0}}\) term from \(f_{a}\) also appears in the expressions for \(f_{d}\) and \(f_{e}\), and are hence reused. The full circuit diagram for the 7-segment decoder will be provided in the simulation section.

Simulation

With the theoretical groundwork complete, we now simulate the digital building blocks expored in the previous section and analyse their behaviour to ensure they will function correctly in the real circuit. By progressively simulating and combining each block, we eventually work up to the full 3-bit counter as given in Figure 2.1. We use JScircuit as our simulation platform, due to its ability to simulate digital logic and its highly-interactive and visual interface.

The simulation in Figure 5.1 shows the complete NAND-friendly implementation of the 7-segment decoder, with gate resuse where appropriate. One can see from top to bottom that the circuit has the same structure as the block diagram in Figure 4.3.8. There are a total of 18 NAND gates, and 3 NOT gates, but recall the NOT function on \(S_0, S_1\) and \(S_2\) will actually come from \(\overline{Q}\) of the flip-flops. Notice that there are six 3-input and one 4-input NAND gates. Therefore, the total transistor count for the decoder will be \(N_{T} = (11)(2) + (6)(3) + (1)(4) = 44\).

Use the mouse wheel to zoom, and middle-mouse button to pan in the simulation below. The three inputs underneath \(S_0, S_1, S_2\) (either "L" or "H") can be clicked to toggle. Try all combinations from 000 to 111 to test that the decoder works!

Figure 5.1 — 7-segment decoder simulation in JScircuit.

Next, we simulate the counter itself. Recall from Figure 2.1 that this consists of 3 D flip-flop circuits, each with data input \(D\) fed from the corresponding 'next-state logic' block. Further, Figures 4.1.1 and 4.1.2 remind us how the D flip-flop are implemented with logic gates. The simulation below shows the counter sequenitally count from \(000_{2}\) (1) to \(111_{2}\) (10), and then repeat. This output is displayed via 3 LEDs, one for each bit. Also provided is an analog output for each bit, showing either a perfect 0 V or 5 V. Recall that this ideal behaviour is not possible when we implement the RTL logic in the physical circuit In the simulation, the clock signal is provided from a square wave source with a constant period. However, in our physical implementation the clock will be controlled by a button press.

Figure 5.2 — 3-bit counter circuit simulation in JScircuit. D flip-flops and next-state logic blocks are labelled.

One can see that the output is updated on the positive or rising-edge of the clock, whilst the intermediate state of the flip-flops update on the negative or falling edge. Recall from Figure 4.1.1 that the 'slave' D-latch is triggered by \(\overline{CLK}\), not \(CLK\).

Lastly, we combine the two simulations above to produce a digital simulation of our entire 3-bit counter system. The result is shown in Figure 5.3. Note that the binary LED output has been omitted for clarity. Notice that we have the same flip-flops and next-state logic as in Figure 5.2, however the output bits \(S_0, S_1\) and \(S_2\) are now connected to the input of the 7-segment decoder circuit, as was depicted in Figure 5.1. Again, the clock for this circuit is driven by a square wave source, unlike the physical circuit design in the following section.

Figure 5.3 — Complete 3-bit counter simulation in JScircuit. Binary LED output excluded for clarity.

The flip-flops count cleanly and the the 7-segment decoder works nicely and displays the correct digit on the 7-segment display. Notice that the simulation in Figure 5.3 has acutually missed a few NOT gate cancellations and neglected to use the negations of \(S_0, S_1\) and \(S_2\) from the \(\overline{Q}\) output of the flip-flops - whoops!

To realise this circuit physically, we need to use the RTL principles developed in section 2. Whilst clean and functional, the simulation in Figure 5.3 uses ideal logic gates which consume zero current, are magically powered and have infinite input and zero output impedance. We need to now translate the digital circuit in Figure 5.3 into the final realisable circuit using RTL logic. As we have already developed the necessary theory, we simply replace the NAND and NOT gates in Figure 5.3 with the implementations in Figure 3.2. To rehash - this involves one NPN BJT for each gate input, alongside a base \(1k\Omega\) resistor. The BJTs are connected in series, and pulled up to 5V by a \(10k\Omega\) collector pull-up resistor. Recall also that we estimated that with typical NPN BJT parameters and the resistor values chosen, we should be able to handle a fan-out of roughly 6-8. The NOT gate for \(\overline{CLK}\) and \(S_0, S_1, S_2\) inputs to the 7-seg decoder suffer from significant fan-out (5 V HIGH reduced to around 3.1 V), yet this is still ample to bias the BJTs into saturation. As long as we are careful in how we logically interpret our outputs, the circuit will work.

In Figure 5.4, the D flip-flop and next-state logic for bit \(S_0\) is implemented with the RTL gates derived earlier. One can see that fan-out is an issue in the output as logic HIGH sitting around 3.9 V instead of 5 V. Also, logic LOW is around 144 mV due to the \(V_{CE(\text{sat})}\) of the BJT.

Figure 5.4 — D flip-flop simulation for \(S_0\) with RTL logic. \(S_0'\) next-state logic (NOT gate) implemented to show the counting effect of the \(S_0\) bit (i.e. a toggle).

The entire RTL circuit is not simulated due to degraded simulation performance with over 100 transistors. Transistors are governed by the Ebers-Moll equation, which has a non-analytic inverse and thus requires a numerical scheme to solve. Doing this across many transistors connected to each other in complex ways inevitably causes things to bottleneck.

\[ I_C = I_S e^{\frac{V_{BE}}{V_T}} \quad \text{and} \quad V_{BE} = V_T \ln \left( \frac{I_C}{I_S} \right) \]

Figure 5.5 — Ebers-Moll model of a BJT: collector current \( I_C \) depends exponentially on base-emitter voltage \( V_{BE} \), with \( I_S \) being the scale current and \( V_T \) the thermal voltage (\(\approx 26\,\text{mV}\) at room temp).

With the results of the above simulations, we confidently proceed into building the physical circuit schematic and routing up a PCB.

PCB Design with KiCAD 8

Building the physical circuit is a three step process. Firstly, we must capture the schematic into KiCAD's schematic editor (eeschema). Secondly, we design the PCB by laying out components and routing them up. Lastly, the board is carefully assembled and tested piece-by-piece, until the board is functional (there will be inevitably be errrors!).

6.1 Schematic Capture

Schematic capture involves defining all symbols (or using preloaded library symbols), placing them on the schematic, and wiring nodes together to produce the desired circuit. For readability, we place component blocks such as the USB-C power supply, the DFFs and the 7-seg decoder in blue boxes. Further, abiding by the engineering principles of regularity and modularity, we define our NAND and NOT gates as sub-circuits, which simply appear as yellow boxes in Figure 6.1.2. A few of these sub-circuit definitions are given in Figure 6.1.3. One can see how the inputs and output are appropriately defined according to the RTL gate structure explored in Figure 3.2.

Whilst this circuit matches the simulation in Figure 5.3, there are some key differences:

Pan and zoom around the schematic in Figure 6.1.2. to see each module and the wiring between symbols. Note that labelled nets are considered to be the same physical location (i.e. connected). Figure 6.1.3 shows the definition of the gate subcircuits.

Figure 6.1.2 — Full KiCAD schematic for the 3-bit counter circuit, viewable via scroll and zoom controls.
3-input RTL NAND gate subcircuit
Figure 6.1.3 — 3-input RTL NAND gate subcircuit.
4-input RTL NAND gate subcircuit
Figure 6.1.4 — 4-input RTL NAND gate subcircuit. The \(220\Omega\) collector resistor is use to drive one of the LED segments.

Note the various test-points in the schematic above. They seem inconvenient now, but will extremely valuable during assembly! Before moving onto PCB design, we run the electrical rules checker to ensure the circuit is electrically sound.

6.2 PCB Layout & Routing

With the schematic entered, we open up 'pcbnew' and import all components from our schematic into the PCB editor. Upon doing this, there is a mess of lines going everywhere - this is called the rats nest and is a graphical guide to the connections we must make during routing to make the PCB agree with the circuit in our schematic. Additionally, every symbol needs a footprint to physically land on the pcb, through-hole pins, SMT solder pads, indication of polarity, etc. Common components like 0603 resistors and capacitors already have footprints via the KiCAD parts library. Components without footprints must have one designated. This can be designed in the footprint editor.

The PCB design process is a creative one. This part of the project is easily the most time consuming. I spent many hours developing a logical layout of components, only to tear it all down and try something else. The design I settled on is a compromise of presenting circuit modules in an integrated way, clearly segregating them while showing their interconnections, whilst enabling dense and efficient routing of the actual components. The silkscreen shows the full block diagram of the circuit, similar to Figure 2.1. Whilst traces are routed in which ever direction is most appropriate, lines on the silkscreen show how the modules connect. The button input and binary/decimal outputs are clearly and marked and labelled. The USB-C receptacle is mounted on the bottom-left, along with all power circuitry.

To reduce complexity, each logic gate was arranged with resistors and BJTs matching their position and orientations in the schematic. Components were placed as close as was reasonable, due to the high component count. This made gate routing easy, and most often traces were short and straight. Each gate is powered by a small via that drops straight down to layer 3 (5V). The final PCB layout is shown in Figure 6.2.1.

2D rendered PCB
Figure 6.2.1 — PCB layout views. Cycle between copper pour ON, copper pour OFF, and 2D render using the button above.

The board uses a 4-layer stackup. This means internally there are 4 copper layers, each with FR4 dielectric between them. This choice was made as the routing for this board is quite complex due to the ~300 components. More layers gives more routing flexibility, and avoids situations that run into 'dead ends'. The top and bottom layers are both used for signal routing, whilst layer 2 is an uninterrupted ground plane and layer 3 is a 5V plane with minimal routing. Having a continous ground and flexible routing options means traces can be more direct and signal integrity improved. Remaining space on the top and bottom layers are filled with copper connected to ground, with many stitching vias to ensure good grounding throughout.

With the PCB design complete, the gerbers and drill files were exported and the board plus components were ordered from JLCPCB.

6.3 Assembly and Testing

A week later, the blank boards arrived from JLCPCB, along with the components.

'a' segment k-map
Figure 6.3.1 — JLCPCB order.

PCB assembly is a delicate process. Assemble too many components before testing and any error will be incredibly difficult to debug. The best approach stems from starting at the absolute basic, and only proceeding with further assembly when everything until that point is verified to be working. The general method I used to assembly the board is outlined below. Keep in mind that some of these steps take minutes, while some take days. The golden tool for this step is the multimeter. It is particularly useful for measuring continuity, voltage, resistance and capacitance in this design.

  1. Probe the blank board to ensure GND and 5V plane are connected to all necessary pads (or at least a large sample of them).
  2. Solder on USB-C receptable and CC resistors. Check 5V is received, then finish power supply.
  3. Assemble button, RC debounce filter and CLK LED (see the video in Figure 6.3.2). Check that the voltage on CLK transitions cleanly and does not bounce (see Figure 6.3.3)
  4. Assemble RTL NOT gate to produce \(\overline{CLK}\). Check gate works as intended. Note inital fan-out voltage drop CLK. I.e. HIGH output from CLK is no longer a perfect 5V.
  5. Assembly just one of the NAND gates in the \(S_0\) master latch. Use wires from 5V/GND and multimeter to test functionality according to the NAND truth table.
  6. Assemble D-latch feedback path and test 'memory' functionality (this is in fact an SR-latch). Solder in the last three gates to produce the entire D-latch. Test behaviour matches exact D-latch behaviour.
  7. Assemble second (slave) D-latch and solder up the next-state logic (simple NOT gate for \(S_0\)). Now, test \(S_0\) D flip-flop works correctly, only updating on the positive edge of the clock/button press. Given the nature of the \(S_0\) bit, the \(Q\) output of the flip-flop should toggle between ~0V and ~3V (due to fan-out reduction).
  8. Wire \(S_0\) output to indicator LED.
  9. Repeat for all other flip-flops - counter should now be working with correct binary LED output.
  10. Install 7-segment display, manually testing segments illuminate with \(220\Omega\) resistor and wire from 5V. Solder up the \(f_{a}\) sub-circuit of the decoder and test that segment \(a\) illuminated correctly given the current decimal output.
  11. Finish soldering 7-segment decoder and extensively test the working PCB.
Figure 6.3.2 — Soldering demonstration of tactile push button.

This process is methodical and makes the isolation of errors simple. Whilst seemingly straightforward, this process was very frustrating and at times seemed like 'magic' was the difference between things working and not working. For example, when assembling the slave D-latch for the \(S_0\) flip flop, the \(Q\) and \(\overline{Q}\) outputs seems to be the same voltage sometimes, and sometimes the wrong way around. This was baffling as the first (master) latch was tested and working! After several hundred probes by the multimeter, and manually energising the inputs of various NAND gates, I found the problem - I had neglected to solder just one leg of one transistor. Whilst the other two were secured soldered on, the third leg was making an intermittent connection to its pad which seemed to depend on which way the wind was blowing! Mistakes like this train you to not overlook the simple stuff.

When only the power and push button were soldered on, I decided to dig into and verify the debouncing of the button. To do this, I energised the board from a USB-C battery bank and connected the output of the button to channel 1 on a digital oscilloscope. By scaling the voltage and time axis appropriately, to about \(2ms\)/div, the super fast button transitions became clear. Before soldering the RC filter on, many button bounces could be seen on each press when zooming in liberally to the \(100 \mu\)s/div range. With the RC filter soldered on, the result is as shown in the background of Figure 6.3.2.

Partially assembled PCB
Figure 6.3.3 — Partially assembled PCB showing debounced button transition on oscilloscope.

One can see the gentle rise from 0-5V with no bouncing present. Interestingly, I used two cursors to approximately measure the 63% rise-time. To no surprise it was roughly \(10ms = \tau \). Overkill? Yes - but this experiment confirms the button is debounced correctly.

The assembled functional board is shown in Figure 6.3.4. Note the red jumper between two pads in the 7-seg decoder. I made a mistake and routed \(S_0\) to the input of one of the NAND gates, when it should have been \(S_2\). Can you find this error in the schematic? Luckily the erroneous pad could be left exposed (as it was needed to connect later gates), and the resistor could be 'tombstoned' on its other pad, and bridged over to source \(S_2\) from the input of a nearby gate. Techniques such as jumping between pads, cutting traces and 'soldering in 3D' are common workarounds for simple mistakes in PCB design - mistakes should be expected!

Fully assembled PCB
Figure 6.3.4 — Fully assembled PCB showing zero-state.

Finished Design

With the PCB assembled and the bugs taken care of, we finally see the counter working in all its glory. Figure 7.1 shows the final assembly with the PCB mounted inside a 3D printed case. The video in Figure 7.2 shows the manual operation of a complete cycle from \(000_{2}\) (0) to \(111_{2}\) (7). Following that is an evaluation of the final design along with an outline of future experiments to measure the counter's performance.

Figure 7.1 — Final assembled 3-bit RTL counter in its enclosure.
Figure 7.2 — Interactive demonstration of manual input control using tactile switch for incrementing the 3-bit counter.

Upon powering up from a USB-C source, the circuit produces a stable 5 V on \(V_{CC}\), and the green power LED turns on. The circuit boots up to the 0-state typically, but depending on slight variations in power supply, I have seen it start on the 7-state. This isssue is due to the fact that our finite state machine lacks a 'reset' state, i.e. an additional button which takes us from whichever state we are currently on to the 0-state. States before 'reset' has been pressed would typically be seen as invalid and may cause significant issues is real-world systems. For a 3-bit counter which wraps around from 7 to 0 anyway, this is of little concern.

When the button is pressed, the circuit changes state on the positive clock edge as desired. Changes are immediately seen on the binary LED output, as well as the 7-segment display. One future experiment may be to use an oscilloscope to measure the propagation delay along the critical path, i.e. the path through the circuit which encounters the maximal number of gates - and hence has the maximal delay. Propagation delay is caused by the parasitic capacitances associated with the gates' resistors and transistor. This creates a typical RC charging curve rather than instantenous transitions. This is what fundamentally limits the speed of computation.

Whilst the circuit works perfectly and seems to have fantastic signal integrity, it may have been nice to add some extra bulk capacitance physically close to the gates in the 7-seg decoder and flip-flops just to ensure a clean and stable 5 V supply. Additionally, the CLK button is correctly hardware debounced with an time constant of roughly \(\tau = 60\,\text{ms}\), yet partially activating the button by carefully pressing it (deliberately) on its side can cause some bouncing. In such cases the counter rapidly transitions through its states, often appearing to be skipping numbers.

It may also be nice to use the oscilloscope to determine the maximum frequency our counter can handle. In this experiment, we would bypass the CLK button, instead connecting it to the output of a 5V square wave function generator. We would then plot this input and one of the output bits on the scope, increasing the frequency until some undesirable behaviour starts to happen. I believe this limit will be intimately linked to the propagation delay along the critical path.

Lastly, the counter is of very high build quality and looks great inside its 3D printed case (in a cool red/blue co-extruded filament!). The silkscreen is informative, showing clearly the input and outputs (both decimal and binary), alongside a silkscreen block diagram (similar to Figure 1.2) showing the function of the components encircled by each block. Power delivery is easy, with USB-C cables and sources being readily available in modern times. All considered the counter provides great educational value, whilst maintaining a beautifully rudimentary and chaotic design.

Conclusion

In summary, this project has successfully explored computing in its purest form. By starting with an analysis of resistor-transistor logic (RTL), we were able to develop a framework for realising sequential logic circuits in the real world, using the saturation mode of bipolar junction transistors (BJTs) to perform logic operations, such as NAND and NOT. Using only these two logic gates, we were able to develop digital logic blocks such as the D flip-flop to store bit state, next-state combinational logic and a 3-bit to 7-segment decoder. The simulations provided us with an appreciation for the idealities we assume when building circuits from pure logic gates, verses real-world RTL limitations. We saw especially how fan-out limits the practical design. Using KiCAD 8, we then built the entire counter from RTL gate subcircuits, complete with USB-C power, binary and decimal outputs, an input button and various helpful indicator LEDs and silkscreen markings. Assembly proved frustrating, yet we were able to methodically work back through our assumptions to solve problems, and use creative solutions to wire things up correctly. The result was a functional, fully working interactive 3-bit counter, with a stunning design and rich educational value. This project has cemented the basics of digital logic, allowing the reader to move onto larger, more complex circuits such as CPLDs, FPGAs and of course, the CPU.