summaryrefslogtreecommitdiffstatshomepage
path: root/docs
diff options
context:
space:
mode:
author Olivier Galibert <galibert@pobox.com>2012-11-06 16:27:39 +0000
committer Olivier Galibert <galibert@pobox.com>2012-11-06 16:27:39 +0000
commitfac1ca581b31525279ead471282c1eed1790daab (patch)
tree3870b4c93b8c72f54062dbe68a0cbd2750c9ec26 /docs
parent54b3c7a31dd607b734e55d9ca96254f0801abb6b (diff)
6502: Rewrite modern, cycle-exact and interruptible [O. Galibert]
Diffstat (limited to 'docs')
-rw-r--r--docs/m6502.txt470
1 files changed, 470 insertions, 0 deletions
diff --git a/docs/m6502.txt b/docs/m6502.txt
index e69de29bb2d..6b010e64ab0 100644
--- a/docs/m6502.txt
+++ b/docs/m6502.txt
@@ -0,0 +1,470 @@
+The new 6502 family implementation
+----------------------------------
+
+ 1. Introduction
+
+The new 6502 family implementation has been created to reach
+sub-instruction accuracy in observable behaviour. It is designed with
+3 goals in mind:
+
+- every bus cycle must happen at the exact time it would happen in a
+ real cpu, and every access the real cpu does is done
+
+- instructions can be interrupted at any time in the middle then
+ restarted at that point transparently
+
+- instructions can be interrupted even from within a memory handler
+ for bus contention/wait states emulation purposes
+
+Point 1 has been ensured through bisimulation with the gate-level
+simulation perfect6502. Point 2 has been ensured structurally through
+a code generator which will be explained in section 8. Point 3 is not
+done yet due to lack of support on the memory subsystem side, but
+section 9 shows how it will be handled.
+
+
+ 2. The 6502 family
+
+The MOS 6502 family has been large and productive. A large number of
+variants exist, varying on bus sizes, i/o, and even opcodes. Some
+offshots (g65c816, hu6280) even exist that live elsewhere in the mame
+tree. The final class hierarchy is this:
+
+ 6502
+ |
+ +------+--------+--+--+-------+-------+
+ | | | | | |
+ 6510 deco16 6504 6509 n2a03 65c02
+ | |
+ +-----+-----+ r65c02
+ | | | |
+6510t 7501 8502 +---+---+
+ | |
+ 65ce02 65sc02
+ |
+ 4510
+
+The 6510 adds an up to 8 bits i/o port, with the 6510t, 7501 and 8502
+being software-identical variants with different pin count (hence i/o
+count), die process (nmos, hnmos, etc) and clock support.
+
+The deco16 is a Deco variant with a small number of not really understood
+additional instructions and some i/o.
+
+The 6504 is a pin and address-bus reduced version.
+
+The 6509 adds internal support for paging.
+
+The n2a03 is the nes variant with the D flag disabled and sound
+functionality integrated.
+
+The 65c02 is the very first cmos variant with some additional
+instructions, some fixes, and most of the undocumented instructions
+turned into nops. The R (rockwell, but eventually produced by wdc too
+among others) variant adds a number of bitwise instructions and also
+stp and wai. The sc variant, used by the Lynx portable console, looks
+identical to the R variant. The 's' probably indicates a
+static-ram-cell process allowing full dc-to-max clock control.
+
+The 65ce02 is the final evolution of the ISA in this hierarchy, with
+additional instructions, registers, and removals of a lot of dummy
+accesses that slowed the original 6502 down by at least 25%. The 4510
+is a 65ce02 with integrated mmu and gpio support.
+
+
+ 3. Usage of the classes
+
+All the cpus are standard modern cpu devices, with all the normal
+interaction with the device infrastructure. To include one of these
+cpu in your driver you need to include "cpu/m6502/<cpu>.h" and then do
+a MCFG_CPU_ADD("tag", <CPU>, clock).
+
+6510 variants port i/o callbacks are setup through:
+ MCFG_<CPU>_PORT_CALLBACKS(READ8(type, read_method), WRITE8(type, write_method))
+
+And the pullup and floating lines mask is given through:
+ MCFG_<CPU>_PORT_PULLS(pullups, floating)
+
+In order to see all bus accesses on the memory handlers it is possible
+to disable accesses through the direct map (at a cpu cost, of course)
+with:
+ MCFG_M6502_DISABLE_DIRECT()
+
+In that case, transparent decryption support is also disabled,
+everything goes through normal memory-map read/write calls. The state
+of the sync line is given by the cpu method get_sync(), making
+implementing the decryption in the handler possible.
+
+In a final addition, the cpu method get_cycle() gives the current time
+in cycles since the start of the machine from the point of view of the
+cpu. Or, in other words, what is usually called the cycle number for
+the cpu when somebody talks about bus contention or wait states. The
+call is designed to be fast (no system-wide sync, usually no call to
+machine.time()) and is precise. Cycle number for every access is
+exact at the sub-instruction level.
+
+The 4510 special nomap line is accessible through get_nomap().
+
+Other than these specifics, these are perfectly normal cpu classes.
+
+
+ 4. General structure of the emulations
+
+Each variant is emulated through up to 4 files:
+- <cpu>.h = header for the cpu class
+- <cpu>.c = implementation of most of the cpu class
+- d<cpu>.lst = dispatch table for the cpu
+- o<cpu>.lst = opcode implementation code for the cpu
+
+The last two are optional. They're used to generate a <cpu>.inc file
+in the object directory which is included by the .c file.
+
+At a minimum, the class must include a constructor and an enum picking
+up the correct input line ids. See m65sc02 for a minimalist example.
+The header can also include specific configuration macros (see m8502)
+and also the class can include specific memory accessors (more on
+these later, simple example in m6504).
+
+If the cpu has its own dispatch table, the class must also include the
+declaration (but not definition) of disasm_entries, do_exec_full and
+do_exec_partial, the declaration and definition of disasm_disassemble
+(identical for all classes but refers to the class-specific
+disasm_entries array) and include the .inc file (which provides the
+missing definitions). Support for the generation must also be added
+to cpu.mak.
+
+If the cpu has in addition its own opcodes, their declaration must be
+done through a macro, see f.i. m65c02. The .inc file will provide the
+definitions.
+
+
+ 5. Dispatch tables
+
+Each d<cpu>.lst is the dispatch table for the cpu. Lines starting
+with '#' are comments. The file must include 257 entries, the first
+256 being opcodes and the 257th what the cpu should do on reset. In
+the 6502 irq and nmi actually magically call the "brk" opcode, hence
+the lack of specific description for them.
+
+Entries 0 to 255, i.e. the opcodes, must have one of these two
+structures:
+- opcode_addressing-mode
+- opcode_middle_addressing-mode
+
+Opcode is traditionally a three-character value. Addressing mode must
+be a 3-letter value corresponding to one of the DASM_* macros in
+m6502.h. Opcode and addressing mode are used to generate the
+disassembly table. The full entry text is used in the opcode
+description file and the dispatching methods, allowing for per-cpu
+variants for identical-looking opcodes.
+
+An entry of "." was usable for unimplemented/unknown opcodes,
+generating "???" in the disassembly, but is not a good idea at this
+point since it will infloop in execute() if encountered.
+
+
+ 6. Opcode descriptions
+
+Each o<cpu>.lst file includes the cpu-specific opcodes descriptions.
+An opcode description is a series of lines starting by an opcode entry
+by itself and followed by a series of indented lines with code
+executing the opcode.
+
+For instance the asl <absolute adress> opcode looks like this:
+
+asl_aba
+ TMP = read_pc();
+ TMP = set_h(TMP, read_pc());
+ TMP2 = read(TMP);
+ write(TMP, TMP2);
+ TMP2 = do_asl(TMP2);
+ write(TMP, TMP2);
+ prefetch();
+
+First the low part of the address is read, then the high part (read_pc
+is auto-incrementing). Then, now that the address is available the
+value to shift is read, then re-written (yes, the 6502 does that),
+shifted then the final result is written (do_asl takes care of the
+flags). The instruction finishes with a prefetch of the next
+instruction, as all non-cpu-crashing instructions do.
+
+Available bus-accessing functions are:
+- read(adr) - standard read
+- read_direct(adr) - read from program space
+- read_pc() - read at the PC address and increment it
+- read_pc_noinc() - read at the PC address
+- read_9() - 6509 indexed-y banked read
+- write(adr, val) - standard write
+- prefetch() - instruction prefetch
+- prefetch_noirq() - instruction prefetch without irq check
+
+Cycle counting is done by the code generator which detects (through
+string matching) the accesses and generates the appropriate code. In
+addition to the bus-accessing functions a special line can be used to
+wait for the next event (irq or whatever). "eat-all-cycles;" on a
+line will do that wait then continue. It is used by wai_imp and
+stp_imp for the m65c02.
+
+Due to the constraints of the code generation, some rules have to be
+followed:
+
+- in general, stay with one instruction/expression per line
+
+- there must be no side effects in the parameters of a bus-accessing
+ function
+
+- local variables lifetime must not go past a bus access. In general,
+ it's better to leave them to helper methods (like do_asl) which do not
+ do bus accesses. Note that "TMP" and "TMP2" are not local variables,
+ they're variables of the class.
+
+- single-line then or else constructs must have braces around them if
+ they're calling a bus-accessing function
+
+The per-opcode generated code are methods of the cpu class. As such
+they have complete access to other methods of the class, variables of
+the class, everything.
+
+
+ 7. Memory interface
+
+For better opcode reuse with the mmu/banking variants, a memory access
+subclass has been created. It's called memory_interface, declared in
+m6502_device, and provides the following accessors:
+
+- UINT8 read(UINT16 adr) - normal read
+- UINT8 read_direct(UINT16 adr) - direct read
+- UINT8 read_decrypted(UINT16 adr) - decrypted data read
+- void write(UINT16 adr, UINT8 val) - normal write
+
+- UINT8 read_9(UINT16 adr) - special y-indexed 6509 read, defaults to read()
+- void write_9(UINT16 adr, UINT8 val); - special y-indexed 6509 write, defaults to write()
+
+Two implementations are given by default, one usual,
+mi_default_normal, one disabling direct access, mi_default_nd. A cpu
+that wants its own interface (see 6504 or 6509 for instance) must
+override device_start, intialize mintf there then call init().
+
+
+ 8. The generated code
+
+A code generator is used to support interrupting and restarting an
+instruction in the middle. This is done through a two-level state
+machine with updates only at the boundaries. More precisely,
+inst_state tells you which main state you're in. It's equal to the
+opcode byte when 0-255, and 256 means reset. It's always valid and
+used by instructions like rmb. inst_substate indicates at which step
+we are in an instruction, but it set only when an instruction has been
+interrupted. Let's go back to the asl <abs> code:
+
+asl_aba
+ TMP = read_pc();
+ TMP = set_h(TMP, read_pc());
+ TMP2 = read(TMP);
+ write(TMP, TMP2);
+ TMP2 = do_asl(TMP2);
+ write(TMP, TMP2);
+ prefetch();
+
+
+The complete generated code is:
+void m6502_device::asl_aba_partial()
+{
+switch(inst_substate) {
+case 0:
+ if(icount == 0) { inst_substate = 1; return; }
+case 1:
+ TMP = read_pc();
+ icount--;
+ if(icount == 0) { inst_substate = 2; return; }
+case 2:
+ TMP = set_h(TMP, read_pc());
+ icount--;
+ if(icount == 0) { inst_substate = 3; return; }
+case 3:
+ TMP2 = read(TMP);
+ icount--;
+ if(icount == 0) { inst_substate = 4; return; }
+case 4:
+ write(TMP, TMP2);
+ icount--;
+ TMP2 = do_asl(TMP2);
+ if(icount == 0) { inst_substate = 5; return; }
+case 5:
+ write(TMP, TMP2);
+ icount--;
+ if(icount == 0) { inst_substate = 6; return; }
+case 6:
+ prefetch();
+ icount--;
+}
+ inst_substate = 0;
+}
+
+
+One can see that the initial switch() restarts the instruction at the
+appropriate substate, that icount is updated after each access, and
+upon reaching 0 the instruction is interrupted and the substate
+updated. Since most instructions are started from the beginning a
+specific variant is generated for when inst_substate is known to be 0:
+
+void m6502_device::asl_aba_full()
+{
+ if(icount == 0) { inst_substate = 1; return; }
+ TMP = read_pc();
+ icount--;
+ if(icount == 0) { inst_substate = 2; return; }
+ TMP = set_h(TMP, read_pc());
+ icount--;
+ if(icount == 0) { inst_substate = 3; return; }
+ TMP2 = read(TMP);
+ icount--;
+ if(icount == 0) { inst_substate = 4; return; }
+ write(TMP, TMP2);
+ icount--;
+ TMP2 = do_asl(TMP2);
+ if(icount == 0) { inst_substate = 5; return; }
+ write(TMP, TMP2);
+ icount--;
+ if(icount == 0) { inst_substate = 6; return; }
+ prefetch();
+ icount--;
+}
+
+That variant removes the switch, avoiding a costly computed branch and
+also an inst_substate write. There is in addition a fair chance that
+the decrement-test with zero pair is compiled into something
+efficient.
+
+All these opcode functions are called through two virtual methods,
+do_exec_full and do_exec_partial, which are generated into a 257-entry
+switch statement. Pointers-to-methods being expensive to call, a
+virtual function implementing a switch has a fair chance of being
+better.
+
+The execute main call ends up very simple:
+void m6502_device::execute_run()
+{
+ if(inst_substate)
+ do_exec_partial();
+
+ while(icount > 0) {
+ if(inst_state < 0x100) {
+ PPC = NPC;
+ inst_state = IR;
+ if(machine().debug_flags & DEBUG_FLAG_ENABLED)
+ debugger_instruction_hook(this, NPC);
+ }
+ do_exec_full();
+ }
+}
+
+If an instruction was partially executed finish it (icount will then
+be zero if it still doesn't finish). Then try to run complete
+instructions. The NPC/IR dance is due to the fact that the 6502 does
+instruction prefetching, so the instruction PC and opcode come from
+the prefetch results.
+
+
+ 9. Future bus contention/delay slot support
+
+Supporting bus contention and delay slots in the context of the code
+generator only requires being able to abort a bus access when not
+enough cycles are available into icount, and restart it when cycles
+have become available again. The implementation plan is to:
+
+- Have a delay() method on the cpu that removes cycles from icount.
+ If icount becomes zero or less, having it throw a suspend() exception.
+
+- Change the code generator to generate this:
+void m6502_device::asl_aba_partial()
+{
+switch(inst_substate) {
+case 0:
+ if(icount == 0) { inst_substate = 1; return; }
+case 1:
+ try {
+ TMP = read_pc();
+ } catch(suspend) { inst_substate = 1; return; }
+ icount--;
+ if(icount == 0) { inst_substate = 2; return; }
+case 2:
+ try {
+ TMP = set_h(TMP, read_pc());
+ } catch(suspend) { inst_substate = 2; return; }
+ icount--;
+ if(icount == 0) { inst_substate = 3; return; }
+case 3:
+ try {
+ TMP2 = read(TMP);
+ } catch(suspend) { inst_substate = 3; return; }
+ icount--;
+ if(icount == 0) { inst_substate = 4; return; }
+case 4:
+ try {
+ write(TMP, TMP2);
+ } catch(suspend) { inst_substate = 4; return; }
+ icount--;
+ TMP2 = do_asl(TMP2);
+ if(icount == 0) { inst_substate = 5; return; }
+case 5:
+ try {
+ write(TMP, TMP2);
+ } catch(suspend) { inst_substate = 5; return; }
+ icount--;
+ if(icount == 0) { inst_substate = 6; return; }
+case 6:
+ try {
+ prefetch();
+ } catch(suspend) { inst_substate = 6; return; }
+ icount--;
+}
+ inst_substate = 0;
+}
+
+A modern try/catch costs nothing if an exception is not thrown. Using
+this the control will go back to the main loop, which will then look
+like this:
+
+void m6502_device::execute_run()
+{
+ if(waiting_cycles) {
+ icount -= waiting_cycles;
+ waiting_cycles = 0;
+ }
+
+ if(icount > 0 && inst_substate)
+ do_exec_partial();
+
+ while(icount > 0) {
+ if(inst_state < 0x100) {
+ PPC = NPC;
+ inst_state = IR;
+ if(machine().debug_flags & DEBUG_FLAG_ENABLED)
+ debugger_instruction_hook(this, NPC);
+ }
+ do_exec_full();
+ }
+
+ waiting_cycles = -icount;
+ icount = 0;
+}
+
+A negative icount means that the cpu won't be able to do anything for
+some time in the future, because it's either waiting for the bus to be
+free or for a peripheral to answer. These cycles will be counted
+until elapsed and then normal processing will go on. It's important
+to note that the exception path only happens when the contention/wait
+state goes further than the scheduling slice of the cpu. That should
+not usually be the case, so the cost should be minimal.
+
+ 10. Current TODO
+
+- Implement the bus contention/wait states stuff, but that requires
+ support on the memory map side first.
+
+- Integrate the i/o subsystems in the 4510
+
+- Possibly integrate the sound subsytem in the n2a03
+
+- Add decent hookups for the apple 3 madness