Data Buses: From Individual Bits to Bytes

So far, every signal we've worked with has been a single wire carrying one bit. But a real processor doesn't work with individual bits — it works with words: 8 bits, 16 bits, 32 bits, or more. Connecting each bit separately would turn your schematic into an impossible tangle of lines. You need a bus.

A bus is simply a bundle of wires treated as a single logical unit. In Verilog, you declare a bus using vector syntax:

wire [7:0] data;   // 8 wires, indexed 7 (MSB) down to 0 (LSB)

You can access individual bits: data[3] is the fourth bit (0-indexed). You can assign the whole bus at once: assign data = 8'b10101100;. You can connect one bus to another, and all 8 wires match up in parallel. It's like replacing 8 tiny garden hoses with one fat pipe — the same water flows, but the setup is infinitely cleaner.

To see buses in action, let's build an 8-bit ripple-carry adder. The idea is simple: chain 8 Full Adders together, connecting the carry-out of each one to the carry-in of the next. The first bit (bit 0) gets its carry-in from an external signal (usually 0 for addition). The last one produces the final carry-out, which tells you if the result overflowed.

In Verilog:

module Adder8(
    output [7:0] sum,
    output c_out,
    input [7:0] a,
    input [7:0] b,
    input c_in
);
    wire [7:0] c;
    
    FullAdder fa0(sum[0], c[0], a[0], b[0], c_in);
    FullAdder fa1(sum[1], c[1], a[1], b[1], c[0]);
    FullAdder fa2(sum[2], c[2], a[2], b[2], c[1]);
    FullAdder fa3(sum[3], c[3], a[3], b[3], c[2]);
    FullAdder fa4(sum[4], c[4], a[4], b[4], c[3]);
    FullAdder fa5(sum[5], c[5], a[5], b[5], c[4]);
    FullAdder fa6(sum[6], c[6], a[6], b[6], c[5]);
    FullAdder fa7(sum[7], c[7], a[7], b[7], c[6]);
    
    assign c_out = c[7];
endmodule

This is called a "ripple-carry" adder because the carry signal ripples from the least significant bit to the most significant bit, like a bucket brigade passing water down a line. Each Full Adder waits for its carry-in to arrive from the previous stage. The sum is computed in parallel across all bits, but the carry has to propagate sequentially through all 8 stages.

This propagation delay is the critical timing consideration. A real Full Adder takes a few nanoseconds for its carry output to stabilize after its inputs change. In a ripple-carry adder, the carry at bit 7 depends on the carry from bit 6, which depends on bit 5, and so on — the total delay is 8 times the per-stage carry delay. For an 8-bit adder this is acceptable; for a 64-bit adder, it's a performance disaster. That's why modern processors use faster adder architectures like carry-lookahead, but the ripple-carry is the perfect way to understand the concept.

The syntax [7:0] sum declares a bus where bit 7 is the most significant. This is conventional and matches how we write binary numbers. When you instantiate FullAdder fa0(sum[0], ...), you're connecting a single bit of the bus to a port. Verilog lets you drill down to individual bits or slice out ranges with sum[3:0].

Level 35 ("Adder8 in Verilog") asks you to build this exact 8-bit adder. For the first time, you'll see the output as a number (0 to 255) rather than individual 0/1 signals. You'll also notice something interesting: when you add, say, 200 + 100, the result exceeds 255, and the carry-out goes high — that's the overflow flag, which is how a processor knows the result doesn't fit in a single byte.