Here’s a program I just wrote and assembled for an 8-bit PIC device, which assembles under MPASM (and gpasm) using  the instruction set of the 6502, rather than PIC native code:

        processor 16f1508

        LDA# 0x5a
        LDX# 0xef
store5a STA 0x2000,X
        BNE store5a 
        STA 0x2000,X
        LDA# 0xa5
        BIT 0x2000
        ROL A,
        BIT 0x2000
        BEQ store5a

This example works as intended, setting the 240 bytes of PIC linear memory to all Z’s in ASCII and then performing a couple of ultimately pointless bit tests and stack operations.  Besides the inclusion of a macro definition file to enable this translation a few deviations from standard 6502 syntax give it away: superfluous argument placeholders such as the comma after the ROtate Left Accumulator instruction (a second argument is necessary in case the first one were to have be indexed with X), as well as the strange absorption of the immediate-mode hash signs into the opcode itself.

Assembly-language translation for PIC processors has been done before, using the familiar shorthand of another processor’s instruction set but usually with more of an eye on efficiency and readability than full duplication.  The program snippet above, which would require just 27 bytes of program storage on a true 6502 machine, expands into 247 PIC 14-bit words, for a total of over 400 bytes or about 5% of this device’s flash memory.  Not surprisingly, the macros have to be defined fairly pessimistically and can’t attempt any sort of optimization after the fact on their own.  (Although an optimizing assembler could well do so.)  Mimicking the 6502 on a RAM-banking/ROM-paging PIC device with only one working register and a barely accessible return stack takes a lot of work behind the scenes to accomplish the following in particular:

  • the X and Y index registers (to which purpose I assign FSR0L and FSR1L just in case a pointer access is relative to page boundary and we fortuitously just  need to copy that page into FSR0H or FSR1H)
  • single-opcode stack push/pull operations
  • indexed memory accesses
  • result-less compare and bit test operations

Nevertheless, several 6502 opcodes do map onto a single PIC instruction and don’t require any sort of temporary storage, atomicity or memory protection. These are more along the lines of the assembly-language translations usually attempted:

  • LDA# (becomes: movlw)
  • DEX (becomes: decf FSR0L,f)
  • CLC (becomes: bcf STATUS,C)
  • ROL A (becomes: rlf WREG)

I have not yet tested the full set of 6502 macro definitions extensively, so there are probably several bugs still lurking.  Be sure to disable case sensitivity (MPLAB’s radio button, or gpasm -i) if using uppercase for the 6502 invocations, as I have in order to distinguish the two instruction sets above.

So what features of the 6502 are supported?  Basically, everything except the Binary Coded Decimal (BCD) mode and the oVerflow flag which are the only parts of 6502 that are completely alien to the PIC architecture.  Several opcodes do require special handling or otherwise have a different syntax than on a real 6502 machine:

  1. The Negative flag is supported by means of referring to bit 7 of a particular file register in an additional operand; result-less compares and bit tests require the intermediate results to be stored in a PIC file register.
  2. The immediate mode requires that the hash sign (#) indicating a literal value be placed on the opcode itself, since it is a totally different macro than for a memory access
  3. Indexed indirect (using X) and indirect indexed (using Y) addressing modes are supported, but in this case the question mark (?) character  is required in order to highlight the difference to the assembler after its preprocessor strips off the all-important parentheses.  The parenthesis can still be used in source code, in order to keep some semblance to the 6502 modes:
    • LDA? (0x2000),(X) in lieu of LDA (0x2000,X)…a rarely used 6502 mode anyway!
    • LDA? (0x2000),Y in lieu of LDA (0x2000),Y

Interrupts should work properly, whether using 6502 macros within the ISR or the interrupted code.   Seven of the PIC file registers (0x79 through 0x7f) get used for storing temporary values during the course of a macro.  If not using 6502 macros in interrupt routines, these locations can also be used for temporary storage by PIC code until the next 6502 macro:

  • 0x79 holds the state of the STATUS register in bits 4-0, as well as the GIE bit of INTCON in bit 7
  • 0x7a holds the value of the WREG/Accumulator
  • 0x7b holds the value of the Bank Select Register
  • 0x7c holds the value of the FSR0L/X register
  • 0x7d holds the value of FSR0H
  • 0x7e holds the value of the FSR1L/Y register
  • 0x7f holds the value of FSR1H

One reason for all the extra generated code is that for any instruction that accesses intermediate results in memory or the STATUS register, the GIE bit of INTCON is turned off (SEI in 6502 parlance).  As with previous posts, this is not an efficiency exercise.  I wanted to explore how closely these two 8-bit favorites really could be made to resemble each other.  The code generated will be far from optimized, but the fact that it can be done at all within MPASM’s relatively limited macro expansion capability is kind of neat.