Pages

Monday, March 31, 2025

Entry 18: Building SRAM

Let's continue building a 16x4 SRAM (from entry 16) now that we have the 4-line to 16-line decoder as well as D latches. This is where we left of:

Design Components

  1. Storage: 16 rows of 4-bit D latches (64 latches total).
  2. Address Decoder: Converts 4-bit address to 16 enable lines (1-of-16 selector).
  3. Control Logic: manage when we save data to RAM.

-tbc-

1. 64 D latches (see entry 16), in rows 0 to 15 and columns 0 to 3:

2. Address decoder (see entry 17)

Writing data to SRAM

This is what we are trying to achieve in this section:

  • I have four data bits D3-D0, for example 0110 (decimal 6).
  • I have four address bits A3-A0, for example 0011 (decimal 3).
  • I want to store the data (6) att location/address 3; the 4 D latches at row 3 should contain 0110 after the save is complete.
  • The rest of the D flip-flops should be left alone.

-cont:

Step-by-Step Design

1. Storage (16x4 D Latches)

  • Latch Choice: Use D latch.
    • For 16 rows × 4 bits, you’ll need 64 latches total.
  • Connections per Row:
    • D0–D3 (data inputs).
    • Q0–Q3 (outputs).

-tbc-

So, for example, D3, the highest order data bit, should be connected to the D pin of all D latches in the  Bit 3 column (and so on):

-cont:

2. Address Decoder (4-to-16)

  • Purpose: Selects 1 of 16 rows based on A0–A3.
  • Inputs: A0–A3 (4-bit address).
  • Outputs: 16 lines (0–15, active low), each selecting one row.
  • Wiring: Connect decoder outputs (Pins 1–16) to the "Load" of each row.

-tbc-

This makes sense. We connect the address A0-A3 (4 bit) to the decoder. We connect the output Q0 from the decoder to the "Load" (L) of the four D latches in row 0 (location 0 / address 0). 

We connect Q1 from the decoder to L of the four D latches at address 1 and so on. 

Now, if the address is, say 0011 (decimal 3), and the decoder is enabled (E = 1), then L = 1 for the 4 D flip-flops at address 3 (and 0 everywhere else). The 4 D latches at address 3 will store the data on D0-D3. The rest of the D latches will ignore the data.

When the decoder E = 0 (disabled), all L=0 and no data is latched. This should take care of "writing data to SRAM".

Reading data from SRAM

This is what we are trying to achieve in this section:

  • I have four bits on the address bus A3-A0, for example 0011 (decimal 3).
  • I want Q3-Q0 to be equal to the Q outputs of the four latches at location 3, 0110 in the picture below. Q3-Q0 are two-state output.
  • I want W3-W0 to be the corresponding tri-state output, W = Q if three state switches are enabled, and W = Z otherwise. 

-cont:

4. Data Output (Tri-State Outputs)

  • Read Path: Each row’s Q0–Q3 outputs connect to a set of 4 tri-state buffers.
    • 16 rows × 4 buffers = 64 buffers
    • Buffer Inputs: Q0–Q3 from each row.
    • Buffer Outputs: W0-W3 
    • Buffer Enable : Controlled by address decoder.

Row 0 of RAM:

When E0 = 1, the tri-state buffers are enabled, and W0-W3 is equal to Q0-Q3 of address 0.

Row/address 1 looks the same, but "Enable" is labeled E1. When E1 = 1, W0-W3 is equal to Q0-Q3 of address 1. In total, we have 16 tri-state "Enable" pins, E0..E15. At most 1 of them may be high.

Next, we connect a second 4-to-16 line decoder to the enable-lines E0-E15:

When this second decoder is enabled, and the address is, say 0011 (decimal 3), then E3 = 1, while the remaining buffers are disabled. W0-W3 becomes equal to Q0-Q3 at address 3. When this second decoder is disabled, then all E0..E15 are 0 and W = ZZZZ.

For the final step, we also want two-state output Q0-Q3, where Q0-Q3 are always the Q-outputs of the D latches at address D0-D3. This can be accomplished by a third 4-to-16 line decoder. This decoder is connected exactly like the second one, but the it is always enabled.

Summary

  • Four input pins D3-D0 (data).
  • Four input pins A3-A0 (address).
  • Four output pins W3-W0 (tri-state output).
  • Four output pins Q3-Q0 (two-state output).
  • Input pin "Enable" of the first decoder (decoder to Load). When this E=1, data D3-D0 is written to RAM at address A3-A0. When this E=0, nothing is written to RAM.
  • Input pin "Enable" of the second decoder (decoder to Enable Tri-state). When this E=1, W3-W0 is read from SRAM at address A3-A0. When this E=0, W3-W0 is ZZZZ (disconnected).

The ACEL chip "SixteenByFourRam" is designed precisely like this. Note that

  • The "Enable" of the first decoder (decoder to Load) is denoted "Load" - it loads data into RAM.
  • The "Enable" of the second decoder (decoder to Enable Tri-state) is denoted "Write" - it writes data to W3-W0.

Here is the symbol of SixteenByFourRam.

Testing

