Yes — and this is how almost all accurate emulators are written*; see documents such as 64doc.txt. It's not much more complicated than simple memory access counting though — the 6502 will perform a memory access every single cycle, it can usually get a meaningful result within the remainder of the cycle after an access (i.e. I'm handwaving a little to avoid a discussion of what's pipelined and what isn't; see the documentation).
So e.g. for ADC #54
the processor must (i) read the opcode; (ii) read the operand. That's two cycles.
For ADC ($32), Y
that's:
- read opcode
- read operand
- read from $32 to get low byte of address
- read from $33 to get high byte of address, add Y to low byte of address
- read from (high byte of address):(low byte of address + Y), as there's only been time to perform the low-byte calculation
- oh, wait, if there was carry then the last result was wrong, better read again. If not then, great, everything's fine, don't bother with this cycle.
So it's either 5 or 6 cycles.
You can always emulate the memory access more as a step-by-step timed thing, and perform the actual operation as an orthogonal step. It's also easy to use the same logic for read, write or read-modify-write: reads and writes have the same timing but do a different memory access at the end, tead-modify-writes all write the read value back for a cycle while working out the real result, then write the real result.
*) because performing all the memory accesses simultaneously, not including any that are redundant, then warping time forward a little is absolutely nothing like the real hardware. And it'll trip you up as soon as a memory access is to anything with an independent concept of time — a timer or anything that might generate interrupts, or just RAM itself if the machine scans RAM for its video output; never mind that it requires you to add special cases around instructions like CLI
and SEI
**. Emulators needn't be structured like they were in the 1990s any more.
**) IRQ status is sampled on the penultimate cycle of every operation. CLI
and SEI
adjust the bit during the final cycle. So even if an interrupt is pending then a CLI
won't result in an interrupt until after the instruction after the CLI
. Which could itself be an SEI
. So a CLI
/SEI
pair while an interrupt is pending should result in a trip to the interrupt handler after the SEI
has executed, with the interrupt flag set. This happens naturally if you're emulating the cycle-by-cycle behaviour of a 6502, tends to be a huge hack if you're working operation-by-operation and time warping. Or, much more likely, such emulators just plain get the behaviour wrong.