- Published on
Memory Interface Connections A Complete Deep Dive
- Authors

- Name
- Yinhuan Yuan
Memory Interface Connections: A Complete Deep Dive
Let me explain how memory connects to the Hack computer in exhaustive detail - this is where program instructions and data actually live!
The Big Picture: Two Separate Memory Systems
The Harvard Architecture Advantage
The Hack computer uses Harvard architecture - separate instruction and data memory:
Traditional Von Neumann (one memory):
────────────────────────────────────
┌──────────┐
│ Memory │
│ (Both │
│ Inst+Data│
└────┬─────┘
│
Single Bus
│
┌───────┴────────┐
│ │
▼ ▼
[Fetch [Load/Store
Instruct] Data]
│ │
└────┬───────────┘
│
CONFLICT! Can't do both at once!
Harvard (separate memories):
────────────────────────────
┌──────────┐ ┌──────────┐
│ ROM │ │ RAM │
│(Instruct)│ │ (Data) │
└────┬─────┘ └────┬─────┘
│ │
Inst Bus Data Bus
│ │
▼ ▼
[Fetch [Load/Store
Instruct] Data]
│ │
└────┬────────────┘
│
NO CONFLICT! Simultaneous access! ✓
Advantages:
───────────
✓ Fetch instruction AND access data in same cycle
✓ Simpler bus arbitration
✓ Faster execution (no waiting)
✓ Separate optimization for each memory type
✓ Security (ROM can't be modified)
Disadvantages:
──────────────
✗ Two separate address buses needed
✗ Two separate data buses
✗ More complex board layout
✗ Can't execute code from RAM (in pure Harvard)
Memory Map Overview
Hack Computer Memory Organization:
INSTRUCTION MEMORY (ROM):
┌────────────────────────────┐
│ 0x0000 - 0x7FFF (32K) │
│ │
│ Program Code │
│ (Read-Only) │
│ │
│ Connected to PC │
└────────────────────────────┘
DATA MEMORY (RAM):
┌────────────────────────────┐
│ 0x0000 - 0x3FFF (16K) │
│ General Purpose RAM │
├────────────────────────────┤
│ 0x4000 - 0x5FFF (8K) │
│ Screen Memory (optional) │
├────────────────────────────┤
│ 0x6000 - 0x6000 (1 word) │
│ Keyboard Register (opt) │
├────────────────────────────┤
│ 0x6001 - 0x7FFF │
│ Reserved / Unused │
└────────────────────────────┘
Connected to A Register
Connection 1: ROM (Instruction Memory)
Purpose and Role
What ROM Does:
──────────────
├─ Stores program instructions
├─ Read-only (written once during programming)
├─ Addressed by Program Counter (PC)
├─ Outputs instruction to Control Unit
└─ Fastest access (no write circuitry)
Why EEPROM for "ROM":
─────────────────────
├─ Electrically Erasable (can reprogram)
├─ Non-volatile (keeps data when power off)
├─ Fast read access (~70ns)
├─ Easy to program with USB programmer
└─ Perfect for development!
Hardware Choice: AT28C256 EEPROM
AT28C256 Specifications:
────────────────────────
Capacity: 32K × 8 bits (32,768 bytes)
Organization: 32K addresses × 8-bit data
Package: 28-pin DIP
Technology: CMOS EEPROM
Access Time: 70ns (typical), 150ns (max)
Power: 5V ±10%
Endurance: 10,000 write cycles
Retention: 10 years data retention
Why AT28C256?
─────────────
✓ Perfect size (32K matches Hack requirement)
✓ Available in DIP package (easy prototyping)
✓ 5V operation (matches 74HC logic)
✓ Fast enough for MHz operation
✓ Cheap (~$3-5 per chip)
✓ Easy to program (standard EEPROM programmer)
✓ Two chips give us 16-bit instruction width
AT28C256 Pinout (Detailed)
AT28C256 (28-pin DIP)
┌──────┴──────┐
A14 │1 28│ VCC (+5V)
A12 │2 27│ /WE (Write Enable)
A7 │3 26│ A13
A6 │4 25│ A8
A5 │5 24│ A9
A4 │6 23│ A11
A3 │7 22│ /OE (Output Enable)
A2 │8 21│ A10
A1 │9 20│ /CE (Chip Enable)
A0 │10 19│ D7
D0 │11 18│ D6
D1 │12 17│ D5
D2 │13 16│ D4
GND │14 15│ D3
└─────────────┘
PIN FUNCTIONS:
──────────────
ADDRESS PINS (A0-A14):
├─ 15 address lines = 2^15 = 32K addresses
├─ Connected directly to Program Counter
├─ Must be stable before /OE asserted
└─ Valid throughout read cycle
DATA PINS (D0-D7):
├─ 8-bit data output (half of 16-bit instruction)
├─ Connected to instruction bus
├─ Tri-state (hi-Z when /OE high)
└─ Drive valid data when /OE low
CONTROL PINS:
/CE (Chip Enable, pin 20):
├─ Active LOW
├─ Must be LOW to access chip
├─ For Hack: tied to GND (always enabled)
└─ Power saving if disabled (hi-Z outputs)
/OE (Output Enable, pin 22):
├─ Active LOW
├─ Controls data output drivers
├─ For Hack: tied to GND (always outputting)
└─ When HIGH: data pins are hi-Z
/WE (Write Enable, pin 27):
├─ Active LOW
├─ For Hack: tied to VCC (never write during operation)
├─ Only used during programming with EEPROM programmer
└─ READ ONLY during normal operation
POWER PINS:
VCC (pin 28): +5V
GND (pin 14): Ground
Complete ROM Connection Schematic
We need TWO AT28C256 chips for 16-bit instructions:
ROM Schematic - Full 16-bit Instruction Memory:
ADDRESSING (shared by both chips):
──────────────────────────────────
Program Counter:
PC[0] ───────┬────────────────┐
PC[1] ───────┼────────────────┤
PC[2] ───────┼────────────────┤
PC[3] ───────┼────────────────┤
PC[4] ───────┼────────────────┤
PC[5] ───────┼────────────────┤
PC[6] ───────┼────────────────┤
PC[7] ───────┼────────────────┤
PC[8] ───────┼────────────────┤
PC[9] ───────┼────────────────┤
PC[10] ──────┼────────────────┤
PC[11] ──────┼────────────────┤
PC[12] ──────┼────────────────┤
PC[13] ──────┼────────────────┤
PC[14] ──────┴────────────────┤
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ ROM_LO (U1) │ │ ROM_HI (U2) │
│ AT28C256 │ │ AT28C256 │
│ │ │ │
A0 ◄─┤10 │ │ 10├─► A0
A1 ◄─┤9 │ │ 9├─► A1
A2 ◄─┤8 │ │ 8├─► A2
A3 ◄─┤7 │ │ 7├─► A3
A4 ◄─┤6 │ │ 6├─► A4
A5 ◄─┤5 │ │ 5├─► A5
A6 ◄─┤4 │ │ 4├─► A6
A7 ◄─┤3 │ │ 3├─► A7
A8 ◄─┤25 │ │ 25├─► A8
A9 ◄─┤24 │ │ 24├─► A9
A10 ◄─┤21 │ │ 21├─► A10
A11 ◄─┤23 │ │ 23├─► A11
A12 ◄─┤2 │ │ 2├─► A12
A13 ◄─┤26 │ │ 26├─► A13
A14 ◄─┤1 │ │ 1├─► A14
│ │ │ │
│ │ │ │
│ DATA OUT │ │ DATA OUT │
│ │ │ │
D0 ──┤11 │ │ 11├── D8
D1 ──┤12 │ │ 12├── D9
D2 ──┤13 │ │ 13├── D10
D3 ──┤15 │ │ 15├── D11
D4 ──┤16 │ │ 16├── D12
D5 ──┤17 │ │ 17├── D13
D6 ──┤18 │ │ 18├── D14
D7 ──┤19 │ │ 19├── D15
│ │ │ │
└───────────────┘ └───────────────┘
│ │
└──────────┬──────────┘
│
INST[15:0]
(16-bit instruction bus)
│
▼
To Control Unit
CONTROL SIGNALS (fixed for read-only):
──────────────────────────────────────
Both chips:
GND ──► /CE (pin 20) [Always enabled]
GND ──► /OE (pin 22) [Always outputting]
VCC ──► /WE (pin 27) [Never writing]
POWER (both chips):
───────────────────
VCC ──► pin 28 (with 0.1µF bypass cap)
GND ──► pin 14
INSTRUCTION BUS OUTPUT:
───────────────────────
ROM_LO (U1) provides: INST[7:0] (low byte)
ROM_HI (U2) provides: INST[15:8] (high byte)
Combined: INST[15:0] → Control Unit
Timing Characteristics
AT28C256 Read Cycle Timing:
Parameters (from datasheet):
────────────────────────────
Symbol Parameter Min Typ Max Unit
──────────────────────────────────────────────────────────────
tACC Address to Output Valid - 70 150 ns
tCE Chip Enable to Output - 70 150 ns
tOE Output Enable to Output - - 70 ns
tOH Output Hold from Address 10 - - ns
tDF Output Disable Time - - 100 ns
Critical Timing (worst case):
──────────────────────────────
Address setup → Data valid: 150ns maximum
Output enable → Data valid: 70ns maximum
Data hold after address: 10ns minimum
Timing Diagram:
───────────────
Time: 0 50 100 150 200 250 300 (ns)
│ │ │ │ │ │ │
Address: ──┴───────────────────────────────────── (stable)
──►│◄─────────────────────────────────
New address applied
/CE: ════════════════════════════════════════ (LOW, enabled)
/OE: ════════════════════════════════════════ (LOW, enabled)
Data: ═══════╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╲══════════════
└─► Invalid ──►│◄─ Valid Data
150ns
(worst case)
Output ═══════════════════════════╱╲╱╲╱════════
Enable └─►│◄─ Valid
to 70ns
Data:
For Hack Computer at 8 MHz:
───────────────────────────
Clock period: 125ns
Available time: 125ns - setup time (12ns) = 113ns
ROM access time: 150ns maximum
Margin: NEGATIVE! Too slow!
Solution:
─────────
Option 1: Use faster EEPROM (70ns versions exist)
Option 2: Wait states (add extra cycle for memory)
Option 3: Lower clock speed to 6 MHz (167ns period)
Option 4: Pipeline (fetch ahead)
Practical choice: 70ns EEPROM at 8 MHz ✓
Read Operation Step-by-Step
ROM Read Cycle (detailed):
Scenario: Fetch instruction at address 0x0100
Step 1: PC outputs address (t=0ns)
───────────────────────────────────
Program Counter:
PC_Out[14:0] = 0x0100 (256 decimal)
Electrical:
┌──────────┐
│ PC │
│ U6-U9 │
│ 74HC161 │
└────┬─────┘
│
PC[14:0] = 0000000100000000
│
▼
(propagates to ROM address pins)
Step 2: Address propagation (t=0-25ns)
───────────────────────────────────────
Signal travels through:
├─ PC output buffer: 5ns
├─ PCB trace: 1ns (speed of light in FR4)
├─ ROM input buffer: 5ns
└─ Internal address decode: 14ns
Total: 25ns to address decoder stable
Step 3: Memory cell selection (t=25-50ns)
──────────────────────────────────────────
Inside EEPROM:
├─ Row decoder activates
├─ Column decoder activates
├─ Selected memory cell found
├─ Floating gate transistor read
└─ Sense amplifier detects charge
Time: ~25ns for cell selection
Step 4: Data appears at outputs (t=50-70ns)
────────────────────────────────────────────
From memory cell to output pins:
├─ Sense amplifier: 10ns
├─ Output buffer: 10ns
└─ Pin driver: 5ns
At t=70ns:
ROM_LO.D[7:0] = valid data (low byte)
ROM_HI.D[7:0] = valid data (high byte)
INST[15:0] = complete instruction ✓
Step 5: Data stable (t=70-125ns)
─────────────────────────────────
Data remains valid:
├─ Output drivers maintain voltage
├─ No change until address changes
├─ Plenty of time for control unit
└─ Setup time met (12ns before clock)
Complete timing:
────────────────
t=0ns: PC changes to 0x0100
t=25ns: Address decoded in ROM
t=70ns: Data valid at ROM outputs
t=113ns: Setup time margin (13ns)
t=125ns: Clock edge (next cycle starts)
Timing margin: 55ns ✓
Safe operation at 8 MHz!
Electrical View:
────────────────
PC Output ROM Input
│ │
════════════════════════ Address lines
│ 25ns │
└───────────────►│
│
[Decode]
│
│ 45ns
▼
════════════ Data lines
│ │
ROM Output (to Control Unit)
Programming the ROM
Before Operation: Loading Program
──────────────────────────────────
The EEPROM must be programmed with Hack machine code:
Step 1: Write Hack Assembly
────────────────────────────
Example program:
@17 // Load 17 into A
D=A // Copy A to D
@100 // Load 100 into A
M=D // Store D to RAM[100]
Step 2: Assemble to Machine Code
─────────────────────────────────
Assembler output (binary):
0000000000010001 // @17
1110110000010000 // D=A
0000000001100100 // @100
1110001100001000 // M=D
Step 3: Create ROM Image Files
───────────────────────────────
Split into two 8-bit files:
ROM_LO.bin (low byte):
Address Data (binary) Data (hex)
0x0000 00010001 0x11
0x0001 00010000 0x10
0x0002 01100100 0x64
0x0003 00001000 0x08
ROM_HI.bin (high byte):
Address Data (binary) Data (hex)
0x0000 00000000 0x00
0x0001 11101100 0xEC
0x0002 00000000 0x00
0x0003 11100011 0xE3
Step 4: Program EEPROM Chips
─────────────────────────────
Using TL866 or similar programmer:
1. Insert ROM_LO chip into programmer
2. Load ROM_LO.bin file
3. Click "Program"
4. Verify successful
5. Remove chip, label "ROM LO"
6. Insert ROM_HI chip
7. Load ROM_HI.bin file
8. Click "Program"
9. Verify successful
10. Remove chip, label "ROM HI"
Step 5: Install in Computer
────────────────────────────
1. Insert ROM_LO into U1 socket
- Pin 1 (A14) toward indicator
- Check orientation!
2. Insert ROM_HI into U2 socket
- Match orientation with ROM_LO
- Double-check pin 1 location
3. Power on
4. Program runs from address 0x0000 ✓
Optional: ZIF Sockets
─────────────────────
Use Zero Insertion Force sockets:
├─ Easy chip removal
├─ No bent pins
├─ Rapid development
└─ ~$5 each, worth it!
Connection 2: RAM (Data Memory)
Purpose and Role
What RAM Does:
──────────────
├─ Stores program data (variables)
├─ Read AND write operations
├─ Addressed by A Register
├─ Accessed via M (memory[A])
└─ Volatile (loses data when power off)
Why SRAM:
─────────
✓ Fast access (55ns typical)
✓ No refresh needed (unlike DRAM)
✓ Simple interface (like ROM + write)
✓ TTL compatible (5V)
✓ Random access (any address any time)
Hardware Choice: AS6C4008 SRAM
AS6C4008 Specifications:
────────────────────────
Capacity: 512K × 8 bits (524,288 bytes)
Organization: 512K addresses × 8-bit data
Package: 32-pin DIP
Technology: CMOS SRAM
Access Time: 55ns (typical), 70ns (max)
Power: 5V ±10%
Standby: <1µA (when /CE high)
Active: 50mA typical
Why AS6C4008?
─────────────
✓ Large capacity (we use only 32K of 512K)
✓ Fast enough (55ns < 70ns ROM)
✓ DIP package available
✓ 5V operation
✓ Standard SRAM interface
✓ No refresh needed
✓ Available and affordable (~$3-5)
✗ Overkill capacity (but that's okay!)
Alternative: AS6C62256 (32K × 8)
────────────────────────────────
✓ Exact size match (32K)
✓ 28-pin DIP (smaller)
✓ Same interface
✓ Also works perfectly!
AS6C4008 Pinout (Detailed)
AS6C4008 (32-pin DIP)
┌──────┴──────┐
A18 │1 32│ VCC (+5V)
A16 │2 31│ A17
A15 │3 30│ A14
A12 │4 29│ A13
A7 │5 28│ A8
A6 │6 27│ A9
A5 │7 26│ A11
A4 │8 25│ /OE (Output Enable)
A3 │9 24│ A10
A2 │10 23│ /CE (Chip Enable)
A1 │11 22│ D7
A0 │12 21│ D6
I/O0 │13 20│ D5
I/O1 │14 19│ D4
I/O2 │15 18│ D3
GND │16 17│ /WE (Write Enable)
└─────────────┘
PIN FUNCTIONS:
──────────────
ADDRESS PINS (A0-A18):
├─ 19 address lines = 2^19 = 512K addresses
├─ For Hack: only use A0-A14 (32K)
├─ A15-A18: tied to GND (unused)
├─ Connected to A Register output
└─ Must be stable before access
DATA PINS (I/O0-I/O7, or D0-D7):
├─ BIDIRECTIONAL! (Read or Write)
├─ Three-state outputs
├─ During READ: RAM drives bus
├─ During WRITE: CPU drives bus
└─ Critical: only one driver at a time!
CONTROL PINS:
/CE (Chip Enable, pin 23):
├─ Active LOW
├─ For Hack: tied to GND (always enabled)
├─ Selecting which RAM chip (if multiple)
└─ Low power when HIGH (standby mode)
/OE (Output Enable, pin 25):
├─ Active LOW
├─ Controls READ operation
├─ When LOW: RAM drives data bus
├─ When HIGH: data pins are hi-Z
└─ Connected to READ_M signal
/WE (Write Enable, pin 17):
├─ Active LOW
├─ Controls WRITE operation
├─ When LOW: RAM captures data from bus
├─ When HIGH: no write occurs
└─ Connected to WRITE_M signal
TRUTH TABLE:
────────────
/CE /OE /WE │ Operation │ Data Pins
─────────────────────────────────────────
H X X │ Standby │ Hi-Z (disconnected)
L L H │ READ │ Output (RAM → CPU)
L H L │ WRITE │ Input (CPU → RAM)
L L L │ FORBIDDEN! │ BUS FIGHT!
L H H │ Deselected │ Hi-Z
POWER PINS:
VCC (pin 32): +5V
GND (pin 16): Ground
Complete RAM Connection Schematic
Two AS6C4008 chips for 16-bit data:
RAM Schematic - Full 16-bit Data Memory:
ADDRESSING (from A Register):
──────────────────────────────
A Register:
A[0] ────────┬────────────────┐
A[1] ────────┼────────────────┤
A[2] ────────┼────────────────┤
A[3] ────────┼────────────────┤
A[4] ────────┼────────────────┤
A[5] ────────┼────────────────┤
A[6] ────────┼────────────────┤
A[7] ────────┼────────────────┤
A[8] ────────┼────────────────┤
A[9] ────────┼────────────────┤
A[10] ───────┼────────────────┤
A[11] ───────┼────────────────┤
A[12] ───────┼────────────────┤
A[13] ───────┼────────────────┤
A[14] ───────┴────────────────┤
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ RAM_LO (U3) │ │ RAM_HI (U4) │
│ AS6C4008 │ │ AS6C4008 │
│ │ │ │
A0 ◄─┤12 │ │ 12├─► A0
A1 ◄─┤11 │ │ 11├─► A1
A2 ◄─┤10 │ │ 10├─► A2
A3 ◄─┤9 │ │ 9├─► A3
A4 ◄─┤8 │ │ 8├─► A4
A5 ◄─┤7 │ │ 7├─► A5
A6 ◄─┤6 │ │ 6├─► A6
A7 ◄─┤5 │ │ 5├─► A7
A8 ◄─┤28 │ │ 28├─► A8
A9 ◄─┤27 │ │ 27├─► A9
A10 ◄─┤24 │ │ 24├─► A10
A11 ◄─┤26 │ │ 26├─► A11
A12 ◄─┤4 │ │ 4├─► A12
A13 ◄─┤29 │ │ 29├─► A13
A14 ◄─┤30 │ │ 30├─► A14
GND ─►┤3,2,1 │ │ 1,2,3├─ GND
│ (A15-A18) │ │ (A15-A18) │
│ │ │ │
│ DATA I/O │ │ DATA I/O │
│ (bidirection) │ │ (bidirection) │
│ │ │ │
I/O0 ◄┤13 │ │ 13├─► I/O0
I/O1 ◄┤14 │ │ 14├─► I/O1
I/O2 ◄┤15 │ │ 15├─► I/O2
I/O3 ◄┤18 │ │ 18├─► I/O3
I/O4 ◄┤19 │ │ 19├─► I/O4
I/O5 ◄┤20 │ │ 20├─► I/O5
I/O6 ◄┤21 │ │ 21├─► I/O6
I/O7 ◄┤22 │ │ 22├─► I/O7
│ │ │ │
└───────┬───────┘ └───────┬───────┘
│ │
│ To Bus │
│ Transceiver │
▼ ▼
CONTROL SIGNALS:
────────────────
Both chips:
GND ────────► /CE (pin 23) [Always enabled]
READ_M ────► /OE (pin 25) [Output when reading]
WRITE_M ────► /WE (pin 17) [Write when pulsed]
POWER (both chips):
───────────────────
VCC ──► pin 32 (with 0.1µF bypass cap)
GND ──► pin 16
BUS INTERFACE (via 74HC245 transceivers):
──────────────────────────────────────────
RAM data pins don't connect directly to CPU bus!
Need bidirectional buffers (see next section)
The Bidirectional Problem
Challenge: RAM Data Pins are Bidirectional
───────────────────────────────────────────
RAM during READ:
RAM I/O pins → OUTPUT mode → drive bus
RAM during WRITE:
RAM I/O pins → INPUT mode → receive from bus
But CPU also uses same bus:
CPU → drives bus during WRITE
CPU ← receives bus during READ
Direct Connection (BAD):
────────────────────────
CPU Bus ──────┬─────── RAM I/O pins
│
CONFLICT!
If both try to drive:
├─ CPU says HIGH (5V)
├─ RAM says LOW (0V)
├─ SHORT CIRCUIT through drivers!
├─ Excessive current
└─ CHIP DAMAGE!
Solution: Bus Transceiver (74HC245)
────────────────────────────────────
CPU Bus ◄──┬──► [74HC245] ◄──┬──► RAM I/O
DIR /OE
The 74HC245 acts as a "traffic cop":
├─ Controls direction of data flow
├─ Isolates CPU from RAM when needed
├─ Prevents bus conflicts
└─ Provides buffering
74HC245 Bus Transceiver
74HC245 Pinout and Function:
────────────────────────────
74HC245 (20-pin DIP)
┌──────┴──────┐
DIR │1 20│ VCC
A1 │2 19│ B1
A2 │3 18│ B2
A3 │4 17│ B3
A4 │5 16│ B4
A5 │6 15│ B5
A6 │7 14│ B6
A7 │8 13│ B7
A8 │9 12│ B8
GND │10 11│ /OE
└─────────────┘
PIN FUNCTIONS:
──────────────
A1-A8: "A" side data pins (typically CPU side)
B1-B8: "B" side data pins (typically RAM side)
DIR (pin 1): Direction control
├─ DIR = 0: B → A (data flows from RAM to CPU) = READ
├─ DIR = 1: A → B (data flows from CPU to RAM) = WRITE
└─ Sets direction of ALL 8 bits together
/OE (pin 11): Output Enable
├─ /OE = 0: Transceiver active (data flows)
├─ /OE = 1: All outputs hi-Z (isolated)
└─ Master enable for whole chip
TRUTH TABLE:
────────────
/OE DIR │ Operation │ Data Flow
──────────────────────────────────────
L L │ B → A │ RAM to CPU (READ)
L H │ A → B │ CPU to RAM (WRITE)
H X │ Hi-Z │ Isolated
Internal Operation:
───────────────────
When DIR = 0 (READ):
B pins → Input buffers → A pins
A pins ← Output drivers ← (from B)
B drivers: disabled (hi-Z)
A drivers: enabled
When DIR = 1 (WRITE):
A pins → Input buffers → B pins
B pins ← Output drivers ← (from A)
A drivers: disabled (hi-Z)
B drivers: enabled
This prevents bus conflicts! ✓
Complete RAM Bus Interface
Full 16-bit RAM Interface with Transceivers:
CPU DATA BUS (16 bits):
───────────────────────
DB[0-7] ◄────────┐
│
DB[8-15] ◄────────┼───┐
│ │
▼ ▼
┌──────────────────┐
│ U5 U6 │
│ 74HC245 74HC245 │
│ [7:0] [15:8] │
│ │
DIR ───►│1 1 │◄─── DIR (shared)
│ │
/OE ────►│11 11 │◄─── /OE (shared)
│ │
│ A side (CPU) │
│ B side (RAM) │
└──────┬──────┬────┘
│ │
▼ ▼
RAM_D[0-7] RAM_D[8-15]
│ │
▼ ▼
┌─────────────────────┐
│ RAM_LO RAM_HI │
│ AS6C4008 AS6C4008 │
│ (U3) (U4) │
└─────────────────────┘
CONTROL SIGNAL GENERATION:
──────────────────────────
From Control Unit:
READ_M signal (active HIGH when reading)
WRITE_M signal (active HIGH when writing)
Bus Transceiver Control:
DIR = WRITE_M
├─ When WRITE_M=1: DIR=1 → CPU to RAM
└─ When WRITE_M=0: DIR=0 → RAM to CPU
/OE = 0 (always enabled for Hack)
└─ Could gate with (READ_M OR WRITE_M)
RAM Control:
/OE = NOT READ_M
├─ When READ_M=1: /OE=0 → RAM outputs enabled
└─ When READ_M=0: /OE=1 → RAM outputs hi-Z
/WE = NOT WRITE_M
├─ When WRITE_M=1: /WE=0 → Write to RAM
└─ When WRITE_M=0: /WE=1 → No write
Control Logic Circuit (using 74HC04 inverters):
────────────────────────────────────────────────
┌────────┐
WRITE_M ─┤ NOT ├──► /WE (to RAM)
└────────┘
74HC04
┌────────┐
READ_M ──┤ NOT ├──► /OE (to RAM)
└────────┘
74HC04
WRITE_M ─────────────► DIR (to 74HC245)
(direct connection!)
Complete Connection Summary:
────────────────────────────
A[14:0] ──────────────────────► RAM Address
READ_M ──┬──► NOT ──► RAM /OE
│
└──► (used for transceiver)
WRITE_M ─┬──► NOT ──► RAM /WE
│
└──────────► Transceiver DIR
DB[15:0] ◄──► 74HC245 ◄──► RAM I/O[15:0]
Memory Timing Diagrams
RAM Read Cycle
Operation: D = M (read from memory)
Preconditions:
──────────────
A = 0x0100 (address already in A register)
RAM[0x0100] = 0xABCD (data stored there)
Control Signals:
────────────────
READ_M = 1 (active)
WRITE_M = 0 (inactive)
Detailed Timing:
────────────────
t=0ns: A Register stable
───────────────────────
A_Out[14:0] = 0x0100
A Register
┌────┐
│ U1 │
│ U2 │
└─┬──┘
│
A_Out = 0x0100
│
▼
t=10ns: Address reaches RAM
────────────────────────────
Propagation through traces
RAM Chips
┌────┐
┌─►│ U3 │
│ │ U4 │
│ └─┬──┘
Addr │
0x0100 │
t=15ns: Control signals asserted
─────────────────────────────────
READ_M = 1 → /OE = 0 (inverter delay)
┌────────┐
READ_M ─┤ NOT ├─► /OE = 0
└────────┘
5ns
t=20ns: RAM address decode starts
──────────────────────────────────
Internal row/column decode
Memory cell selection begins
Inside RAM:
[Address Decoder]
│
▼
[Row Select]
[Col Select]
│
▼
[Memory Cell at 0x0100]
t=65ns: RAM data valid at chip outputs
───────────────────────────────────────
AS6C4008 access time: 55ns typical
RAM I/O pins:
RAM_D[15:0] = 0xABCD ✓
t=70ns: Through transceiver to CPU bus
───────────────────────────────────────
74HC245 propagation: 5ns
┌─────────┐
RAM_D ─┤74HC245 ├─► DB[15:0]
│ DIR=0 │ (B → A)
└─────────┘
5ns
DB[15:0] = 0xABCD ✓
t=80ns: To X input MUX
──────────────────────
Data available for ALU
DB[15:0] ──┐
├─ MUX ──► X_IN
A_Out ─────┘
(A_OR_M=1)
t=100ns: Through ALU
────────────────────
If operation is just "D = M":
ALU might just pass through
ALU_Out = 0xABCD
t=125ns: Clock edge, save to D
──────────────────────────────
LOAD_D pulses
D ← 0xABCD ✓
Complete Timing Diagram:
─────────────────────────
Time: 0 25 50 75 100 125 (ns)
│ │ │ │ │ │
CLK: ───╗ ╔════╗ ╔════
╚═════╝ ╚═════╝
A_Out: ──── 0x0100 ───────────────── (stable)
READ_M: ════════════════════════════ (HIGH)
/OE: ───╗ ╔════════════════════ (LOW - output enable)
╚════╝
15ns
RAM_D: ═══════════╱╲╱╲╲═════════════
└──►│◄─ Valid
65ns
DB[15:0]: ═══════════════╱╲╱╲╲═════════
└──►│◄─ Valid
70ns
ALU_Out: ════════════════════╱╲╱╲╲═══
└─►│ Valid
100ns
LOAD_D: ─────────────────────╗ ╔═══
╚══╝
125ns
D_Out: ──── old ─────────────┴─ 0xABCD
RAM Write Cycle
Operation: M = D (write to memory)
Preconditions:
──────────────
A = 0x0100 (address in A register)
D = 0x1234 (data to write)
Control Signals:
────────────────
READ_M = 0 (inactive)
WRITE_M = 1 (active)
Detailed Timing:
────────────────
t=0ns: A and D registers stable
────────────────────────────────
A_Out = 0x0100 (address)
D_Out = 0x1234 (data)
┌────┐ ┌────┐
│ A │ │ D │
│Reg │ │Reg │
└─┬──┘ └─┬──┘
│ │
0x0100 0x1234
t=10ns: Address reaches RAM
────────────────────────────
A_Out → RAM address pins
RAM
┌────┐
0x0100►│Addr│
└────┘
t=15ns: Data on CPU bus
────────────────────────
D_Out drives data bus
(no transceiver in the way yet)
D_Out ──────────► DB[15:0] = 0x1234
t=20ns: Control signals active
───────────────────────────────
WRITE_M = 1 → /WE = 0 (inverter)
WRITE_M = 1 → DIR = 1 (direct)
┌────────┐
WRITE_M┤ NOT ├─► /WE = 0
└────────┘
WRITE_M─────────────► DIR = 1
t=25ns: Transceiver direction set
──────────────────────────────────
DIR = 1 → A to B (CPU to RAM)
Data flows through 74HC245:
┌─────────┐
DB ───►│74HC245 │──► RAM_D
(A) │ DIR=1 │ (B)
└─────────┘
5ns
RAM_D[15:0] = 0x1234
t=30ns: RAM receives data
─────────────────────────
Data present at RAM I/O pins
/WE is LOW (write enabled)
Address is stable
RAM sees:
├─ Address: 0x0100
├─ Data: 0x1234
└─ /WE: LOW → WRITE!
t=50ns: RAM write begins
────────────────────────
Internal write circuitry activates
Inside RAM:
[Addr] ──► [Row/Col Decode]
│
▼
[Memory Cell 0x0100]
│
[Data] ──► [Write Driver] ──► CELL
│
WRITING!
t=80ns: RAM write complete
───────────────────────────
Data stored in memory cell
RAM[0x0100] = 0x1234 ✓
Note: /WE must be LOW for minimum
write pulse width (typically 50ns)
t=125ns: /WE goes HIGH
───────────────────────
Write cycle ends
RAM latches data
WRITE_M = 0 → /WE = 1
Write is COMPLETE ✓
Complete Timing Diagram:
─────────────────────────
Time: 0 25 50 75 100 125 (ns)
│ │ │ │ │ │
CLK: ───╗ ╔════╗ ╔════
╚═════╝ ╚═════╝
A_Out: ──── 0x0100 ───────────────── (stable)
D_Out: ──── 0x1234 ───────────────── (stable)
WRITE_M: ════════════════════════╗ ╔═
╚════╝
(pulse)
DIR: ════════════════════════╗ ╔═ (1 = write)
╚════╝
/WE: ───╗ ╔══════
╚═══════════════════╝
◄──────80ns────────►
(write pulse)
DB[15:0]: ──── 0x1234 ──────────────── (data from D)
RAM_D: ════════╱╲╱╲╲════════════════ (through transceiver)
└──►│◄─ Valid
25ns
RAM Cell: ──── old ────────────┴─ 0x1234
│
Write complete
Critical Timing Parameters:
───────────────────────────
Address setup before /WE: 10ns (met: 30ns) ✓
Data setup before /WE: 0ns (met: 5ns) ✓
/WE pulse width: 50ns (met: 80ns) ✓
Data hold after /WE: 0ns (met: many) ✓
Write is SAFE at 8 MHz! ✓
Memory Access Through Instructions
Example 1: Load from Memory (D = M)
Complete Instruction Execution:
Assembly: @100
D=M
Machine Code:
@100: 0000000001100100 (A-instruction)
D=M: 1111110000010000 (C-instruction)
▲
This is 'a' bit = 1 (use M not A)
Cycle 1: @100 (Load Address)
─────────────────────────────
t=0ns: PC = address of @100 instruction
ROM[PC] = 0000000001100100
t=70ns: Instruction fetched
Control unit decodes:
├─ A-instruction (bit 15 = 0)
└─ Value = 0x0064 (100 decimal)
t=110ns: Clock edge
LOAD_A pulses
A ← 0x0064
Result: A = 0x0064 (100) ✓
Cycle 2: D=M (Read Memory)
───────────────────────────
Phase 1: Fetch instruction (0-70ns)
─────────────────────────────────────
t=0ns: PC = address of D=M instruction
ROM[PC] = 1111110000010000
t=70ns: Instruction fetched
Control decodes:
├─ C-instruction (bit 15 = 1)
├─ a-bit = 1 (use M, not A)
├─ ALU control = 110000 (pass Y)
├─ dest = 010 (save to D)
└─ jump = 000 (no jump)
Phase 2: Memory read (0-80ns, parallel!)
──────────────────────────────────────────
t=0ns: A = 0x0064
A_Out → RAM address
t=15ns: READ_M = 1 (from control unit)
/OE = 0 (to RAM)
t=65ns: RAM[0x0064] data valid
Assume RAM[100] = 0x0042
t=70ns: Data through transceiver
DB = 0x0042
Phase 3: Through ALU (70-110ns)
────────────────────────────────
t=70ns: Data reaches X input mux
A_OR_M = 1 → select M
X_IN = 0x0042
t=85ns: Through ALU preprocessing
ALU control = 110000
├─ zx=1, nx=1: X becomes !0 = 0xFFFF
├─ zy=0, ny=0: Y unchanged
├─ f=0: AND operation
└─ Result meaningless, but...
Wait, let me recheck the control:
For D=M, we want to pass M through
Actually, "pass Y through" operation:
zx=1, nx=1, zy=0, ny=0, f=0, no=0
└─ This gives: (!X) AND Y
When X=0 (from zx): !0 = 0xFFFF
0xFFFF AND Y = Y
Result = Y = M value ✓
t=100ns: ALU_Out = 0x0042
Phase 4: Save to D (110-125ns)
───────────────────────────────
t=110ns: Clock edge
LOAD_D = 1 (from dest bits)
D ← 0x0042
Result: D = 0x0042 (value from RAM[100]) ✓
Complete Data Flow:
───────────────────
A Register
│
│ 0x0064 (address)
▼
RAM Chip
│
│ RAM[100] = 0x0042
▼
RAM I/O pins
│
│ (through transceiver)
▼
Data Bus
│
│ DB = 0x0042
▼
X Input MUX
│
│ (A_OR_M=1 selects M)
▼
ALU
│
│ (pass through)
▼
D Register
│
▼
D = 0x0042 ✓
Example 2: Store to Memory (M = D)
Complete Instruction Execution:
Assembly: @100
M=D
Preconditions:
──────────────
A = 0x0064 (100) [from previous @100]
D = 0x0042 (66) [value to store]
Cycle 1: M=D (Write Memory)
────────────────────────────
Phase 1: Fetch instruction (0-70ns)
────────────────────────────────────
t=0ns: ROM[PC] → instruction
C-instruction: 1110001100001000
t=70ns: Control unit decodes:
├─ a-bit = 1 (use M)
├─ ALU control = 001100 (pass X, which is A when a=0... wait)
│
Let me recalculate for M=D:
└─ Actually, dest=001 (M), source is irrelevant
ALU just outputs D value
Phase 2: ALU processes (70-100ns)
──────────────────────────────────
We need ALU to output D value:
The ALU receives:
├─ Y input: D = 0x0042
└─ Control to pass Y through
t=100ns: ALU_Out = 0x0042
Phase 3: Write to memory (100-125ns)
─────────────────────────────────────
t=100ns: WRITE_M = 1 (from dest bits)
A_Out = 0x0064 (address)
ALU_Out = 0x0042 (data)
t=105ns: DIR = 1 (CPU to RAM)
/WE = 0 (write enable)
t=110ns: Data reaches RAM
RAM receives:
├─ Address: 0x0064
├─ Data: 0x0042
└─ /WE: LOW
t=125ns: Write completes
RAM[100] ← 0x0042 ✓
Complete Data Flow:
───────────────────
D Register
│
│ D = 0x0042 (data)
▼
ALU
│
│ (passes through)
▼
Data Bus
│
│ DB = 0x0042
▼
Transceiver (DIR=1)
│
│ (CPU → RAM direction)
▼
RAM I/O pins
│
│ Address from A = 0x0064
▼
RAM[100] = 0x0042 ✓
Example 3: Memory Arithmetic (M = M + 1)
Instruction: M = M + 1
Preconditions:
──────────────
A = 0x0064 (address 100)
RAM[100] = 0x0005
This requires:
1. Read RAM[100] → 0x0005
2. Add 1 → 0x0006
3. Write back to RAM[100]
All in ONE instruction cycle!
Phase 1: Read Memory (0-80ns)
──────────────────────────────
t=0ns: A = 0x0064
A_Out → RAM address
t=15ns: READ_M might be asserted
(even though we're also writing!)
t=65ns: RAM[100] = 0x0005
Data valid at RAM outputs
t=70ns: M = 0x0005 through transceiver
To X input MUX
Phase 2: ALU Computation (70-100ns)
────────────────────────────────────
ALU control for "+1":
zx=0, nx=1, zy=1, ny=1, f=1, no=1
Computation:
├─ X = M = 0x0005
├─ Process: !X + !0 = NOT(!X + !0)
├─ Result: 0x0006
└─ (This is the magic of Hack ALU encoding!)
t=100ns: ALU_Out = 0x0006
Phase 3: Write Back (100-125ns)
────────────────────────────────
t=100ns: WRITE_M = 1
A still = 0x0064 (same address!)
t=105ns: ALU_Out drives data bus
DB = 0x0006
t=110ns: Through transceiver to RAM
DIR = 1 (write direction)
/WE = 0 (write enable)
t=125ns: Write completes
RAM[100] ← 0x0006 ✓
Critical Insight:
─────────────────
RAM can READ and WRITE in same cycle!
Timeline:
├─ Early: Read old value (0-70ns)
├─ Middle: Compute new value (70-100ns)
└─ Late: Write new value (100-125ns)
No conflict because timing is carefully separated! ✓
Complete Data Flow (circular!):
────────────────────────────────
A Register
│
│ 0x0064 (address)
│
├──────────────────┐
│ │
▼ │
RAM [READ] │
│ │
│ 0x0005 │
▼ │
X Input MUX │
│ │
▼ │
ALU │
│ │
│ +1 operation │
▼ │
ALU_Out = 0x0006 │
│ │
▼ │
Data Bus │
│ │
▼ │
Transceiver │
│ │
▼ │
RAM [WRITE] ◄──────────┘
│
│ 0x0064 (same address!)
▼
RAM[100] = 0x0006 ✓
Beautiful feedback loop!
PCB Layout for Memory
Physical Placement Strategy
Board Layout (10cm × 15cm):
┌─────────────────────────────────────────┐
│ Y = 0mm │
│ [Power connectors, switches] │
├─────────────────────────────────────────┤
│ Y = 20mm │
│ [Program Counter - 74HC161 × 4] │
│ │ │
│ │ PC[14:0] │
│ ▼ │
│ Y = 40mm │
│ ┌──────────────────────────────────┐ │
│ │ INSTRUCTION ROM (U1, U2) │ │
│ │ 2× AT28C256 (ZIF sockets) │ │
│ │ │ │
│ │ Address ◄─ PC │ │
│ │ Data ──► Instruction Bus │ │
│ └──────────────────────────────────┘ │
│ │ │
│ │ INST[15:0] │
│ ▼ │
│ Y = 80mm │
│ [Control Unit decode logic] │
│ │
├─────────────────────────────────────────┤
│ Y = 100mm │
│ [A Register - 74HC574 × 2] │
│ │ │
│ │ A[14:0] │
│ ▼ │
│ Y = 120mm │
│ ┌──────────────────────────────────┐ │
│ │ DATA RAM (U3, U4) │ │
│ │ 2× AS6C4008 │ │
│ │ │ │
│ │ Address ◄─ A │ │
│ │ Data ◄──► Transceiver │ │
│ └───────┬──────────────────────────┘ │
│ │ │
│ │ RAM_D[15:0] │
│ ▼ │
│ Y = 150mm │
│ ┌──────────────────────────────────┐ │
│ │ BUS TRANSCEIVERS (U5, U6) │ │
│ │ 2× 74HC245 │ │
│ │ │ │
│ │ A side ◄──► CPU Data Bus │ │
│ │ B side ◄──► RAM Data │ │
│ └──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
Critical Trace Routing
GROUP 1: PC to ROM Address (15 traces)
───────────────────────────────────────
From: PC (U6-U9 outputs)
To: ROM (U1, U2 address pins)
Routing:
├─ Length: 40-60mm
├─ Width: 0.4mm (15 mil)
├─ Spacing: 0.3mm
├─ Layer: Top (F.Cu)
├─ Keep parallel
├─ Bus route as group
└─ No vias
Why critical:
├─ These traces determine instruction fetch speed
├─ Must be fast (setup time for ROM)
└─ Keep lengths matched within 5mm
GROUP 2: A Register to RAM Address (15 traces)
───────────────────────────────────────────────
From: A Register (U1, U2 outputs)
To: RAM (U3, U4 address pins)
Routing:
├─ Length: 50-70mm
├─ Width: 0.4mm
├─ Spacing: 0.3mm
├─ Layer: Top (F.Cu)
├─ Parallel routing
└─ Keep away from data buses
Why critical:
├─ RAM access speed depends on address stability
├─ These traces active every data access
└─ Route carefully to avoid crosstalk
GROUP 3: ROM to Instruction Bus (16 traces)
────────────────────────────────────────────
From: ROM (U1, U2 data pins)
To: Control Unit
Routing:
├─ Length: 60-80mm
├─ Width: 0.4mm
├─ Layer: Top for most, Bottom for crossings
├─ May need vias for complex routing
└─ Critical timing path!
Why critical:
├─ Instruction must be stable before decode
├─ Sets up all other control signals
└─ Bottleneck for instruction fetch
GROUP 4: RAM to Transceiver (16 traces)
────────────────────────────────────────
From: RAM (U3, U4 I/O pins)
To: Transceiver (U5, U6 B-side)
Routing:
├─ Length: 30-50mm (keep SHORT!)
├─ Width: 0.4mm
├─ Spacing: 0.4mm (wider for isolation)
├─ Layer: Top preferred
└─ Bidirectional - route carefully!
Why critical:
├─ Bidirectional signals (tricky!)
├─ Fast switching required
├─ Short traces reduce reflections
└─ Minimize stub lengths
GROUP 5: Transceiver to CPU Bus (16 traces)
────────────────────────────────────────────
From: Transceiver (U5, U6 A-side)
To: Main CPU data bus
Routing:
├─ Length: variable (bus connection)
├─ Width: 0.4mm
├─ Fan-out to multiple destinations
├─ Star topology if possible
└─ Use bottom layer for longer runs
Why critical:
├─ Main data highway
├─ Connects to multiple destinations
├─ Bus loading considerations
└─ Must handle bidirectional flow
Decoupling and Power Distribution
Memory ICs Need Stable Power:
AT28C256 (ROM):
───────────────
Current draw:
├─ Active: 30mA (during access)
├─ Standby: 100µA (when /CE high)
└─ Switching spikes: 50mA peak
Decoupling:
├─ 0.1µF ceramic (close to VCC pin)
├─ Place within 5mm of pin 28
└─ Connect directly to power plane
AS6C4008 (RAM):
───────────────
Current draw:
├─ Active: 50mA (during access)
├─ Standby: 1µA (when /CE high)
├─ Write: 70mA peak
└─ Read: 40mA typical
Decoupling:
├─ 0.1µF ceramic (close to VCC pin)
├─ 10µF tantalum (for write spikes)
├─ Place ceramic within 5mm
└─ Tantalum within 20mm
74HC245 (Transceiver):
──────────────────────
Current draw:
├─ Standby: <1mA
├─ Active: 5mA per gate
└─ Peak: 40mA (all 8 bits switching)
Decoupling:
├─ 0.1µF ceramic
└─ Standard 74HC practice
Power Plane Strategy:
──────────────────────
Bottom Layer:
┌────────────────────────────────┐
│ VCC Plane (partial fill) │
│ ┌──────────────────────────┐ │
│ │ ███████████████████████ │ │
│ │ ██ Under RAM chips ████ │ │
│ │ ███████████████████████ │ │
│ └──────────────────────────┘ │
│ │
│ GND Plane (large fill) │
│ ████████████████████████████ │
│ ████████████████████████████ │
│ ████████████████████████████ │
└────────────────────────────────┘
Via stitching:
├─ Every 10mm around perimeter
├─ Connect top ground to plane
├─ Multiple vias per IC
└─ Low inductance path
Summary: Memory Interface Philosophy
Key Design Principles
1. Separation of Concerns
Instruction Memory (ROM):
└─ Read-only, addressed by PC
└─ No bidirectional complexity
Data Memory (RAM):
└─ Read/write, addressed by A
└─ Bidirectional handled by transceiver
2. Timing is Everything
All operations must fit in clock cycle:
├─ ROM access: 70ns
├─ RAM read: 70ns
├─ RAM write: 80ns (includes setup)
└─ Total cycle: 125ns @ 8MHz ✓
3. Bus Management
Prevent conflicts with:
├─ Transceivers (74HC245)
├─ Direction control
├─ Careful timing
└─ Tri-state outputs
4. Simplicity Through Harvard
Separate memories mean:
├─ No bus arbitration needed
├─ Simultaneous fetch + data access
├─ Simpler control logic
└─ Faster execution
The Beautiful Integration
The memory system connects everything:
Program Counter ──► ROM ──► Instructions ──► Control Unit
│
▼
Control Signals
│
┌────────────────────────┼─────────┐
│ │ │
▼ ▼ ▼
A Register ──► RAM ◄─── Transceiver ◄──── Data Bus ◄─── ALU
Complete computational feedback loop! ✓
The memory interface is the foundation - it's where programs live and where data resides. Everything else in the computer exists to move data between memory and the ALU!
Would you like me to explain:
- How to optimize memory access for speed?
- Adding memory-mapped I/O (screen, keyboard)?
- The complete instruction fetch-execute cycle with all connections?
- How to expand memory beyond 32K?