So to test SixteenByFourRam, let's connect 4 DIP-switches to D,  4 DIP-switches to A, buttons to Load and Write and Q to 4 LED's.

  • DIP 1-4 is connected to A
  • DIP 5-8 is connected to D
  • Load: Loads the data D into address A
  • Q is connected to LED's.
  //Define chips
  auto* ram = new SixteenByFourRam((char*)("SRAM"));

  //Add all chips to be emulated here
  Chip* chips[] = {
    ram
  };

  //Add all connections here
  ConnectionBase* connections[] = {
    new ConnectionFour(ard, 2, ram->A, 1),
    new ConnectionFour(ard, 6, ram->D, 1),
    new Connection(ard, 10, ram->Load),
    new Connection(ard, 11, ram->Write),
    new ConnectionFour(ram->Q, ard, 22, 1)
  };

If you set an address on DIP 1-4, The LED's will display the value stored at this address. If you set some data on DIP 5-8 and click Load, this data is stored at the current address. W is tri-state and not connected, but you can check that W = ZZZZ unless you click "Write". Try this out on Wokwi, project "SRAM 16x4". Wokwi has a few steps for you to follow to better understand how the chip (as well as 74LS189) works.

Here is a physical setup:

Address is set to 0001 (or decimal 1) and the LED's show that 0011 (decimal 3) is stored at this address. Data is set to 0110 (decimal 6). If I click "Load", the data at address 1 will change to 0110 (decimal 6).

Next, we need to figure out how to connect the memory to the address and the data bus, ... in entry 19.




Sunday, March 30, 2025

Entry 17: Address decoder

Decoder

In the previous entry, we concluded that we need an address decoder. Specifically, we need a 4-line to 16-line decoder:

The IC 74LS174 is a "4-Line to 16-Line Decoder/Demultiplexer". It has

  • 4 inputs, labeled A, B, C, D
  • inputs G1 and G2. G1 and G2 must both be low for decoding
  • 16 outputs labeled 0 through 15

This is the function table of 74LS174:

Note that (when G1 and G2 are L) precisely one output pin is low, the rest are high.The 74LS174 can be designed from basic gates. Here is the logic diagram:


I designed this and named it Decoder4To16. It has

  • 4 inputs, labeled D (D[0], D[1], D[2], D[3])
  • An input E (Enable). E must be high for decoding
  • 16 outputs labeled Q (Q[0] to Q[15])

This is the function table of Decoder4To16:

Note that (when E = 1) precisely one output pin is high, the rest are low. This is the symbol I will use for the 4-line to 16-line decoder:

Testing the decoder

We can try this out using the following setup:

  //Define chips
  auto* decoder = new Decoder4To16();

  //Add all chips to be emulated here
  Chip* chips[] = {
    decoder
  };

  //Add all connections here
  ConnectionBase* connections[] = {
    new ConnectionFour(ard, 2, decoder->D, 1), //2,3,4,5 to D3, D2, D1, D0
    new Connection(ard, 6, decoder->E),
    new ConnectionSixteen(decoder->Q, ard, 22, 0), //Q0 to 22..Q15 to 37
  };

DIP 1 is connected to E (Enable) on the decoder and DIP 5-8 are connected to the input D. Sixteen LED's are connected to the output Q. In the picture, E=1 and the decoder is enabled. D = 0010, which is decimal 2. Therefore, Q2 is 1 (the rest are 0), as indicated by the LED. Try this out on Wokwi. The project name is "Address Decoder"

Here is the same physical setup with wires connected to the 4-line to 16-line decoder. Note that the two LED's to the left are unused (as well as the two on the right). The one which is on is the one with label "2".

Let's return to building a RAM.., in entry 18.

Saturday, March 29, 2025

Entry 16: Memories and D latch

Computer Memory

Having worked on the register and the bus, I figure the next step must be to look at the memory. I asked Grok to tell me about memories in a computer. Here is the (curated) answer:

Memories in a computer are the storage systems that hold data, instructions, and intermediate results needed for processing. They come in various types, each with a specific role, speed, and capacity, working together to keep the computer running smoothly. Since you’ve been exploring registers (like your 4-bit D flip-flop setup) and buses, understanding memory ties it all together—registers are the fastest memory, while other types scale up in size and slow down in speed. Let’s dive into the details.


Types of Computer Memory

Memory is organized in a hierarchy based on speed, cost, and proximity to the CPU:

1. Registers

  • What: Tiny, ultra-fast storage inside the CPU, built from flip-flops (e.g., your 4-bit register).
  • Size: A few bytes.
  • Speed: Nanoseconds (same as CPU clock).
  • Use: Holds data being processed right now.
  • Example: Your 4-bit register stores 0000–1111, updated on a clock edge.

2. Cache

  • What: Small, fast memory between registers and main memory, often SRAM (Static RAM).
  • Size: Kilobytes to megabytes (e.g., 32 KB in early PCs, 8 MB in modern CPUs).
  • Speed: A few nanoseconds (faster than RAM, slower than registers).
  • Use: Stores frequently used data/instructions to reduce main memory access.
  • Note: Came later with 80286 and beyond.

