Bus -- the "highway system" inside a computer

The various components inside a computer need to constantly exchange information. The CPU has to fetch instructions from memory and read/write data. How is this information transmitted? The answer is —Bus。

A bus is a set of shared communication lines connecting the CPU, memory, hard drive, graphics card, and other devices, just like a city's road system — with main roads and branch roads, each with a clear purpose.

This lecture will help you understand the division of labor among the three types of buses, and use Python to simulate a complete memory read operation.


Highway system analogy: three kinds of roads, three kinds of uses

Imagine you're delivering packages in a big city. The courier needs to know three things:where to deliver、What to send?、Does the recipient need to sign?。

Inside the computer, there are also three exactly analogous "highways":

Bus typeFeaturesTransfer directionlife analogy
Address BusThe memory address to be accessed for transmissionCPU → Memory (one-way)The "house number" on the delivery slip — telling the recipient "I'm looking for this address"
Data BusTransmit the actual data contentbidirectionalThe "package" in the courier's hands — the item itself, which can be sent out or received
Control BusTransmit control signals such as read/write and interruptsCPU ↔ Components (bidirectional)The "receipt" — telling the other party whether it is currently a pickup or a delivery, and whether the transfer is complete

The width of the address bus determines how much memory the CPU can access. A 32-bit address bus can access 2^32 = 4GB of memory space, and a 64-bit address bus can theoretically access 2^64 bytes (about 16EB). This is why a 32-bit operating system can use at most 4GB of memory.


Complete flow of a memory read operation

Suppose the CPU needs to read a piece of data from memory address 100. The entire process is divided into 4 steps, with the three buses working together:

Step breakdown

  1. CPU places address: The CPU places the address value 100 onto theAddress buson. The memory has now received the address.
  2. CPU sends signal: CPU atControl busand issues a "read" signal (READ) on it. The memory knows the other party wants to fetch something.
  3. Store data in memory: After the memory receives the address and the read signal, it finds the data stored at address 100 and places it onto theData buson.
  4. CPU receives data: The CPU reads the data from the data bus and stores it into an internal register. The transfer is complete.

sequence diagram

The following ASCII timing diagram shows the bus state changes during the execution of this instruction:

时间  |   地址总线       控制总线        数据总线
------|------------------------------------------
T0    |   100           IDLE            XXXX       ← CPU 放上地址
T1    |   100           READ            XXXX       ← CPU 发出读信号
T2    |   100           READ            0x2A       ← 内存放上数据
T3    |   XXXX          IDLE            0x2A       ← CPU 取走数据
------|------------------------------------------
结果: CPU 从地址 100 读到了数据 0x2A

Code demonstration: Simulating the bus interaction process with Python

The following Python code fully simulates the collaboration process of the address bus, data bus, and control bus during a memory read operation.

Example

"""
Bus Interaction Simulation - A Complete Memory Read Operation (Example Demo)

This program simulates the complete process of the CPU reading data from memory through three buses:
1. Address bus: CPU issues the memory address to access
2. Control bus: CPU issues read/write control signals
3. Data bus: memory returns/receives data

Each bus state change prints a detailed log, making it easy to observe the collaborative work of the three buses.
"""


import time


class AddressBus:
    """Address bus - unidirectional: CPU sends address to memory"""
    def __init__(self):
        self.value = None  # Current address value on the bus
        self.width = 16    # 16-bit address bus, addressable 64KB

    def set_address(self, addr):
        """CPU puts the address on the address bus"""
        self.value = addr
        print(f" [Address Bus] CPU puts address: {addr} (0x{addr:04X})")
        return addr

    def clear(self):
        """Clear bus"""
        self.value = None
        print(f[Address bus] Bus cleared, entering idle state)


