The CPU is fetching and executing code from ROM. The last piece is
RAM. Without it, most of the 6502 instruction set is crippled. The
stack — the mechanism the CPU uses to call subroutines and return
from them — lives at 01FF in RAM. The zero page, 00FF,
is the fastest addressing mode on the 6502. JSR, RTS, PHA,
PLA, and any instruction that writes to memory all require RAM.
With ROM alone you have a read-only machine that can loop, branch,
and compare, but nothing else.
Address decode for RAM
The RAM decode is the mirror of the ROM decode. The 62256 maps to the lower half of the address space, 7FFF. A15 is 0 for all addresses in that range. The 62256's CE# pin is active low and it needs to go low when A15 is low — which means CE# can connect directly to A15. No gate required. When A15 is 0, CE# is 0, and the RAM is selected. When A15 is 1, CE# is 1, and the RAM is deselected.
The RAM also needs OE# (Output Enable) and WE# (Write Enable) signals. These come from the CPU's RWB pin (pin 34 — read/write bar). When the CPU is reading (RWB = 1), OE# must go low to make the RAM drive the data bus. That is another inverter: connect both inputs of a second NAND gate on the 74HC00 to CPU RWB, and wire the gate output to RAM OE#.
When the CPU is writing (RWB = 0), WE# must go low to latch the byte into RAM. Wire CPU RWB directly to RAM WE#. When RWB is 0 the CPU is writing; WE# = 0 selects the write mode. When RWB is 1 the CPU is reading; WE# = 1 disables write mode.
Wiring the RAM
Seat the 62256 on the second breadboard beside the AT28C256. They share the same address bus and data bus connections, so most of the wiring is a matter of tapping into the existing lines. Add a 100nF decoupling capacitor next to the 62256.
Connect the address lines. The 62256 has the same 15-address-pin footprint as the AT28C256, and the same pins carry the same signals:
RAM A0–A14 -> CPU A0–A14 (same as ROM A0–A14)Connect the data bus:
RAM D0–D7 -> CPU D0–D7 (same lines as ROM D0–D7)Both chips share the address and data buses. Only CE# determines which one is active at any given moment. Because the ROM and RAM occupy non-overlapping address ranges, they will never both assert CE# at the same time.
Connect the control pins:
RAM CE# -> CPU pin 25 (A15) directly
RAM OE# -> second NAND gate output (74HC00, both inputs tied to CPU pin 34 / RWB)
RAM WE# -> CPU pin 34 (RWB) directlyModified test program
Update ~/f03b-practice/test.s to write a known value to RAM and
read it back. The result — success or failure — will be visible on
the address LEDs as two different halting addresses:
.org $8000
reset:
lda #$aa
sta $0000
lda $0000
cmp #$aa
beq success
fail:
jmp fail
success:
jmp success
.org $fffc
.word reset
.word resetThe program writes 0000, reads it back into the
accumulator, and compares it with $AA. beq success branches to the
success loop if the comparison matched (the zero flag is set). If
it did not match, execution falls through to the fail loop.
fail and success are both infinite loops — jmp fail and
jmp success — but they sit at different addresses in the ROM. The
address lines will show one pattern if the CPU is halted at fail
and a different pattern if it is halted at success. That difference
is visible on the LEDs.
Assemble and burn:
vasm6502_oldstyle -Fbin -dotdir test.s -o test.bin
minipro -p AT28C256 -w test.binReplace the AT28C256 in the circuit with the freshly programmed chip.
Verification
Apply power. The CPU resets, executes the test program, and halts in one of the two loops.
The success loop is at a higher ROM offset than fail because it
appears later in the source. After assembling, look at the map:
jmp fail assembles to $8000 + a few bytes, and jmp success
assembles to a slightly higher address. The exact addresses depend on
the instruction lengths of the preceding code, but the two patterns
on A0–A7 will be distinct.
To interpret the result: if the LED pattern is completely stable (no flickering — just a fixed combination of lit and unlit), the CPU has halted in one of the two loops. If it is cycling rapidly through many patterns, the CPU is not executing the expected code — check the wiring before reading the LEDs.
To determine which loop: connect an LED to A15 as a sanity check
(it should be lit, confirming the CPU is in ROM at all times). Then
compare the A0–A7 pattern against the address printed by vasm in
its listing output. Run the assembler with a listing flag to see the
addresses:
vasm6502_oldstyle -Fbin -dotdir -L test.lst test.s -o test.binThe listing file test.lst will show the address of every
instruction. Find the address of jmp fail and jmp success,
convert the low byte to binary, and compare with the LED pattern.
If the CPU halts at success, RAM read and write are both working.
If it halts at fail, the comparison failed — the value read back
from RAM did not match what was written. The most likely cause is
a wiring problem on the data bus between the CPU and the RAM, or on
OE# or WE#. Check that RAM OE# is driven by the NAND gate output,
not tied directly to GND (which would cause bus contention when the
ROM and RAM try to drive the data bus simultaneously).
Bus contention
One failure mode to be aware of when both ROM and RAM are wired: bus contention. This happens when both chips try to drive the data bus at the same moment. The address decode must guarantee that exactly one of ROM CE# and RAM CE# is low at any given time. With the circuit as described — ROM CE# = NOT(A15), RAM CE# = A15 — they are complements of each other. When A15 is 1, ROM is selected, RAM is not. When A15 is 0, RAM is selected, ROM is not. They cannot both be selected simultaneously.
If you accidentally wire RAM CE# to NOT(A15) instead of A15 directly, both chips will be selected for every address, and both will try to drive the data bus. The result is a logic conflict: one chip drives a pin high while the other drives it low, and the chips fight each other through near-zero impedance. This draws excessive current and will heat the chips. If anything on the board gets warm within the first few seconds of power-up, remove power and check the CE# wiring.