6502 Part 3 — Adding ROM
6502 Part 3 — Adding ROM
Now it’s time to let the 6502 actually run code from ROM.
In this part you’ll:
-
Add a 32KB ROM (AT28C256).
-
Use a 74LS00 NAND chip for simple address decoding so you’re ready for RAM + VIA later.
-
Capture the bus with a logic analyser to prove the CPU is fetching and executing your program.
No inputs, outputs, or RAM yet just a clean, minimal “CPU + ROM + decode” core.
You can watch the video of this build followed by a description and analysis of what has been built. The video is at the end of this post.
1. The Plan
We’ll use a very simple memory map that matches what you’ll want later:
-
$0000–7FFF→ future RAM -
$8000–FFFF→ ROM
Right now you only populate the ROM, but the 74LS00 will generate both selects so RAM drops in easily in Part 4.
Key points:
-
The 6502 reset vector lives at
$FFFC–FFFD(top of memory), so that must be in ROM. -
We’ll map a 32KB ROM into
$8000–FFFF. -
That means:
-
CPU address
$8000→ ROM offset$0000 -
CPU address
$FFFF→ ROM offset$7FFF -
Reset vector at CPU
$FFFC→ ROM offset$7FFC
-
So we burn our program starting at ROM offset $0000, and place the vectors at the very end of the chip.
2. Parts You’ll Use
From your parts stash:
-
MOS 6502 CPU
-
AT28C256 (32KB EEPROM)
-
74LS00 (quad 2-input NAND)
-
Existing:
-
5V regulated supply
-
Clock from Part 2 (555 + 74LS04, or similar)
-
Proper /RESET circuit from Part 2
-
-
Hook-up wire, breadboard, decoupling caps
-
Logic analyser
I’ll refer to signals by name (A0, D0, etc.). Always double-check exact pin numbers against datasheets for your specific 6502 and ROM.
3. Wiring the ROM to the 6502
3.1 Power & basics
-
6502:
-
VCC → +5V
-
GND pins → 0V
-
-
AT28C256:
-
VCC → +5V
-
GND → 0V
-
-
Add 100nF decoupling caps close to both chips between VCC and GND.
(You should already have clock and reset wired from Part 2.)
3.2 Address bus
Connect the lower 15 address lines 1:1:
-
6502 A0–A14 → AT28C256 A0–A14
We’ll use A15 only for decoding with the 74LS00 (not directly into the ROM).
3.3 Data bus
-
6502 D0–D7 ↔ AT28C256 D0–D7
Direct bidirectional bus:
-
ROM drives data during reads
-
6502 drives during writes (but /WE on ROM is disabled so it ignores them)
3.4 ROM control pins
On AT28C256 the following pin connections are required:
-
/WE→ +5V (disabled, read-only) -
/CE(chip enable) → from 74LS00 (our ROM select) -
/OE(output enable):-
Simple & safe option here: tie
/OEto GND (always enabled) -
We rely on
/CE+ address decode so ROM only drives bus in its address range. -
This is fine at low clock speeds with only one bus device.
-
3.5 CPU control pins
/IRQ→ +5V/NMI→ +5V/RDY→ +5V/RES → Connect to the reset circuit built in part 2.
/ϕ0 → Connect to the clock circuit built in part 2.
4. Using 74LS00 for Address Decode
4.1 ROM chip enable
We will use Gate 1 to make the ROM Chip Enable (CE) from A15
Inputs: A15, A15
Output: /ROMCE
Because it’s NAND:
-
If A15 = 1 → output = 0 → ROM enabled
-
If A15 = 0 → output = 1 → ROM disabled
Connect:
-
74LS00 gate 1 inputs → 6502 A15
-
74LS00 gate 1 output → AT28C256
/CE
Now the ROM occupies $8000–FFFF cleanly.
5. Tiny Test Program for ROM-Only System
We want a program that:
-
Uses no stack, no zero-page writes.
-
Just loops forever doing simple instructions.
-
Makes the address & data behaviour obvious on a logic analyser.
Target runtime start address: $8000.
Example program (shown as bytes to burn):
At CPU address $8000 (ROM offset $0000):
Byte sequence at ROM offset $0000:
78 D8 A2 00 E8 EA EA 4C 05 80
Now set the vectors at the top of ROM.
Because ROM is mapped $8000–FFFF:
-
CPU
$FFFA→ ROM$7FFA -
CPU
$FFFC→ ROM$7FFC
Write these at the end of the ROM image:
-
NMI vector ($FFFA–FFFB): point to $8000 →
00 80 -
RESET vector ($FFFC–FFFD): point to $8000 →
00 80 -
IRQ/BRK vector ($FFFE–FFFF): point to $8000 →
00 80
So last 6 bytes of ROM (offsets $7FFA–7FFF) are:
00 80 00 80 00 80
If you’re building the binary manually:
-
Fill 32KB with
$EA(NOP) by default. -
Insert the program at offset
$0000. -
Patch vectors at
$7FFAonwards as above.
Burn that into the AT28C256.
6. Compile the program
7. Burn the rom
8. Hooking Up the Logic Analyser
To prove its working without RAM or I/O, we can watch the bus using a logic analyser.
Clip the analyser onto:
-
A15..A8 (top address lines) — especially A15, because it shows ROM area.
-
A7..A0 (optional if you’ve got channels)
-
D7..D0 (data bus if you’ve got channels)
-
Φ0 (clock)
What you should see
After power-on and release of /RESET:
-
Vector fetch:
-
CPU reads from
$FFFCand$FFFD. -
Data bus should show
00then80. -
Tells you it’s loading start address
$8000.
-
-
Jump to $8000:
-
Address bus jumps to
$8000. -
You’ll see sequential fetches:
-
$8000→78(SEI) -
$8001→D8(CLD) -
$8002→A2 -
$8003→00 -
$8004→E8 -
$8005→EA -
$8006→EA -
$8007→4C -
$8008→05 -
$8009→80
-
-
Then it loops back to
$8005/$8004region depending how you step through the JMP. -
On your analyser this looks like a repeating pattern of addresses and opcodes: a stable, deterministic loop.
-
-
A15 behaviour:
-
During the loop, A15 stays high (1), confirming everything’s executing in the ROM window
$8000–FFFF. -
That’s your 74LS00 decode doing its job.
-
If you see:
-
Correct vector reads,
-
Correct opcodes on D0–D7,
-
Repeating address pattern in the
$8000range,
then your 6502 + ROM + decode core is working.
9. My Result
On a 6502:
-
The address bus is valid when ϕ0 is high.
-
So we only trust rows where
clock = 1.
Each CSV row (simplified):
We interpret:
-
a15 = bit 15
-
a14 = bit 14
-
a1 = bit 1
-
A0 is missing, so the least significant bit is unknown. That’s fine for our purposes.
We only care about:
-
“Is this in
$8000–FFFF?” (A15 = 1) -
“Is this at the very top of memory?” (all high)
-
“Are we staying in the ROM window once running?”
Here are a few key moments from the actual CSV
Execution in ROM at $8000With A15=1 and all lower bits 0, that corresponds to $8000 (modulo A0).
This is exactly where our Reset routine lives: the CPU is fetching from the ROM window.
Shortly after reset, we see samples where ϕ2 is high and all address bits a15..a1 are 1:
That pattern is $FFFE/$FFFF (again, A0 unknown in this capture).
This is exactly where the 6502 expects the vectors:
-
$FFFC–FFFDfor RESET -
$FFFE–FFFFfor IRQ/BRK
Seeing the CPU hit the very top of memory right after reset tells us it’s fetching the reset vector from our ROM, as designed.
Jumping into the ROM window and loopingAfter the vector fetch, the trace shows repeated accesses with:
-
clock=1 -
a15=1consistently
So the CPU is:
-
Staying in the
$8000–FFFFregion (A15 high → ROM enabled) -
Cycling through a small set of addresses in that region over and over
That is exactly what our test program does:
-
Execute from
$8000 -
Run a tiny loop
-
Never leave the ROM space
Even without seeing data or A0, the pattern:
-
Top-of-memory accesses right after reset
-
Followed by stable execution in the
$8000region
is strong evidence that:
-
The ROM is correctly mapped to
$8000–FFFF -
The vectors are correct
-
The CPU is happily fetching and executing your code from ROM
If I was able to extend the capture later to include D0–D7 and A0, you’ll be able to see the exact opcodes:
-
$FFFC → 00 -
$FFFD → 80 -
$8000 → 78 -
$8001 → D8 -
etc.
For Part 3, the address only capture is enough to prove the core is alive.
10. Video
11. Why This Sets You Up for Part 4
By the end of Part 3 you’ve:
-
A proper ROM mapped into the top of memory.
-
A 74LS00 already generating:
-
/ROMCEfor the AT28C256.
-
- Verified CPU execution with a logic analyser instead of “hoping”.
Next step (Part 4):
-
Drop in your 32KB SRAM.
-
Use the same 74LS00 to add address decoding for a RAM chip.








Comments
Post a Comment