diff --git a/lib6502/config.h b/lib6502/config.h new file mode 100644 index 0000000..4de5ccb --- /dev/null +++ b/lib6502/config.h @@ -0,0 +1,9 @@ +#ifndef __config_h +#define __config_h + +#define PACKAGE_NAME "lib6502" +#define PACKAGE_VERSION "1.0" +#define PACKAGE_BUGREPORT "firstName (at) lastName (dot) com" +#define PACKAGE_COPYRIGHT "Copyright (c) 2005 Ian Piumarta" + +#endif /* __config_h */ diff --git a/lib6502/examples/lib1.c b/lib6502/examples/lib1.c new file mode 100644 index 0000000..6c0de7a --- /dev/null +++ b/lib6502/examples/lib1.c @@ -0,0 +1,107 @@ +#include +#include + +#include "lib6502.h" + +/* Emulated OS functions. */ + +#define WRCH 0xFFEE /* Write accumulator to stdout. */ + +/* Write the accumulator to stdout. This function will be invoked + * when the emulated program calls 0xFFEE. + */ +int wrch(M6502 *mpu, uint16_t address, uint8_t data) +{ + int pc; + + /* Write the character. + */ + putchar(mpu->registers->a); + + /* We arrived here from a JSR instruction. The stack contains the + * saved PC. Pop it off the stack. + */ + pc = mpu->memory[++mpu->registers->s + 0x100]; + pc |= mpu->memory[++mpu->registers->s + 0x100] << 8; + + /* The JSR instruction pushes the value of PC before it has been + * incremented to point to the instruction after the JSR. Return PC + * + 1 as the address for the next insn. Returning non-zero + * indicates that we handled the 'subroutine' ourselves, and the + * emulator should pretend the original 'JSR' neveer happened at + * all. + */ + return pc + 1; /* JSR pushes next insn addr - 1 */ +} + + +/* Exit gracefully. We arrange for this function to be called when + * the emulator tries to transfer control to address 0. + */ +int done(M6502 *mpu, uint16_t address, uint8_t data) +{ + char buffer[64]; + + /* Dump the internal state of the processor. + */ + M6502_dump(mpu, buffer); + + /* Print a cute message and quit. + */ + printf("\nBRK instruction\n%s\n", buffer); + exit(0); +} + +int main() +{ + M6502 *mpu = M6502_new(0, 0, 0); /* Make a 6502 */ + unsigned pc = 0x1000; /* PC for 'assembly' */ + + /* Install the two callback functions defined above. + */ + M6502_setCallback(mpu, call, WRCH, wrch); /* Calling FFEE -> wrch() */ + M6502_setCallback(mpu, call, 0, done); /* Calling 0 -> done() */ + + /* A few macros that dump bytes into the 6502's memory. + */ +# define gen1(X) (mpu->memory[pc++]= (uint8_t)(X)) +# define gen2(X,Y) gen1(X); gen1(Y) +# define gen3(X,Y,Z) gen1(X); gen2(Y,Z) + + /* Hand-assemble the program. + */ + gen2(0xA2, 'A' ); // LDX #'A' + gen1(0x8A ); // TXA + gen3(0x20,0xEE,0xFF); // JSR FFEE + gen1(0xE8 ); // INX + gen2(0xE0, 'Z'+1 ); // CPX #'Z'+1 + gen2(0xD0, -9 ); // BNE 0x1002 + gen2(0xA9, '\n' ); // LDA #'\n' + gen3(0x20,0xEE,0xFF); // JSR FFEE + gen2(0x00,0x00 ); // BRK + + /* Just for fun: disssemble the program. + */ + { + char insn[64]; + uint16_t ip= 0x1000; + while (ip < pc) + { + ip += M6502_disassemble(mpu, ip, insn); + printf("%04X %s\n", ip, insn); + } + } + + /* Point the RESET vector at the first instruction in the assembled + * program. + */ + M6502_setVector(mpu, RST, 0x1000); + + /* Reset the 6502 and run the program. + */ + M6502_reset(mpu); + M6502_run(mpu); + M6502_delete(mpu); /* We never reach here, but what the hey. */ + + return 0; +} diff --git a/lib6502/html/lib6502.html b/lib6502/html/lib6502.html new file mode 100644 index 0000000..38df58c --- /dev/null +++ b/lib6502/html/lib6502.html @@ -0,0 +1,350 @@ + + + + + +lib6502(3) + +
LIB6502(3)               BSD Library Functions Manual               LIB6502(3)
+
+NAME
+     lib6502 - 6502 microprocessor emulator
+
+SYNOPSIS
+     #include <stdint.h>
+     #include <lib6502.h>
+
+     M6502 *
+     M6502_new(M6502_Registers *registers, M6502_Memory memory,
+             M6502_Callbacks *callbacks);
+
+     void
+     M6502_reset(M6502 *mpu);
+
+     void
+     M6502_nmi(M6502 *mpu);
+
+     void
+     M6502_irq(M6502 *mpu);
+
+     uint16_t
+     M6502_getVector(M6502 *mpu, vector);
+
+     uint16_t
+     M6502_setVector(M6502 *mpu, vector, uint16_t address);
+
+     M6502_Callback
+     M6502_getCallback(M6502 *mpu, type, uint16_t address);
+
+     M6502_Callback
+     M6502_setCallback(M6502 *mpu, type, uint16_t address,
+             M6502_Callback callback);
+
+     void
+     M6502_run(M6502 *mpu);
+
+     int
+     M6502_disassemble(M6502 *mpu, uint16_t addres_s, char buffer[64]);
+
+     void
+     M6502_dump(M6502 *mpu, char buffer[64]);
+
+     void
+     M6502_delete(M6502 *mpu);
+
+DESCRIPTION
+     M6502_new() creates an instance of a 6502 microprocessor.  M6502_reset(),
+     M6502_nmi() and M6502_irq() place it into the states associated with the
+     hardware signals for reset, non-maskable interrupt and interrupt request,
+     respectively.  The macros M6502_getVector() and M6502_setVector() read
+     and write the vectors through which the processor jumps in response to
+     the above signals.  The macros M6502_getCallback() and M6502_setVecttor()
+     read and write client-supplied functions that intercept accesses to mem-
+     ory.  M6502_run() begins emulated execution.  M6502_dump() and
+     M6502_disassemble() create human-readable representations of processor or
+     memory state.  M6502_delete() frees all resources associated with a pro-
+     cessor instance.  Each of these functions and macros is described in more
+     detail below.
+
+     M6502_new() returns a pointer to a M6502 structure containing at least
+     the following members:
+
+     struct _M6502
+     {
+         M6502_Registers  *registers;   /* processor state */
+         uint8_t          *memory;      /* memory image */
+         M6502_Callbacks  *callbacks;   /* r/w/x callbacks */
+     };
+
+     These members are initialised according to the supplied registers, memory
+     and callbacks arguments.  If a given argument is NULL, the corresponding
+     member is initialised automatically with a suitable (non-NULL) value.
+
+     The members of M6502 are as follows:
+
+     registers  the processor state, containing all registers and condition
+                codes.
+
+     memory     a block of at least 64 kilobytes of storage containing the
+                processor's memory.  (An array type M6502_Memory, suitable for
+                defining values to pass as the memory argument, is defined in
+                the #include <lib6502.h>
+                include file.)
+
+     callbacks  a structure mapping processor memory accesses to client call-
+                back functions.
+
+     Access to the contents of the registers and memory members can be made
+     directly.  The registers member is a M6502_Registers containing the fol-
+     lowing members:
+
+     struct _M6502_Registers
+     {
+         uint8_t   a;  /* accumulator */
+         uint8_t   x;  /* X index register */
+         uint8_t   y;  /* Y index register */
+         uint8_t   p;  /* processor status register */
+         uint8_t   s;  /* stack pointer */
+         uint16_t pc;  /* program counter */
+     };
+
+     The memory member is an array of unsigned char and can be indexed
+     directly.  In addition, two convenience macros M6502_getVector() and
+     M6502_setVector() provide access to the reset and interrupt vectors
+     within memory.  M6502_getVector() returns the address stored in the named
+     vector which must be precisely one of the following:
+
+           RST  the reset vector.
+
+           NMI  the non-maskable interrupt vector.
+
+           IRQ  the interrupt request vector.
+
+     M6502_setVector() stores its address argument in the named vector and
+     returns the new value.
+
+     The callbacks member contains an opaque structure mapping processor mem-
+     ory accesses to client callback functions.  Whenever the processor per-
+     forms an access for which a corresponding entry exists in the the
+     callbacks structure, the emulator suspends execution and invokes the
+     callback to complete the operation.  Each callback function should have a
+     signature equivalent to:
+
+           int callback (M6502 *mpu, uint16_t address, uint8_t data);
+
+     The macros M6502_getCallback() and M6502_setCallback() read and write
+     entries in the callbacks structure.  These macros identify a unique mem-
+     ory access operation from the specified address on which it operates and
+     type of access involved.  The type argument must be one of the following:
+
+     read   the callback is invoked when the processor attempts to read from
+            the given address.  The emulator passes the effective address of
+            the operation to the callback in its address argument.  (The data
+            argument is undefined.)  The value returned by the callback will
+            be used by the emulator as the result of the read operation.
+
+     write  the callback is invoked when the processor attempts to write to
+            the given address.  The emulator passes the effective address of
+            the operation to the callback in its address argument and the byte
+            being written in the data argument.  The emulator will not perform
+            the write operation before invoking the callback; if the write
+            should complete, the callback must modify the processor's memory
+            explicitly.  The valued returned from the callback is ignored.
+
+     call   the callback is invoked when the processor attempts to transfer
+            control to the given address by any instruction other than a rela-
+            tive branch.  The emulator passes the destination address to the
+            callback in its address argument and the instruction that initi-
+            ated the control transfer in its data argument (one of JMP, JSR,
+            BRK, RTS or RTI).  If the callback returns zero (the callback
+            refuses to handle the operation) the emulator will allow the oper-
+            ation to complete as normal.  If the callback returns a non-zero
+            address (indicating that the callback has handled the operation
+            internally) the emulator will transfer control to that address.
+
+     M6502_getCallback() returns zero if there is no callback associated with
+     the given type and address.  Passing zero as the callback argument of
+     M6502_setCallback() removes any callback that might have been associated
+     with type and address.
+
+     M6502_run() emulates processor execution in the given mpu by repeatedly
+     fetching the instruction addressed by pc and dispatching to it.  This
+     function normally never returns.
+
+     M6502_dump() writes a (NUL-terminated) symbolic representation of the
+     processor's internal state into the supplied buffer.  Typical output
+     resembles:
+
+           PC=1010 SP=01FE A=0A X=5B Y=00 P=D1 NV-B---C
+
+     M6502_disassemble() writes a (NUL-terminated) symbolic representation of
+     the instruction in the processor's memory at the given address into the
+     supplied buffer.  It returns the size (in bytes) of the instruction.  (In
+     other words, the amount by which address should be incremented to arrive
+     at the next instruction.)  Typical output resembles:
+
+           1009 cpx #5B
+
+     (The buffer arguments are oversized to allow for future expansion.)
+
+     M6502_delete() frees the resources associated with the given mpu. Any
+     members that were allocated implicitly (passed as NULL to M6502_new())
+     are deallocated.  Members that were initialised from non-NULL arguments
+     are not deallocated.
+
+IMPLEMENTATION NOTES
+     You can share the memory and callbacks members of M6502 between multiple
+     instances to simulate multiprocessor hardware.
+
+RETURN VALUES
+     M6502_new() returns a pointer to a M6502 structure.  M6502_getVector()
+     and M6502_setVector() return the contents of the given vector.
+     M6502_getCallback() and M6502_setCallback() return the M6502_Callback
+     function associated with the given address and access type.
+     M6502_disassemble() returns the size (in bytes) of the instruction at the
+     given address.  M6502_reset(), M6502_nmi(), M6502_irq(), M6502_run(),
+     M6502_dump() and M6502_delete() don't return anything (unless you forgot
+     to include
+
+EXAMPLES
+     The following program creates a 6502 processor, sets up callbacks for
+     printing characters and halting after a BRK instruction, stores a program
+     into memory that prints the alphabet, disassembles the program on stdout,
+     and then executes the program.
+
+           #include <stdint.h>
+           #include <stdlib.h>
+           #include <stdio.h>
+
+           #include "lib6502.h"
+
+           #define WRCH    0xFFEE
+
+           int wrch(M6502 *mpu, uint16_t address, uint8_t data)
+           {
+             int pc;
+             putchar(mpu->registers->a);
+             pc  = mpu->memory[++mpu->registers->s + 0x100];
+             pc |= mpu->memory[++mpu->registers->s + 0x100] << 8;
+             return pc + 1;  /* JSR pushes next insn addr - 1 */
+           }
+
+           int done(M6502 *mpu, uint16_t address, uint8_t data)
+           {
+             char buffer[64];
+             M6502_dump(mpu, buffer);
+             printf("\nBRK instruction\n%s\n", buffer);
+             exit(0);
+           }
+
+           int main(int argc, char **argv)
+           {
+             M6502    *mpu = M6502_new(0, 0, 0);
+             unsigned  pc  = 0x1000;
+
+             mpu->callbacks->call[WRCH] = wrch;     /* write character */
+             mpu->callbacks->call[0000] = done;     /* reached after BRK */
+
+           # define gen1(X)        (mpu->memory[pc++] = (uint8_t)(X))
+           # define gen2(X,Y)      gen1(X); gen1(Y)
+           # define gen3(X,Y,Z)    gen1(X); gen2(Y,Z)
+
+             gen2(0xA2, 'A'     );  /* LDX #'A'   */
+             gen1(0x8A          );  /* TXA        */
+             gen3(0x20,0xEE,0xFF);  /* JSR FFEE   */
+             gen1(0xE8          );  /* INX        */
+             gen2(0xE0, 'Z'+1   );  /* CPX #'Z'+1 */
+             gen2(0xD0, -9      );  /* BNE 1002   */
+             gen2(0xA9, '\n'    );  /* LDA #'\n'  */
+             gen3(0x20,0xEE,0xFF);  /* JSR FFEE   */
+             gen2(0x00,0x00     );  /* BRK        */
+
+             {
+               uint16_t ip = 0x1000;
+               while (ip < pc)
+                 {
+                   char insn[64];
+                   ip += M6502_disassemble(mpu, ip, insn);
+                   printf("%04X %s\n", ip, insn);
+                 }
+             }
+
+             M6502_setVector(mpu, RST, 0x1000);
+
+             M6502_reset(mpu);
+             M6502_run(mpu);
+             M6502_delete(mpu);
+
+             return 0;
+           }
+
+DIAGNOSTICS
+     If M6502_new() cannot allocate sufficient memory it prints "out of mem-
+     ory" to stderr and exits with a non-zero status.
+
+     If M6502_run() encounters an illegal or undefined instruction, it prints
+     "undefined instruction" and the processor's state to stderr, then exits
+     with a non-zero status.
+
+COMPATIBILITY
+     M6502 is a generic name. The initial letter is mandated by C naming con-
+     ventions and chosen in deference to MOS Technology, the original design-
+     ers of the processor.  To the best of my knowledge the 'M' prefix was
+     never stamped on a physical 6502.
+
+     The emulator implements the CMOS version of the processor (NMOS bugs in
+     effective address calculations involving page boundaries are corrected)
+     but does not tolerate the execution of undefined instructions (which were
+     all no-ops in the first-generation CMOS hardware).  It would be nice to
+     support the several alternative instruction sets (model-specific undocu-
+     mented instructions in NMOS models, and various documented extensions in
+     the later CMOS models) but there are currently no plans to do so.
+
+     The emulated 6502 will run much faster than real hardware on any modern
+     computer.  The fastest 6502 hardware available at the time of writing has
+     a clock speed of 14 MHz.  On a 2 GHz PowerPC, the emulated 6502 runs at
+     almost 300 MHz.
+
+SEE ALSO
+     run6502(1)
+
+     For development tools, documentation and source code: http://6502.org
+
+AUTHORS
+     The software and manual pages were written by Ian Piumarta.
+
+     The software is provided as-is, with absolutely no warranty, in the hope
+     that you will enjoy and benefit from it.  You may use (entirely at your
+     own risk) and redistribute it under the terms of a very liberal license
+     that does not seek to restrict your rights in any way (unlike certain so-
+     called 'open source' licenses that significantly limit your freedom in
+     the name of 'free' software that is, ultimately, anything but free).  See
+     the file COPYING for details.
+
+BUGS
+     M6502_getVector() and M6502_setVector() evaluate their arguments more
+     than once.
+
+     The out-of-memory condition and attempted execution of illegal/undefined
+     instructions should not be fatal errors.
+
+     There is no way to limit the duration of execution within M6502_run() to
+     a certain number of instructions or cycles.
+
+     The emulator should support some means of implicit interrupt generation,
+     either by polling or in response to (Unix) signals.
+
+     The COMPATIBILITY section in this manual page has been diverted from its
+     legitimate purpose.
+
+     The plural of 'callback' really aught to be 'callsback'.
+
+     Please send bug reports (and feature requests) to the author at: first-
+     Name (at) lastName (dot) com.  (See AUTHORS above for suitable values of
+     firstName and lastName.)
+
+BSD                            October 31, 2005                            BSD
+
+ + + \ No newline at end of file diff --git a/lib6502/html/run6502.html b/lib6502/html/run6502.html new file mode 100644 index 0000000..8a5a0a3 --- /dev/null +++ b/lib6502/html/run6502.html @@ -0,0 +1,275 @@ + + + + + +run6502(1) + +
RUN6502(1)                BSD General Commands Manual               RUN6502(1)
+
+NAME
+     run6502 - execute a 6502 microprocessor program
+
+SYNOPSIS
+     run6502 [option ...]
+     run6502 [option ...] -B [file ...]
+
+DESCRIPTION
+     The run6502 command emulates the execution of a 6502 microprocessor.  It
+     creates a memory image from the contents of one or more files on the com-
+     mand line and then simulates a power-on hardware reset to begin execu-
+     tion.
+
+     In its first form, run6502 emulates an embedded 6502 processor with 64
+     kilobytes of RAM, no memory-mapped hardware, and no input-output capabil-
+     ities.  Limited interaction with the machine is possible only through the
+     -G, -M and -P options.
+
+     In its second form (with the -B option) run6502 provides minimal emula-
+     tion of Acorn 'BBC Model B' hardware with 32 kilobytes of RAM, 16 kilo-
+     bytes of paged language ROMs, and 16 kilobytes of operating system ROM.
+     A few MOS calls are intercepted to provide keyboard input and screen out-
+     put via stdin and stdout.  Switching between the sixteen paged read-only
+     memory banks is also supported by the usual memory-mapped control regis-
+     ter.  Any file arguments after the -B are loaded into successive paged
+     ROM banks (starting at 15 and working down towards 0) before execution
+     begins.
+
+   Options
+     -B      enable minimal Acorn 'BBC Model B' hardware emulation:
+
+             o   the contents of memory between addresses 0x8000 and 0xBFFF
+                 are copied into paged ROM number 0;
+
+             o   memory between 0x8000 and 0xBFFF becomes bank-switchable
+                 between sixteen different ROM images;
+
+             o   the memory-mapped pages ('FRED', 'JIM' and 'SHEILA') between
+                 0xFC00 and 0xFEFF are initialised to harmless values;
+
+             o   the upper half of the address space is write-protected; and
+
+             o   callbacks are installed on several OS entry points to provide
+                 input-output via stdin and stdout.
+
+             Any remaining non-option arguments on the command line will name
+             files to be loaded successively into paged ROMs, starting at 15
+             and working downwards towards 0.
+
+     -d addr end
+             dump memory from the address addr (given in hexadecimal) up to
+             (but not including) end.  The end argument is either an absolute
+             address or a relative address specified as a '+' character fol-
+             lowed by the number (in hexadecimal) of bytes to dump.  In other
+             words, the following two options dump the same region of memory:
+
+                   -d 8000  C000
+                   -d 8000 +4000
+
+             The format of the dump cannot currently be modified and consists
+             of the current address followed by one, two or three hexadecimal
+             bytes, and a symbolic representation of the instruction at that
+             address.
+
+     -G addr
+             arrange that subroutine calls to addr will behave as if there
+             were an implementation of getchar(3) at that address, reading a
+             character from stdin and returning it in the accumulator.
+
+     -h      print a summary of the available options and then exit.
+
+     -I addr
+             set the IRQ (interrupt request) vector (the address to which the
+             processor will transfer control upon execution of a BRK instruc-
+             tion).  Setting this address to zero will cause execution to halt
+             (and the emulator to exit) when a BRK instruction is encountered.
+
+     -i addr file
+             Load file into the memory image at the address addr (in hexadeci-
+             mal), skipping over any initial '#!' interpreter line.
+
+     -l addr file
+             Load file into the memory image at the address addr (in hexadeci-
+             mal).
+
+     -M addrio
+             arrange that memory reads from address addrio will return the
+             next character on stdin (blocking if necessary), and memory
+             writes to addrio will send the value written to stdout.
+
+     -N addr
+             set the NMI (non-maskable interrupt) vector to addr.
+
+     -P addr
+             arrange that subroutine calls to addr will behave as if there
+             were an implementation of putchar(3) at that address, writing the
+             contents of the accumulator to stdout.
+
+     -R addr
+             set the RST (hardware reset) vector.  The processor will transfer
+             control to this address when emulated execution begins.
+
+     -s addr end file
+             save the contents of memory from the address addr up to end
+             (exclusive) to the given file.  As with the -d option, end can be
+             absolute or '+' followed by a byte count.
+
+     -v      print version information and then exit.
+
+     -X addr
+             arrange that any transfer of control to the address addr will
+             cause an immediate exit with zero exit status.
+
+     -x      exit immediately.  (Useful after -d or when run6502 is being used
+             as a trivial 'image editor', with several -l options followed by
+             -s and -x.)
+
+     file ...
+             following a -B option, load one or more ROM image files into suc-
+             cessive paged ROM slots.  Other than the paging aspect, this is
+             equivalent to:
+
+                   -l 8000 image
+
+EXAMPLES
+   A Very Simple Program
+     The perl(1) command can be used to create a binary file from hexadecimal
+     input:
+
+         echo a2418a20eeffe8e05bd0f7a90a20eeff00 |
+         perl -e 'print pack "H*",<STDIN>' > temp.img
+
+     The file can be loaded and executed with:
+
+         run6502 -l 1000 temp.img -R 1000 -P FFEE -X 0
+
+     The contents of the file can be inspected symbolically with:
+
+         run6502 -l 1000 temp.img -d 1000 +12
+
+     The options passed to run6502 in the above examples have the following
+     effects:
+
+     -l 1000 temp.img
+             loads the file temp.img into memory at address 0x8000.
+
+     -R 1000
+             sets the reset vector (the address of first instruction to be
+             executed after 'power on') to 0x1000.
+
+     -P FFEE
+             arranges for calls to address 0xFFEE to behave as if there were
+             an implementation of putchar(3) at that address.
+
+     -X 0    arranges for transfers of control to address 0 to exit from the
+             emulator.  This works in the above example because the final
+             'BRK' instruction causes an implicit subroutine call through an
+             uninitialised interrupt vector to location 0.  To see this
+             instruction...
+
+     -d 1000 +12
+             disassembles 18 bytes of memory at address 0x8000.
+
+   Standalone Images
+     The -i option is designed for use in the 'interpreter command' appearing
+     on the first line of an executable script.  Adding the line
+
+         #!run6502 -R 1000 -P FFEE -X 0 -i 1000
+
+     (with no leading spaces and a single trailing newline character) to the
+     temp.img file from the first example turns it into a script.  If the file
+     is made executable with
+
+         chmod +x temp.img
+
+     it can be run like a standalone program:
+
+         ./temp.img
+
+   A Very Complex Program
+     Consider a pair of files named os1.2 and basic2 containing (legally-
+     acquired, of course) ROM images of Acorn MOS 1.2 and BBC Basic 2.  The
+     following command loads each of the images into memory at the appropriate
+     address, cleans up the regions of memory containing memory-mapped i/o on
+     the BBC computer, saves a snapshot of the entire memory to the file image
+     and then exits:
+
+         run6502 -l C000 os1.2 -l 8000 basic2 -B -s0 +10000 image -x
+
+     Running the generated image with
+
+         run6502 image
+
+     will cold-start the emulated hardware, run the OS for a while, and then
+     drop into the language ROM.  Basic programs can then be entered, edited
+     and run from the terminal.
+
+     More details are given in the README file available in the examples
+     directory of the distribution.
+
+   Exercises
+     Create a standalone image (one that can be run as a program, with a '#!'
+     interpreter line at the beginning) that contains Basic2 and OS1.2 (as
+     described above).  This image should be no larger than 32K (memory below
+     0x8000, which would be full of zeroes, should not appear in the image
+     file).
+
+DIAGNOSTICS
+     If nothing goes wrong, none.  Otherwise lots.  They should be self-
+     explanatory.  I'm too lazy to enumerate them.
+
+COMPATIBILITY
+     See lib6502(3) for a discussion of the emulated instruction set.
+
+SEE ALSO
+     lib6502(3)
+
+     The file examples/README in the lib6502 distribution.  (Depending on your
+     system this may be installed in /usr/doc/lib6502, /usr/local/doc/lib6502,
+     /usr/share/doc/lib6502, or similar.)
+
+     http://piumarta.com/software/lib6502 for updates and documentation.
+
+     http://6502.org for lots of 6502-related resources.
+
+AUTHORS
+     The software and manual pages were written by Ian Piumarta.
+
+     The software is provided as-is, with absolutely no warranty, in the hope
+     that you will enjoy and benefit from it.  You may use (entirely at your
+     own risk) and redistribute it under the terms of a very liberal license
+     that does not seek to restrict your rights in any way (unlike certain so-
+     called 'open source' licenses that significantly limit your freedom in
+     the name of 'free' software that is, ultimately, anything but free).  See
+     the file COPYING for details.
+
+BUGS
+     o   Options must appear one at a time.
+
+     o   Any attempt (in a load or save operation) to transfer data beyond
+         0xFFFF is silently truncated at the end of memory.
+
+     o   There is no way to specify the slot into which a ROM image should be
+         loaded, other than implicitly according to the order of arguments on
+         the command line.
+
+     o   Execution can only be started via the emulated power-up reset.  There
+         is no support for 'warm-starting' execution in an image at an arbi-
+         trary address.
+
+     o   Even though the emulator fully supports them, there is no way to
+         artificially generate a hardware interrupt request, non-maskable
+         interrupt, or reset condition.  If you need these, read lib6502(3)
+         and write your own shell.
+
+     o   The Acorn 'BBC Model B' hardware emulation is totally lame.
+
+     Please send bug reports (and feature requests) to the author at: first-
+     Name (at) lastName (dot) com.  (See AUTHORS above for suitable values of
+     firstName and lastName.)
+
+BSD                            October 31, 2005                            BSD
+
+ + + \ No newline at end of file diff --git a/lib6502/lib6502.c b/lib6502/lib6502.c new file mode 100644 index 0000000..84549fb --- /dev/null +++ b/lib6502/lib6502.c @@ -0,0 +1,893 @@ +/* lib6502.c -- MOS Technology 6502 emulator -*- C -*- */ + +/* Copyright (c) 2005 Ian Piumarta + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the 'Software'), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software and that both the + * above copyright notice(s) and this permission notice appear in supporting + * documentation. + * + * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. + */ + +/* Last edited: 2013-06-07 23:03:39 by piumarta on emilia.local + * + * BUGS: + * - RTS and RTI do not check the return address for a callback + * - the disassembler cannot be configured to read two bytes for BRK + * - architectural variations (unimplemented/extended instructions) not implemented + * - ANSI versions (from from gcc extensions) of the dispatch macros are missing + * - emulator+disassembler in same object file (library is kind of pointless) + */ + +#include +#include + +#include "lib6502.h" + +typedef uint8_t byte; +typedef uint16_t word; + +enum { + flagN= (1<<7), /* negative */ + flagV= (1<<6), /* overflow */ + flagX= (1<<5), /* unused */ + flagB= (1<<4), /* irq from brk */ + flagD= (1<<3), /* decimal mode */ + flagI= (1<<2), /* irq disable */ + flagZ= (1<<1), /* zero */ + flagC= (1<<0) /* carry */ +}; + +#define getN() (P & flagN) +#define getV() (P & flagV) +#define getB() (P & flagB) +#define getD() (P & flagD) +#define getI() (P & flagI) +#define getZ() (P & flagZ) +#define getC() (P & flagC) + +#define setNVZC(N,V,Z,C) (P= (P & ~(flagN | flagV | flagZ | flagC)) | (N) | ((V)<<6) | ((Z)<<1) | (C)) +#define setNZC(N,Z,C) (P= (P & ~(flagN | flagZ | flagC)) | (N) | ((Z)<<1) | (C)) +#define setNZ(N,Z) (P= (P & ~(flagN | flagZ )) | (N) | ((Z)<<1) ) +#define setZ(Z) (P= (P & ~( flagZ )) | ((Z)<<1) ) +#define setC(C) (P= (P & ~( flagC)) | (C)) + +#define NAND(P, Q) (!((P) & (Q))) + +#define tick(n) +#define tickIf(p) + +/* memory access (indirect if callback installed) -- ARGUMENTS ARE EVALUATED MORE THAN ONCE! */ + +#define putMemory(ADDR, BYTE) \ + ( writeCallback[ADDR] \ + ? writeCallback[ADDR](mpu, ADDR, BYTE) \ + : (memory[ADDR]= BYTE) ) + +#define getMemory(ADDR) \ + ( readCallback[ADDR] \ + ? readCallback[ADDR](mpu, ADDR, 0) \ + : memory[ADDR] ) + +/* stack access (always direct) */ + +#define push(BYTE) (memory[0x0100 + S--]= (BYTE)) +#define pop() (memory[++S + 0x0100]) + +/* adressing modes (memory access direct) */ + +#define implied(ticks) \ + tick(ticks); + +#define immediate(ticks) \ + tick(ticks); \ + ea= PC++; + +#define abs(ticks) \ + tick(ticks); \ + ea= memory[PC] + (memory[PC + 1] << 8); \ + PC += 2; + +#define relative(ticks) \ + tick(ticks); \ + ea= memory[PC++]; \ + if (ea & 0x80) ea -= 0x100; \ + tickIf((ea >> 8) != (PC >> 8)); + +#define indirect(ticks) \ + tick(ticks); \ + { \ + word tmp; \ + tmp= memory[PC] + (memory[PC + 1] << 8); \ + ea = memory[tmp] + (memory[tmp + 1] << 8); \ + PC += 2; \ + } + +#define absx(ticks) \ + tick(ticks); \ + ea= memory[PC] + (memory[PC + 1] << 8); \ + PC += 2; \ + tickIf((ticks == 4) && ((ea >> 8) != ((ea + X) >> 8))); \ + ea += X; + +#define absy(ticks) \ + tick(ticks); \ + ea= memory[PC] + (memory[PC + 1] << 8); \ + PC += 2; \ + tickIf((ticks == 4) && ((ea >> 8) != ((ea + Y) >> 8))); \ + ea += Y + +#define zp(ticks) \ + tick(ticks); \ + ea= memory[PC++]; + +#define zpx(ticks) \ + tick(ticks); \ + ea= memory[PC++] + X; \ + ea &= 0x00ff; + +#define zpy(ticks) \ + tick(ticks); \ + ea= memory[PC++] + Y; \ + ea &= 0x00ff; + +#define indx(ticks) \ + tick(ticks); \ + { \ + byte tmp= memory[PC++] + X; \ + ea= memory[tmp] + (memory[tmp + 1] << 8); \ + } + +#define indy(ticks) \ + tick(ticks); \ + { \ + byte tmp= memory[PC++]; \ + ea= memory[tmp] + (memory[tmp + 1] << 8); \ + tickIf((ticks == 5) && ((ea >> 8) != ((ea + Y) >> 8))); \ + ea += Y; \ + } + +#define indabsx(ticks) \ + tick(ticks); \ + { \ + word tmp; \ + tmp= memory[PC ] + (memory[PC + 1] << 8) + X; \ + ea = memory[tmp] + (memory[tmp + 1] << 8); \ + } + +#define indzp(ticks) \ + tick(ticks); \ + { \ + byte tmp; \ + tmp= memory[PC++]; \ + ea = memory[tmp] + (memory[tmp + 1] << 8); \ + } + +/* insns */ + +#define adc(ticks, adrmode) \ + adrmode(ticks); \ + { \ + byte B= getMemory(ea); \ + if (!getD()) \ + { \ + int c= A + B + getC(); \ + int v= (int8_t)A + (int8_t)B + getC(); \ + fetch(); \ + A= c; \ + setNVZC((A & 0x80), (((A & 0x80) > 0) ^ (v < 0)), (A == 0), ((c & 0x100) > 0)); \ + next(); \ + } \ + else \ + { \ + int l, h, s; \ + /* inelegant & slow, but consistent with the hw for illegal digits */ \ + l= (A & 0x0F) + (B & 0x0F) + getC(); \ + h= (A & 0xF0) + (B & 0xF0); \ + if (l >= 0x0A) { l -= 0x0A; h += 0x10; } \ + if (h >= 0xA0) { h -= 0xA0; } \ + fetch(); \ + s= h | (l & 0x0F); \ + /* only C is valid on NMOS 6502 */ \ + setNVZC(s & 0x80, !(((A ^ B) & 0x80) && ((A ^ s) & 0x80)), !s, !!(h & 0x80)); \ + A= s; \ + tick(1); \ + next(); \ + } \ + } + +#define sbc(ticks, adrmode) \ + adrmode(ticks); \ + { \ + byte B= getMemory(ea); \ + if (!getD()) \ + { \ + int b= 1 - (P &0x01); \ + int c= A - B - b; \ + int v= (int8_t)A - (int8_t) B - b; \ + fetch(); \ + A= c; \ + setNVZC(A & 0x80, ((A & 0x80) > 0) ^ ((v & 0x100) != 0), A == 0, c >= 0); \ + next(); \ + } \ + else \ + { \ + /* this is verbatim ADC, with a 10's complemented operand */ \ + int l, h, s; \ + B= 0x99 - B; \ + l= (A & 0x0F) + (B & 0x0F) + getC(); \ + h= (A & 0xF0) + (B & 0xF0); \ + if (l >= 0x0A) { l -= 0x0A; h += 0x10; } \ + if (h >= 0xA0) { h -= 0xA0; } \ + fetch(); \ + s= h | (l & 0x0F); \ + /* only C is valid on NMOS 6502 */ \ + setNVZC(s & 0x80, !(((A ^ B) & 0x80) && ((A ^ s) & 0x80)), !s, !!(h & 0x80)); \ + A= s; \ + tick(1); \ + next(); \ + } \ + } + +#define cmpR(ticks, adrmode, R) \ + adrmode(ticks); \ + fetch(); \ + { \ + byte B= getMemory(ea); \ + byte d= R - B; \ + setNZC(d & 0x80, !d, R >= B); \ + } \ + next(); + +#define cmp(ticks, adrmode) cmpR(ticks, adrmode, A) +#define cpx(ticks, adrmode) cmpR(ticks, adrmode, X) +#define cpy(ticks, adrmode) cmpR(ticks, adrmode, Y) + +#define dec(ticks, adrmode) \ + adrmode(ticks); \ + fetch(); \ + { \ + byte B= getMemory(ea); \ + --B; \ + putMemory(ea, B); \ + setNZ(B & 0x80, !B); \ + } \ + next(); + +#define decR(ticks, adrmode, R) \ + fetch(); \ + tick(ticks); \ + --R; \ + setNZ(R & 0x80, !R); \ + next(); + +#define dea(ticks, adrmode) decR(ticks, adrmode, A) +#define dex(ticks, adrmode) decR(ticks, adrmode, X) +#define dey(ticks, adrmode) decR(ticks, adrmode, Y) + +#define inc(ticks, adrmode) \ + adrmode(ticks); \ + fetch(); \ + { \ + byte B= getMemory(ea); \ + ++B; \ + putMemory(ea, B); \ + setNZ(B & 0x80, !B); \ + } \ + next(); + +#define incR(ticks, adrmode, R) \ + fetch(); \ + tick(ticks); \ + ++R; \ + setNZ(R & 0x80, !R); \ + next(); + +#define ina(ticks, adrmode) incR(ticks, adrmode, A) +#define inx(ticks, adrmode) incR(ticks, adrmode, X) +#define iny(ticks, adrmode) incR(ticks, adrmode, Y) + +#define bit(ticks, adrmode) \ + adrmode(ticks); \ + fetch(); \ + { \ + byte B= getMemory(ea); \ + P= (P & ~(flagN | flagV | flagZ)) \ + | (B & (0xC0)) | (((A & B) == 0) << 1); \ + } \ + next(); + +#define tsb(ticks, adrmode) \ + adrmode(ticks); \ + fetch(); \ + { \ + byte b= getMemory(ea); \ + b |= A; \ + putMemory(ea, b); \ + setZ(!b); \ + } \ + next(); + +#define trb(ticks, adrmode) \ + adrmode(ticks); \ + fetch(); \ + { \ + byte b= getMemory(ea); \ + b |= (A ^ 0xFF); \ + putMemory(ea, b); \ + setZ(!b); \ + } \ + next(); + +#define bitwise(ticks, adrmode, op) \ + adrmode(ticks); \ + fetch(); \ + A op##= getMemory(ea); \ + setNZ(A & 0x80, !A); \ + next(); + +#define and(ticks, adrmode) bitwise(ticks, adrmode, &) +#define eor(ticks, adrmode) bitwise(ticks, adrmode, ^) +#define ora(ticks, adrmode) bitwise(ticks, adrmode, |) + +#define asl(ticks, adrmode) \ + adrmode(ticks); \ + { \ + unsigned int i= getMemory(ea) << 1; \ + putMemory(ea, i); \ + fetch(); \ + setNZC(i & 0x80, !i, i >> 8); \ + } \ + next(); + +#define asla(ticks, adrmode) \ + tick(ticks); \ + fetch(); \ + { \ + int c= A >> 7; \ + A <<= 1; \ + setNZC(A & 0x80, !A, c); \ + } \ + next(); + +#define lsr(ticks, adrmode) \ + adrmode(ticks); \ + { \ + byte b= getMemory(ea); \ + int c= b & 1; \ + fetch(); \ + b >>= 1; \ + putMemory(ea, b); \ + setNZC(0, !b, c); \ + } \ + next(); + +#define lsra(ticks, adrmode) \ + tick(ticks); \ + fetch(); \ + { \ + int c= A & 1; \ + A >>= 1; \ + setNZC(0, !A, c); \ + } \ + next(); + +#define rol(ticks, adrmode) \ + adrmode(ticks); \ + { \ + word b= (getMemory(ea) << 1) | getC(); \ + fetch(); \ + putMemory(ea, b); \ + setNZC(b & 0x80, !(b & 0xFF), b >> 8); \ + } \ + next(); + +#define rola(ticks, adrmode) \ + tick(ticks); \ + fetch(); \ + { \ + word b= (A << 1) | getC(); \ + A= b; \ + setNZC(A & 0x80, !A, b >> 8); \ + } \ + next(); + +#define ror(ticks, adrmode) \ + adrmode(ticks); \ + { \ + int c= getC(); \ + byte m= getMemory(ea); \ + byte b= (c << 7) | (m >> 1); \ + fetch(); \ + putMemory(ea, b); \ + setNZC(b & 0x80, !b, m & 1); \ + } \ + next(); + +#define rora(ticks, adrmode) \ + adrmode(ticks); \ + { \ + int ci= getC(); \ + int co= A & 1; \ + fetch(); \ + A= (ci << 7) | (A >> 1); \ + setNZC(A & 0x80, !A, co); \ + } \ + next(); + +#define tRS(ticks, adrmode, R, S) \ + fetch(); \ + tick(ticks); \ + S= R; \ + setNZ(S & 0x80, !S); \ + next(); + +#define tax(ticks, adrmode) tRS(ticks, adrmode, A, X) +#define txa(ticks, adrmode) tRS(ticks, adrmode, X, A) +#define tay(ticks, adrmode) tRS(ticks, adrmode, A, Y) +#define tya(ticks, adrmode) tRS(ticks, adrmode, Y, A) +#define tsx(ticks, adrmode) tRS(ticks, adrmode, S, X) + +#define txs(ticks, adrmode) \ + fetch(); \ + tick(ticks); \ + S= X; \ + next(); + +#define ldR(ticks, adrmode, R) \ + adrmode(ticks); \ + fetch(); \ + R= getMemory(ea); \ + setNZ(R & 0x80, !R); \ + next(); + +#define lda(ticks, adrmode) ldR(ticks, adrmode, A) +#define ldx(ticks, adrmode) ldR(ticks, adrmode, X) +#define ldy(ticks, adrmode) ldR(ticks, adrmode, Y) + +#define stR(ticks, adrmode, R) \ + adrmode(ticks); \ + fetch(); \ + putMemory(ea, R); \ + next(); + +#define sta(ticks, adrmode) stR(ticks, adrmode, A) +#define stx(ticks, adrmode) stR(ticks, adrmode, X) +#define sty(ticks, adrmode) stR(ticks, adrmode, Y) +#define stz(ticks, adrmode) stR(ticks, adrmode, 0) + +#define branch(ticks, adrmode, cond) \ + if (cond) \ + { \ + adrmode(ticks); \ + PC += ea; \ + tick(1); \ + } \ + else \ + { \ + tick(ticks); \ + PC++; \ + } \ + fetch(); \ + next(); + +#define bcc(ticks, adrmode) branch(ticks, adrmode, !getC()) +#define bcs(ticks, adrmode) branch(ticks, adrmode, getC()) +#define bne(ticks, adrmode) branch(ticks, adrmode, !getZ()) +#define beq(ticks, adrmode) branch(ticks, adrmode, getZ()) +#define bpl(ticks, adrmode) branch(ticks, adrmode, !getN()) +#define bmi(ticks, adrmode) branch(ticks, adrmode, getN()) +#define bvc(ticks, adrmode) branch(ticks, adrmode, !getV()) +#define bvs(ticks, adrmode) branch(ticks, adrmode, getV()) + +#define bra(ticks, adrmode) \ + adrmode(ticks); \ + PC += ea; \ + fetch(); \ + tick(1); \ + next(); + +#define jmp(ticks, adrmode) \ + adrmode(ticks); \ + PC= ea; \ + if (mpu->callbacks->call[ea]) \ + { \ + word addr; \ + externalise(); \ + if ((addr= mpu->callbacks->call[ea](mpu, ea, 0))) \ + { \ + internalise(); \ + PC= addr; \ + } \ + } \ + fetch(); \ + next(); + +#define jsr(ticks, adrmode) \ + PC++; \ + push(PC >> 8); \ + push(PC & 0xff); \ + PC--; \ + adrmode(ticks); \ + if (mpu->callbacks->call[ea]) \ + { \ + word addr; \ + externalise(); \ + if ((addr= mpu->callbacks->call[ea](mpu, ea, 0))) \ + { \ + internalise(); \ + PC= addr; \ + fetch(); \ + next(); \ + } \ + } \ + PC=ea; \ + fetch(); \ + next(); + +#define rts(ticks, adrmode) \ + tick(ticks); \ + PC = pop(); \ + PC |= (pop() << 8); \ + PC++; \ + fetch(); \ + next(); + +#define brk(ticks, adrmode) \ + tick(ticks); \ + PC++; \ + push(PC >> 8); \ + push(PC & 0xff); \ + P |= flagB; \ + push(P | flagX); \ + P |= flagI; \ + { \ + word hdlr= getMemory(0xfffe) + (getMemory(0xffff) << 8); \ + if (mpu->callbacks->call[hdlr]) \ + { \ + word addr; \ + externalise(); \ + if ((addr= mpu->callbacks->call[hdlr](mpu, PC - 2, 0))) \ + { \ + internalise(); \ + hdlr= addr; \ + } \ + } \ + PC= hdlr; \ + } \ + fetch(); \ + next(); + +#define rti(ticks, adrmode) \ + tick(ticks); \ + P= pop(); \ + PC= pop(); \ + PC |= (pop() << 8); \ + fetch(); \ + next(); + +#define nop(ticks, adrmode) \ + fetch(); \ + tick(ticks); \ + next(); + +#define ill(ticks, adrmode) \ + fetch(); \ + tick(ticks); \ + fflush(stdout); \ + fprintf(stderr, "\nundefined instruction %02X\n", memory[PC-1]); \ + return; + +#define phR(ticks, adrmode, R) \ + fetch(); \ + tick(ticks); \ + push(R); \ + next(); + +#define pha(ticks, adrmode) phR(ticks, adrmode, A) +#define phx(ticks, adrmode) phR(ticks, adrmode, X) +#define phy(ticks, adrmode) phR(ticks, adrmode, Y) +#define php(ticks, adrmode) phR(ticks, adrmode, P | flagX | flagB) + +#define plR(ticks, adrmode, R) \ + fetch(); \ + tick(ticks); \ + R= pop(); \ + setNZ(R & 0x80, !R); \ + next(); + +#define pla(ticks, adrmode) plR(ticks, adrmode, A) +#define plx(ticks, adrmode) plR(ticks, adrmode, X) +#define ply(ticks, adrmode) plR(ticks, adrmode, Y) + +#define plp(ticks, adrmode) \ + fetch(); \ + tick(ticks); \ + P= pop(); \ + next(); + +#define clF(ticks, adrmode, F) \ + fetch(); \ + tick(ticks); \ + P &= ~F; \ + next(); + +#define clc(ticks, adrmode) clF(ticks, adrmode, flagC) +#define cld(ticks, adrmode) clF(ticks, adrmode, flagD) +#define cli(ticks, adrmode) clF(ticks, adrmode, flagI) +#define clv(ticks, adrmode) clF(ticks, adrmode, flagV) + +#define seF(ticks, adrmode, F) \ + fetch(); \ + tick(ticks); \ + P |= F; \ + next(); + +#define sec(ticks, adrmode) seF(ticks, adrmode, flagC) +#define sed(ticks, adrmode) seF(ticks, adrmode, flagD) +#define sei(ticks, adrmode) seF(ticks, adrmode, flagI) + +#define do_insns(_) \ + _(00, brk, implied, 7); _(01, ora, indx, 6); _(02, ill, implied, 2); _(03, ill, implied, 2); \ + _(04, tsb, zp, 3); _(05, ora, zp, 3); _(06, asl, zp, 5); _(07, ill, implied, 2); \ + _(08, php, implied, 3); _(09, ora, immediate, 3); _(0a, asla,implied, 2); _(0b, ill, implied, 2); \ + _(0c, tsb, abs, 4); _(0d, ora, abs, 4); _(0e, asl, abs, 6); _(0f, ill, implied, 2); \ + _(10, bpl, relative, 2); _(11, ora, indy, 5); _(12, ora, indzp, 3); _(13, ill, implied, 2); \ + _(14, trb, zp, 3); _(15, ora, zpx, 4); _(16, asl, zpx, 6); _(17, ill, implied, 2); \ + _(18, clc, implied, 2); _(19, ora, absy, 4); _(1a, ina, implied, 2); _(1b, ill, implied, 2); \ + _(1c, trb, abs, 4); _(1d, ora, absx, 4); _(1e, asl, absx, 7); _(1f, ill, implied, 2); \ + _(20, jsr, abs, 6); _(21, and, indx, 6); _(22, ill, implied, 2); _(23, ill, implied, 2); \ + _(24, bit, zp, 3); _(25, and, zp, 3); _(26, rol, zp, 5); _(27, ill, implied, 2); \ + _(28, plp, implied, 4); _(29, and, immediate, 3); _(2a, rola,implied, 2); _(2b, ill, implied, 2); \ + _(2c, bit, abs, 4); _(2d, and, abs, 4); _(2e, rol, abs, 6); _(2f, ill, implied, 2); \ + _(30, bmi, relative, 2); _(31, and, indy, 5); _(32, and, indzp, 3); _(33, ill, implied, 2); \ + _(34, bit, zpx, 4); _(35, and, zpx, 4); _(36, rol, zpx, 6); _(37, ill, implied, 2); \ + _(38, sec, implied, 2); _(39, and, absy, 4); _(3a, dea, implied, 2); _(3b, ill, implied, 2); \ + _(3c, bit, absx, 4); _(3d, and, absx, 4); _(3e, rol, absx, 7); _(3f, ill, implied, 2); \ + _(40, rti, implied, 6); _(41, eor, indx, 6); _(42, ill, implied, 2); _(43, ill, implied, 2); \ + _(44, ill, implied, 2); _(45, eor, zp, 3); _(46, lsr, zp, 5); _(47, ill, implied, 2); \ + _(48, pha, implied, 3); _(49, eor, immediate, 3); _(4a, lsra,implied, 2); _(4b, ill, implied, 2); \ + _(4c, jmp, abs, 3); _(4d, eor, abs, 4); _(4e, lsr, abs, 6); _(4f, ill, implied, 2); \ + _(50, bvc, relative, 2); _(51, eor, indy, 5); _(52, eor, indzp, 3); _(53, ill, implied, 2); \ + _(54, ill, implied, 2); _(55, eor, zpx, 4); _(56, lsr, zpx, 6); _(57, ill, implied, 2); \ + _(58, cli, implied, 2); _(59, eor, absy, 4); _(5a, phy, implied, 3); _(5b, ill, implied, 2); \ + _(5c, ill, implied, 2); _(5d, eor, absx, 4); _(5e, lsr, absx, 7); _(5f, ill, implied, 2); \ + _(60, rts, implied, 6); _(61, adc, indx, 6); _(62, ill, implied, 2); _(63, ill, implied, 2); \ + _(64, stz, zp, 3); _(65, adc, zp, 3); _(66, ror, zp, 5); _(67, ill, implied, 2); \ + _(68, pla, implied, 4); _(69, adc, immediate, 3); _(6a, rora,implied, 2); _(6b, ill, implied, 2); \ + _(6c, jmp, indirect, 5); _(6d, adc, abs, 4); _(6e, ror, abs, 6); _(6f, ill, implied, 2); \ + _(70, bvs, relative, 2); _(71, adc, indy, 5); _(72, adc, indzp, 3); _(73, ill, implied, 2); \ + _(74, stz, zpx, 4); _(75, adc, zpx, 4); _(76, ror, zpx, 6); _(77, ill, implied, 2); \ + _(78, sei, implied, 2); _(79, adc, absy, 4); _(7a, ply, implied, 4); _(7b, ill, implied, 2); \ + _(7c, jmp, indabsx, 6); _(7d, adc, absx, 4); _(7e, ror, absx, 7); _(7f, ill, implied, 2); \ + _(80, bra, relative, 2); _(81, sta, indx, 6); _(82, ill, implied, 2); _(83, ill, implied, 2); \ + _(84, sty, zp, 2); _(85, sta, zp, 2); _(86, stx, zp, 2); _(87, ill, implied, 2); \ + _(88, dey, implied, 2); _(89, bit, immediate, 2); _(8a, txa, implied, 2); _(8b, ill, implied, 2); \ + _(8c, sty, abs, 4); _(8d, sta, abs, 4); _(8e, stx, abs, 4); _(8f, ill, implied, 2); \ + _(90, bcc, relative, 2); _(91, sta, indy, 6); _(92, sta, indzp, 3); _(93, ill, implied, 2); \ + _(94, sty, zpx, 4); _(95, sta, zpx, 4); _(96, stx, zpy, 4); _(97, ill, implied, 2); \ + _(98, tya, implied, 2); _(99, sta, absy, 5); _(9a, txs, implied, 2); _(9b, ill, implied, 2); \ + _(9c, stz, abs, 4); _(9d, sta, absx, 5); _(9e, stz, absx, 5); _(9f, ill, implied, 2); \ + _(a0, ldy, immediate, 3); _(a1, lda, indx, 6); _(a2, ldx, immediate, 3); _(a3, ill, implied, 2); \ + _(a4, ldy, zp, 3); _(a5, lda, zp, 3); _(a6, ldx, zp, 3); _(a7, ill, implied, 2); \ + _(a8, tay, implied, 2); _(a9, lda, immediate, 3); _(aa, tax, implied, 2); _(ab, ill, implied, 2); \ + _(ac, ldy, abs, 4); _(ad, lda, abs, 4); _(ae, ldx, abs, 4); _(af, ill, implied, 2); \ + _(b0, bcs, relative, 2); _(b1, lda, indy, 5); _(b2, lda, indzp, 3); _(b3, ill, implied, 2); \ + _(b4, ldy, zpx, 4); _(b5, lda, zpx, 4); _(b6, ldx, zpy, 4); _(b7, ill, implied, 2); \ + _(b8, clv, implied, 2); _(b9, lda, absy, 4); _(ba, tsx, implied, 2); _(bb, ill, implied, 2); \ + _(bc, ldy, absx, 4); _(bd, lda, absx, 4); _(be, ldx, absy, 4); _(bf, ill, implied, 2); \ + _(c0, cpy, immediate, 3); _(c1, cmp, indx, 6); _(c2, ill, implied, 2); _(c3, ill, implied, 2); \ + _(c4, cpy, zp, 3); _(c5, cmp, zp, 3); _(c6, dec, zp, 5); _(c7, ill, implied, 2); \ + _(c8, iny, implied, 2); _(c9, cmp, immediate, 3); _(ca, dex, implied, 2); _(cb, ill, implied, 2); \ + _(cc, cpy, abs, 4); _(cd, cmp, abs, 4); _(ce, dec, abs, 6); _(cf, ill, implied, 2); \ + _(d0, bne, relative, 2); _(d1, cmp, indy, 5); _(d2, cmp, indzp, 3); _(d3, ill, implied, 2); \ + _(d4, ill, implied, 2); _(d5, cmp, zpx, 4); _(d6, dec, zpx, 6); _(d7, ill, implied, 2); \ + _(d8, cld, implied, 2); _(d9, cmp, absy, 4); _(da, phx, implied, 3); _(db, ill, implied, 2); \ + _(dc, ill, implied, 2); _(dd, cmp, absx, 4); _(de, dec, absx, 7); _(df, ill, implied, 2); \ + _(e0, cpx, immediate, 3); _(e1, sbc, indx, 6); _(e2, ill, implied, 2); _(e3, ill, implied, 2); \ + _(e4, cpx, zp, 3); _(e5, sbc, zp, 3); _(e6, inc, zp, 5); _(e7, ill, implied, 2); \ + _(e8, inx, implied, 2); _(e9, sbc, immediate, 3); _(ea, nop, implied, 2); _(eb, ill, implied, 2); \ + _(ec, cpx, abs, 4); _(ed, sbc, abs, 4); _(ee, inc, abs, 6); _(ef, ill, implied, 2); \ + _(f0, beq, relative, 2); _(f1, sbc, indy, 5); _(f2, sbc, indzp, 3); _(f3, ill, implied, 2); \ + _(f4, ill, implied, 2); _(f5, sbc, zpx, 4); _(f6, inc, zpx, 6); _(f7, ill, implied, 2); \ + _(f8, sed, implied, 2); _(f9, sbc, absy, 4); _(fa, plx, implied, 4); _(fb, ill, implied, 2); \ + _(fc, ill, implied, 2); _(fd, sbc, absx, 4); _(fe, inc, absx, 7); _(ff, ill, implied, 2); + + + +void M6502_irq(M6502 *mpu) +{ + if (!(mpu->registers->p & flagI)) + { + mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc >> 8); + mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc & 0xff); + mpu->memory[0x0100 + mpu->registers->s--] = mpu->registers->p; + mpu->registers->p &= ~flagB; + mpu->registers->p |= flagI; + mpu->registers->pc = M6502_getVector(mpu, IRQ); + } +} + + +void M6502_nmi(M6502 *mpu) +{ + mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc >> 8); + mpu->memory[0x0100 + mpu->registers->s--] = (byte)(mpu->registers->pc & 0xff); + mpu->memory[0x0100 + mpu->registers->s--] = mpu->registers->p; + mpu->registers->p &= ~flagB; + mpu->registers->p |= flagI; + mpu->registers->pc = M6502_getVector(mpu, NMI); +} + + +void M6502_reset(M6502 *mpu) +{ + mpu->registers->p &= ~flagD; + mpu->registers->p |= flagI; + mpu->registers->pc = M6502_getVector(mpu, RST); +} + + +/* the compiler should elminate all call to this function */ + +static void oops(void) +{ + fprintf(stderr, "\noops -- instruction dispatch missing\n"); +} + + +void M6502_run(M6502 *mpu) +{ +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) + + static void *itab[256]= { &&_00, &&_01, &&_02, &&_03, &&_04, &&_05, &&_06, &&_07, &&_08, &&_09, &&_0a, &&_0b, &&_0c, &&_0d, &&_0e, &&_0f, + &&_10, &&_11, &&_12, &&_13, &&_14, &&_15, &&_16, &&_17, &&_18, &&_19, &&_1a, &&_1b, &&_1c, &&_1d, &&_1e, &&_1f, + &&_20, &&_21, &&_22, &&_23, &&_24, &&_25, &&_26, &&_27, &&_28, &&_29, &&_2a, &&_2b, &&_2c, &&_2d, &&_2e, &&_2f, + &&_30, &&_31, &&_32, &&_33, &&_34, &&_35, &&_36, &&_37, &&_38, &&_39, &&_3a, &&_3b, &&_3c, &&_3d, &&_3e, &&_3f, + &&_40, &&_41, &&_42, &&_43, &&_44, &&_45, &&_46, &&_47, &&_48, &&_49, &&_4a, &&_4b, &&_4c, &&_4d, &&_4e, &&_4f, + &&_50, &&_51, &&_52, &&_53, &&_54, &&_55, &&_56, &&_57, &&_58, &&_59, &&_5a, &&_5b, &&_5c, &&_5d, &&_5e, &&_5f, + &&_60, &&_61, &&_62, &&_63, &&_64, &&_65, &&_66, &&_67, &&_68, &&_69, &&_6a, &&_6b, &&_6c, &&_6d, &&_6e, &&_6f, + &&_70, &&_71, &&_72, &&_73, &&_74, &&_75, &&_76, &&_77, &&_78, &&_79, &&_7a, &&_7b, &&_7c, &&_7d, &&_7e, &&_7f, + &&_80, &&_81, &&_82, &&_83, &&_84, &&_85, &&_86, &&_87, &&_88, &&_89, &&_8a, &&_8b, &&_8c, &&_8d, &&_8e, &&_8f, + &&_90, &&_91, &&_92, &&_93, &&_94, &&_95, &&_96, &&_97, &&_98, &&_99, &&_9a, &&_9b, &&_9c, &&_9d, &&_9e, &&_9f, + &&_a0, &&_a1, &&_a2, &&_a3, &&_a4, &&_a5, &&_a6, &&_a7, &&_a8, &&_a9, &&_aa, &&_ab, &&_ac, &&_ad, &&_ae, &&_af, + &&_b0, &&_b1, &&_b2, &&_b3, &&_b4, &&_b5, &&_b6, &&_b7, &&_b8, &&_b9, &&_ba, &&_bb, &&_bc, &&_bd, &&_be, &&_bf, + &&_c0, &&_c1, &&_c2, &&_c3, &&_c4, &&_c5, &&_c6, &&_c7, &&_c8, &&_c9, &&_ca, &&_cb, &&_cc, &&_cd, &&_ce, &&_cf, + &&_d0, &&_d1, &&_d2, &&_d3, &&_d4, &&_d5, &&_d6, &&_d7, &&_d8, &&_d9, &&_da, &&_db, &&_dc, &&_dd, &&_de, &&_df, + &&_e0, &&_e1, &&_e2, &&_e3, &&_e4, &&_e5, &&_e6, &&_e7, &&_e8, &&_e9, &&_ea, &&_eb, &&_ec, &&_ed, &&_ee, &&_ef, + &&_f0, &&_f1, &&_f2, &&_f3, &&_f4, &&_f5, &&_f6, &&_f7, &&_f8, &&_f9, &&_fa, &&_fb, &&_fc, &&_fd, &&_fe, &&_ff }; + + register void **itabp= &itab[0]; + register void *tpc; + +# define begin() fetch(); next() +# define fetch() tpc= itabp[memory[PC++]] +# define next() goto *tpc +# define dispatch(num, name, mode, cycles) _##num: name(cycles, mode) oops(); next() +# define end() + +#else /* (!__GNUC__) || (__STRICT_ANSI__) */ + +# define begin() for (;;) switch (memory[PC++]) { +# define fetch() +# define next() break +# define dispatch(num, name, mode, cycles) case 0x##num: name(cycles, mode); next() +# define end() } + +#endif + + register byte *memory= mpu->memory; + register word PC; + word ea; + byte A, X, Y, P, S; + M6502_Callback *readCallback= mpu->callbacks->read; + M6502_Callback *writeCallback= mpu->callbacks->write; + +# define internalise() A= mpu->registers->a; X= mpu->registers->x; Y= mpu->registers->y; P= mpu->registers->p; S= mpu->registers->s; PC= mpu->registers->pc +# define externalise() mpu->registers->a= A; mpu->registers->x= X; mpu->registers->y= Y; mpu->registers->p= P; mpu->registers->s= S; mpu->registers->pc= PC + + internalise(); + + begin(); + do_insns(dispatch); + end(); + +# undef begin +# undef internalise +# undef externalise +# undef fetch +# undef next +# undef dispatch +# undef end + + (void)oops; +} + + +int M6502_disassemble(M6502 *mpu, word ip, char buffer[64]) +{ + char *s= buffer; + byte *b= mpu->memory + ip; + + switch (b[0]) + { +# define _implied return 1; +# define _immediate sprintf(s, "#%02X", b[1]); return 2; +# define _zp sprintf(s, "%02X", b[1]); return 2; +# define _zpx sprintf(s, "%02X,X", b[1]); return 2; +# define _zpy sprintf(s, "%02X,Y", b[1]); return 2; +# define _abs sprintf(s, "%02X%02X", b[2], b[1]); return 3; +# define _absx sprintf(s, "%02X%02X,X", b[2], b[1]); return 3; +# define _absy sprintf(s, "%02X%02X,Y", b[2], b[1]); return 3; +# define _relative sprintf(s, "%04X", ip + 2 + (int8_t)b[1]); return 2; +# define _indirect sprintf(s, "(%02X%02X)", b[2], b[1]); return 3; +# define _indzp sprintf(s, "(%02X)", b[1]); return 2; +# define _indx sprintf(s, "(%02X,X)", b[1]); return 2; +# define _indy sprintf(s, "(%02X),Y", b[1]); return 2; +# define _indabsx sprintf(s, "(%02X%02X,X)", b[2], b[1]); return 3; + +# define disassemble(num, name, mode, cycles) case 0x##num: s += sprintf(s, "%s ", #name); _##mode + do_insns(disassemble); +# undef _do + } + + return 0; +} + + +void M6502_dump(M6502 *mpu, char buffer[64]) +{ + M6502_Registers *r= mpu->registers; + uint8_t p= r->p; +# define P(N,C) (p & (1 << (N)) ? (C) : '-') + sprintf(buffer, "PC=%04X SP=%04X A=%02X X=%02X Y=%02X P=%02X %c%c%c%c%c%c%c%c", + r->pc, 0x0100 + r->s, + r->a, r->x, r->y, r->p, + P(7,'N'), P(6,'V'), P(5,'?'), P(4,'B'), P(3,'D'), P(2,'I'), P(1,'Z'), P(0,'C')); +# undef P +} + + +static void outOfMemory(void) +{ + fflush(stdout); + fprintf(stderr, "\nout of memory\n"); + abort(); +} + + +M6502 *M6502_new(M6502_Registers *registers, M6502_Memory memory, M6502_Callbacks *callbacks) +{ + M6502 *mpu= calloc(1, sizeof(M6502)); + if (!mpu) outOfMemory(); + + if (!registers) { registers = (M6502_Registers *)calloc(1, sizeof(M6502_Registers)); mpu->flags |= M6502_RegistersAllocated; } + if (!memory ) { memory = (uint8_t *)calloc(1, sizeof(M6502_Memory )); mpu->flags |= M6502_MemoryAllocated; } + if (!callbacks) { callbacks = (M6502_Callbacks *)calloc(1, sizeof(M6502_Callbacks)); mpu->flags |= M6502_CallbacksAllocated; } + + if (!registers || !memory || !callbacks) outOfMemory(); + + mpu->registers = registers; + mpu->memory = memory; + mpu->callbacks = callbacks; + + return mpu; +} + + +void M6502_delete(M6502 *mpu) +{ + if (mpu->flags & M6502_CallbacksAllocated) free(mpu->callbacks); + if (mpu->flags & M6502_MemoryAllocated ) free(mpu->memory); + if (mpu->flags & M6502_RegistersAllocated) free(mpu->registers); + + free(mpu); +} diff --git a/lib6502/lib6502.h b/lib6502/lib6502.h new file mode 100644 index 0000000..7039833 --- /dev/null +++ b/lib6502/lib6502.h @@ -0,0 +1,75 @@ +#ifndef __m6502_h +#define __m6502_h + + +#include +#include + +typedef struct _M6502 M6502; +typedef struct _M6502_Registers M6502_Registers; +typedef struct _M6502_Callbacks M6502_Callbacks; + +typedef int (*M6502_Callback)(M6502 *mpu, uint16_t address, uint8_t data); + +typedef M6502_Callback M6502_CallbackTable[0x10000]; +typedef uint8_t M6502_Memory[0x10000]; + +enum { + M6502_NMIVector= 0xfffa, M6502_NMIVectorLSB= 0xfffa, M6502_NMIVectorMSB= 0xfffb, + M6502_RSTVector= 0xfffc, M6502_RSTVectorLSB= 0xfffc, M6502_RSTVectorMSB= 0xfffd, + M6502_IRQVector= 0xfffe, M6502_IRQVectorLSB= 0xfffe, M6502_IRQVectorMSB= 0xffff +}; + +struct _M6502_Registers +{ + uint8_t a; /* accumulator */ + uint8_t x; /* X index register */ + uint8_t y; /* Y index register */ + uint8_t p; /* processor status register */ + uint8_t s; /* stack pointer */ + uint16_t pc; /* program counter */ +}; + +struct _M6502_Callbacks +{ + M6502_CallbackTable read; + M6502_CallbackTable write; + M6502_CallbackTable call; +}; + +struct _M6502 +{ + M6502_Registers *registers; + uint8_t *memory; + M6502_Callbacks *callbacks; + unsigned int flags; +}; + +enum { + M6502_RegistersAllocated = 1 << 0, + M6502_MemoryAllocated = 1 << 1, + M6502_CallbacksAllocated = 1 << 2 +}; + +extern M6502 *M6502_new(M6502_Registers *registers, M6502_Memory memory, M6502_Callbacks *callbacks); +extern void M6502_reset(M6502 *mpu); +extern void M6502_nmi(M6502 *mpu); +extern void M6502_irq(M6502 *mpu); +extern void M6502_run(M6502 *mpu); +extern int M6502_disassemble(M6502 *mpu, uint16_t addr, char buffer[64]); +extern void M6502_dump(M6502 *mpu, char buffer[64]); +extern void M6502_delete(M6502 *mpu); + +#define M6502_getVector(MPU, VEC) \ + ( ( ((MPU)->memory[M6502_##VEC##VectorLSB]) ) \ + | ((MPU)->memory[M6502_##VEC##VectorMSB] << 8) ) + +#define M6502_setVector(MPU, VEC, ADDR) \ + ( ( ((MPU)->memory[M6502_##VEC##VectorLSB]= ((uint8_t)(ADDR)) & 0xff) ) \ + , ((MPU)->memory[M6502_##VEC##VectorMSB]= (uint8_t)((ADDR) >> 8)) ) + +#define M6502_getCallback(MPU, TYPE, ADDR) ((MPU)->callbacks->TYPE[ADDR]) +#define M6502_setCallback(MPU, TYPE, ADDR, FN) ((MPU)->callbacks->TYPE[ADDR]= (FN)) + + +#endif __m6502_h diff --git a/lib6502/man/M6502_delete.3 b/lib6502/man/M6502_delete.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_delete.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_disassemble.3 b/lib6502/man/M6502_disassemble.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_disassemble.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_dump.3 b/lib6502/man/M6502_dump.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_dump.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_getCallback.3 b/lib6502/man/M6502_getCallback.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_getCallback.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_getVector.3 b/lib6502/man/M6502_getVector.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_getVector.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_irq.3 b/lib6502/man/M6502_irq.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_irq.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_new.3 b/lib6502/man/M6502_new.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_new.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_nmi.3 b/lib6502/man/M6502_nmi.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_nmi.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_reset.3 b/lib6502/man/M6502_reset.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_reset.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_run.3 b/lib6502/man/M6502_run.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_run.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_setCallback.3 b/lib6502/man/M6502_setCallback.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_setCallback.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/M6502_setVector.3 b/lib6502/man/M6502_setVector.3 new file mode 100644 index 0000000..4bd1ff4 --- /dev/null +++ b/lib6502/man/M6502_setVector.3 @@ -0,0 +1 @@ +.so man3/lib6502.3 diff --git a/lib6502/man/lib6502.3 b/lib6502/man/lib6502.3 new file mode 100644 index 0000000..ba96e93 --- /dev/null +++ b/lib6502/man/lib6502.3 @@ -0,0 +1,508 @@ +.\" Copyright (c) 2005 Ian Piumarta +.\" +.\" Permission is hereby granted, free of charge, to any person +.\" obtaining a copy of this software and associated documentation +.\" files (the 'Software'), to deal in the Software without +.\" restriction, including without limitation the rights to use, copy, +.\" modify, merge, publish, distribute, and/or sell copies of the +.\" Software, and to permit persons to whom the Software is furnished +.\" to do so, provided that the above copyright notice(s) and this +.\" permission notice appear in all copies of the Software and that +.\" both the above copyright notice(s) and this permission notice +.\" appear in supporting documentation. +.\" +.\" THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. +.\" +.\" last edited: 2005-11-02 01:18:07 by piumarta on margaux.local +.\" +.Dd October 31, 2005 +.Dt LIB6502 3 LOCAL +.Os "" +.\" ---------------------------------------------------------------- +.Sh NAME +.\" +.Nm lib6502 +.Nd 6502 microprocessor emulator +.\" ---------------------------------------------------------------- +.Sh SYNOPSIS +.\" +.In stdint.h +.In lib6502.h +.Ft M6502 * +.Fn M6502_new "M6502_Registers *registers" "M6502_Memory memory" "M6502_Callbacks *callbacks" +.Ft void +.Fn M6502_reset "M6502 *mpu" +.Ft void +.Fn M6502_nmi "M6502 *mpu" +.Ft void +.Fn M6502_irq "M6502 *mpu" +.Ft uint16_t +.Fn M6502_getVector "M6502 *mpu" "vector" +.Ft uint16_t +.Fn M6502_setVector "M6502 *mpu" "vector" "uint16_t address" +.Ft M6502_Callback +.Fn M6502_getCallback "M6502 *mpu" "type" "uint16_t address" +.Ft M6502_Callback +.Fn M6502_setCallback "M6502 *mpu" "type" "uint16_t address" "M6502_Callback callback" +.Ft void +.Fn M6502_run "M6502 *mpu" +.Ft int +.Fn M6502_disassemble "M6502 *mpu" "uint16_t address" "char buffer[64]" +.Ft void +.Fn M6502_dump "M6502 *mpu" "char buffer[64]" +.Ft void +.Fn M6502_delete "M6502 *mpu" +.\" ---------------------------------------------------------------- +.Sh DESCRIPTION +.\" +.Fn M6502_new +creates an instance of a 6502 microprocessor. +.Fn M6502_reset , +.Fn M6502_nmi +and +.Fn M6502_irq +place it into the states associated with the hardware signals for +reset, non-maskable interrupt and interrupt request, respectively. +The macros +.Fn M6502_getVector +and +.Fn M6502_setVector +read and write the vectors through which the processor jumps in +response to the above signals. The macros +.Fn M6502_getCallback +and +.Fn M6502_setVector +read and write client-supplied functions that intercept accesses to +memory. +.Fn M6502_run +begins emulated execution. +.Fn M6502_dump +and +.Fn M6502_disassemble +create human-readable representations of processor or memory state. +.Fn M6502_delete +frees all resources associated with a processor instance. Each of +these functions and macros is described in more detail below. +.Pp +.Fn M6502_new +returns a pointer to a +.Fa M6502 +structure containing at least the following members: +.Bd -literal +struct _M6502 +{ + M6502_Registers *registers; /* processor state */ + uint8_t *memory; /* memory image */ + M6502_Callbacks *callbacks; /* r/w/x callbacks */ +}; +.Ed +.Pp +These members are initialised according to the supplied +.Fa registers , +.Fa memory +and +.Fa callbacks +arguments. If a given argument is NULL, the corresponding member is +initialised automatically with a suitable (non-NULL) value. +.Pp +The members of +.Fa M6502 +are as follows: +.Bl -tag -width ".Fa callbacks" +.It Fa registers +the processor state, containing all registers and condition codes. +.It Fa memory +a block of at least 64 kilobytes of storage containing the processor's +memory. (An array type +.Vt M6502_Memory, +suitable for defining values to pass as the +.Fa memory +argument, is defined in the +.In lib6502.h +include file.) +.It Fa callbacks +a structure mapping processor memory accesses to client callback +functions. +.El +.Pp +Access to the contents of the +.Fa registers +and +.Fa memory +members can be made directly. +The +.Fa registers +member is a +.Vt M6502_Registers +containing the following members: +.Bd -literal +struct _M6502_Registers +{ + uint8_t a; /* accumulator */ + uint8_t x; /* X index register */ + uint8_t y; /* Y index register */ + uint8_t p; /* processor status register */ + uint8_t s; /* stack pointer */ + uint16_t pc; /* program counter */ +}; +.Ed +.Pp +The +.Fa memory +member is an array of +.Vt unsigned char +and can be indexed directly. In addition, two convenience macros +.Fn M6502_getVector +and +.Fn M6502_setVector +provide access to the reset and interrupt vectors within +.Fa memory . +.Fn M6502_getVector +returns the address stored in the named +.Fa vector +which must be precisely one of the following: +.Bl -tag -width ".Dv RST" -offset indent +.It Dv RST +the reset vector. +.It Dv NMI +the non-maskable interrupt vector. +.It Dv IRQ +the interrupt request vector. +.El +.Pp +.Fn M6502_setVector +stores its +.Fa address +argument in the named +.Fa vector +and returns the new value. +.Pp +The +.Fa callbacks +member contains an opaque structure mapping processor memory accesses +to client callback functions. Whenever the processor performs an +access for which a corresponding entry exists in the the +.Fa callbacks +structure, the emulator suspends execution and invokes the callback to +complete the operation. Each callback function should have a +signature equivalent to: +.Bd -ragged -offset indent +int +.Va callback +(M6502 *mpu, uint16_t address, uint8_t data); +.Ed +.Pp +The macros +.Fn M6502_getCallback +and +.Fn M6502_setCallback +read and write entries in the +.Fa callbacks +structure. These macros identify a unique memory access operation +from the specified +.Fa address +on which it operates and +.Fa type +of access involved. The +.Fa type +argument must be one of the following: +.Bl -tag -width ".Dv write" +.It Dv read +the +.Fa callback +is invoked when the processor attempts to read from the +given address. The emulator passes the effective address of the +operation to the callback in its +.Fa address +argument. (The +.Fa data +argument is undefined.) The value returned by the callback will be +used by the emulator as the result of the read operation. +.It Dv write +the +.Fa callback +is invoked when the processor attempts to write to the +given address. The emulator passes the effective address of the +operation to the callback in its +.Fa address +argument and the byte being written in the +.Fa data +argument. The emulator will not perform the write operation before +invoking the callback; if the write should complete, the callback must +modify the processor's +.Fa memory +explicitly. The valued returned from the callback is ignored. +.It Dv call +the +.Fa callback +is invoked when the processor attempts to transfer control to the +given address by any instruction other than a relative branch. The +emulator passes the destination address to the callback in its +.Fa address +argument and the instruction that initiated the control transfer in +its +.Fa data +argument (one of JMP, JSR, BRK, RTS or RTI). If the callback returns +zero (the callback refuses to handle the operation) the emulator will +allow the operation to complete as normal. If the callback returns a +non-zero address (indicating that the callback has handled the +operation internally) the emulator will transfer control to that +address. +.El +.Pp +.Fn M6502_getCallback +returns zero if there is no callback associated with the given +.Fa type +and +.Fa address . +Passing zero as the +.Fa callback +argument of +.Fn M6502_setCallback +removes any callback that might have been associated with +.Fa type +and +.Fa address . +.Pp +.Fn M6502_run +emulates processor execution in the given +.Fa mpu +by repeatedly fetching the instruction addressed by +.Fa pc +and dispatching to it. This function normally never returns. +.Pp +.Fn M6502_dump +writes a (NUL-terminated) symbolic representation of the processor's +internal state into the supplied +.Fa buffer . +Typical output resembles: +.Bd -literal -offset indent +PC=1010 SP=01FE A=0A X=5B Y=00 P=D1 NV-B---C +.Ed +.Pp +.Fn M6502_disassemble +writes a (NUL-terminated) symbolic representation of the instruction +in the processor's memory at the given +.Fa address +into the supplied +.Fa buffer . +It returns the size (in bytes) of the instruction. (In other words, +the amount by which +.Fa address +should be incremented to arrive at the next instruction.) +Typical output resembles: +.Bd -literal -offset indent +1009 cpx #5B +.Ed +.Pp +(The +.Fa buffer +arguments are oversized to allow for future expansion.) +.Pp +.Fn M6502_delete +frees the resources associated with the given +.Fa mpu. +Any members that were allocated implicitly (passed as NULL to +.Fn M6502_new ) +are deallocated. Members that were initialised from non-NULL +arguments are not deallocated. +.\" ---------------------------------------------------------------- +.Sh IMPLEMENTATION NOTES +.\" +You can share the +.Fa memory +and +.Fa callbacks +members of +.Vt M6502 +between multiple instances to simulate multiprocessor hardware. +.\" ---------------------------------------------------------------- +.Sh RETURN VALUES +.\" +.Fn M6502_new +returns a pointer to a +.Vt M6502 +structure. +.Fn M6502_getVector +and +.Fn M6502_setVector +return the contents of the given +.Fa vector . +.Fn M6502_getCallback +and +.Fn M6502_setCallback +return the +.Vt M6502_Callback +function associated with the given +.Fa address +and access +.Fa type . +.Fn M6502_disassemble +returns the size (in bytes) of the instruction at the given +.Fa address . +.Fn M6502_reset , +.Fn M6502_nmi , +.Fn M6502_irq , +.Fn M6502_run , +.Fn M6502_dump +and +.Fn M6502_delete +don't return anything (unless you forgot to include +.In lib6502.h ) . +.\" ---------------------------------------------------------------- +.Sh EXAMPLES +.\" +The following program creates a 6502 processor, sets up callbacks for +printing characters and halting after a BRK instruction, stores a +program into memory that prints the alphabet, disassembles the program +on stdout, and then executes the program. +.Bd -literal -offset indent -compact + +#include +#include +#include + +#include "lib6502.h" + +#define WRCH 0xFFEE + +int wrch(M6502 *mpu, uint16_t address, uint8_t data) +{ + int pc; + putchar(mpu->registers->a); + pc = mpu->memory[++mpu->registers->s + 0x100]; + pc |= mpu->memory[++mpu->registers->s + 0x100] << 8; + return pc + 1; /* JSR pushes next insn addr - 1 */ +} + +int done(M6502 *mpu, uint16_t address, uint8_t data) +{ + char buffer[64]; + M6502_dump(mpu, buffer); + printf("\\nBRK instruction\\n%s\\n", buffer); + exit(0); +} + +int main(int argc, char **argv) +{ + M6502 *mpu = M6502_new(0, 0, 0); + unsigned pc = 0x1000; + + mpu->callbacks->call[WRCH] = wrch; /* write character */ + mpu->callbacks->call[0000] = done; /* reached after BRK */ + +# define gen1(X) (mpu->memory[pc++] = (uint8_t)(X)) +# define gen2(X,Y) gen1(X); gen1(Y) +# define gen3(X,Y,Z) gen1(X); gen2(Y,Z) + + gen2(0xA2, 'A' ); /* LDX #'A' */ + gen1(0x8A ); /* TXA */ + gen3(0x20,0xEE,0xFF); /* JSR FFEE */ + gen1(0xE8 ); /* INX */ + gen2(0xE0, 'Z'+1 ); /* CPX #'Z'+1 */ + gen2(0xD0, -9 ); /* BNE 1002 */ + gen2(0xA9, '\\n' ); /* LDA #'\\n' */ + gen3(0x20,0xEE,0xFF); /* JSR FFEE */ + gen2(0x00,0x00 ); /* BRK */ + + { + uint16_t ip = 0x1000; + while (ip < pc) + { + char insn[64]; + ip += M6502_disassemble(mpu, ip, insn); + printf("%04X %s\\n", ip, insn); + } + } + + M6502_setVector(mpu, RST, 0x1000); + + M6502_reset(mpu); + M6502_run(mpu); + M6502_delete(mpu); + + return 0; +} +.Ed +.\" ---------------------------------------------------------------- +.Sh DIAGNOSTICS +.\" +If +.Fn M6502_new +cannot allocate sufficient memory it prints "out of memory" to stderr +and exits with a non-zero status. +.Pp +If +.Fn M6502_run +encounters an illegal or undefined instruction, it prints "undefined +instruction" and the processor's state to stderr, then exits with a +non-zero status. +.\" ---------------------------------------------------------------- +.Sh COMPATIBILITY +.\" +M6502 is a generic name. The initial letter is mandated by C naming +conventions and chosen in deference to MOS Technology, the original +designers of the processor. To the best of my knowledge the 'M' +prefix was never stamped on a physical 6502. +.Pp +The emulator implements the CMOS version of the processor (NMOS bugs +in effective address calculations involving page boundaries are +corrected) but does not tolerate the execution of undefined +instructions (which were all no-ops in the first-generation CMOS +hardware). It would be nice to support the several alternative +instruction sets (model-specific undocumented instructions in NMOS +models, and various documented extensions in the later CMOS models) +but there are currently no plans to do so. +.Pp +The emulated 6502 will run much faster than real hardware on any +modern computer. The fastest 6502 hardware available at the time of +writing has a clock speed of 14 MHz. On a 2 GHz PowerPC, the emulated +6502 runs at almost 300 MHz. +.\" ---------------------------------------------------------------- +.Sh SEE ALSO +.\" +.Xr run6502 1 +.Pp +For development tools, documentation and source code: +.Pa http://6502.org +.\" ---------------------------------------------------------------- +.Sh AUTHORS +.\" +The software and manual pages were written by Ian Piumarta. +.Pp +The software is provided as-is, with absolutely no warranty, in the +hope that you will enjoy and benefit from it. You may use (entirely +at your own risk) and redistribute it under the terms of a very +liberal license that does not seek to restrict your rights in any way +(unlike certain so-called 'open source' licenses that significantly +limit your freedom in the name of 'free' software that is, ultimately, +anything but free). See the file COPYING for details. +.\" ---------------------------------------------------------------- +.Sh BUGS +.\" +.Fn M6502_getVector +and +.Fn M6502_setVector +evaluate their arguments more than once. +.Pp +The out-of-memory condition and attempted execution of +illegal/undefined instructions should not be fatal errors. +.Pp +There is no way to limit the duration of execution within +.Fn M6502_run +to a certain number of instructions or cycles. +.Pp +The emulator should support some means of implicit interrupt +generation, either by polling or in response to (Unix) signals. +.Pp +The +.Sx COMPATIBILITY +section in this manual page has been diverted from its legitimate +purpose. +.Pp +The plural of 'callback' really aught to be 'callsback'. +.Pp +Please send bug reports (and feature requests) to the author at: +firstName (at) lastName (dot) com. (See +.Sx AUTHORS +above for suitable values of firstName and lastName.) diff --git a/lib6502/man/run6502.1 b/lib6502/man/run6502.1 new file mode 100644 index 0000000..d11bf18 --- /dev/null +++ b/lib6502/man/run6502.1 @@ -0,0 +1,380 @@ +.\" Copyright (c) 2005 Ian Piumarta +.\" +.\" Permission is hereby granted, free of charge, to any person +.\" obtaining a copy of this software and associated documentation +.\" files (the 'Software'), to deal in the Software without +.\" restriction, including without limitation the rights to use, copy, +.\" modify, merge, publish, distribute, and/or sell copies of the +.\" Software, and to permit persons to whom the Software is furnished +.\" to do so, provided that the above copyright notice(s) and this +.\" permission notice appear in all copies of the Software and that +.\" both the above copyright notice(s) and this permission notice +.\" appear in supporting documentation. +.\" +.\" THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. +.\" +.\" last edited: 2005-11-02 01:18:22 by piumarta on margaux.local +.\" +.Dd October 31, 2005 +.Dt RUN6502 1 LOCAL +.Os "" +.\" ---------------------------------------------------------------- +.Sh NAME +.\" +.Nm run6502 +.Nd execute a 6502 microprocessor program +.\" ---------------------------------------------------------------- +.Sh SYNOPSIS +.\" +.Nm run6502 +.Op Ar option ... +.Nm run6502 +.Op Ar option ... +.Fl B +.Op Ar +.\" ---------------------------------------------------------------- +.Sh DESCRIPTION +The +.Nm run6502 +command emulates the execution of a 6502 microprocessor. It creates a +memory image from the contents of one or more files on the command +line and then simulates a power-on hardware reset to begin execution. +.Pp +In its first form, +.Nm run6502 +emulates an embedded 6502 processor with 64 kilobytes of RAM, no +memory-mapped hardware, and no input-output capabilities. Limited +interaction with the machine is possible only through the +.Fl G , M +and +.Fl P +options. +.Pp +In its second form (with the +.Fl B +option) +.Nm run6502 +provides minimal emulation of Acorn 'BBC Model B' hardware with 32 +kilobytes of RAM, 16 kilobytes of paged language ROMs, and 16 +kilobytes of operating system ROM. A few MOS calls are intercepted to +provide keyboard input and screen output via stdin and stdout. +Switching between the sixteen paged read-only memory banks is also +supported by the usual memory-mapped control register. Any +.Ar file +arguments after the +.Fl B +are loaded into successive paged ROM banks (starting at 15 and working +down towards 0) before execution begins. +.\" ---------------------------------------------------------------- +.Ss Options +.\" +.Bl -tag -width indent +.It Fl B +enable minimal Acorn 'BBC Model B' hardware emulation: +.Bl -bullet +.It +the contents of memory between addresses 0x8000 and 0xBFFF are copied +into paged ROM number 0; +.It +memory between 0x8000 and 0xBFFF becomes bank-switchable between +sixteen different ROM images; +.It +the memory-mapped pages ('FRED', 'JIM' and 'SHEILA') between 0xFC00 +and 0xFEFF are initialised to harmless values; +.It +the upper half of the address space is write-protected; and +.It +callbacks are installed on several OS entry points to provide +input-output via stdin and stdout. +.El +.Pp +Any remaining non-option arguments on the command line will name files +to be loaded successively into paged ROMs, starting at 15 and working +downwards towards 0. +.It Fl d Ar addr Ar end +dump memory from the address +.Ar addr +(given in hexadecimal) up to (but not including) +.Ar end . +The +.Ar end +argument is either an absolute address or a relative address specified +as a '+' character followed by the number (in hexadecimal) of bytes to +dump. In other words, the following two options dump the same region +of memory: +.Bd -ragged -offset indent +.Fl d +8000 C000 +.Ed +.Bd -ragged -offset indent -compact +.Fl d +8000 +4000 +.Ed +.Pp +The format of the dump cannot currently be modified and consists of +the current address followed by one, two or three hexadecimal bytes, +and a symbolic representation of the instruction at that address. +.It Fl G Ar addr +arrange that subroutine calls to +.Ar addr +will behave as if there were an implementation of +.Xr getchar 3 +at that address, reading a character from stdin and returning it in +the accumulator. +.It Fl h +print a summary of the available options and then exit. +.It Fl I Ar addr +set the IRQ (interrupt request) vector (the address to which the +processor will transfer control upon execution of a BRK instruction). +Setting this address to zero will cause execution to halt (and the +emulator to exit) when a BRK instruction is encountered. +.It Fl i Ar addr Ar file +Load +.Ar file +into the memory image at the address +.Ar addr +(in hexadecimal), skipping over any initial '#!' interpreter line. +.It Fl l Ar addr Ar file +Load +.Ar file +into the memory image at the address +.Ar addr +(in hexadecimal). +.It Fl M Ar addrio +arrange that memory reads from address +.Ar addrio +will return the next character on stdin (blocking if necessary), and +memory writes to +.Ar addrio +will send the value written to stdout. +.It Fl N Ar addr +set the NMI (non-maskable interrupt) vector to +.Ar addr . +.It Fl P Ar addr +arrange that subroutine calls to +.Ar addr +will behave as if there were an implementation of +.Xr putchar 3 +at that address, writing the contents of the accumulator to stdout. +.It Fl R Ar addr +set the RST (hardware reset) vector. The processor will transfer +control to this address when emulated execution begins. +.It Fl s Ar addr Ar end Ar file +save the contents of memory from the address +.Ar addr +up to +.Ar end +(exclusive) to the given +.Ar file . +As with the +.Fl d +option, +.Ar end +can be absolute or '+' followed by a byte count. +.It Fl v +print version information and then exit. +.It Fl X Ar addr +arrange that any transfer of control to the address +.Ar addr +will cause an immediate exit with zero exit status. +.It Fl x +exit immediately. (Useful after +.Fl d +or when +.Nm run6502 +is being used as a trivial 'image editor', with several +.Fl l +options followed by +.Fl s +and +.Fl x . ) +.It Ar +following a +.Fl B +option, load one or more ROM image +files +into successive paged ROM slots. Other than the paging aspect, this +is equivalent to: +.Bd -ragged -offset indent +.Fl l Ar 8000 Ar image +.Ed +.El +.\" ---------------------------------------------------------------- +.Sh EXAMPLES +.\" +.Ss A Very Simple Program +The +.Xr perl 1 +command can be used to create a binary file from hexadecimal input: +.Bd -literal + echo a2418a20eeffe8e05bd0f7a90a20eeff00 | + perl -e 'print pack "H*",' > temp.img +.Ed +.Pp +The file can be loaded and executed with: +.Bd -literal + run6502 -l 1000 temp.img -R 1000 -P FFEE -X 0 +.Ed +.Pp +The contents of the file can be inspected symbolically with: +.Bd -literal + run6502 -l 1000 temp.img -d 1000 +12 +.Ed +.Pp +The options passed to +.Nm run6502 +in the above examples have the following effects: +.Bl -tag -width offset +.It \-l 1000 temp.img +loads the file +.Pa temp.img +into memory at address 0x8000. +.It \-R 1000 +sets the reset vector (the address of first instruction to be executed +after 'power on') to 0x1000. +.It \-P FFEE +arranges for calls to address 0xFFEE to behave as if there were an +implementation of +.Xr putchar 3 +at that address. +.It \-X 0 +arranges for transfers of control to address 0 to exit from the +emulator. This works in the above example because the final 'BRK' +instruction causes an implicit subroutine call through an +uninitialised interrupt vector to location 0. To see this +instruction... +.It \-d 1000 +12 +disassembles 18 bytes of memory at address 0x8000. +.El +.Ss Standalone Images +The +.Fl i +option is designed for use in the 'interpreter command' appearing on +the first line of an executable script. Adding the line +.Bd -literal + #!run6502 -R 1000 -P FFEE -X 0 -i 1000 +.Ed +.Pp +(with no leading spaces and a single trailing newline character) +to the +.Pa temp.img +file from the first example turns it into a script. If the file is +made executable with +.Bd -literal + chmod +x temp.img +.Ed +.Pp +it can be run like a standalone program: +.Bd -literal + ./temp.img +.Ed +.Ss A Very Complex Program +Consider a pair of files named +.Pa os1.2 +and +.Pa basic2 +containing (legally-acquired, of course) ROM images of Acorn MOS 1.2 +and BBC Basic 2. The following command loads each of the images into +memory at the appropriate address, cleans up the regions of memory +containing memory-mapped i/o on the BBC computer, saves a snapshot of +the entire memory to the file +.Pa image +and then exits: +.Bd -literal + run6502 -l C000 os1.2 -l 8000 basic2 -B -s0 +10000 image -x +.Ed +.Pp +Running the generated image with +.Bd -literal + run6502 image +.Ed +.Pp +will cold-start the emulated hardware, run the OS for a while, and +then drop into the language ROM. Basic programs can then be entered, +edited and run from the terminal. +.Pp +More details are given in the +.Pa README +file available in the +.Pa examples +directory of the distribution. +.Ss Exercises +Create a standalone image (one that can be run as a program, with +a '#!' interpreter line at the beginning) that contains Basic2 and +OS1.2 (as described above). This image should be no larger than 32K +(memory below 0x8000, which would be full of zeroes, should not appear +in the image file). +.\" ---------------------------------------------------------------- +.Sh DIAGNOSTICS +.\" +If nothing goes wrong, none. Otherwise lots. They should be +self-explanatory. I'm too lazy to enumerate them. +.\" ---------------------------------------------------------------- +.Sh COMPATIBILITY +.\" +See +.Xr lib6502 3 +for a discussion of the emulated instruction set. +.\" ---------------------------------------------------------------- +.Sh SEE ALSO +.\" +.Xr lib6502 3 +.Pp +The file +.Pa examples/README +in the lib6502 distribution. (Depending on your system this may be +installed in +.Pa /usr/doc/lib6502 , +.Pa /usr/local/doc/lib6502 , +.Pa /usr/share/doc/lib6502 , +or similar.) +.Pp +.Pa http://piumarta.com/software/lib6502 +for updates and documentation. +.Pp +.Pa http://6502.org +for lots of 6502-related resources. +.\" ---------------------------------------------------------------- +.Sh AUTHORS +.\" +The software and manual pages were written by +.An "Ian Piumarta" . +.Pp +The software is provided as-is, with absolutely no warranty, in the +hope that you will enjoy and benefit from it. You may use (entirely +at your own risk) and redistribute it under the terms of a very +liberal license that does not seek to restrict your rights in any way +(unlike certain so-called 'open source' licenses that significantly +limit your freedom in the name of 'free' software that is, ultimately, +anything but free). See the file COPYING for details. +.\" ---------------------------------------------------------------- +.Sh BUGS +.\" +.Bl -bullet +.It +Options must appear one at a time. +.It +Any attempt (in a load or save operation) to transfer data beyond +0xFFFF is silently truncated at the end of memory. +.It +There is no way to specify the slot into which a ROM image should be +loaded, other than implicitly according to the order of arguments on +the command line. +.It +Execution can only be started via the emulated power-up reset. There +is no support for 'warm-starting' execution in an image at an +arbitrary address. +.It +Even though the emulator fully supports them, there is no way to +artificially generate a hardware interrupt request, non-maskable +interrupt, or reset condition. If you need these, read +.Xr lib6502 3 +and write your own shell. +.It +The Acorn 'BBC Model B' hardware emulation is totally lame. +.El +.Pp +Please send bug reports (and feature requests) to the author at: +firstName (at) lastName (dot) com. (See +.Sx AUTHORS +above for suitable values of firstName and lastName.) diff --git a/lib6502/run6502.c b/lib6502/run6502.c new file mode 100644 index 0000000..c379328 --- /dev/null +++ b/lib6502/run6502.c @@ -0,0 +1,524 @@ +/* run6502.c -- 6502 emulator shell -*- C -*- */ + +/* Copyright (c) 2005 Ian Piumarta + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the 'Software'), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, provided that the above copyright notice(s) and this + * permission notice appear in all copies of the Software and that both the + * above copyright notice(s) and this permission notice appear in supporting + * documentation. + * + * THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. + */ + +/* Last edited: 2005-11-02 01:18:58 by piumarta on margaux.local + */ + +#include +#include +#include +#include +#include + +#include "config.h" +#include "lib6502.h" + +#define VERSION PACKAGE_NAME " " PACKAGE_VERSION " " PACKAGE_COPYRIGHT + +typedef uint8_t byte; +typedef uint16_t word; + +static char *program= 0; + +static byte bank[0x10][0x4000]; + + +void fail(const char *fmt, ...) +{ + va_list ap; + fflush(stdout); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + + +void pfail(const char *msg) +{ + fflush(stdout); + perror(msg); + exit(1); +} + + +#define rts \ + { \ + word pc; \ + pc = mpu->memory[++mpu->registers->s + 0x100]; \ + pc |= mpu->memory[++mpu->registers->s + 0x100] << 8; \ + return pc + 1; \ + } + + +int osword(M6502 *mpu, word address, byte data) +{ + byte *params= mpu->memory + mpu->registers->x + (mpu->registers->y << 8); + + switch (mpu->registers->a) + { + case 0x00: /* input line */ + /* On entry: XY+0,1=>string area, + * XY+2=maximum line length, + * XY+3=minimum acceptable ASCII value, + * XY+4=maximum acceptable ASCII value. + * On exit: Y is the line length (excluding CR), + * C is set if Escape terminated input. + */ + { + word offset= params[0] + (params[1] << 8); + byte *buffer= mpu->memory + offset; + byte length= params[2], minVal= params[3], maxVal= params[4], b= 0; + if (!fgets(buffer, length, stdin)) + { + putchar('\n'); + exit(0); + } + for (b= 0; b < length; ++b) + if ((buffer[b] < minVal) || (buffer[b] > maxVal) || ('\n' == buffer[b])) + break; + buffer[b]= 13; + mpu->registers->y= b; + mpu->registers->p &= 0xFE; + break; + } + + default: + { + char state[64]; + M6502_dump(mpu, state); + fflush(stdout); + fprintf(stderr, "\nOSWORD %s\n", state); + fail("ABORT"); + } + break; + } + + rts; +} + + +int osbyte(M6502 *mpu, word address, byte data) +{ + switch (mpu->registers->a) + { + case 0x7A: /* perform keyboard scan */ + mpu->registers->x= 0x00; + break; + + case 0x7E: /* acknowledge detection of escape condition */ + return 1; + break; + + case 0x82: /* read machine higher order address */ + mpu->registers->y= 0x00; + mpu->registers->x= 0x00; + break; + + case 0x83: /* read top of OS ram address (OSHWM) */ + mpu->registers->y= 0x0E; + mpu->registers->x= 0x00; + break; + + case 0x84: /* read bottom of display ram address */ + mpu->registers->y= 0x80; + mpu->registers->x= 0x00; + break; + + case 0x89: /* motor control */ + break; + + case 0xDA: /* read/write number of items in vdu queue (stored at 0x026A) */ + return 0; + break; + + default: + { + char state[64]; + M6502_dump(mpu, state); + fflush(stdout); + fprintf(stderr, "\nOSBYTE %s\n", state); + fail("ABORT"); + } + break; + } + + rts; +} + + +int oscli(M6502 *mpu, word address, byte data) +{ + byte *params= mpu->memory + mpu->registers->x + (mpu->registers->y << 8); + char command[1024], *ptr= command; + while (('*' == *params) || (' ' == *params)) + ++params; + while (13 != *params) + *ptr++= *params++; + *ptr= '\0'; + system(command); + rts; +} + + +int oswrch(M6502 *mpu, word address, byte data) +{ + switch (mpu->registers->a) + { + case 0x0C: + fputs("\033[2J\033[H", stdout); + break; + + default: + putchar(mpu->registers->a); + break; + } + fflush(stdout); + rts; +} + + +static int writeROM(M6502 *mpu, word address, byte value) +{ + return 0; +} + + +static int bankSelect(M6502 *mpu, word address, byte value) +{ + memcpy(mpu->memory + 0x8000, bank[value & 0x0F], 0x4000); + return 0; +} + + +static int doBtraps(int argc, char **argv, M6502 *mpu) +{ + unsigned addr; + + /* Acorn Model B ROM and memory-mapped IO */ + + for (addr= 0x8000; addr <= 0xFBFF; ++addr) mpu->callbacks->write[addr]= writeROM; + for (addr= 0xFC00; addr <= 0xFEFF; ++addr) mpu->memory[addr]= 0xFF; + for (addr= 0xFE30; addr <= 0xFE33; ++addr) mpu->callbacks->write[addr]= bankSelect; + for (addr= 0xFE40; addr <= 0xFE4F; ++addr) mpu->memory[addr]= 0x00; + for (addr= 0xFF00; addr <= 0xFFFF; ++addr) mpu->callbacks->write[addr]= writeROM; + + /* anything already loaded at 0x8000 appears in bank 0 */ + + memcpy(bank[0x00], mpu->memory + 0x8000, 0x4000); + + /* fake a few interesting OS calls */ + +# define trap(vec, addr, func) mpu->callbacks->call[addr]= (func) + trap(0x020C, 0xFFF1, osword); + trap(0x020A, 0xFFF4, osbyte); +//trap(0x0208, 0xFFF7, oscli ); /* enable this to send '*COMMAND's to system(3) :-) */ + trap(0x020E, 0xFFEE, oswrch); + trap(0x020E, 0xE0A4, oswrch); /* NVWRCH */ +#undef trap + + return 0; +} + + +static void usage(int status) +{ + FILE *stream= status ? stderr : stdout; + fprintf(stream, VERSION"\n"); + fprintf(stream, "please send bug reports to: %s\n", PACKAGE_BUGREPORT); + fprintf(stream, "\n"); + fprintf(stream, "usage: %s [option ...]\n", program); + fprintf(stream, " %s [option ...] -B [image ...]\n", program); + fprintf(stream, " -B -- minimal Acorn 'BBC Model B' compatibility\n"); + fprintf(stream, " -d addr last -- dump memory between addr and last\n"); + fprintf(stream, " -G addr -- emulate getchar(3) at addr\n"); + fprintf(stream, " -h -- help (print this message)\n"); + fprintf(stream, " -I addr -- set IRQ vector\n"); + fprintf(stream, " -l addr file -- load file at addr\n"); + fprintf(stream, " -M addr -- emulate memory-mapped stdio at addr\n"); + fprintf(stream, " -N addr -- set NMI vector\n"); + fprintf(stream, " -P addr -- emulate putchar(3) at addr\n"); + fprintf(stream, " -R addr -- set RST vector\n"); + fprintf(stream, " -s addr last file -- save memory from addr to last in file\n"); + fprintf(stream, " -v -- print version number then exit\n"); + fprintf(stream, " -X addr -- terminate emulation if PC reaches addr\n"); + fprintf(stream, " -x -- exit wihout further ado\n"); + fprintf(stream, " image -- '-l 8000 image' in available ROM slot\n"); + fprintf(stream, "\n"); + fprintf(stream, "'last' can be an address (non-inclusive) or '+size' (in bytes)\n"); + exit(status); +} + + +static int doHelp(int argc, char **argv, M6502 *mpu) +{ + usage(0); + return 0; +} + + +static int doVersion(int argc, char **argv, M6502 *mpu) +{ + puts(VERSION); + exit(0); + return 0; +} + + +static unsigned long htol(char *hex) +{ + char *end; + unsigned long l= strtol(hex, &end, 16); + if (*end) fail("bad hex number: %s", hex); + return l; +} + + +static int loadInterpreter(M6502 *mpu, word start, const char *path) +{ + FILE *file= 0; + int count= 0; + byte *memory= mpu->memory + start; + size_t max= 0x10000 - start; + int c= 0; + + if ((!(file= fopen(path, "r"))) || ('#' != fgetc(file)) || ('!' != fgetc(file))) + return 0; + while ((c= fgetc(file)) >= ' ') + ; + while ((count= fread(memory, 1, max, file)) > 0) + { + memory += count; + max -= count; + } + fclose(file); + return 1; +} + + +static int save(M6502 *mpu, word address, unsigned length, const char *path) +{ + FILE *file= 0; + int count= 0; + if (!(file= fopen(path, "w"))) + return 0; + while ((count= fwrite(mpu->memory + address, 1, length, file))) + { + address += count; + length -= count; + } + fclose(file); + return 1; +} + + +static int load(M6502 *mpu, word address, const char *path) +{ + FILE *file= 0; + int count= 0; + size_t max= 0x10000 - address; + if (!(file= fopen(path, "r"))) + return 0; + while ((count= fread(mpu->memory + address, 1, max, file)) > 0) + { + address += count; + max -= count; + } + fclose(file); + return 1; +} + + +static int doLoadInterpreter(int argc, char **argv, M6502 *mpu) +{ + if (argc < 3) usage(1); + if (!loadInterpreter(mpu, htol(argv[1]), argv[2])) pfail(argv[2]); + return 2; +} + + +static int doLoad(int argc, char **argv, M6502 *mpu) /* -l addr file */ +{ + if (argc < 3) usage(1); + if (!load(mpu, htol(argv[1]), argv[2])) pfail(argv[2]); + return 2; +} + + +static int doSave(int argc, char **argv, M6502 *mpu) /* -l addr size file */ +{ + if (argc < 4) usage(1); + if (!save(mpu, htol(argv[1]), htol(argv[2]), argv[3])) pfail(argv[3]); + return 3; +} + + +#define doVEC(VEC) \ + static int do##VEC(int argc, char **argv, M6502 *mpu) \ + { \ + unsigned addr= 0; \ + if (argc < 2) usage(1); \ + addr= htol(argv[1]); \ + M6502_setVector(mpu, VEC, addr); \ + return 1; \ + } + +doVEC(IRQ); +doVEC(NMI); +doVEC(RST); + +#undef doVEC + + +/* Emulate getchar(3) at addr */ +static int gTrap(M6502 *mpu, word addr, byte data) { mpu->registers->a= getchar(); rts; } + +static int doGtrap(int argc, char **argv, M6502 *mpu) +{ + unsigned addr; + if (argc < 2) usage(1); + addr= htol(argv[1]); + M6502_setCallback(mpu, call, addr, gTrap); + return 1; +} + +/* Emulate putchar(3) at addr */ +static int pTrap(M6502 *mpu, word addr, byte data) { putchar(mpu->registers->a); rts; } + +static int doPtrap(int argc, char **argv, M6502 *mpu) +{ + unsigned addr; + if (argc < 2) usage(1); + addr= htol(argv[1]); + M6502_setCallback(mpu, call, addr, pTrap); + return 1; +} + + +/* Emulate memory-mapped stdio at addr */ +static int mTrapRead(M6502 *mpu, word addr, byte data) { return getchar(); } +static int mTrapWrite(M6502 *mpu, word addr, byte data) { return putchar(data); } + +static int doMtrap(int argc, char **argv, M6502 *mpu) +{ + unsigned addr= 0; + if (argc < 2) usage(1); + addr= htol(argv[1]); + M6502_setCallback(mpu, read, addr, mTrapRead); + M6502_setCallback(mpu, write, addr, mTrapWrite); + return 1; +} + +/* Terminate emulation if PC reaches addr */ +static int xTrap(M6502 *mpu, word addr, byte data) { exit(0); return 0; } + +static int doXtrap(int argc, char **argv, M6502 *mpu) +{ + unsigned addr= 0; + if (argc < 2) usage(1); + addr= htol(argv[1]); + M6502_setCallback(mpu, call, addr, xTrap); + return 1; +} + + +static int doDisassemble(int argc, char **argv, M6502 *mpu) +{ + unsigned addr= 0, last= 0; + if (argc < 3) usage(1); + addr= htol(argv[1]); + last= ('+' == *argv[2]) ? addr + htol(1 + argv[2]) : htol(argv[2]); + while (addr < last) + { + char insn[64]; + int i= 0, size= M6502_disassemble(mpu, addr, insn); + printf("%04X ", addr); + while (i++ < size) printf("%02X", mpu->memory[addr + i - 1]); + while (i++ < 4) printf(" "); + putchar(' '); + i= 0; + while (i++ < size) putchar(isgraph(mpu->memory[addr + i - 1]) ? mpu->memory[addr + i - 1] : ' '); + while (i++ < 4) putchar(' '); + printf(" %s\n", insn); + addr += size; + } + return 2; +} + + +int main(int argc, char **argv) +{ + M6502 *mpu= M6502_new(0, 0, 0); + int bTraps= 0; + + program= argv[0]; + + if ((2 == argc) && ('-' != *argv[1])) + { + if ((!loadInterpreter(mpu, 0, argv[1])) && (!load(mpu, 0, argv[1]))) + pfail(argv[1]); + doBtraps(0, 0, mpu); + } + else + while (++argv, --argc > 0) + { + int n= 0; + if (!strcmp(*argv, "-B")) bTraps= 1; + else if (!strcmp(*argv, "-d")) n= doDisassemble(argc, argv, mpu); + else if (!strcmp(*argv, "-G")) n= doGtrap(argc, argv, mpu); + else if (!strcmp(*argv, "-h")) n= doHelp(argc, argv, mpu); + else if (!strcmp(*argv, "-i")) n= doLoadInterpreter(argc, argv, mpu); + else if (!strcmp(*argv, "-I")) n= doIRQ(argc, argv, mpu); + else if (!strcmp(*argv, "-l")) n= doLoad(argc, argv, mpu); + else if (!strcmp(*argv, "-M")) n= doMtrap(argc, argv, mpu); + else if (!strcmp(*argv, "-N")) n= doNMI(argc, argv, mpu); + else if (!strcmp(*argv, "-P")) n= doPtrap(argc, argv, mpu); + else if (!strcmp(*argv, "-R")) n= doRST(argc, argv, mpu); + else if (!strcmp(*argv, "-s")) n= doSave(argc, argv, mpu); + else if (!strcmp(*argv, "-v")) n= doVersion(argc, argv, mpu); + else if (!strcmp(*argv, "-X")) n= doXtrap(argc, argv, mpu); + else if (!strcmp(*argv, "-x")) exit(0); + else if ('-' == **argv) usage(1); + else + { + /* doBtraps() left 0x8000+0x4000 in bank 0, so load */ + /* additional images starting at 15 and work down */ + static int bankSel= 0x0F; + if (!bTraps) usage(1); + if (bankSel < 0) fail("too many images"); + if (!load(mpu, 0x8000, argv[0])) pfail(argv[0]); + memcpy(bank[bankSel--], + 0x8000 + mpu->memory, + 0x4000); + n= 1; + } + argc -= n; + argv += n; + } + + if (bTraps) + doBtraps(0, 0, mpu); + + M6502_reset(mpu); + M6502_run(mpu); + M6502_delete(mpu); + + return 0; +}