class DataBus:
    """Data bus - Bidirectional: both CPU and memory can send and receive data"""
    def __init__(self):
        self.value = None  # Current data value on the bus
        self.width = 8     # 8-bit data bus, transfers one byte at a time
        self.direction = None  # 'TO_CPU' or 'TO_MEMORY'

    def send_from_cpu(self, data):
        """CPU sends data to the bus"""
        self.value = data
        self.direction = 'TO_MEMORY'
        print(f" [Data Bus] CPU → Bus, Data: {data} (0x{data:02X})")
        return data

    def send_from_memory(self, data):
        Memory sends data to the bus
        self.value = data
        self.direction = 'TO_CPU'
        print(f[Data Bus] Memory → Bus, data: {data} (0x{data:02X}))
        return data

    def read_to_cpu(self):
        """CPU reads data from the data bus"""
        data = self.value
        print(f[Data Bus] CPU reads data from the bus: {data} (0x{data:02X}))
        self.clear()
        return data

    def read_to_memory(self):
        """Memory reads data from the data bus (used during write operations)"""
        data = self.value
        print(f[Data Bus] Memory reads data from the bus: {data} (0x{data:02X}))
        self.clear()
        return data

    def clear(self):
        """Clear bus"""
        self.value = None
        self.direction = None
        print(f[Data bus] Bus cleared, entering idle state)


class ControlBus:
    """Control bus - bidirectional: transmits read/write signals, interrupt signals, etc."""
    # Control signal type
    READ = 'MEM_READ'        # Memory Read Signal
    WRITE = 'MEM_WRITE'      # Memory Write Signal
    IDLE = 'IDLE'            # Idle

    def __init__(self):
        self.signal = self.IDLE

    def set_signal(self, signal):
        """Set control signals"""
        self.signal = signal
        signal_name = {
            self.READ: 'Memory Read (MEM_READ)',
            self.WRITE: 'Memory write (MEM_WRITE)',
            self.IDLE: 'Idle (IDLE)'
        }
        print(f[Control Bus] CPU sends signal: {signal_name.get(signal, signal)})

    def clear(self):
        """Clear signal"""
        self.signal = self.IDLE
        print(f[Control bus] Signals cleared, entering idle state)


class SimpleMemory:
    """Simplified memory module - 256 bytes of storage space"""
    def __init__(self, size=256):
        self.size = size
        self.data = {}
        # Initialize some demo data
        for i in range(size):
            self.data[i] = i % 256  # Address i stores value i % 256
        # Place some special values at specific addresses for demonstration
        self.data[100] = 42         # Address 100: store 42 (0x2A)
        self.data[200] = 0xAB       # Address 200: store 0xAB (171)
        self.data[50] = ord('R')    # Address 50: stores the ASCII code of letter 'R'
        self.data[51] = ord('U')
        self.data[52] = ord('N')
        self.data[53] = ord('O')
        self.data[54] = ord('O')
        self.data[55] = ord('B')

    def read(self, addr):
        Read data from the specified address
        if 0 <= addr < self.size:
            return self.data.get(addr, 0)
        else:
            raise ValueError(fAddress {addr} exceeds memory range (0-{self.size-1}))

    def write(self, addr, data):
        Write data to the specified address
        if 0 <= addr < self.size:
            self.data[addr] = data
        else:
            raise ValueError(fAddress {addr} exceeds memory range (0-{self.size-1}))


