Digital Logic from First Principles

What is digital logic?

Digital logic is the foundation of every computer, smartphone, and microcontroller on the planet. It is the discipline of processing information using signals that have exactly two states: HIGH (1) and LOW (0), typically corresponding to voltage levels (e.g., 3.3 V and 0 V in modern CMOS circuits).

Because only two states are possible, the mathematics of digital logic is elegantly simple — it is Boolean algebra, named after mathematician George Boole. Complex behaviour, from adding two 64-bit numbers to rendering a 3D scene, emerges from millions or billions of simple two-state switching elements called transistors, arranged into logic gates, then into larger functional blocks, and finally into complete systems.

Digital logic divides into two broad categories:

  • Combinational logic — outputs depend only on the current inputs; there is no memory. Examples: adders, multiplexers, decoders.
  • Sequential logic — outputs depend on current inputs and past state stored in memory elements. Examples: flip-flops, registers, counters, CPUs.
Why study this? Every software abstraction — from a Python list to a GPU shader — ultimately executes as electrical signals passing through logic gates. Understanding digital logic gives you insight into how hardware works, helps you write faster code, and is essential for embedded systems, FPGA design, and computer architecture.

Binary numbers

Humans count in base 10 (decimal), using ten digit symbols (0–9). Computers use base 2 (binary), with only two digit symbols: 0 and 1. Each digit position in a binary number represents a power of 2:

Decimal Binary (4-bit) Powers of 2
0 0000 0
1 0001 2⁰ = 1
2 0010 2¹ = 2
3 0011 2¹ + 2⁰ = 3
7 0111 4 + 2 + 1 = 7
8 1000 2³ = 8
15 1111 8 + 4 + 2 + 1 = 15

A single binary digit is called a bit. Eight bits form a byte, which can represent 256 different values (0–255). A 32-bit integer can represent over 4 billion values.

In Gate Lab, wires carry either a HIGH (1) or LOW (0) signal. Multi-bit bus components let you route groups of wires — for example, an 8-bit bus carries the eight individual bits of a byte in parallel.

Boolean algebra

Boolean algebra defines a set of rules for working with values that are either true (1) or false (0). Three fundamental operations form the basis of all digital logic:

  • AND (·): The result is 1 only if all inputs are 1. Written as A · B or A AND B.
  • OR (+): The result is 1 if at least one input is 1. Written as A + B or A OR B.
  • NOT (¬ / overbar): Inverts the value. Written as ¬A, A', or NOT A.

Key laws

Boolean algebra obeys laws you can use to simplify expressions before implementing them in hardware:

  • Identity: A · 1 = A, A + 0 = A
  • Null / domination: A · 0 = 0, A + 1 = 1
  • Complement: A · ¬A = 0, A + ¬A = 1
  • Idempotency: A · A = A, A + A = A
  • De Morgan's theorem: ¬(A · B) = ¬A + ¬B and ¬(A + B) = ¬A · ¬B
  • Distributive: A · (B + C) = (A · B) + (A · C)
De Morgan's theorem is especially useful: it shows that a NAND gate is equivalent to an OR gate with inverted inputs, and a NOR gate is equivalent to an AND gate with inverted inputs. This means NAND and NOR gates alone can implement any Boolean function — they are called universal gates.

Logic gates

A logic gate is an electronic circuit that implements a Boolean operation. Each gate accepts one or more binary inputs and produces a single binary output according to a fixed rule. Gates are the atoms of digital design — every complex circuit is a structured arrangement of gates.

Basic gates: AND, OR, NOT

AND

Y = A · B

Output is HIGH only when all inputs are HIGH. Think of two switches in series — both must be closed for current to flow.

OR

Y = A + B

Output is HIGH when at least one input is HIGH. Think of two switches in parallel — either one being closed allows current through.

NOT (Inverter)

Y = ¬A

Flips the input value. HIGH becomes LOW; LOW becomes HIGH. Used constantly to invert control signals and enable active-low logic.

Truth tables exhaustively list every possible combination of inputs alongside the corresponding output:

A B AND OR NOT A
0 0 0 0 1
0 1 0 1 1
1 0 0 1 0
1 1 1 1 0

Universal gates: NAND and NOR

NAND ("Not AND") and NOR ("Not OR") are called universal gates because any Boolean function — and therefore any digital circuit — can be built using only NANDs or only NORs. This matters in real chip manufacturing because it means a single standard cell can implement all logic.

NAND

Y = ¬(A · B)

AND followed by NOT. Output is LOW only when all inputs are HIGH. A single NAND gate with its inputs tied together acts as a NOT gate.

NOR

Y = ¬(A + B)

OR followed by NOT. Output is HIGH only when all inputs are LOW. Commonly used to implement SR latches.

XOR and XNOR

XOR

Y = A ⊕ B

Output is HIGH when inputs are different. XOR is the core of binary adders — it computes the sum bit without the carry.

XNOR

Y = ¬(A ⊕ B)

Output is HIGH when inputs are equal. XNOR is used in equality comparators and error-checking circuits.