3. Main Memory (RAM)

  • What: Primary storage for active programs and data, typically DRAM (Dynamic RAM).
  • Size: Megabytes to gigabytes (e.g., 64 KB in 8085, 16 GB in modern PCs).
  • Speed: Tens of nanoseconds (e.g., ~50 ns access time).
  • Use: Holds the operating system, applications, and data not currently in registers/cache.
  • Volatility: Loses data when power’s off.

4. Secondary Storage

  • What: Larger, slower, non-volatile storage (e.g., HDDs, SSDs).
  • Size: Gigabytes to terabytes (e.g., 1 TB SSD).
  • Speed: Milliseconds (HDD) to microseconds (SSD)—way slower than RAM.
  • Use: Long-term storage for files, programs not currently running.
  • Note: External to the CPU, accessed via I/O controllers, not directly tied to the bus like RAM.

5. ROM (Read-Only Memory)

  • What: Non-volatile memory for firmware or boot code (e.g., BIOS in PCs).
  • Size: Kilobytes.
  • Speed: Similar to RAM (~50 ns).
  • Use: Stores permanent instructions.
  • Types: PROM, EPROM, EEPROM (programmable variants).

How Memory Works Together

  • Hierarchy: Data moves from slow, large storage (HDD/SSD) to fast, small storage (registers) as needed.
  • Bus Connection:
    • Address Bus: CPU specifies memory location.
    • Data Bus: Transfers data (e.g., 4 bits in your setup).
    • Control Bus: Signals read (RD̅) or write (WR̅).
  • Tri-State Role: Memory chips (like RAM) have tri-state outputs to drive the data bus only when selected, avoiding conflicts with registers or other devices.

Memory Technologies

SRAM (Static RAM)

  • How: Uses flip-flops (like your D flip-flops) per bit—6 transistors.
  • Pros: Fast, stable (no refresh needed).
  • Cons: Expensive, large (fewer bits per chip).
  • Use: Cache, registers.

DRAM (Dynamic RAM)

  • How: Uses 1 transistor + 1 capacitor per bit—stores charge.
  • Pros: Cheap, dense (more bits per chip).
  • Cons: Slower, needs periodic refresh (capacitors leak).
  • Use: Main memory (e.g., 8085’s 64 KB RAM).

ROM Variants

  • PROM: Programmable once (fuses blown).
  • EPROM: Erasable with UV light (e.g., 2716, 2 KB).
  • EEPROM: Electrically erasable (modern flash precursor).

Breadboard Idea

Build a tiny memory system:

  • Register: Your setup.
  • RAM: 7489 SRAM (16x4):
    • Connect Q0–Q3 to RAM data in/out via the bus.
    • Use switches for 4-bit address (A0–A3).
    • Button for write (WR̅), read via bus to 74HC4511.
  • Test: Store 0101 at address 0000, read it back—display shows "5".
-end-

I will go for Grok's "Breadboard Idea" and build an SRAM similar to the IC 7489. Note that there is also an IC 74189. These IC's are both 16x4 RAM. Not sure what the difference is. In any case, it is referred to as "7489" below.

Building SRAM

"How can I design the 7489 myself using gates and flip-flops?":

Designing your own version of the 7489—a 16x4-bit SRAM (Static Random Access Memory)—is an awesome challenge! The 7489 is a small memory chip with 16 locations, each storing 4 bits, supporting read and write operations via a 4-bit data bus. To replicate it, we’ll build a simplified equivalent using D latches for storage and logic gates for address decoding. This ties into your 4-bit projects perfectly. Let’s break it down step-by-step.
-tbc-

This is my visualization of 7489:

Each empty cell holds a 0 or a 1. It is 16 x 4 as it has 16 locations (or addresses) and 4 bits per location (64 bits in total). 

-cont:

7489 Overview

  • Capacity: 16 words, 4 bits each (64 bits total).
  • Inputs:
    • 4-bit address (A0–A3) to select 1 of 16 locations.
    • 4-bit data input (D0–D3) for writing.
  • Outputs: 4-bit data output (Q0–Q3).
  • Goal: Store 4 bits at any of 16 addresses.

Design Components

  1. Storage: 16 rows of 4-bit D latches (64 latches total).
  2. Address Decoder: Converts 4-bit address to 16 enable lines (1-of-16 selector).
  3. Control Logic: Gates to manage when RAM is to store data.

1. Storage (16x4 D Latches)

  • Latch Choice: Use 74LS75 (quad D latch, 4 latches per IC, active-low enable).

-tbc-

Two things that I noted: We need 64 "D latches", which is new to me. Also, I do not know what an "address decoder" is.

D latch

After checking with Grok, a D latch is not the same as any of the two D flip-flops we have looked at so far (one ungated, one gated). Instead, it is basically a gated SR latch. We looked at gated SR latches in entry 14, and concluded that there are two types: a NAND version and a NOR version. This is the gated SR latch, NAND version:

It is just like a regular SR latch, with a "gate" L. When L = 1 (gate open), it is a regular SR latch, while it ignores Set and Reset when L = 0.

To make this into a D latch, we simply connect D to Set and D, through an inverter, to Reset:

  • If L = 1 (gate open) and D = 1, then S = 1 and R = 0 and Q becomes 1.
  • If L = 1 (gate open) and D = 0, then S = 0 and R = 1 and Q becomes 0.
  • If L = 0 (gate closed) then Q will not change, no matter the value of D.

