6502 Part 3 — Adding ROM

6502 Part 3 — Adding ROM

In Part 1 we powered up the 6502 and watched it running a NOP test with just the CPU and LEDs on the address bus.
In Part 2 we created a reset circuit and a simple 555 timer as a clock for the CPU.

Now it’s time to let the 6502 actually run code from ROM.

In this part you’ll:

  1. Add a 32KB ROM (AT28C256).

  2. Use a 74LS00 NAND chip for simple address decoding so you’re ready for RAM + VIA later.

  3. 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–FFFFROM

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

Our connections can be seen in the circuit diagram below.

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 /OE to 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

On the 6502 the following pin connections are required:
  • /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):

; 6502 ROM Test Program for $8000–$FFFF ; For 32KB ROM (AT28C256) mapped at $8000 ; Simple loop for logic analyser testing .org $8000 Reset: SEI ; Disable IRQs CLD ; Clear decimal mode LDX #$00 ; X = 0 Loop: INX ; Increment X every loop NOP NOP JMP Loop ; Tight infinite loop ; --- Vectors at top of memory ($FFFA–$FFFF) --- .org $FFFA .word Reset ; NMI vector → start of our code .word Reset ; RESET vector → start of our code .word Reset ; IRQ/BRK vector → start of our code

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:

  1. Fill 32KB with $EA (NOP) by default.

  2. Insert the program at offset $0000.

  3. Patch vectors at $7FFA onwards as above.

Burn that into the AT28C256.

6. Compile the program

To compile the program in windows, open up the command prompt (not powershell)
and goto the directory where the program is stored.
The type the following command: -
vasm6502_oldstyle -Fbin -dotdir rom_test.asm -o rom_full.bin
This is the program now compiled.

7. Burn the rom

To burn the rom I am using a TL866II Plus Universal Programmer EEPROM Flash with Xgpro software.
First we want to ensure that the rom is erased so we don't have random 1s and 0s in it.
Then we need to load the program into memory
Once loaded we then need to burn the program onto the ROM chip

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)

I just connected the clock and address lines A15 to A1 as that's all the connections I could make. This is shown below, a bit of a mess, but it works.

What you should see

After power-on and release of /RESET:

  1. Vector fetch:

    • CPU reads from $FFFC and $FFFD.

    • Data bus should show 00 then 80.

    • Tells you it’s loading start address $8000.

  2. Jump to $8000:

    • Address bus jumps to $8000.

    • You’ll see sequential fetches:

      • $800078 (SEI)

      • $8001D8 (CLD)

      • $8002A2

      • $800300

      • $8004E8

      • $8005EA

      • $8006EA

      • $80074C

      • $800805

      • $800980

    • Then it loops back to $8005/$8004 region depending how you step through the JMP.

    • On your analyser this looks like a repeating pattern of addresses and opcodes: a stable, deterministic loop.

  3. 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 $8000 range,

then your 6502 + ROM + decode core is working.

9. My Result

I have include the cvs file of the logic analyser output here. It is quite large, but from that you can see the following results

Hence why I put it into a CVS file to look at it more closely.

On a 6502:

  • The address bus is valid when ϕ0 is high.

  • So we only trust rows where clock = 1.

Each CSV row (simplified):

Time(s) clock a15 a14 ... a1

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 $8000
0.96888 clock=1 a15=1, others=0

With 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.

Vector fetch at the top of memory

Shortly after reset, we see samples where ϕ2 is high and all address bits a15..a1 are 1:

... clock=1 a15=1 a14=1 ... a1=1

That pattern is $FFFE/$FFFF (again, A0 unknown in this capture).
This is exactly where the 6502 expects the vectors:

  • $FFFC–FFFD for RESET

  • $FFFE–FFFF for 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 looping

After the vector fetch, the trace shows repeated accesses with:

  • clock=1

  • a15=1 consistently

So the CPU is:

  • Staying in the $8000–FFFF region (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 $8000 region

is strong evidence that:

  1. The ROM is correctly mapped to $8000–FFFF

  2. The vectors are correct

  3. 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

This is a video of the entire process which is on my YouTube site.

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:

    • /ROMCE for 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

Popular posts from this blog

Math Behind Logic Gates

6502 - Part 2 Reset and Clock Circuit

Building a 6502 NOP Test