class CPU:
    """Simplified CPU - interacts with memory through the bus"""
    def __init__(self, addr_bus, data_bus, ctrl_bus, memory):
        self.addr_bus = addr_bus   # Address bus
        self.data_bus = data_bus   # Data bus
        self.ctrl_bus = ctrl_bus   # Control bus
        self.memory = memory       # Memory
        # CPU internal registers
        self.mdr = 0               # Data register (Memory Data Register)
        self.mar = 0               # Address Register (Memory Address Register)

    def memory_read(self, addr):
        """
Execute a complete memory read operation

Process:
T0: MAR ← addr (internal preparation)
T1: address bus ← addr, control bus ← READ
T2: Memory reads data, data bus ← data
T3: MDR ← data bus, clear all buses

Return: the read data value
        """

        print(f"\n{'='*55}")
        print(fOperation start: CPU reads data from memory address {addr})
        print(f"{'='*55}")

        # T0: CPU internal preparation — load the address into MAR
        self.mar = addr
        print(f"\n[T0 - CPU internal preparation]")
        print(f[CPU Internal] MAR (Memory Address Register) ← {addr})

        # T1: CPU places the address on the address bus and simultaneously issues a read signal on the control bus.
        print(f"\n[T1 - Issue address and read signal]")
        self.addr_bus.set_address(addr)
        self.ctrl_bus.set_signal(ControlBus.READ)

        # Simulate signal transmission delay
        time.sleep(0.001)

        # T2: Memory response — based on the address on the address bus, find the data and place it on the data bus
        print(f"\n[T2 - Memory Response]")
        data = self.memory.read(addr)
        self.data_bus.send_from_memory(data)

        # Simulate signal transmission delay
        time.sleep(0.001)

        # T3: CPU reads data from the data bus into MDR. Buses are cleared.
        print(f"\n[T3 - CPU receives data]")
        self.mdr = self.data_bus.read_to_cpu()
        self.ctrl_bus.clear()
        self.addr_bus.clear()

        # Output the result
        print(f"\n{'='*55}")
        print(f"Operation完Cheng: fromAddress {addr} (0x{addr:04X}) read取toData {self.mdr} (0x{self.mdr:02X})")
        print(f"{'='*55}")

        return self.mdr

    def memory_write(self, addr, data):
        """
Execute a complete memory write operation

Process:
        T0: MAR ← addr, MDR ← data
T1: Address Bus ← addr, Data Bus ← data, Control Bus ← WRITE
T2: Memory takes data from the data bus and writes to address addr
T3: Clear all buses
        """

        print(f"\n{'='*55}")
        print(fOperation start: CPU writes data {data} to memory address {addr})
        print(f"{'='*55}")

        # T0: CPU internal preparation
        self.mar = addr
        self.mdr = data
        print(f"\n[T0 - CPU internal preparation]")
        print(f" [CPU internal] MAR ← {addr}, MDR ← {data}")

        # T1: CPU puts the address and data on the bus and issues a write signal
        print(f"\n[T1 - issue address, data, and write signal]")
        self.addr_bus.set_address(addr)
        self.data_bus.send_from_cpu(data)
        self.ctrl_bus.set_signal(ControlBus.WRITE)

        time.sleep(0.001)

        # T2: Memory takes data from the data bus and writes it
        print(f"\n[T2 - Memory Write]")
        received = self.data_bus.read_to_memory()
        self.memory.write(addr, received)
        print(f"  [Memoryinside部] Address {addr} (0x{addr:04X}) ← {received} (0x{received:02X})")

        time.sleep(0.001)

        # T3: Clear all buses
        print(f"\n[T3 - Clear Bus]")
        self.ctrl_bus.clear()
        self.addr_bus.clear()

        print(f"\n{'='*55}")
        print(f"Operation complete: {data} has been written to address {addr}")
        print(f"{'='*55}")