Note that the D latch does not have a clock signal. If L = 1 then Q updates immediately to the value D. I designed the component and named it DLatch according to the schedule above. This is the symbol I will use for it:

Here is the truth table for a D latch:

Address decoder

Not sure about the "Address Decoder". If I understand this correctly, we have four bits on the address bus, e.g. 0011 (or 3), coming into the decoder. Going out from the decoder will be 16 lines (numbered 0 to 15), and precisely one will be high. If the address is 0011, the decoder will make pin 3 high, and the rest low. 

I need to investigate this further,... in entry 17.

Entry 15: Register version 2: gated and bus-friendly

A better register

In entry 9, we created QuadDFlipFlop, a first version of a register. Starting from this design, let's replace the ungated D flip-flops with gated ones, and add four tri-state buffers to the output pins. We then have a register we can connect to a data bus.

Here is the schematics of what I will build.

This component will be called a FourBitRegister, see ACEL.

  • D0-D3 are data input pins, connected to the data line D of the D flip-flops.
  • Load is an input pin, controlling all the gates of the D flip-flops.
  • Clear is an input pin, setting all the D flip-flops to Q = 0.
  • Clock is an input pin, a common clock signal for all the D flip-flops.
  • Write is an input pin, a common "enable" for all the tri-state buffers.
  • Q0-Q3 are output pins, the data stored in the D flip-flops. These are not tri-state.
  • W0-W3 are tri-state output pins. When Write = 0 they are in state Z ("disconnected"). When Write = 1, W = Q. 

74LS173

The design of FourBitRegister is very similar to 74LS173/74HC173:

  • 1D-4D are the "D0-D3"
  • 1Q-4Q are teh "W0-W3)
  • M and N together is the "Enable"
  • G1 and G2 together is the "Load"
74LS173 does not have non-tri-state outputs.

Moving data from one register to another using the data bus

To test this out, we connect two registers (A and B) to a data bus. The goal is to move data from register A to register B. We also need DIP-switches to get data onto the bus (through tri-state buffers). This is the schematics.

Code:

  //Define chips
  auto* registerA = new FourBitRegister((char*)("Register A"));
  auto* registerB = new FourBitRegister((char*)("Register B"));
  auto* buffers = new QuadThreeStateSwitches();
  auto* bus = new BusLineFour();

  //Add all chips to be emulated here
  Chip* chips[] = {
    registerA, registerB, buffers, bus
  };

  //Add all connections here
  ConnectionBase* connections[] = {
    //Connect DIP switches to pins 2-5
    new ConnectionFour(ard, 2, buffers->D, 1),
    new ConnectionFour(buffers->W, bus->X),
    new Connection(ard, 6, buffers->Enable),
    //Register A
    new Connection(ard, 7, registerA->Clock),
    new Connection(ard, 8, registerA->Load),
    new Connection(ard, 9, registerA->Write),
    new ConnectionFour(registerA->W, bus->X),
    new ConnectionFour(bus->W, registerA->D),
    //Register B
    new Connection(ard, 7, registerB->Clock),
    new Connection(ard, 22, registerB->Load),
    new Connection(ard, 23, registerB->Write),
    new ConnectionFour(registerB->W, bus->X),
    new ConnectionFour(bus->W, registerB->D),
  };

Try this out yourself on Wokwi, project "Two registers and a bus".



Friday, March 28, 2025

Entry 14: Gated latches and flip-flops

In entry 13 we connected the output of a four D flip-flops (basic register) to the bus (through tri-state buffers). Now we want to do the reverse, connect the bus to the input of the register. The register designed in enty 9, QuadDFlipFlops,  will not quite work. 

This register will always load the data from the bus when the clock goes high. We need to be able to control when it will load data from the bus. We need an additional control line to the register, which we may call "load". The register shall only load data when load is high. It shall ignore the bus when load is low. A term for this feature is "Gated enable inputs" and the pin controlling the gate, the one I call "load", is also called "data enable".

Gated SR-latch

In order to make QuadDFlipFlops gated, we need to make a gated D flip-flop. D flip-flops are made from SR latches, so we need to go all the way back to these guys, and make them gated. This was the SR latches of the NAND type, that we created in entry 7:


We want to make this "gated" by adding a "Load pin". When Load is high (gate open), it should functions as before.  When Load is low (gate closed), there should be no change in the latch, no matter the values of Set and Reset. To accomplish this is simple, no need to ask Grok:


Note that when Load is 0, the two NAND gates on the left will always output 1 and there is "no change" in the latch. When Load is 1, the two NAND gates on the left will always output the values of Reset and Set. The latch then functions as before. This symbol makes sense for a gated NAND latch (L for Load):

Here is the full truth-table of the NAND latch with load.

These are the components I created:  

  • "NandLatchGated" with precisely this functionality, see ACEL
  • "NorLatchGated", a gated SR-latch based on NOR gates.
The pins are the same as for the andLatch and the NorLatch, but with the extra input pin "load". I am not going to test these, as they are very similar to the ungated versions, but you could certainly go ahead and do so. 

