You've already read the Address Decoding page of the 6502 Primer, so you have the basics of what you need to understand how to handle reading/writing multiple devices. Let's do a quick summary of what's going on with address decoding.
Roughly, when the CPU intends to read an address, it places it on the its
A0-15 pins, sets
R/WB high, waits around for a bit, and then reads the data bus for the value. It neither knows nor cares what what put the data on the bus or how it was selected. In particular, it doesn't know or care what addresses anything else saw; it just knows what it put out on its own address pins.
A Simple Configuration
In a really simple configuration of 32 KB RAM and 32 KB ROM, one could set up the decoding as follows:
A0-14 connected directly to RAM
A0-14 and ROM
A15 connected to:
/CS (chip select) pin, and
- an inverter, which in turn is connected to:
/CS pin, which sees low when
A15 is high and high when
A15 is low.
When the CPU reads address
$7FFF, both the RAM and ROM will see
$7FFF on their address pins,
A0-14. (Neither has an
A15 pin because they're only 32K devices.) But
/CS will be low for the RAM because
A15 is low, causing it to read address
$7FFF and put its value on the data bus, and
/CS will be high for the ROM (because it gets inverted
A15) which means the ROM will do nothing.
When CPU reads
$FFFF, again both the RAM and ROM will see
$7FFF on their address pins (because neither has an
A15 pin; they see only the bottom 15 bits of the address). But in this case
/CS will be high for the RAM (because
A15 is high) and low for the ROM (because inverted
A15 is low) and this time the RAM will ignore the access and the ROM, being selected, will place the contents of its location
$7FFF on the data bus.
The key takeaway here is that each device has its own address and select pins, and what they see there and how they react to it depends on how the address decoding logic is set up. The devices don't necessarily see the actual address on the CPU's address bus: in the ROM read example above it saw address
$7FFF even though the CPU's address bus had
$FFFF (an address that the ROM doesn't have) on it.
Now let's say you have two 32K RAM chips and a 32K ROM chip. Since this is 96K of memory, it can't all be accessed directly via setting
A0-15, since that gives you only 64K addresses. But what you could do is have another device that stores a bit determining whether the top half of memory will access the RAM or ROM. (For simplicity we'll leave out here the details of exactly how you talk to that device.)
Calling that the 'bank bit', you could then set up your decoding as follows:
A0-14 connected directly to
A0-14 of RAM0, ROM and RAM1.
- directly to
/CS (chip select) of RAM0.
- to an inverter whose output is connected to:
- bank bit device
/CS. That in turn has separate connections to:
/CS, held high when the bank bit device is not selected, and
/CS, also held high when the bank bit device is not selected.
When the bank bit device is selected:
- If the bank bit is 0, it lowers ROM
/CS, selecting it, and leaves RAM1
- If the bank bit is 1, it leaves ROM
/CS high and lowers RAM1
/CS, selecting it.
Now if the system starts on reset with the bank bit set to 0, reads will work just as in the simple configuration above. But if your code sets the bank bit to 1 (though magical undescribed means in this example, but via more decoding and an I/O address in the real world), the CPU will now be reading RAM1 in the higher addresses. In this way you can switch back and forth between ROM and RAM1 under CPU control whenever you like.
Obviously this general idea can be expanded to any degree you like: given any arbitrary number of 32K memories you can use
A0-14 as the address for each device, and
A15 and other information external to the address bus to decide which device gets accessed. And of course you are not restricted to 32K banks: you can do this remapping in arbitrarily small chunks, to the point where a sufficiently complex device (a full memory management unit) might translate all of
A0-A15 from the CPU to any arbitrary addresses for other devices that vary depending on its current settings. (E.g., at one point in time a CPU read of
$FFFF might read
$00FF of RAM7, and at another point read
$1234 of RAM19.)
So that's address decoding: mapping arbitrary addreses on the CPU's
A0-A15 pins to any other arbitrary addresses on any other devices.
Address decoding logic doesn't work instantly; it takes time for the devices doing the address mapping (no matter how simple) detect the signals from the 6502, translate them, and set up the desired output signals. For example, the inverter we used above might be a 74LS04 which takes up to 5 ns to produce the proper inverted output after its input changes.
All of the remapping has to be done quickly enough that the device being accessed can still have the data on the data bus by the time the 6502 is going to read it. The Visual Guide to 65xx CPU Timing describes this in a lot more detail; I find the section "How the CPU Interacts With the Outside World" and its first diagram very helpful.
At lower speeds and with simple address decoding there are usually no problems, but at 4 MHz (which you mentioned you were aiming for) the time it takes to do the decoding could well become a problem, especially if you're using the rising edge of φ2 to enable things. You'll notice in the diagram I mentioned above that at 5 MHz you have only 90 ns between φ2 going high and when your devices must have their data stable on the data bus: if you're using a 74LS610 it may need 40 ns to do its thing, leaving not a lot of time for your device to read the address and set the data values. (That exact number may be wrong because I'm probably not reading the data sheet properly, but but you get the general idea. All the access numbers for that part are in that range.)
So think about how much decoding you really need! There's a lot of good discussion of both decoding and timing in this forum thread.