# ===== Main program: run demo =====
if __name__ == "__main__":
    print("=" * 60)
    print(Bus Interaction Simulation — EXAMPLE Computer Organization Principles Demo)
    print("=" * 60)
    print()

    # 1. Initialize bus
    print("[System Initialization] Created address bus, data bus, control bus, memory module")
    addr_bus = AddressBus()
    data_bus = DataBus()
    ctrl_bus = ControlBus()
    memory = SimpleMemory()
    print()

    # 2. View the initial state of memory
    print("[Memory Initial State] Data preview at key addresses:")
    for addr in [50, 51, 52, 53, 54, 55, 100, 200]:
        ch = chr(memory.data[addr]) if 32 <= memory.data[addr] < 127 else '?'
        print(f"  Address {addr} (0x{addr:04X}): {memory.data[addr]:3d} (0x{memory.data[addr]:02X}) '{ch}'")
    print()

    # 3. Initialize CPU
    cpu = CPU(addr_bus, data_bus, ctrl_bus, memory)

    # 4. Demo: Reading data from memory
    print("\n" + "▼" * 60)
    print(Demo 1: Read data from memory address 100)
    print("▼" * 60)
    val1 = cpu.memory_read(100)

    print("\n" + "▼" * 60)
    print(Demo 2: Read data from memory address 200)
    print("▼" * 60)
    val2 = cpu.memory_read(200)

    print("\n" + "▼" * 60)
    print(Demo 3: Read sequentially from memory addresses 50-55 to assemble the characters (EXAMPLE))
    print("▼" * 60)
    chars = []
    for addr in range(50, 56):
        val = cpu.memory_read(addr)
        chars.append(chr(val))
    print(f"\n"Assembled string: {''.join(chars)}")

    # 5. Demo: Writing Data to Memory
    print("\n" + "▼" * 60)
    print("Demo 4: Write data 77 to memory address 10")
    print("▼" * 60)
    cpu.memory_write(10, 77)

    # 6. Verify write result
    print("\n" + "▼" * 60)
    print("Demo 5: Verify write — read data back from address 10")
    print("▼" * 60)
    val3 = cpu.memory_read(10)
    print(f"\nVerification: write 77, read {val3}, {'success' if val3 == 77 else 'failure'})

    print("\n" + "=" * 60)
    print(" All demonstrations complete!")
    print("=" * 60)

Bus width and actual performance

The width of the address bus determines "how much memory it can manage"

Each line of the address bus transmits one binary bit. N address lines can represent 2^N different addresses.

Address bus widthAddressable spaceTypical CPU
16-bit2^16 = 64 KBIntel 8086 (1978)
20-bit2^20 = 1 MBIntel 8088 (IBM PC, 1981)
24-bit2^24 = 16 MBIntel 80286 (1982)
32-bit2^32 = 4 GBIntel 80386 (1985)
64-bit2^64 ≈ 16 EBModern x86-64 processors

Note: Modern 64-bit CPUs typically have fewer than 64 actual address lines (e.g., 48), because 2^64 bytes far exceeds the actual physical memory requirements today. 48 address lines can already address 256 TB, which is sufficient for current use.

The width of the data bus determines "how much data can be transferred at one time"

An 8-bit data bus transfers 1 byte at a time, while a 64-bit data bus transfers 8 bytes at a time. This is why, for the same task, 64-bit systems are often faster than 32-bit systems — the amount of data transferred per single operation is doubled.

Signal types of the control bus

The control bus is not as uniform as the address and data buses. It is a collection of many types of signal lines, including:

  • MEM_READ / MEM_WRITE: Memory read/write enable signals
  • IO_READ / IO_WRITE: I/O port read/write signals
  • INTERRUPT: Interrupt request signal — the way external devices notify the CPU
  • CLOCK: Clock signal — the "heartbeat" that drives the entire system to work synchronously
  • RESET: Reset signal — returning the CPU to its initial state

Summary and verification

One-sentence summary:Address busnegative责「go哪里」,Data busnegative责「Fortune什么」,Control busnegative责「做什么」——three条buseach司its职,cooperatesame完ChengeveryonetimesDatatransmission。

self-test questions

  1. When the CPU wants to read a piece of data from memory, who places the information on the address bus? And who places the information on the data bus?
  2. If a computer's address bus has only 16 lines, what is the maximum amount of memory it can manage?
  3. What is the biggest difference between the control bus and the address bus and data bus? (Hint: think about the "composition" of the control bus)

Reference answer: 1. The address is placed by the CPU, and the data is placed by the memory. 2. 2^16 = 65536 bytes = 64KB. 3. The address and data buses transmit uniform digital signals (address values/data values), while the control bus is a set of distinct discrete signal lines, each with its own independent function.

other extensions