Gated D flip-flop

The D flip-flop we created in entry 8, DFlipFlop,  was "ungated". Creating a gated D flip-flop with an extra Load pin is simple. Just replace the slave SR latch with a gated one. The truth-table of a gated D flip-flop when Preset = 0 and Clear = 0 is then

As before, If Clear = 1, Q becomes 0 and if Preset = 1, Q becomes 1. This symbol makes sense for a gated D flip-flop (L for Load):

The name of this flip-flop in ACEL is DFlipFlopGated. Pins are:

  • Input pin: D
  • Input pin: Clock
  • Input pin: Clear
  • Input pin: Preset
  • Input pin: Load
  • Output pin: Q
  • Output pin: Q_Bar.

Again, I am not going to test this, as it is similar to the ungated version. I assume it will work.

In the next entry, I will create a new register based on four gated D flip-flops. I will also add four tri-state buffers to the output so that the output can be connected directly to the bus.

Entry 13: Connecting Register to Bus

Register output to bus

The goal of this entry is to make the first version of our register, "Quad D flip-flop" (see entry 9), "bus-friendly" by completing the following schematics we looked at in entry 12:

Each of the four tri-state buffers has an "enable", and all of them will be connected. This will make them all enabled or none of them enabled:

Quad three state switch

I combined four three state switches into a component QuadThreeStateSwitch.

  • Four D input pins: D[4] (by this I mean D[0], D[1], D[2], D[3])
  • Input pin: Enable (connected to all switches)
  • Four W output pins: W[4] (that is, W[0], W[1], W[2], W[3])

The data bus

The data bus is simply four wires that can be connected to various components. Each wire is tri-state, that is, it can be either connected to ground, to +5V, or be disconnected / floating (Z). I created a component with four wires, BusLineFour. To set and read the state of each wire, it has

  • Four connections X[4]. These are used when a component puts data onto the bus.
  • Four connections W[4]. These are used when a component reads data from the bus.
Only tri-state output can be connected to the bus X-input. For example, the output of a tri-state switch can be connected to an X-input. Even though each wire is tri-state, each W-output of the bus can be connected to non-tri-state input of a component. However, if the wire is in the Z-state, the component will read "garbage" (0 or 1 randomly). Only when the wire is "0" or a "1" will the component read a correct signal.

There is nothing to the BusLineFour. If you want to build something created using ACEL, BusLineFour would simply be replaced by four wires. 

Building and testing

We will use these three components:

  • QuadDFlipFlop, our first version register from entry 9.
  • QuadThreeStateSwitches.
  • BusLineFour.

To test this out:

  • Connect 4 DIP switches to the D-inputs of QuadDFlipFlop.
  • Connect a button to QuadDFlipFlop Clock
  • Connect the Q-outputs of QuadDFlipFlop to D-inputs of QuadThreeStateSwitches
  • Connect a button to QuadThreeStateSwitches Enable
  • Connect QuadThreeStateSwitches W-outputs to BusLineFour X-inputs. (we will not use the BusLineFour W-outputs in this example).

This will allow as to latch a number (0 to 15) into the flip-flops. When we set Enable = 1, this value will go onto the bus. When we set Enable = 0, the bus will be "disconnected", ZZZZ. We can check the state of the data bus using "Print".

Making four connections

Many of the connections that we need to make are in "sets of four". Instead of writing four "Connection", I created a "ConnectionFour". For example, if we define

  //Define chips
  auto* reg = new QuadDFlipFlop();
  auto* switches = new QuadThreeStateSwitches();
  auto* bus = new BusLineFour();

then the four connections between the register and the switches can be written

    new ConnectionFour(reg->Q, switches->D),

This is equivalent to four connections, Q[0] to D[0], and so on. Connection4 can also be used with Arduino pins. For example,

    new ConnectionFour(ard, 2, reg->D, 0),

will connect Arduino pin 2 to D[0], ...,  Arduino pin 5 to D[3]. 

    new ConnectionFour(ard, 2, reg->D, 1),

will reverse the order, Arduino pin 2 to D[3], ...,  Arduino pin 5 to D[0]. Here are all the connections:

  ConnectionBase* connections[] = {
    new ConnectionFour(ard, 2, reg->D, 1), //2,3,4,5 to D3, D2, D1, D0
    new Connection(ard, 6, reg->Clock),
    new Connection(ard, 7, switches->Enable),
    new ConnectionFour(reg->Q, switches->D),
    new ConnectionFour(switches->W, bus->X),
  };

Check it out on Wokwi, project "Register to Bus".


The register is not yet complete. The next step is the reverse, connecting the bus to the register allowing the register to get its data from the bus.


Wednesday, March 26, 2025

Entry 12: Tri-state output and tri-state buffer

The goal now is to make the "register" from entry 9 "bus-friendly" by making the output "tri-state" using a tri-state buffer.

Tri-state outputs

Grok:

Tri-state outputs (or three-state outputs) are a feature in digital electronics where an output pin can be in one of three states—not just the usual two (0 or 1). This is super useful when multiple devices share a common connection, like a data bus. Let’s break it down.