A B NAND NOR XOR XNOR
0 0 1 1 0 1
0 1 1 0 1 0
1 0 1 0 1 0
1 1 0 0 0 1

Try it: Place an AND, OR, and XOR gate in Gate Lab. Attach two input pins and one output display to each. Toggle the inputs to verify every row of the truth tables above.

LAUNCH GATE LAB

Combinational circuits

Combinational circuits combine multiple gates to compute a more complex function. The output at any instant depends only on the current inputs — there is no storage or memory.

Multiplexer (MUX)

A multiplexer selects one of several input signals and forwards it to the output, based on the value of a set of select (control) inputs. A 2-to-1 MUX has two data inputs (D0, D1), one select input (S), and one output (Y):

  • When S = 0: Y = D0
  • When S = 1: Y = D1

MUXes appear everywhere in CPU data paths — selecting between ALU results, immediate values, and register file outputs to route the right data to the right destination.

Binary adder

A half adder adds two single bits A and B, producing a Sum and a Carry:

  • Sum = A ⊕ B (XOR)
  • Carry = A · B (AND)

A full adder adds three bits (two operand bits plus a carry-in from the previous column), enabling multi-bit addition by chaining full adders together into a ripple-carry adder. Gate Lab's built-in adder component implements this logic ready to wire into a larger circuit.

Decoder and encoder

A decoder converts a binary code into a one-hot signal (exactly one output is HIGH at a time). A 2-to-4 decoder takes a 2-bit input and drives one of four outputs HIGH based on the binary value. Decoders are used for memory address decoding and 7-segment display control. An encoder does the reverse: converts a one-hot input into a binary code.

Sample circuit: Open the "7-Segment Display" sample in Gate Lab to see a BCD-to-7-segment decoder in action — a direct application of combinational logic.

Open 7-Segment Sample

Sequential logic

Sequential circuits add memory to digital logic. Their outputs depend not only on current inputs but also on the history of past inputs — stored as internal state. A clock signal (a square wave toggling between HIGH and LOW at a fixed frequency) synchronises when state updates happen.

Flip-flops

A flip-flop stores exactly one bit. On each active clock edge (rising or falling), it either holds its value or loads a new one, depending on its type and inputs.

D Flip-Flop

On rising CLK edge: Q ← D

The simplest flip-flop. Captures the value of input D on each clock edge and holds it at Q until the next clock edge. Used in registers and pipelines.

T Flip-Flop

When T = 1: Q ← ¬Q

Toggles its output when T is HIGH on a clock edge. Directly used to build binary counters: each T flip-flop with T tied HIGH divides the clock frequency by 2.

JK Flip-Flop

J=1, K=0: set; J=0, K=1: reset; J=K=1: toggle

The most versatile flip-flop. Subsumes the D and T types as special cases. Eliminates the invalid state of the SR flip-flop.

SR Latch

S=1: set Q to 1; R=1: reset Q to 0

The simplest memory element, built from two cross-coupled NOR gates. Has an invalid state when both S and R are HIGH simultaneously.

Clock edge vs. level triggering. Most flip-flops in Gate Lab are edge-triggered: they only sample inputs at the rising or falling edge of CLK, making timing predictable. SR latches are level-sensitive: their state can change any time the inputs change.

Registers and counters

A register is a group of D flip-flops sharing a common clock and, usually, a common load enable. All flip-flops update simultaneously, capturing a multi-bit value (e.g., 8 or 16 bits) in a single clock cycle. Registers hold operands for the ALU, the program counter (PC), and intermediate pipeline results.

A binary counter increments (or decrements) its stored value by 1 on each clock edge. A 4-bit synchronous counter can count from 0 to 15 before wrapping back to 0. Counters are used in timers, address generators, and loop control in simple state machines.

A shift register moves all stored bits left or right by one position on each clock edge. Shift registers are used to convert serial data to parallel (SIPO) and vice versa (PISO), and form the basis of CRC error-detection circuits.

Sample circuit: Open the "8-Bit Counter" sample in Gate Lab to see a synchronous binary counter built from flip-flops and combinational logic.

Open 8-Bit Counter Sample

Memory: RAM and ROM

Single flip-flops store one bit; registers store a few bytes. For larger amounts of data — programs, lookup tables, frame buffers — circuits use dedicated memory arrays.

ROM (Read-Only Memory)

ROM stores data permanently. You specify the contents when building the circuit; the memory cannot be changed during simulation. ROM is used for instruction memory in simple CPUs, lookup tables, and font character bitmaps. In Gate Lab, a ROM component is loaded with values you enter in the built-in memory editor. Provide an address on the address bus, and the output bus immediately presents the stored value.

RAM (Random-Access Memory)

RAM can be read and written during simulation. Gate Lab's RAM components (128×4 and 128×8) have an address bus, a data-in bus, a data-out bus, a write-enable (WE) input, and a clock. To write: set the address and data-in, then assert WE before the clock edge. To read: set the address and deassert WE; the stored value appears on data-out.

Gate Lab's built-in memory editor lets you pre-load RAM or ROM with values in hexadecimal, making it straightforward to load a simple program into an instruction memory component for a CPU simulation.