The Three States

  1. Logic 0 (Low): The output is actively driven to ground (~0V). Current flows out of the pin to ground.
  2. Logic 1 (High): The output is actively driven to the supply voltage (~5V for TTL/CMOS). Current flows into the pin from the supply.
  3. High-Impedance (Z): The output is effectively disconnected—it neither drives high nor low. It acts like an open switch, letting other devices control the line without interference.
  • Normal Outputs: Only 0 or 1 (e.g., your 74HC74’s Q pins).
  • Tri-State Outputs: 0, 1, or Z (e.g., 74LS173’s Q0–Q3 when controlled by G1/G2).

Why It’s Useful

Imagine a shared wire

  • Without Tri-State: If two devices output at once (e.g., one says 0, another says 1), they fight—current flows between them, causing damage or garbage data.
  • With Tri-State: Only one device is enabled at a time (outputs 0 or 1), while others are in Z-state (disconnected). No conflict!
-end-

So, here is how we can check if an output pin is 0, 1, or "Z":

Logic 0:

If an LED turns on when connected to the output, as in the picture, the output is low.

Logic 1:

If an LED turns on when connected to the output, as in the picture, the output is high.

High-Impedance (Z):

If the LED does not turn on in any of the cases, the output is in state Z.

In order to make the output of our register tri-state, we need four tri-state buffers between Q0–Q3 and the bus. This lets you enable/disable the register’s output. 

Let's begin by investigating a single tri-state buffer.

Tri-state buffer

A single tri-state buffer is a simple yet powerful digital building block that lets you control whether a signal passes through to its output or gets disconnected, making it perfect for connecting devices to a shared bus—like your 4-bit register setup. It’s the key to the tri-state outputs we’ve been discussing, and understanding how it works at the gate and transistor level ties together a lot of what you’ve been exploring. Let’s dig into its operation.


What It Does

  • Inputs:
    • A (Data Input): The signal you want to pass (0 or 1).
    • EN (Enable): Controls whether the output is active or high-impedance (Z).
  • Output (Y):
    • When enabled: Y = A (0 or 1).
    • When disabled: Y = Z (high-impedance, disconnected).

Truth table:

A tri-state buffer cannot be constructed from basic gates. Instead, it is typically implemented with transistors. Like basic gates, I will not investigate the electronics behind it.

The 74LS126 has 4 tri-state buffers:

ACEL also has tri-state buffers.

Tri-state buffer in ACEL

The tri-state buffer in ACEL is called ThreeStateSwitch

  • The input is labeled D (for data)
  • Enable is labeled E
  • The output is labeled W (for write)

Here is how we can try it out. Add two buttons "D" and "E" to control the inputs. Here is the code, where D is connected to pin 2 and E to pin 3:

  auto* TriStateBuffer = new ThreeStateSwitch();

  //Add all chips to be emulated here
  Chip* chips[] = {
    TriStateBuffer
  };

  //Add all connections here
  ConnectionBase* connections[] = {
    new Connection(ard, 2, TriStateBuffer->D),
    new Connection(ard, 3, TriStateBuffer->E),
  };

It is not possible to connect the output W to an Arduino pin using a "Connection", since W is tri-state (cannot make an output pin floating in Arduino). Hower, this is not really an issue, as the output of a tri state buffer will "go on a bus".

Instead, we can use the "print" functionality to investigate the tri-state buffer. Instead of clicking a button to get a print, we can add the line

  emulation.printOnArdEvent(1);

in the setup section. We then automatically get a "print" each time anything happens. 


You can check this out on Wokwi, project name "Tri state buffer".

The goal of the next entry is to complete the connection between the register and the bus adding 4 tri-state buffer.


Entry 11: Data Bus

In entry 9, we designed a first version of a 4 bit register. Looking at the architecture of a simple CPU shows that registers are connected to an internal data bus

Bus

Grok: A bus in electronics and computing is a set of shared electrical pathways (wires or traces) that multiple devices use to communicate data, addresses, or control signals within a system. Think of it as a highway where different components—like the CPU, memory, or registers—take turns sending or receiving information. It’s a key concept in a microprocessor. Let’s break it down.


What Is a Bus?

  • Definition: A collection of parallel conductors (wires) that carry signals between components.
  • Purpose: Simplifies connections—instead of wiring every device to every other device, they all connect to the bus and share it.
  • Analogy: A bus is like a phone party line—everyone’s on the same line, but only one talks at a time.

Types of Buses

  1. Data Bus:
    • Carries actual data (e.g., your 4-bit binary numbers).
    • Width: Number of bits it handles at once.
    • Bidirectional: Data flows both ways (e.g., memory to CPU or CPU to memory).
  2. Address Bus:
    • More on this later
  3. Control Bus:
    • More on this later

How a Bus Works

  • Shared Lines: All devices connect to the same wires (e.g., 4 wires for a 4-bit data bus).
  • Turn-Taking: Only one device drives the bus at a time to avoid conflicts (e.g., two devices outputting 0 and 1 simultaneously).
  • Tri-State Outputs: 
    • Drive the bus (0 or 1) when enabled.
    • Disconnect (Z) when not their turn, letting others take over.

-end-

Here is a nice picture of SAP-1, Simple as possible-1 "computer".



A bunch of "stuff" is connected to a bus, labeled "W bus", running along the middle of the image. This is a 8 bit bus, consisting of 8 wires. The arrows indicate if the component "puts data on the bus" or "reads data from the bus"

Register and Bus

The next question is how to "connect" my register to a bus. Groks curated answer:

Connecting your simple 4-bit register (built from four D flip-flops, like your setup) to a 4-bit data bus is straightforward, but there’s a catch: your register doesn’t have tri-state outputs natively, which are crucial for bus operation. Without tri-state capability, it will always drive the bus (Q outputs are always 0 or 1), potentially clashing with other devices. I’ll show you how to connect it as-is, then add a tri-state buffer to make it bus-friendly, tying it to your earlier projects. -end-

This makes sense. Each output pin of my register is always 0 or 1. Connecting two registers to the bus, like this, would not work:
Say that Q0 of register 1 is 1 (effectively connected to +5V) and Q0 of register 2 is 0 (effectively connected to ground), we would then have a direct connection from +5V to ground, a "short circuit".  We need to make sure that at most one register is connected to the bus at one moment in time. Each output pin of a register should be either 0, 1, or "disconnected".

So, to make our register "bus-friendly", we must investigate the tri-state buffer. This is the next entry.





Tuesday, March 25, 2025

Entry 10: Seven segment display

In the previous entry we created a first version of a 4 bit register, the QuadDFlipFlop. The content of the register was displayed in binary using 4 LED's. Also, remember how we can convert between decimal, binary and hex numbers:

We can use four LED's to display a four bit number. There is also a nice component, called a seven segment display, that we can use as an alternative to four LED's.

Seven segment display

Curated answer from Grok on seven segment display:

Seven-Segment Display Basics

  • Segments: Labeled a–g (plus dp for decimal point, optional).
    • a: Top
    • b: Top-right
    • c: Bottom-right
    • d: Bottom
    • e: Bottom-left
    • f: Top-left
    • g: Middle
  • Types:
    • Common Cathode: All segment cathodes tied together to ground; apply 5V to anodes (a–g) to light segments.
    • Common Anode: All anodes tied to 5V; ground cathodes to light segments.
  • We’ll assume common cathode (easiest with 5V logic); adjust if yours is common anode.
-end-

A seven segment display has 10 pins at the back, five at the top (g f cm a b) and five at the bottom (e d cm c dp). This picture shows the pins and the segments:

The pins a-g are connected to the corresponding segments. For example, segment "a", the top one, is controlled by the pin labeled "a", the fourth pin at the top. "cm" is the common cathode/anode and "dp" controls the "dot" at the bottom right. 

Here is the design of common cathode and common anode:

If you have a common anode display, hook up cm to +5V (trough a resistor). To turn on segment "a", ground the pin labeled "a".

If you have a common cathode display, hook up cm to ground (trough a resistor). To turn on segment "a", connect the pin labeled "a" to +5V.

Here is how you can check if you have a common anode or a common cathode: connect a resistor (~330 Ω) to "cm", the middle top or bottom pin.  
  • If the middle segment turns on when you connect "g" (the first upper pin) to ground and the resistor to +5V: common anode.
  • If the middle segment turns on when you connect "g" to +5V and the resistor to ground: common cathode.

This display has a common anode, as cm is connected to +5V and the segment to ground.

Driving a seven segment display

Next, I want the display to show a four bit binary number in hex, 0010 to 2, 1110 to E and so on. Grok's curated answer:

Converting a 4-bit binary number (0 to 15 in decimal, 0 to F in hex) to drive a seven-segment display involves translating the binary input into signals that light up the correct segments (a, b, c, d, e, f, g) to show the corresponding digit. A seven-segment display has 7 individual LED segments arranged to form numbers and some letters, and we’ll use it to display 0–9 and A–F (for hex). Here’s how to do it on a breadboard, tying it to your 4-bit register setup.


Step 1: Define the Mapping

Each 4-bit binary number (0000 to 1111) corresponds to a digit (0–F). You need to light specific segments for each:

BinaryHexDecimalSegments (a-g)
000000a, b, c, d, e, f
000111b, c
001022a, b, d, e, g
001133a, b, c, d, g
010044b, c, f, g
010155a, c, d, f, g
011066a, c, d, e, f, g
011177a, b, c
100088a, b, c, d, e, f, g
100199a, b, c, d, f, g
1010A10a, b, c, e, f, g
1011B11c, d, e, f, g
1100C12a, d, e, f
1101D13b, c, d, e, g
1110E14a, d, e, f, g
1111F15a, e, f, g

Step 2: Hardware Approach

You’ll convert your 4-bit register’s outputs (Q0–Q3) into 7 signals (a–g) to drive the display. Here are two practical methods:

Option 1: Use a BCD-to-Seven-Segment Decoder (74LS47 or 74HC4511)

I may follow up on this later. BCD = Binary Decoded Decimal, meaning that each decimal digit is represented by a fixed number of bits, for example four bits.

Option 2: Custom Logic with Gates