Sample circuit: The "Custom 4×4 RAM" sample shows how to wire a 16-cell addressable memory array built entirely from flip-flops — before using Gate Lab's built-in RAM component.

Open RAM Sample

Building a simple CPU

Every processor, no matter how complex, is built from the components described above. A minimal CPU has five subsystems:

  1. Instruction memory (ROM) — stores the program as binary opcodes. Addressed by the program counter.
  2. Program counter (PC) — an N-bit register that holds the address of the current instruction. Increments by 1 on each cycle; can be loaded with a jump address.
  3. Instruction register (IR) — captures the current instruction from memory and holds it stable while it is decoded and executed.
  4. ALU (Arithmetic-Logic Unit) — performs arithmetic (add, subtract) and logic (AND, OR, XOR) operations on two operand inputs and produces a result and condition flags (zero, carry, overflow). Gate Lab's "4-Bit ALU" sample is a starting point.
  5. Control unit — decodes the opcode and generates the control signals that route data through the data path: which register to read, which ALU operation to perform, whether to write result back to a register or to memory. In Gate Lab this is typically implemented as a programmable custom component or a truth table component.

The basic operation cycle — fetch → decode → execute — repeats every clock cycle (or every few cycles in a pipelined design). With Gate Lab's oscilloscope you can visualise the control signals and data bus values across multiple clock cycles to verify correct operation.

Start small. Begin with a 4-bit accumulator machine: one ALU, one accumulator register, one 16-word ROM (instruction memory), and a hardwired control unit that only supports ADD, AND, and LOAD instructions. Once that works, extend to a full register file, jumps, and branches.

Using Gate Lab

Gate Lab is a free, browser-based digital circuit simulator. No download or account is required. Here is a quick-start guide:

Interface overview

  • Component panel (left sidebar) — components are grouped into categories: Input/Output & Timing, Basic Logic Gates, Programmable, Flip-Flops, Registers & Memory, Counters, Multiplexers & Demultiplexers, Bus Systems, Encoders & Decoders, Data Converters, Arithmetic Units, Comparators, Displays, and Test & Measurement. A search bar at the top filters across all categories. Click a component to place it, or drag it onto the canvas.
  • Canvas — infinite, pannable workspace. Scroll to zoom. Right-click anywhere on the canvas for a context menu (paste, select all, auto-route wires). Right-clicking a selected component shows component-specific actions (copy, delete, open settings). Right-clicking a selected wire shows optimize and delete options.
  • Toolbar (top bar) — simulation controls (play/pause, stop), undo/redo, save/load, import/export, collaborate, and propagation delay toggle.
  • Properties panel — double-click a component to open its settings dialog. Depending on the component, you can change bit width, initial value, clock frequency, duty cycle, and more.

Drawing wires

Click an output pin to start drawing a wire, then click the target input or control pin to complete the connection. Wires route automatically. Click an existing wire to select it; press Delete to remove it. To delete a connection at a pin, click the pin that already has a wire attached.

Running a simulation

Press Shift+Space (or the Play button) to start or pause the simulation. Press Ctrl+Shift+Space (or the Stop button) to stop the simulation and reset all signal states. Input components toggle between HIGH (1) and LOW (0) when clicked during simulation. Clock components pulse automatically at their configured frequency. Active (HIGH) wires are highlighted in teal; inactive (LOW) wires remain gray. Use the propagation delay toggle to slow signal propagation and trace logic step by step.

Using the oscilloscope

Place an oscilloscope component on the canvas. It has one input pin per channel, labelled CH0, CH1, and so on. Connect any wire directly to a channel pin — the same way you would connect any other component. During simulation the scope draws a continuous waveform for each connected channel, scrolling left to right. The 1-channel variant is free; 2-, 4-, and 8-channel variants are available with a Pro subscription.

Custom scripted components

Gate Lab has two programmable component types in the Programmable category:

  • Custom Component — combinational logic only. The script body runs every time any input changes. No clock or persistent state.
  • Custom Sequential Component — clocked logic with persistent state. The script body runs on each rising edge of the designated clock pin.

Both use the same scripting language. A script has two parts: a header section and a logic body. The header declares the component's interface using keyword lines:

  • inputs: A, B, Data[8] — input pins (use [N] for N-bit buses)
  • outputs: Result[8], Carry — output pins
  • clock: CLK — clock pin (sequential components only)
  • state: counter[8], ram[16][8] — variables that persist between clock cycles (sequential only)
  • vars: temp — scratch variables; reset to 0 before each execution

The logic body is a flat sequence of statements ending with semicolons. All values are 64-bit signed integers; 0 is LOW and any non-zero value is HIGH. Supported operations include arithmetic (+, -, *, /, %), bitwise (&, |, ^, ~, <<, >>), comparison (==, !=, <, >), and if / else conditionals. Comments start with #.

Refer to the full scripting language reference for the complete syntax and worked examples. Custom components can be saved and reused across circuits.

Ready to start? Open Gate Lab and try recreating the 2-to-1 MUX from this guide using only AND, OR, and NOT gates — then compare it to the built-in MUX component.

LAUNCH GATE LAB