For full 0–F display, design combinational logic for each segment using your 4-bit inputs (Q0–Q3). This is more work but educational.

  • Inputs: Q3 (8), Q2 (4), Q1 (2), Q0 (1).
  • Outputs: a, b, c, d, e, f, g (1 = on, 0 = off).
  • Logic Equations (from truth table):
    • a = Q3̅Q2̅Q1̅Q0̅ + Q3̅Q2̅Q1Q0̅ + Q3̅Q2Q1̅Q0 + Q3̅Q2Q1Q0 + Q3Q2̅Q1̅Q0 + Q3Q2Q1Q0̅ + Q3Q2Q1Q0
    • b = Q3̅Q2̅Q1̅Q0 + Q3̅Q2̅Q1Q0 + Q3̅Q2Q1Q0̅ + Q3Q2̅Q1̅Q0 + Q3Q2̅Q1Q0 + Q3Q2Q1̅Q0
    • c = Q3̅Q2̅Q1Q0̅ + Q3̅Q2Q1Q0 + Q3Q2̅Q1̅Q0 + Q3Q2Q1̅Q0 + Q3Q2Q1Q0
    • d = Q3̅Q2̅Q1̅Q0 + Q3̅Q2Q1̅Q0 + Q3Q2̅Q1Q0̅ + Q3Q2Q1̅Q0 + Q3Q2Q1Q0
    • e = Q3̅Q2̅Q1̅Q0 + Q3Q2̅Q1̅Q0 + Q3Q2̅Q1Q0 + Q3Q2Q1̅Q0 + Q3Q2Q1Q0
    • f = Q3̅Q2̅Q1̅Q0 + Q3̅Q2Q1̅Q0 + Q3Q2̅Q1̅Q0 + Q3Q2Q1̅Q0 + Q3Q2Q1Q0
    • g = Q3̅Q2̅Q1̅Q0 + Q3̅Q2Q1Q0̅ + Q3Q2̅Q1̅Q0 + Q3Q2Q1̅Q0 + Q3Q2Q1Q0
  • Implementation:
    • Use 74HC00 (NAND), 74HC08 (AND), 74HC32 (OR) gates to build each equation.
    • Example: "a" needs 7 AND gates (for each term) feeding an OR gate.
    • Total: ~20–30 gates (impractical without a PLD).

-end-

74LS47

The simple choice is to use the 74LS47:

You connect the four binary inputs to A, B, C, and D, where A is the lowest bit (Q0) and D the highest (Q3). The output pins a-g are the connected to the corresponding pins on the display (not sure what pins 3,4, and 5 do).

Grok's answer above allows us to figure out how this IC works internally.

Whether the output pin "a" should be high can be determined from the input pins Q3-Q3 according to the logic equations above. We can simplify the formula for a:

            a = t1 + t2 + t3 + t4 + t5 + t6 + t7

where t1 = Q3̅Q2̅Q1̅Q0̅  and so on (the bar on top means inverted Q). "Product" in a logical equation means "AND". Therefore, we can get t1 using a four input AND gate. We hook up the four inverted Q's to the input pins and the output pin is t1.

Another four input AND gate will give us t2 = Q3̅Q2̅Q1Q0̅  , and so on. Sum "+" in a logical equation means "OR". Therefore, we connect t1..t7 to a seven input OR gate. The output of this OR gate is precisely "a".

Example: If Q = 0000 (Q = 0) then t1 = 1 and a = 1. This is correct, segment "a", the top segment, should be on when Q = 0. If Q = 0001 (Q = 1) then all t1..t7 are 0, and a = 0. This too is correct, segment a should be off when Q = 1. 

There are obviously more clever ways of designing your own 74LS47 using basic gates. But the point is that it can be built from basic gates, it contains to "mysteries". I guess it could be fun to build the 74LS47 from basic gates, but I am not going to do that.

ACEL Seven segment decoder

Acel has

  • SevenSegmentDecoderAnode to be used with a common anode display. Connect "cm" through a resistor and to +5V.
  • SevenSegmentDecoderCathode to be used with a common cathode display. Connect "cm" through a resistor and to ground.
The pins are as follows:
  • Input pins Q[3], Q[2], Q[1], Q[0] where Q[3] is the highest bit (these corresponds to the pins D, C, B, A on 74LS47)
  • Output pins S[0], ... , S[6]  (these corresponds to the pins a..g on 74LS47)

Here is an example with a common anode display. Four DIP-switches are connected to the input pins:

  //Define chips
  auto* decoder = new SevenSegmentDecoderAnode();

  //Add all chips to be emulated here
  Chip* chips[] = {
    decoder
  };

  //Add all connections here
  ConnectionBase* connections[] = {
    new Connection(ard, 2, decoder->Q[3]),
    new Connection(ard, 3, decoder->Q[2]),
    new Connection(ard, 4, decoder->Q[1]),
    new Connection(ard, 5, decoder->Q[0]),
    new Connection(decoder->S[0], ard, 22),
    new Connection(decoder->S[1], ard, 23),
    new Connection(decoder->S[2], ard, 24),
    new Connection(decoder->S[3], ard, 25),
    new Connection(decoder->S[4], ard, 26),
    new Connection(decoder->S[5], ard, 27),
    new Connection(decoder->S[6], ard, 28),
  };


You can also try this out on Wokwi, project "Seven segment decoder".