6809.c

    // If checking the logic of opcode covers, the original emulator on which this
    // was based is currently in 6809-fallback.c

    // turn this on occasionally and make sure regression tests still working OK
    // Caught by cputest:
    //#define TESTING_ERROR_DETECTION_1 1
    // Caught by sorenroug test (I haven't actually found a test yet that succeeds in cputest and fails in sorenroug test)
    // - wait a minute... I know, use the broken TFR B,X code! (TO DO)
    //#define TESTING_ERROR_DETECTION_2 1

    /*

        6809.c    - cycle accurate simple 6809 cpu emulator
        Copyright (C) 2001  Arto Salmi
                            Joze Fabcic
        modification into a static binary translator
        Copyright (c) 2020  Graham Toal (Last edited $Date: 2021/11/02 03:03:59 $)

        This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation; either version 2 of the License, or
        (at your option) any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program; if not, write to the Free Software
        Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

        $Log: 6809.c,v $
        Revision 1.27  2021/11/02 03:03:59  gtoal
        remove EFI
        add a few brackets around expressions to simplify for subset-C parser

        Revision 1.26  2021/11/01 00:46:37  gtoal
        another expr simplified

        Revision 1.25  2021/11/01 00:14:31  gtoal
        making cosmetic changes to generated C code, so that my new mini-C parser can read it.

        Revision 1.24  2021/10/28 07:36:12  gtoal
        added some comments around code that needs to be checked for a problem Malban discovered

        Revision 1.23  2021/10/28 07:26:17  gtoal
        Added a flush to bra() - temporary. There may be other places where one is needed too...

        Revision 1.22  2021/10/28 06:41:04  gtoal
        two bugfixes fed back from Malban's work on starwars

        Revision 1.21  2021/06/11 22:08:14  gtoal
        Adding RCS headers

    */

    const char *SBT_Version = "6809.c: $Revision: 1.27 $";

    /*

    These are the types that the run-time for the generated code uses:

    typedef uint32_t UINT32;
    typedef int32_t SINT32;
    typedef uint16_t UINT16;
    typedef int16_t SINT16;
    typedef uint8_t UINT8;
    typedef int8_t SINT8;

    UINT8 memory[0x10000], *memory_DP = &memory[0xD000];

    // Modify this header to suit your target...
    SINT32 res, A, B, D, C;
    UINT16 PC, X, Y, S, U, Z, DP, arg, ea, val;
    UINT8 E, F, I, N, H, V, CC, msn, lsn;

    - there are several redundant casts in the generated code which
    I should remove... by default most things will be unsigned; only
    a few casts to SINTx will be needed.
     */

    /*
        This module generates the C translations of 6809 opcodes, as detected
        by 6809dasm.c. ( http://gtoal.com/vectrex/6809sbt/6809dasm.c.html )
        We *only* handle a single opcode here, passed to us from the disassembler.
        That keeps this code quite clean and totally separate from the mess that
        6809dasm has become! (that module probably deserves a rewrite)

        The code uses the def/use chain technique described in my Static Binary
        Translation HOWTO ( http://gtoal.com/sbt/ ) to reduce a lot of unnecessary
        flag setting.  However a lot more optimisation could still be done - but may
        not be worth the effort as this now looks acceptable.  (Actually it wouldn't
        be unreasonable to omit those optimisations altogether and allow the compiler
        to handle them behind the scenes, but doing them here looks better as long
        as they are done correctly.)

        The initial implementation used the HOWTO's technique of passing the def/use
        register info to 'dumpf' explicitly.  It should however be possible to infer
        that info just from the code string, with a little care being taken to catch
        dependencies on the left-hand side of assignments such as memory[X] = Y - in
        this case both X and Y are 'use' registers.  (And memory is an 'Always' def,
        if you were wondering...)  We've made small steps in that direction by using
        the R_lookup() procedure quite heavily.

        The sequence of handling statements here is marginally problematic - although
        the def/use chain eliminates redundant loads, it still leaves the problem of
        constant expression folding unhandled.  To do that properly, I think I need
        to parse the generated code strings as proper C expressions!  And then be
        very careful with what is remembered and what is forgotten when a flush() is
        executed.  I'm definitely tempted to attempt that optimisation.  Remembered
        registers may also include symbolic substitution as well as constant folding,
        so that ea = X; Y = ea becomes Y = X...  as long as 'ea' is not used again.
        Whether that needs a second layer of def/use handling or just some care in
        writing the opcode covers, remains to be seen...

        Although we don't handle Phi nodes yet (and probably never will), a lot of
        optimisation can be done locally:
        1) follow through paths from entry to a procedure until return at the same
           level, note which regs and flags are used as inputs (or not used at all)
           and avoid flushing flags which get overwritten in the procedure
        2) half-carry flag only ever used in DAA instruction, so if no DAA used,
           never flush the half-carry.  If it is used, work back to where it was
           set and as long as it is linear, keep those but drop any others.
           Possibly can be done by replacing 'maybe used half carry' bit with
           'definitely used half carry' bit in def/use flags?
        3) technique of working backwards from where flags are used may be
           useful in more places that just H/DAA...
        4) remembering of values when assigned - even symbolic, not necessarily
           for constants - might allow great simplification of comparisons and
           branches, e.g. "if (memory[S--] > 0x45) ... instead of what it does
           generate.  Might only work for ==0 != 0 at first, care needs to be
           taken with signedness for more complex tests.

    These are mostly from Minestorm derivatives, reusing
    some of the Minestorm internal code.

    Warning: undetected bios call to e7b5 from 04a6
    Warning: undetected bios call to e7d2 from 0826
    Warning: undetected bios call to e8e3 from 10c6
    Warning: undetected bios call to e98a from 0585
    Warning: undetected bios call to ea3e from 04a1
    Warning: undetected bios call to ea6d from 05e5
    Warning: undetected bios call to ea9d from 0bcd
    Warning: undetected bios call to eaa8 from 0bdc
    Warning: undetected bios call to eacf from 12f4
    Warning: undetected bios call to eaf0 from 0144

    I may have solved the transitive closure problem...
    When chaining backwards looking for remaining registers in def/use chain,
    whenever you come to a label, that label has to be tagged with all the
    places that the code could have come from. (as in orion data flow)
    So instead of just going linearly backwards, you also need to go backwards
    though all possible 'come from' locations as well.
    However if you ever hit a location that you've already visited
    during this traverse, stop. (at least for that branch) to avoid
    recursion caused by loops.

    Conclusion - have entire program in memory, not just
    the current buffer up to a flush point.

        GT 2020/03/29
     */

    /*
    There are certainly a few places in this code where I've taken
    some shortcuts, and implemented features with quick hacks of which
    I'm not proud - but the majority of those are in places where I
    knew what was needed to implement the features properly - they
    just needed a much more complex infrastructure to be in place
    which wasn't feasible during the early stages of development
    while so much was still not working.  For those hacks, I had
    plans to reimplement them properly later once a basic level
    of functionality was working.

    HOWEVER, one problem did surface which was caused by a lack
    of foresight at the design stage, and it appears that
    the temporary solutions I have tried are just making it
    worse.  That is the area of pre-decremented and post-incremented
    registers:

                    ADDA    ,-X
                    ADDA    ,--X
                    ADDA    ,-Y
                    ADDA    ,--Y
                    ADDA    ,-U
                    ADDA    ,--U
                    ADDA    ,-S
                    ADDA    ,--S
                    ADDA    [,--X]
                    ADDA    [,--Y]
                    ADDA    [,--U]
                    ADDA    [,--S]
                    ADDA    ,X+
                    ADDA    ,X++
                    ADDA    ,Y+
                    ADDA    ,Y++
                    ADDA    ,U++
                    ADDA    ,S++
                    ADDA    [,X++]
                    ADDA    [,Y++]
                    ADDA    [,U++]
                    ADDA    [,S++]

    The code to handle these needs to be completely rewritten.
    for any operation, the pre-decrement register operations
    should be handled first; then the operation performed as
    if no pre-decrement is present, then on completion, any
    post-increments should be performed.

    This will unfortunately generate less concise or readable
    code, but that is the price that has to be paid for
    correctness.  Tidying up the generated C may be done
    as a post-processing pass if required, but if so, that
    will necessitate a new infrastructure for handling
    multiple statements over several lines, i.e. a proper
    C expression parser and an AST.  So be it.

    I think I've now completed this, but will leave this comment
    here until I've had a little longer to look at the code and
    confirm it is OK...

    */

    #include <stdio.h>
    #include <stdarg.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    #include <assert.h>
    #include "6809.h"

    extern int rom_start, rom_end; // communicate with 6809dasm.c
    int HTOI(char *s) {
      int I = 0;
      while (*s != '\0') {
        int c = *s++;
        if (('0' <= c) && (c <= '9')) {
          I = (I << 4) | (c - '0');
        } else if (('a' <= c) && (c <= 'f')) {
          I = (I << 4) | (c - 'a' + 0xa);
        } else if (('A' <= c) && (c <= 'F')) {
          I = (I << 4) | (c - 'A' + 0xA);
        } else {
          fprintf(stderr, "Bad hex: %s\n", s);
        }
      }
      return I;
    }

    // register sizes in translated code not the same! The generated code now relies
    // primarily on casting (eg (UINT8)Reg) as a substitute for masking (Reg&0xff)
    // (although not 100% consistently so revisit that later)

    unsigned X, Y, S, U, PC;
    unsigned A, B, DP;
    unsigned CC, H, N, Z, V, C;
    unsigned E, F, I;
    unsigned iPC, CodePTR, NextPC;

    /*
    Notes:
       Whenever you assign to A or B, set D = (A<<8) | B (no longer true)
       Whenever CC is read from, consolidate flags into CC
       Whenever CC is written to, split CC into flags
       Flags are lazily evaluated -
         canonicalisation is only needed when converting in or out of CC;
         tests of flag bits are done similar to "(C != 0)" or "(N < 0)"

       If assignments to variables can be remembered symbolically, then
          A = mem[...]; N = A; if (N < 0) ...
       might end up being translated as
          A = mem[...]; if (A < 0) ...
       or if we're very lucky,
          if (mem[...] < 0) ...
       which would more closely match the original intent.

       The scheme above, of keeping individual CC flags separate,  is believed
       to be more suitable to a translator which handled redundant loads/
       dead stores than one which uses lookup tables to update all the CC bits
       within a combined CC at once. (cf 6502 and z80 handling in Orion SBT)

       Eventually, constant values in registers will be remembered and folded,
         so optimising writes to PC *should* fall out in the wash and not need
         a special case handler - but initially that's what it will get.
       For now almost all PC manipulations are from values of PC known at
       translate time.  If/when we add a fallback interpreter for handling
       unknown code, some extra effort may need to be put in to the handling
       of the PC...

       Same *ought* to apply to 'clockticks' in an ideal world.  At the moment
       clockticks are pretty much ignored because the code to add clock ticks
       depending on conditional execution is quite broken.

       There are some internal variables which are handled like registers
         for optimisation purposes.  If we run out of bits (32 max for now)
         in the def/usr code, we can overlay any variables that are only used
         within a single opcode cover, i.e. written to, read from, then never
         accessed again.  For example msn and lsn in DAA, and tmp1 and tmp2 in EXG.
         However for simplicity it may just be easier to move to 64 bit 'long long's.

       ALL short opcodes that use DP need DP in the RHS of the def/use params.

     */

    char *UC(char *lc) {
      int i=0; // try to copy in place, if that doesn't work create a buffer to copy into.
      while (lc[i] != '\0') {
        if ((isalpha(lc[i])) && (islower(lc[i]))) lc[i] = toupper(lc[i]);
        i++;
      }
      return lc;
    }

    UINT8 *memory = NULL;
    UINT8 *dpmem = NULL;

    #ifndef FALSE
    #define FALSE (0!=0)
    #define TRUE  (0==0)
    #endif

    #define MAX 40960
    long defs[MAX], uses[MAX];
    char *codes[MAX];
    int nextfree = 0;

    int ishex(int c) {
      return ('0' <= c && c <= '9')
          || ('A' <= c && c <= 'F')
          || ('a' <= c && c <= 'f');
    }

    int R_lookup(char *expr) {
      // TO DO!  '//' and '/*' ... '*/' comments in code strings
      static char varname[128];
      char *s, *v;
      int Regs = 0;
      // single letter: A B D X Y S U H N Z V C
      // two letter: PC DP CC EA 0x?????
      // three letter: val res msn lsn arg
      // multi: clockticks
      // Unless we end up parsing complete expressions, some of those above will never be seen in a call to R_lookup()
      // Need to handle both kinds of comments if they appear in 'expr'
      s = expr;
      for (;;) {
        if (*s == '\0') break;
        if ((s[0] == 'c') && (strncmp(s, "clockticks", strlen("clockticks")) == 0)) {
          Regs |= R_CLOCKTICKS; s += strlen("clockticks"); continue;
        }
        if (s[0] == '0' && s[1] == 'x' && ishex(s[2])) {
          s += 2; while (ishex(s[0])) s+=1; continue;
        } else if (!isalnum(s[1])) {
          if (s[0] == 'A') { Regs |= R_A; s += 1; continue; }
          if (s[0] == 'B') { Regs |= R_B; s += 1; continue; }
          if (s[0] == 'D') { Regs |= R_D; s += 1; continue; }
          if (s[0] == 'X') { Regs |= R_X; s += 1; continue; }
          if (s[0] == 'Y') { Regs |= R_Y; s += 1; continue; }
          if (s[0] == 'S') { Regs |= R_S; s += 1; continue; }
          if (s[0] == 'U') { Regs |= R_U; s += 1; continue; }
          if (s[0] == 'H') { Regs |= R_H; s += 1; continue; }
          if (s[0] == 'N') { Regs |= R_N; s += 1; continue; }
          if (s[0] == 'Z') { Regs |= R_Z; s += 1; continue; }
          if (s[0] == 'V') { Regs |= R_V; s += 1; continue; }
          if (s[0] == 'C') { Regs |= R_C; s += 1; continue; }
          if (s[0] == 'E') { Regs |= R_E; s += 1; continue; }
          if (s[0] == 'F') { Regs |= R_F; s += 1; continue; }
          if (s[0] == 'I') { Regs |= R_I; s += 1; continue; }
        } else if (!isalnum(s[2])) {
          if (s[0] == 'P' && s[1] == 'C') { Regs |= R_PC; s += 2; continue; }
          if (s[0] == 'D' && s[1] == 'P') { Regs |= R_DP; s += 2; continue; }
          if (s[0] == 'C' && s[1] == 'C') { Regs |= R_CC; s += 2; continue; }
          if (s[0] == 'e' && s[1] == 'a') { Regs |= R_EA; s += 2; continue; }
        }
        if (isalpha(s[0])) {
          v = varname;
          while (isalnum(*v++ = s[0])) s += 1;
          *--v = '\0';
          if (strcmp(varname, "val") == 0) {
            Regs |= R_VAL;
          } else if (strcmp(varname, "res") == 0) {
            Regs |= R_RES;
          } else if (strcmp(varname, "msn") == 0) {
            Regs |= R_MSN;
          } else if (strcmp(varname, "lsn") == 0) {
            Regs |= R_LSN;
          } else if (strcmp(varname, "arg") == 0) {
            Regs |= R_ARG;
          } else if (strcmp(varname, "clockticks") == 0) {
            Regs |= R_CLOCKTICKS;
          } else if (strcmp(varname, "memory") == 0) { // Is memory_DP handled properly yet? Should activate DP reg...
            Regs |= R_Always; // should be in the dest not the src regs...
          } else if (strcmp(varname, "UINT8") == 0) { // not relevant
          } else if (strcmp(varname, "SINT8") == 0) {
          } else if (strcmp(varname, "UINT16") == 0) {
          } else if (strcmp(varname, "SINT16") == 0) {
          } else {
            fprintf(stderr, "R_lookup: '%s' not handled in '%s'.\n", varname, expr);
          }
        } else if (*s == '\0') {
          break;
        } else s += 1; // skip anything else.
      }
      return Regs;
    }

    void dumpf(int def, int use, char *s, ...);

    static int clockticks = 0, cond_clockticks = 0;
    void CK (int ticks)
    {
      if (Do_Timing) dumpf(R_CLOCKTICKS, R_CLOCKTICKS, "clockticks = clockticks + %d;", ticks);
    }

    /* conditional_clockticks would do a dumpf to set clockticks equal
    to the sum of clockticks plus the parameter but doesn't update
    clockticks itself.  For use in conditional branches.
    Ideally delta-clockticks chain starts afresh on each basic block
    then gets added to absolute clockticks at end of BB (RTS, JUMPs etc)
    "To do..." */

    void conditional_clockticks (int ticks)
    {
       cond_clockticks += ticks;
    }

    void flush(long regs)
    {
       int i;
       long common_regs;

       for (i = nextfree - 1; i >= 0; --i) {
         if ((common_regs = (regs & defs[i])) != 0) {
            /* if bits in 'defs' then this needs to be dumped. */
            // fprintf(stdout, "// %02d lookfor: %02x Item %d - def %02x\n", nextfree, regs, i, defs[i]);
             defs[i] |= R_Always;   /* flag this line for printing */
             regs &= ~common_regs;  /* don't need to look for these ones any more */
          }
          if (defs[i] & R_Always)
             regs |= uses[i];       /* Any unconditionals + parents */
       }

       fflush(stdout);
       for (i = 0; i < nextfree; i++) {
    #ifdef DEBUG
          fprintf (stdout, "/* %03d [def:%04x] [use:%04x] */", i, defs[i], uses[i]);
    #endif
          if (Gen_C) {
            if (strncmp(codes[i], "case ", 5) == 0) {
              fprintf (stdout, "  %s%s\n", (defs[i] & R_Always ? "" : " // "), codes[i]);
            } else if ((defs[i] & R_Always) == 0) {
              if (Single_Step) {
                fprintf (stdout, "/**/%s\n", codes[i]); // we can't optimise in a dual cpu situation...
              } else {
                fprintf (stdout, " // %s\n", codes[i]);
              }
            } else if (strncmp(codes[i], "     ", 5) == 0) {
              fprintf (stdout, "  %s\n", codes[i]);
            } else {
              fprintf (stdout, "    %s\n", codes[i]);
            }
          } else {
            fprintf (stdout, "%s%s\n", (defs[i] & R_Always ? "" : " // "), codes[i]);
          }
          /* should free strings here... */
       }
       fflush(stdout);
       nextfree = 0;
    }

    void unconditional_flush(char *inst)
    {
       flush(R_FLUSH);
       fprintf (stdout, "          clockticks += %d; /*poll_nmi(opadd);*/\n", clockticks);
       clockticks = 0; cond_clockticks = 0;
       fprintf (stdout, "          %s\n", inst);
       nextfree = 0;
    }

    void conditional_flush(char *inst)
    {
       flush(R_FLUSH);
       if (cond_clockticks) fprintf (stdout, "              clockticks += %d;\n", clockticks + cond_clockticks);
       // TO DO: fprintf (stdout, "              poll_nmi(opadd); /* if (...) return to dispatch loop - PC is address of NMI proc - real PC is on stack */\n");
       cond_clockticks = 0;
       fprintf (stdout, "          %s\n", inst);
       nextfree = 0;
    }

    static char *reg = NULL;
    static int post_increment = 0;
    void set_post(char *r, int incr) {
      reg = r; post_increment = incr;
    }
    void post(void) { // output the delayed post-increment for indexed() arguments.
                      // pre-decrements have already been handled more sneakily.
      if (reg && post_increment) dumpf(R_Always|R_lookup(reg), R_lookup(reg), "%s = %s + %d;", reg, reg, post_increment);
      reg = NULL; post_increment = 0;
    }

    signed short SEX8(UINT8 tmp);

    unsigned int imm_byte(void)
    {
      return memory[NextPC-1];
    }

    char *imm_byte_s(void)
    {
      static char tmp[16];
      sprintf (tmp, "(SINT8)0x%02x", (int8_t)memory[NextPC-1]&0xff);
      return strdup (tmp);
    }

    int imm_word(void)
    {
      return (memory[NextPC-2] << 8) | memory[NextPC-1];
    }

    char *imm_word_s(void)
    {
      static char tmp[7];
      sprintf (tmp, "0x%04x", ((memory[NextPC-2] << 8) | memory[NextPC-1])&0xffff);
      return strdup (tmp);
    }

    void
    push_stack8(char *stack, char *data)
    {
      // Malban wants the auto-increment/decrements separated from the memory access -
      //  not sure why, but there's no reason not to, so here it is (and in other similar cases)
      dumpf(R_Always|R_lookup(stack), R_lookup(stack),                "%s = %s - 1;", stack, stack);
      dumpf(R_Always,                 R_lookup(stack)|R_lookup(data), "memory[%s] = %s;", stack, data);
      CK (1);
    }

    void
    pop_stack8(char *stack, char *dest) // stack adjustment done by caller
    {
      CK (1);
      // Malban wants the auto-increment/decrements separated from the memory access -
      //  not sure why, but there's no reason not to, so here it is (and in other similar cases)
      dumpf(R_Always|R_lookup(dest),  R_lookup(stack), "%s = memory[%s];", dest, stack);
      dumpf(R_Always|R_lookup(stack), R_lookup(stack), "%s = %s + 1;", stack, stack);
    }

    char *
    RDMEM(char *addr)
    {
      static char tmp[256];
      sprintf (tmp, "memory[%s]", addr); // bypassing the use/def chain?????
      return strdup (tmp);
    }

    char *
    RDMEM16(char *addr)
    {
      //static char tmp[256];
      CK (1);
      if (strncmp(addr, "DP|", 3) == 0) {
        // this is ugly and we unpick it later which is a dirty hack.
        addr = addr+strlen("DP|");
        if (strncmp(addr, "(UINT16)", strlen("(UINT16)")) == 0) {
          addr[5] = '8';addr[6] = ')';addr[7] = ' ';
        }
        if (strncmp(addr, "0x", 2) == 0) {
          dumpf(R_EA|R_Always, R_DP, "ea = memory[DP|0x%02x]<<8;", HTOI(addr+2));
          dumpf(R_EA|R_Always, R_DP, "ea = ea | memory[DP|0x%02x];", (SEX8(HTOI(addr+2))+1)&0xff);
        } else {
          dumpf(R_EA|R_Always, R_DP|R_lookup(addr), "ea = memory[DP|%s]<<8;", addr);
          dumpf(R_EA|R_Always, R_DP|R_lookup(addr), "ea = ea | memory[DP|(UINT8)((SINT8)%s + 1))];", addr);
        }
      } else {
        if (strncmp(addr, "0x", 2) == 0) {
          dumpf(R_EA|R_Always, 0, "ea = memory[0x%04x]<<8;", HTOI(addr+2));
          dumpf(R_EA|R_Always, 0, "ea = ea | memory[0x%04x];", HTOI(addr+2)+1);
        } else {
          dumpf(R_EA|R_Always, R_lookup(addr), "ea = memory[%s]<<8;", addr);
          dumpf(R_EA|R_Always, R_lookup(addr), "ea = ea | memory[(UINT16)((%s)+1)];", addr);
        }
        // sprintf (tmp, "((memory[%s]<<8)|memory[(UINT16)((%s)+1)])", addr, addr);
      }
      return "ea";
    }

    void
    push_stack16(char *stack, char *data)
    {                                                                 // memory is a byte anyway. so casting not needed
      // Malban wants the auto-increment/decrements separated from the memory access -
      //  not sure why, but there's no reason not to, so here it is (and in other similar cases)
      dumpf(R_Always|R_lookup(stack), R_lookup(stack),                "%s = %s - 1;", stack, stack);
      dumpf(R_Always,                 R_lookup(stack)|R_lookup(data), "memory[%s] = %s;", stack, data);
      dumpf(R_Always|R_lookup(stack), R_lookup(stack),                "%s = %s - 1;", stack, stack);
      dumpf(R_Always,                 R_lookup(stack)|R_lookup(data), "memory[%s] = %s >> 8;", stack, data);
      CK (1);
    }

    void
    pop_stack16(char *stack, char *dest) // stack adjustment done by caller
    {
      CK (1);
      dumpf(R_Always|R_lookup(dest),   R_lookup(stack),                "%s = memory[%s]<<8;", dest, stack);
      dumpf(R_Always|R_lookup(stack),  R_lookup(stack),                "%s = %s + 1;", stack, stack);
      dumpf(R_Always|R_lookup(dest),   R_lookup(stack)|R_lookup(dest), "%s = %s | memory[%s];", dest, dest, stack);
      dumpf(R_Always|R_lookup(stack),  R_lookup(stack),                "%s = %s + 1;", stack, stack);
    }

    char *
    direct(void)
    {
      static char tmp[16];
      sprintf (tmp, "DP|0x%02x", (memory[NextPC-1])&0xff);
      return strdup (tmp);
    }

    char *get_d (void);

    char *f(char *s, ...)
    {
       static FILE *nullfile = NULL;
       static char *buff = NULL;
       static int bufflen = 0;
       va_list ap;

       if (nullfile == NULL) {
          nullfile = fopen ("/dev/null", "w");
          if (nullfile == NULL) {
             fprintf (stderr, "Major error - cannot open /dev/null\n");
             exit (1);
          }
       }
       if (buff == NULL) {
          buff = malloc (bufflen = 256);
          if (buff == NULL) exit (2);              // if malloc failed so will printf!
       }
       {
          int string_length;

          va_start (ap, s);
          string_length = vfprintf (nullfile, s, ap);
          va_end (ap);

          if (string_length + 1 >= bufflen) {
             free (buff);
             buff = malloc (bufflen = 2 * string_length);
             if (buff == NULL) exit (3);
          }
          va_start (ap, s);
          //vsprintf (buff, s, ap);
          vsnprintf (buff, 0x7FFFFFFF, s, ap);
          va_end (ap);
       }

       return strdup (buff);
    }

    char *
    indexed (void)          /* note take 1 extra cycle */
    {
      char *regname[4] = { "X", "Y", "U", "S" };
      unsigned post = memory[CodePTR]; // imm_byte ();
      char *R = regname[(post >> 5) & 0x3];

      if (post & 0x80)
        {
          switch (post & 0x1f)
            {

            case 0x00:          set_post(R, 1); CK (6); return R;
            case 0x01:          set_post(R, 2); CK (7); return R;

            case 0x02:          CK (6); dumpf(R_Always|R_lookup(R), R_lookup(R),     "%s = %s - 1;", R, R); return R;
            case 0x03:          CK (7); dumpf(R_Always|R_lookup(R), R_lookup(R),     "%s = %s - 2;", R, R); return R;

            case 0x04:          CK (4); return R;
            case 0x05:          CK (5); return f("(UINT16)(%s + (SINT8)B)", R); // need to check hardware manual as to whether A or B are signed
            case 0x06:          CK (5); return f("(UINT16)(%s + (SINT8)A)", R);
            case 0x08:          CK (5); return f("(UINT16)(%s + %s)", R, imm_byte_s()); // TO DO: UNSIGNED OR SIGNED???
            case 0x09:          CK (8); return f("(UINT16)(%s + %s)", R, imm_word_s());
            case 0x0b:          CK (8); return f("(UINT16)(%s + %s)", R, get_d());
            case 0x0c:          CK (5); return f("0x%04x", (NextPC+SEX8(imm_byte()))&0xffff);
            case 0x0d:          CK (9); return f("0x%04x", (NextPC+imm_word())&0xffff);

            case 0x11:          set_post(R, 2); CK (7); CK (2); return RDMEM16(R);
            case 0x13:          CK (7); dumpf(R_Always|R_lookup(R), R_lookup(R),     "%s = %s - 2;", R, R); CK (2); return RDMEM16(R);

            case 0x14:          CK (4); CK (2); return RDMEM16(R);

            case 0x15:          CK (5); CK (2);
                                dumpf(R_EA|R_Always, R_lookup(R)|R_B, "ea = memory[(UINT16)(%s + (SINT8)B)]<<8;", R);
                                dumpf(R_EA|R_Always, R_lookup(R)|R_B, "ea = ea | memory[(UINT16)((%s + (SINT8)B) + 1)];", R); // wrap when B = 255, 0x7f, 0x80???
                                return "ea";

            case 0x16:          CK (5); CK (2);
                                dumpf(R_EA|R_Always, R_lookup(R)|R_A, "ea = memory[(UINT16)(%s + (SINT8)A)]<<8;", R);
                                dumpf(R_EA|R_Always, R_lookup(R)|R_A, "ea = ea | memory[(UINT16)((%s + (SINT8)A) + 1)];", R); // wrap when A = 255, 0x7f, 0x80???
                                return "ea";

            case 0x18:          CK (5); CK (2); // I HOPE THIS DOES NOT WRAP WITHIN 256 BYTE BLOCK!
                                dumpf(R_EA|R_Always, R_lookup(R), "ea = memory[(UINT16)(%s + (SINT8)%s)]<<8;", R, imm_byte_s());
                                dumpf(R_EA|R_Always, R_lookup(R), "ea = ea | memory[(UINT16)(%s + 0x%04x)];", R, (SEX8(imm_byte())+1)&0xffff);
                                assert(strcmp(R, "PC") != 0);
                                return "ea";

            case 0x19:          CK (8); CK (2);
                                dumpf(R_EA|R_Always, R_lookup(R), "ea = memory[(UINT16)(%s + %s)]<<8;", R, imm_word_s());
                                dumpf(R_EA|R_Always, R_lookup(R), "ea = ea | memory[(UINT16)(%s + 0x%04x)];", R, (imm_word()+1)&0xffff);
                                assert(strcmp(R, "PC") != 0);
                                return "ea";

            case 0x1b:          CK (8); CK (2);
                                dumpf(R_EA|R_Always, R_lookup(R)|R_A|R_B, "ea = memory[(UINT16)(%s + %s)]<<8;", R, get_d());
                                dumpf(R_EA|R_Always, R_lookup(R)|R_A|R_B, "ea = ea | memory[(UINT16)((%s + %s) + 1)];", R, get_d());
                                assert(strcmp(R, "PC") != 0);
                                return "ea";

            case 0x1c:          CK (5); CK (2);
                                dumpf(R_EA|R_Always, 0, "ea = memory[0x%04x]<<8;", (NextPC+SEX8(imm_byte()))&0xffff);
                                dumpf(R_EA|R_Always, 0, "ea = ea | memory[0x%04x];", (NextPC+SEX8(imm_byte())+1)&0xffff);
                                return "ea";

            case 0x1d:          CK (9); CK (2);
                                dumpf(R_EA|R_Always, 0, "ea = memory[0x%04x]<<8;", (NextPC+imm_word())&0xffff);
                                dumpf(R_EA|R_Always, 0, "ea = ea | memory[0x%04x];", (NextPC+imm_word()+1)&0xffff);
                                return "ea";

            case 0x1f:          CK (6); CK (2); return RDMEM16(imm_word_s());

            default:            printf ("%X: invalid index post $%02X\n", iPC, post); exit(1);
            }
        } else {
          if (post & 0x10) post |= 0xfff0; else post &= 0x000f;
          CK (5);
          return f("(UINT16)(%s + 0x%04x)", R, post&0xffff);
        }
      assert(__LINE__);
      return ("ea"); /* BUG??? */
    }

    char *
    extended (void)
    {
      static char addr[16];
      // Since this is translating known code at a known fixed address, these can be folded at translate time...
      // (if there was any code in ram, this would not catch it...)
      sprintf(addr, "0x%04x", ((memory[NextPC-2]<<8) | memory[NextPC-1])&0xffff);
      return(strdup(addr));  // or put 0xnnnn in the return string...
    }

    //void Make_D_from_A_and_B(void) {
    //  dumpf(R_D, R_AB, "D = (A << 8) | B;");
    //}

    char *
    get_d(void)
    {
      return "(((UINT8)A<<8)|(UINT8)B)";
    }

    //void Get_A_and_B_from_D(void) { // is this still used?  I think I now use A and B everywhere.
    //  dumpf(R_A, R_D, "A = (UINT8)(D >> 8);");
    //  dumpf(R_B, R_D, "B = (UINT8) D & 0xff;");
    //}

    void
    set_d(char *val)
    {
      assert(__LINE__);
      dumpf(R_A, R_lookup(val), "A = (UINT8)(%s >> 8);", val);
      dumpf(R_B, R_lookup(val), "B = (UINT8)%s;", val);
    }

    /* handle condition code register */

    void
    CC_Reg_from_Individual_Flags(void)
    {
      dumpf(R_Always|R_HNZVC, R_HNZVC, "simplify_flags();"); // canonicalise flags to all 0 or 1
      dumpf(R_Always|R_CC, R_HNZVC, "CC = (((((((((((((E<<1) | F)<<1) | H)<<1) |I)<<1) | N)<<1) | Z)<<1) | V)<<1) | C; // Placeholder for now");
      dumpf(R_Always|R_HNZVC, R_HNZVC, "restore_flags();"); // this is excessive but works and well tested
    }

    void
    Individual_Flags_from_CC_Reg (void)
    {
      dumpf(R_Always|R_HNZVC, R_CC, "C = CC; V = C>>1; Z = V>>1; N = Z>>1; I = N>>1; H = I>>1; F = H>>1; E = (F>>1)&1; F = F&1; H = H&1; I = I&1; N = N&1; Z = Z&1; V = V&1; C = C&1;");
      dumpf(R_Always|R_HNZVC, R_HNZVC, "restore_flags();");
    }

    char *
    get_reg (unsigned nro)
    {
      switch (nro)
        {
        case 0:      return "((A << 8) | B)";      break;
        case 1:      return "X";                   break;
        case 2:      return "Y";                   break;
        case 3:      return "U";                   break;
        case 4:      return "S";                   break;
        case 5:      return "PC";                  break;
        case 8:      return "A";                   break;
        case 9:      return "B";                   break;
        case 10:     CC_Reg_from_Individual_Flags ();
                     return "CC";                  break;
        case 11:     return "(UINT8)(DP >> 8)";    break;
        default:     printf ("%X: invalid register code $%02X\n", iPC, nro); exit(1);
        }
    }

    void
    set_reg (unsigned nro, char *val)
    {
      switch (nro)
        {
        case 0:      dumpf(R_Always|R_A,  R_lookup(val), "A = (UINT8)((%s) >> 8);", val);
                     if (
                          ((strncmp(val, "(SINT8)", strlen("(SINT8)"))) == 0)
                          ||
                          ((strncmp(val, "(UINT8)", strlen("(UINT8)"))) == 0)
                         ) {
                       dumpf(R_Always|R_B,  R_lookup(val), "B = (UINT8)%s;", val+strlen("(SINT8)"));
                     } else {
                       dumpf(R_Always|R_B,  R_lookup(val), "B = (UINT8)%s;", val);
                     }
                     break;
        case 1:      dumpf(R_Always|R_X,  R_lookup(val), "X = %s;", val);              break;
        case 2:      dumpf(R_Always|R_Y,  R_lookup(val), "Y = %s;", val);              break;
        case 3:      dumpf(R_Always|R_U,  R_lookup(val), "U = %s;", val);              break;
        case 4:      dumpf(R_Always|R_S,  R_lookup(val), "S = %s;", val);              break;
        case 5:      dumpf(R_Always|R_PC, R_lookup(val), "PC = %s; // check for missing JUMP", val);             break;
        case 8:      dumpf(R_Always|R_A,  R_lookup(val), "A = %s;", val);              break;
        case 9:      dumpf(R_Always|R_B,  R_lookup(val), "B = %s;", val);              break;
        case 10:     dumpf(R_Always|R_CC, 0,             "CC = %s;", val);
                     Individual_Flags_from_CC_Reg();                          break;
        case 11:     dumpf(R_Always|R_DP, R_lookup(val), "DP = (%s) << 8; checkDP();", val);
                     dumpf(R_Always, R_DP,      "// memory_DP = &memory[DP];");      break;
        default:     printf ("%X: invalid register code $%02X\n", iPC, nro); exit(1);
        }
    }

    /* 8-Bit Accumulator and Memory Instructions */

    void peephole(char *code) {
      // everything goes through here.  Maybe we can fix up some problems...

      // NOTE!!! The peephole procedure is TEMPORARY in this form and basically a
      // dirty hack.  This is one of those 'pay no attention to the man behind the curtain'
      // procedures, for now.

      char *assignment, *lhs, *rhs, *commentp, *comment, *s = strdup(code);
      if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = '\0';
      commentp = strstr(s, "//"); // TO DO: change /* to //  If fixed, could rely on " //"
      if (commentp) {
        comment = strdup(commentp);
        if (comment[strlen(comment)-1] == '\n') comment[strlen(comment)-1] = '\0';
        *commentp = '\0';
      } else comment = "";

      assignment = strstr(s, " = "); // and not in a string
      if (assignment) {
        *assignment = '\0';
        lhs = s;
        rhs = assignment + 3;
        // we need a minimal lexer here
        while (*lhs == ' ') lhs += 1;
        while (*rhs == ' ') rhs += 1;
        while (lhs[strlen(lhs)-1] == ' ') lhs[strlen(lhs)-1] = '\0';
        while (rhs[strlen(rhs)-1] == ' ') rhs[strlen(rhs)-1] = '\0';
        if (rhs[strlen(rhs)-1] == ';') rhs[strlen(rhs)-1] = '\0';
        if (strstr(lhs, "memory[") && strstr(rhs, "memory[")) {
          assert(__LINE__); // should not have memory on both sides of assignment (even for 'inc mem', although inc may need special handling)
        } else if (strstr(lhs, "memory[")) {
          if (strncmp(lhs, "memory[", strlen("memory[")) == 0) {
            lhs += strlen("memory[");
            if (lhs[strlen(lhs)-1] == ']') {
              lhs[strlen(lhs)-1] = '\0';
            } else {
              assert(__LINE__);
            }
            s = f("wr_mem(%s, %s); %s", lhs, rhs, comment);
          } else {
            assert(__LINE__);
          }
        } else if ((s = strstr(rhs, "memory[")) != NULL) {
          while ((s = strstr(rhs, "memory[")) != NULL) { // handle nested memory[memory[]] expressions
            int depth = 0;
            memmove(s, "rd_mem(", strlen("memory["));
                    // "memory["
            while (*s != '\0') {
            if (*s == '[') depth += 1;
              if (*s == ']') {
                if (depth == 0) {
                  *s = ')';
                  break;
                }
                depth -= 1;
              }
              s += 1;
            }
          }
          //if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = '\0';
          s = f("%s = %s; %s", lhs, rhs, comment);
        } else {
          //if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = '\0';
          s = f("%s = %s; %s", lhs, rhs, comment);
        }
        //if (*s == '\0') return;
        if ((lhs=strstr(s, "(SINT8)(SINT8)")) != NULL) {
          // remove the unneccessary (SINT8)
          memmove(lhs+strlen("(SINT8)"), lhs+strlen("(SINT8)(SINT8)"), strlen(lhs+strlen("(SINT8)(SINT8)"))+1);
        }
        if ((lhs=strstr(s, "(UINT8)(SINT8)")) != NULL) {
          // remove the unneccessary (SINT8)
          memmove(lhs+strlen("(UINT8)"), lhs+strlen("(UINT8)(SINT8)"), strlen(lhs+strlen("(UINT8)(SINT8)"))+1);
        }
        codes[nextfree] = s;
      } else {
        // procedure call, label, or something
        //if (*code == '\0') return;
        codes[nextfree] = strdup(code);
      }

      nextfree += 1;
    }

    /* WARNING: There are two different kinds of 64 bit architectures - both support 64 bit
                integers, but one of them also defaults to 64 bit pointers whereas the other
                doesn't.  On one of them (I'm not sure which but probably the one with 64 bit
                pointers) the varargs code below fails - most likely because the final parameter
                before the "..." is a pointer, so all subsequent parameters will also be
                pointer-sized but in fact are not.  I have not yet fixed this.
    */

    void dumpf(int def, int use, char *s, ...)                               /* Works like printf */
    {
       static FILE *nullfile = NULL;
       static char *buff = NULL;
       static int bufflen = 0;
       va_list ap;

       if (nullfile == NULL) {
          nullfile = fopen ("/dev/null", "w");
          if (nullfile == NULL) {
             fprintf (stderr, "Major error - cannot open /dev/null\n");
             exit (1);
          }
       }
       if (buff == NULL) {
          buff = malloc (bufflen = 256);
          if (buff == NULL)
             exit (2);              // if malloc failed so will printf!
       }

       {
       int string_length;

          va_start (ap, s);
          string_length = vfprintf (nullfile, s, ap);
          va_end (ap);

          if (string_length + 1 >= bufflen) {
             free (buff);
             buff = malloc (bufflen = 2 * string_length);
             if (buff == NULL)
                exit (3);
          }
          va_start (ap, s);
         //vsprintf (buff, s, ap);
          vsnprintf (buff, 0x7FFFFFFF, s, ap);
          va_end (ap);
       }
       defs[nextfree] = def;
       uses[nextfree] = use;

       peephole(buff); // should put indent in front of statement? Nah - just use gnu indent...
    }

    void
    adc(char *arg, char *val)  // arg is a register name, val is a data value or memory location
    {
      dumpf(R_ARG,             R_lookup(arg),                         "arg = %s;", arg);
      dumpf(R_VAL,             R_lookup(val),                         "val = %s;", val);
      dumpf(R_RES,             R_ARG|R_VAL|R_C,                       "res = (arg + val) + (C != 0 ? 1:0);");
      dumpf(R_C,               R_RES,                                 "C = (res >> 1) & 0x80;");
      dumpf(R_Z,               R_RES,                                 "Z = (UINT8)res;");
      dumpf(R_N,               R_RES,                                 "N = (UINT8)res;");
      dumpf(R_H,               R_ARG|R_VAL|R_RES|R_C,                 "H = (arg ^ val) ^ ((UINT8)res ^ C);");
      dumpf(R_V,               R_ARG|R_VAL|R_RES|R_C,                 "V = (arg ^ val) ^ ((UINT8)res ^ C);");
      dumpf(R_Always|R_lookup(arg), R_RES,                            "%s = (UINT8)res;", arg);
    }

    void
    add(char *arg, char *opd)
    {
      //    DEF                USE                          CODE
      if (strncmp(opd, "(SINT8)", strlen("(SINT8)")) == 0) opd = opd+strlen("(SINT8)"); //GT: bugfix?
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_ARG,             R_lookup(opd),               "arg = %s;", opd);
      dumpf(R_RES,             R_VAL|R_ARG,                 "res = val + (UINT8)arg;");
    #ifndef TESTING_ERROR_DETECTION_1
      // if defined, we don't generate this line... which should cause a wrong result
      dumpf(R_C,               R_RES,                       "C = (res >> 1) & 0x80;");
    #endif
      dumpf(R_Z,               R_RES,                       "Z = (UINT8)res;");
      dumpf(R_N,               R_RES,                       "N = (UINT8)res;");
      dumpf(R_H,               R_VAL|R_ARG|R_RES|R_C,       "H = (UINT8)((val ^ arg) ^ (res ^ C));");
      dumpf(R_V,               R_VAL|R_ARG|R_RES|R_C,       "V = (UINT8)((val ^ arg) ^ (res ^ C));");
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = (UINT8)res;", arg);
    }

    void
    and(char *arg, char *opd)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_RES,             R_VAL|R_lookup(opd),         "res = val & %s;", opd);
      dumpf(R_Always|R_lookup(arg),    R_RES,               "%s = res;", arg);
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_V,               0,                           "V = 0;");
    }

    void
    asl(char *arg)          /* same as lsl */
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_RES,             R_VAL,                       "res = val << 1;");
      dumpf(R_C,               R_RES,                       "C = res & 0x100;");
      dumpf(R_RES,             R_RES,                       "res = (UINT8)res;");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_V,               R_VAL | R_RES,               "V = val ^ res;");
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = res;", arg);

      CK (2);
    }

    void
    asr(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_C,               R_VAL,                       "C = val & 1;");
      dumpf(R_VAL,             R_VAL,                       "val = (UINT8)((SINT8)val >> 1);");
      dumpf(R_Always|R_lookup(arg),     R_VAL,              "%s = val;", arg);
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_N,               R_VAL,                       "N = val;");

      CK (2);
    }

    void
    bit(char *arg, char *val)
    {
      //    DEF                USE                          CODE
      dumpf(R_RES,             R_lookup(arg)|R_lookup(val), "res = %s & %s;", arg, val);
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_V,               0,                           "V = 0;");
    }

    void
    clr(char *arg)
    {
      //    DEF                USE                          CODE
      if (strstr(arg, "memory") != NULL) { // can be a reg *OR* memory!
        // better to use this?  if (strncmp(arg, "memory", strlen("memory")) == 0) {  // TO DO after checking
        dumpf(R_Always,        R_lookup(arg),               "%s = 0;", arg);
                               // any regs given are in index of memory addr and therefore USEs not DEFs
      } else {
        dumpf(R_Always|R_lookup(arg),   0,                  "%s = 0;", arg);
      }
      dumpf(R_V,               0,                           "V = 0;");
      dumpf(R_Z,               0,                           "Z = 0;");
      dumpf(R_N,               0,                           "N = 0;");
      dumpf(R_C,               0,                           "C = 0;");

      CK (2);
    }

    void
    cmp(char *arg, char *opd)
    {
      //    DEF                USE                          CODE

      if (strncmp(opd, "(SINT8)", strlen("(SINT8)")) == 0) opd = opd+strlen("(SINT8)"); //GT: bugfix?
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_ARG,             R_lookup(opd),               "arg = %s;", opd);
      dumpf(R_RES,             R_VAL|R_ARG,                 "res = val - (UINT8)arg;");
      dumpf(R_C,               R_RES,                       "C = res & 0x100;");
      dumpf(R_Z,               R_RES,                       "Z = (UINT8)res;");
      dumpf(R_N,               R_RES,                       "N = (UINT8)res;");
      dumpf(R_V,               R_VAL|R_ARG|R_RES,           "V = (UINT8)((val ^ arg) & (val ^ res));");
    }

    void
    com(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s ^ 0xff;", arg);
      dumpf(R_Always|R_lookup(arg), R_VAL,                  "%s = val;", arg);
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_N,               R_VAL,                       "N = val;");
      dumpf(R_V,               0,                           "V = 0;");
      dumpf(R_C,               0,                           "C = 1;");

      CK (2);
    }

    void
    daa (void)
    {
        //    DEF                USE                          CODE
      dumpf(R_RES,             R_A,                         "res = A;");
      dumpf(R_MSN,             R_RES,                       "msn = res & 0xf0;");
      dumpf(R_LSN,             R_RES,                       "lsn = res & 0x0f;");

      // Yes, these must be '+', not '|'...

      dumpf(R_RES,             R_RES | R_LSN | R_H,         "res = res + (((lsn > 0x09) || (H & 0x10)) ? 0x06 : 0);");
      // msn and lsn may already be UINT8 ?
      dumpf(R_RES,             R_RES | R_MSN | R_LSN | R_C, "res = res + ((((UINT8)msn > 0x80U) && ((UINT8)lsn > 0x09U)) || (((UINT8)msn > 0x90U) || (C != 0)) ? 0x60 : 0);");

      dumpf(R_C,               R_C | R_RES,                 "C = C | (res & 0x100);");
      dumpf(R_RES,             R_RES,                       "res = (UINT8)res;");
      dumpf(R_Always|R_A,      R_RES,                       "A = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_V,               0,                           "V = 0;");

      CK (2);
    }

    void
    dec(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_RES,             R_VAL,                       "res = (UINT8)(val - 1);");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_V,               R_RES | R_VAL,               "V = val & ~res;");
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = res;", arg);
      CK (2);
    }

    void
    eor(char *arg, char *val)
    {
      //    DEF                USE                          CODE
      dumpf(R_RES,             R_lookup(arg)|R_lookup(val), "res = %s ^ %s;", arg, val);
      dumpf(R_Always|R_lookup(arg),     R_RES,              "%s = res;", arg);
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_V,               0,                           "V = 0;");
    }

    /*
    ## TFR/EXG with unaligned register sizes

    tfr x,b         ; 6809,6309: b low byte of x, a unchanged
    tfr x,a         ; 6809, a low byte of x, b unchanged

    tfr a,x         ; low byte of x has value of a, high byte is $FF
    tfr b,x         ; low byte of x has value of b, high byte is $FF

    exg a,x         ; low byte of x has value of a, high byte is $FF
    exg b,x         ; low byte of x has value of b, high byte is $FF

    According to Motorola 6809 and Hitachi 6309 Programmer's Reference

    Except for the case where the first operand is 8 bit and register CC or DP:
    In this case the 16-bit register has the value of the 8-bit register in
    high and low byte!
    */

    void
    exg(void)
    {
      //    DEF                USE                          CODE
      unsigned post = imm_byte ();

      // TO DO: undocumented case described above...
      if (((post ^ (post << 4)) & 0x80) == 0) {
        char *tmp = get_reg (post >> 4);
        dumpf(R_VAL|R_Always, R_lookup(tmp), "val = %s;", tmp);
        set_reg (post >> 4, get_reg (post & 15));
        set_reg (post & 15, "val");
      }
      CK (8);
    }

    void
    inc(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_RES,             R_VAL,                       "res = (UINT8)(val + 1);");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_V,               R_RES|R_VAL,                 "V = res & ~val;");
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = res;", arg);
      CK (2);
    }

    void
    ld(char *arg, char *opd)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(opd),               "val = (UINT8)%s;", opd);
      dumpf(R_Always|R_lookup(arg), R_VAL,                  "%s = val;", arg);
      dumpf(R_N,               R_VAL,                       "N = val;");
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_V,               0,                           "V = 0;");
    }

    void
    lsr(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_C,               R_VAL,                       "C = val & 1;");
      dumpf(R_VAL,             R_VAL,                       "val = val >> 1;");
      dumpf(R_Always|R_lookup(arg),     R_VAL,              "%s = val;", arg);
      dumpf(R_N,               0,                           "N = 0;");
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      CK (2);
    }

    void
    mul(void)
    {
      //    DEF                USE                          CODE
      dumpf(R_RES,             R_A|R_B,                     "res = (UINT16)(A * B);");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_C,               R_RES,                       "C = res & 0x80;");
      dumpf(R_Always|R_A,      R_RES,                       "A = res >> 8;");
      dumpf(R_Always|R_B,      R_RES,                       "B = (UINT8)res;");
      CK (11);
    }

    void
    neg(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_RES,             R_VAL,                       "res = -val;");
      dumpf(R_V,               R_VAL|R_RES,                 "V = val & (UINT8)res;");
      dumpf(R_C,               R_RES,                       "C = res & 0x100;");
      dumpf(R_VAL,             R_RES,                       "val = (UINT8)res;");
      dumpf(R_Always|R_lookup(arg),     R_VAL,              "%s = val;", arg);
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_N,               R_VAL,                       "N = val;");
      CK (2);
    }

    void
    or(char *arg, char *val)
    {
      //    DEF                USE                          CODE
      dumpf(R_RES,             R_lookup(arg)|R_lookup(val), "res = %s | (UINT8)%s;", arg, val); // val may have been cast to SINT8 already...
      dumpf(R_Always|R_lookup(arg),     R_RES,              "%s = res;", arg);
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_V,               0,                           "V = 0;");
    }

    void
    rol(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_RES,             R_VAL|R_C,                   "res = (val << 1) + (C != 0 ?1:0);"); // TO DO: change '+' for '|'?
      dumpf(R_C,               R_RES,                       "C = res & 0x100;");
      dumpf(R_Z,               R_RES,                       "Z = (UINT8)res;");
      dumpf(R_N,               R_RES,                       "N = (UINT8)res;"); // N, and V are both 8 bit variables so no cast required
      dumpf(R_V,               R_VAL|R_RES,                 "V = (UINT8)res ^ val;");
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = (UINT8)res;", arg);
      CK (2);
    }

    void
    ror(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_RES,             R_lookup(arg)|R_C,           "res = ((UINT8)%s) | ((C != 0) ? 0x100 : 0);", arg);
      dumpf(R_C,               R_RES,                       "C = res & 1;");
      dumpf(R_RES,             R_RES,                       "res = (UINT8)(res >> 1);");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res;");
      dumpf(R_Always|R_lookup(arg),     R_RES,              "%s = res;", arg);
      CK (2);
    }

    void
    sub(char *arg, char *val)
    {
      //    DEF                USE                          CODE
      if (strncmp(val, "(SINT8)", strlen("(SINT8)")) == 0) val = val+strlen("(SINT8)"); //GT: bugfix?
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_ARG,             R_lookup(val),               "arg = %s;", val);
      dumpf(R_RES,             R_VAL|R_ARG,                 "res = val - (UINT8)arg;");
      dumpf(R_C,               R_RES,                       "C = res & 0x100;");
      dumpf(R_Z,               R_RES,                       "Z = (UINT8)res;");
      dumpf(R_N,               R_RES,                       "N = (UINT8)res;"); // cast is redundant but suppresses warning
      dumpf(R_V,               R_VAL|R_ARG|R_RES,           "V = (UINT8)((val ^ arg) & (val ^ res));");
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = (UINT8)(res);", arg);
    }

    void
    sbc(char *arg, char *val)
    {
      //    DEF                USE                          CODE

      if (strncmp(val, "(SINT8)", strlen("(SINT8)")) == 0) val = val+strlen("(SINT8)");
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_ARG,             R_lookup(val),               "arg = %s;", val);
      dumpf(R_RES,             R_VAL|R_ARG|R_C,             "res = (val - (UINT8)arg) - (C != 0 ? 1:0);");
      dumpf(R_C,               R_RES,                       "C = res & 0x100;");
      dumpf(R_Z,               R_RES,                       "Z = (UINT8)res;");
      dumpf(R_N,               R_RES,                       "N = (UINT8)res;");
      dumpf(R_V,               R_VAL|R_ARG|R_RES,           "V = (UINT8)((val ^ arg) & (val ^ res));"); // FIXED!
      dumpf(R_Always|R_lookup(arg), R_RES,                  "%s = (UINT8)res;", arg);
    }

    void
    st(char *arg, char *ea)
    {
      // just FYI, none of the validation tests test storing at all well :-(  val = %s+1 went undetected!
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = %s;", arg);
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_N,               R_VAL,                       "N = val;");
      dumpf(R_V,               0,                           "V = 0;");
      if (strncmp(ea, "0x", 2) == 0) {
        dumpf(R_Always,          R_lookup(ea)|R_VAL,                  "memory[%s] = val;", ea);
      } else {
        dumpf(R_EA,              R_lookup(ea),                "ea = %s;", ea);
        dumpf(R_Always,          R_EA|R_VAL,                  "memory[ea] = val;", ea);
      }
    }

    void
    tst(char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = (UINT8)%s;", arg);
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_N,               R_VAL,                       "N = val;");
      dumpf(R_V,               0,                           "V = 0;");
      CK (2);
    }

    /*
    ## TFR/EXG with unaligned register sizes

    tfr x,b         ; 6809,6309: b low byte of x, a unchanged
    tfr x,a         ; 6809, a low byte of x, b unchanged

    tfr a,x         ; low byte of x has value of a, high byte is $FF
    tfr b,x         ; low byte of x has value of b, high byte is $FF

    exg a,x         ; low byte of x has value of a, high byte is $FF
    exg b,x         ; low byte of x has value of b, high byte is $FF

    According to Motorola 6809 and Hitachi 6309 Programmer's Reference

    Except for the case where the first operand is 8 bit and register CC or DP:
    In this case the 16-bit register has the value of the 8-bit register in
    high and low byte!
    */

    void
    tfr (void)
    {
      unsigned post = imm_byte ();
      char *val;

      /*if(((post ^ (post << 4)) & 0x80) == 0)*/  // fixing TFR A,X ...
      val = get_reg((post >> 4)&15);

      //dumpf(R_Always, 0,      "/* from R%d to R%d */", (post >> 4)&15, post & 15);

      switch (post & 15)
        {
                     //    DEF   USE            CODE
        case 0:      dumpf(R_VAL,         R_lookup(val), "val = %s;", val);
                     dumpf(R_Always|R_A,  R_VAL,         "A = (UINT8)(val >> 8);");
                     dumpf(R_Always|R_B,  R_VAL,         "B = (UINT8)val;");      break;
        case 1:
          if ((((post >> 4)&15) == 8) || (((post >> 4)&15) == 9)) { // TFR A,X or TFR B,X
            dumpf(R_Always|R_X,  R_lookup(val), "X = %s | 0xFF00; // undocumented", val);
          } else {
            dumpf(R_Always|R_X,  R_lookup(val), "X = %s;", val);
          }
          break;

        case 2:      dumpf(R_Always|R_Y,  R_lookup(val), "Y = %s;", val);              break;
        case 3:      dumpf(R_Always|R_U,  R_lookup(val), "U = %s;", val);              break;
        case 4:      dumpf(R_Always|R_S,  R_lookup(val), "S = %s;", val);              break;
        case 5:      dumpf(R_Always|R_PC, R_lookup(val), "PC = %s; // check for missing JUMP", val);             break;
        case 8:      dumpf(R_Always|R_A,  R_lookup(val), "A = (UINT8)%s;", val);              break;
        case 9:      dumpf(R_Always|R_B,  R_lookup(val), "B = (UINT8)%s;", val);              break;
        case 10:     dumpf(R_Always|R_CC, R_lookup(val), "CC = %s;", val);
                     Individual_Flags_from_CC_Reg();                          break;
        case 11:     dumpf(R_Always|R_DP, R_lookup(val), "DP = (%s) << 8; checkDP();", val);
                     dumpf(R_Always, R_DP,      "// memory_DP = &memory[DP];");      break;
        default:     printf ("%X: invalid register code $%02X\n", iPC, post); exit(1);
      }

      CK (6);
    }

    /* 16-Bit Accumulator Instructions */
    void
    abx (void)
    {
      //    DEF                USE                          CODE
      dumpf(R_Always|R_X,               R_X|R_B,                     "X = X + B;");
      CK (3);
    }

    void
    addd(char *val)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(val),               "val = %s;", val);
      dumpf(R_ARG,             R_A|R_B,                     "arg = (A << 8) | B;"); // I think one of the only two places we use ARG at the moment
      dumpf(R_RES,             R_ARG|R_VAL,                 "res = arg + val;");
      dumpf(R_C,               R_RES,                       "C = res & 0x10000;");
      dumpf(R_RES,             R_RES,                       "res = (UINT16)res;");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_V,               R_ARG|R_RES|R_VAL,           "V = ((arg ^ res) & (val ^ res)) >> 8;");
      dumpf(R_N,               R_RES,                       "N = res >> 8;");
      dumpf(R_Always|R_A,      R_RES,                       "A = (UINT8)(res >> 8);");
      dumpf(R_Always|R_B,      R_RES,                       "B = (UINT8)res;");
    }

    void
    cmp16 (char *arg, char *val) // if val is not already simple it will be bracketed
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(arg),               "val = %s;", arg);
      dumpf(R_ARG,             R_lookup(val),               "arg = %s;", val);
      dumpf(R_RES,             R_VAL|R_ARG,                 "res = val - arg;"); // thanks for the bugfix, Malban
      dumpf(R_C,               R_RES,                       "C = res & 0x10000;");
      dumpf(R_RES,             R_RES,                       "res = (UINT16)res;");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_N,               R_RES,                       "N = res >> 8;");
      dumpf(R_V,               R_RES|R_VAL|R_lookup(val),   "V = ((val ^ arg) & (val ^ res)) >> 8;"); // TO DO cast result to UINT8?
    }

    // Plain LDD should no longer be called.
    void
    ldd(char *arg)
    {
      char *s;
      //    DEF                USE                          CODE
      assert(__LINE__);
      if (arg[0] == '(' && (s=strstr(arg, "<<8)|")) && (strstr(s+1, "<<8)|") == NULL) && arg[strlen(arg)-1] == ')') {
        // Split up D into separate parts for assignment to A and B
        *s++ = '\0';
        if ((R_lookup(arg)&R_A) != 0) {
          dumpf(R_Always|R_VAL,             R_lookup(arg+2),             "val = %s;", arg+2);
          s = strchr(s, '|')+1; s[strlen(s)-1] = '\0';
          dumpf(R_Always|R_B,               R_lookup(s),                 "B = %s;", s);
          dumpf(R_Always|R_A,               R_VAL,                       "A = val;");
        } else {
          dumpf(R_Always|R_A,               R_lookup(arg+2),             "A = %s;", arg+2);
          s = strchr(s, '|')+1; s[strlen(s)-1] = '\0';
          dumpf(R_Always|R_B,               R_lookup(s),                 "B = %s;", s);
        }
      } else {
        dumpf(R_Always|R_A,               R_lookup(arg),               "A = (UINT8)((%s) >> 8);", arg); // is this ever passed a rd_mem? If so, cache in arg
        dumpf(R_Always|R_B,               R_lookup(arg),               "B = (UINT8)%s;", arg);
      }
    }

    // ldd_imm_word() == ldd(imm_word_s())
    void ldd_imm_word(void) {
      dumpf(R_Always|R_A,      0,               "A = 0x%02x;", memory[NextPC-2]);
      dumpf(R_Always|R_B,      0,               "B = 0x%02x;", memory[NextPC-1]);
      dumpf(R_Z,               R_A|R_B,         "Z = A|B;");
      dumpf(R_N,               R_A,             "N = A;");
      dumpf(R_V,               0,               "V = 0;");
    }

    // ldd_direct() == ldd(RDMEM16(direct()))
    void ldd_direct(void) {
    #ifdef NEVER
      dumpf(R_Always|R_A,      R_DP,            "A = memory_DP[0x%02x]; // Care needed with I/O space and word fetches", memory[NextPC-1]);
      dumpf(R_Always|R_B,      R_DP,            "B = memory_DP[0x%02x];", memory[NextPC-1]+1);
    #else
      dumpf(R_Always|R_A,      R_DP,            "A = memory[DP|0x%02x]; // Care needed with I/O space and word fetches", memory[NextPC-1]);
      dumpf(R_Always|R_B,      R_DP,            "B = memory[DP|0x%02x];", memory[NextPC-1]+1);
    #endif
      dumpf(R_Z,               R_A|R_B,         "Z = A|B;");
      dumpf(R_N,               R_A,             "N = A;");
      dumpf(R_V,               0,               "V = 0;");
    }

    // ldd_indexed_post() == ldd(RDMEM16(indexed())); post()
    void ldd_indexed_post(void) {
      char *s = indexed();
      if (strlen(s)>12) {// HACK!
        dumpf(R_EA,              R_lookup(s),                   "ea = %s;", s);
        post();
        //ldd(RDMEM16(indexed()));
        dumpf(R_Always|R_A,      R_EA,                        "A = memory[ea]; // Care needed with I/O space and word fetches");
        dumpf(R_Always|R_B,      R_EA,                        "B = memory[(UINT16)(ea+1)];");
      } else {
        dumpf(R_Always|R_A,      R_lookup(s),                 "A = memory[%s]; // Care needed with I/O space and word fetches. Use 'ea' here?", s);
        dumpf(R_Always|R_B,      R_lookup(s),                 "B = memory[(UINT16)(%s+1)];", s);
        post();
      }
      dumpf(R_Z,               R_A|R_B,                     "Z = A|B;");
      dumpf(R_N,               R_A,                         "N = A;");
      dumpf(R_V,               0,                           "V = 0;");
    }

    // ldd_extended() == ldd(RDMEM16(extended()))
    void ldd_extended(void) {
      dumpf(R_Always|R_A,      0,               "A = memory[0x%04x]; // Care needed with I/O space and word fetches", ((memory[NextPC-2]<<8) | memory[NextPC-1])&0xffff);
      dumpf(R_Always|R_B,      0,               "B = memory[0x%04x];", (((memory[NextPC-2]<<8) | memory[NextPC-1]) + 1)&0xffff);
      dumpf(R_Z,               R_A|R_B,         "Z = A|B;");
      dumpf(R_N,               R_A,             "N = A;");
      dumpf(R_V,               0,               "V = 0;");
    }

    void
    ld16(char *reg, char *arg)
    {
      //    DEF                USE                          CODE
      dumpf(R_Always|R_lookup(reg), R_lookup(arg),          "%s = %s;", reg, arg);
      dumpf(R_Z,               R_lookup(reg),               "Z = %s;", reg);
      dumpf(R_N,               R_lookup(reg),               "N = (%s) >> 8;", reg);
      dumpf(R_V,               0,                           "V = 0;");
    }

    void
    sex (void)
    {
      //    DEF                USE                          CODE
      dumpf(R_Z,               R_B,                         "Z = B;");
      //dumpf(R_N,               R_B,                         "N = B & 0x80;");
      dumpf(R_N,               R_B,                         "N = B;");
      //dumpf(R_Always|R_A,      R_B,                         "A = ((B & 0x80) != 0) ? 0xff : 0;");
      dumpf(R_Always|R_A,      R_B,                         "A = ((SINT8)B < 0 ? 0xff : 0);");
      CK (2);
    }

    void
    std(char *addr)
    {
      //    DEF                USE                          CODE
      dumpf(R_Z,               R_A|R_B,                     "Z = A | B;");
      dumpf(R_N,               R_A,                         "N = A;");
      dumpf(R_V,               0,                           "V = 0;");
      if (strncmp(addr, "0x", 2) == 0) {
        dumpf(R_Always,          R_A|R_lookup(addr),          "memory[0x%04x] = A; // Care needed with I/O space and word fetches", HTOI(addr+2));
        dumpf(R_Always,          R_B|R_lookup(addr),          "memory[0x%04x] = B;", HTOI(addr+2)+1);
      } else {
        dumpf(R_EA,              R_lookup(addr),              "ea = %s;", addr);
        dumpf(R_Always,          R_A|R_EA,                    "memory[ea] = A; // Care needed with I/O space and word fetches");
        dumpf(R_Always,          R_B|R_EA,                    "memory[ea + 1] = B;"); // Is it worth wrapping the +1 here (by cast to UINT16 I think)?
      }
    }

    void
    st16(char *arg, char *addr)
    {
      //    DEF                USE                          CODE
      if (strncmp(addr, "(UINT16)", 8) == 0) addr = addr+strlen("(UINT16)");
      dumpf(R_VAL,             R_lookup(arg),               "val = %s;", arg);
      dumpf(R_Z,               R_VAL,                       "Z = val;");
      dumpf(R_N,               R_VAL,                       "N = (UINT8)(val >> 8);");
      dumpf(R_V,               0,                           "V = 0;");
      if (strncmp(addr, "0x", 2) == 0) {
        dumpf(R_Always,          R_VAL,                     "memory[0x%04x] = (UINT8)(val >> 8); // Care needed with I/O space and word fetches", HTOI(addr+2));
        dumpf(R_Always,          R_VAL,                     "memory[0x%04x] = (UINT8)val;", (HTOI(addr+2)+1)&0xffff);
      } else {
        dumpf(R_EA,              R_lookup(addr),            "ea = %s;", addr);
        dumpf(R_Always,          R_VAL|R_EA,                "memory[ea] = (UINT8)(val >> 8); // Care needed with I/O space and word fetches");
        dumpf(R_Always,          R_VAL|R_EA,                "memory[(UINT16)(ea + 1)] = (UINT8)val;");
      }
    }

    void
    subd(char *val)
    {
      //    DEF                USE                          CODE
      dumpf(R_VAL,             R_lookup(val),               "val = %s;", val);
      dumpf(R_ARG,             R_A|R_B,                     "arg = (A << 8) | B;"); // only other place we use ARG so far
      dumpf(R_RES,             R_ARG|R_VAL,                 "res = arg - val;");
      dumpf(R_C,               R_RES,                       "C = res & 0x10000;");
      dumpf(R_RES,             R_RES,                       "res = (UINT16)res;");
      dumpf(R_Z,               R_RES,                       "Z = res;");
      dumpf(R_V,               R_ARG|R_RES|R_VAL,           "V = ((arg ^ val) & (arg ^ res)) >> 8;");
      dumpf(R_Always|R_A,      R_RES,                       "A = (UINT8)(res >> 8);");
      dumpf(R_N,               R_RES,                       "N = res >> 8;");
      dumpf(R_Always|R_B,      R_RES,                       "B = (UINT8)res;");
    }

    /* stack instructions */
    void
    pshs (void)
    {
      //    DEF                USE                          CODE
      unsigned post = imm_byte ();

      CK (5);

      if (post & 0x80) { // Recoded to be more sanitary...
        char tmp[16];
        //dumpf(R_Always|R_PC, 0, "PC = 0x%04x;", NextPC&0xffff); // check this is correct...?
        CK (2);
        //push_stack16 ("S", "PC");
        sprintf(tmp, "0x%02x", NextPC&0xff);
        push_stack8 ("S", tmp);
        sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
        push_stack8 ("S", tmp);
      }
      if (post & 0x40) { CK (2); push_stack16 ("S", "U"); }
      if (post & 0x20) { CK (2); push_stack16 ("S", "Y"); }
      if (post & 0x10) { CK (2); push_stack16 ("S", "X"); }
      if (post & 0x08) { CK (1); push_stack8 ("S", "(DP >> 8)"); }
      if (post & 0x04) { CK (1); push_stack8 ("S", "B"); }
      if (post & 0x02) { CK (1); push_stack8 ("S", "A"); }
      if (post & 0x01) { CK (1); CC_Reg_from_Individual_Flags(); push_stack8 ("S", "CC"); }
    }

    void
    pshu (void)
    {
      //    DEF                USE                          CODE
      unsigned post = imm_byte ();

      CK (5);

      if (post & 0x80) {
        char tmp[16];
        //dumpf(R_Always|R_PC, 0, "PC = 0x%04x;", NextPC&0xffff); // check this is correct...?
        CK (2);
        //push_stack16 ("U", "PC");
        sprintf(tmp, "0x%02x", NextPC&0xff);
        push_stack8 ("U", tmp);
        sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
        push_stack8 ("U", tmp);
      }
      if (post & 0x40) { CK (2); push_stack16 ("U", "S"); }
      if (post & 0x20) { CK (2); push_stack16 ("U", "Y"); }
      if (post & 0x10) { CK (2); push_stack16 ("U", "X"); }
      if (post & 0x08) { CK (1); push_stack8 ("U", "(DP >> 8)"); } // R_lookup
      if (post & 0x04) { CK (1); push_stack8 ("U", "B"); }
      if (post & 0x02) { CK (1); push_stack8 ("U", "A"); }
      if (post & 0x01) { CK (1); CC_Reg_from_Individual_Flags(); push_stack8 ("U", "CC"); }
    }

    void
    puls (void)
    {
      //    DEF                USE                          CODE
      unsigned post = imm_byte ();

      CK (5);

      if (post & 0x01) { CK (1); pop_stack8 ("S", "CC"); Individual_Flags_from_CC_Reg(); }
      if (post & 0x02) { CK (1); pop_stack8 ("S", "A"); }
      if (post & 0x04) { CK (1); pop_stack8 ("S", "B"); }
      if (post & 0x08) {
        CK (1); pop_stack8 ("S", "DP");  dumpf(R_DP, R_DP, "DP = DP << 8; checkDP();");
        dumpf(R_Always, R_DP, "// memory_DP = &memory[DP];");
      }
      if (post & 0x10) { CK (2); pop_stack16 ("S", "X"); }
      if (post & 0x20) { CK (2); pop_stack16 ("S", "Y"); }
      if (post & 0x40) { CK (2); pop_stack16 ("S", "U"); }
      if (post & 0x80) {
        CK (2); pop_stack16 ("S", "PC");
        // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
        dumpf(R_Always,          R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
      }
    }

    void
    pulu (void)
    {
      //    DEF                USE                          CODE
      unsigned post = imm_byte ();

      CK (5);

      if (post & 0x01) { CK (1); pop_stack8 ("U", "CC"); Individual_Flags_from_CC_Reg(); }
      if (post & 0x02) { CK (1); pop_stack8 ("U", "A"); }
      if (post & 0x04) { CK (1); pop_stack8 ("U", "B"); }
      if (post & 0x08) { CK (1); pop_stack8 ("U", "DP"); dumpf(R_DP, R_DP, "DP = DP << 8; checkDP();"); dumpf(R_Always, R_DP, "// memory_DP = &memory[DP];"); }
      if (post & 0x10) { CK (2); pop_stack16 ("U", "X"); }
      if (post & 0x20) { CK (2); pop_stack16 ("U", "Y"); }
      if (post & 0x40) { CK (2); pop_stack16 ("U", "S"); }
      if (post & 0x80) {
        CK (2); pop_stack16 ("U", "PC");
        // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
        dumpf(R_Always,          R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
      }
    }

    /* Miscellaneous Instructions */
    void
    nop (void)
    {
      //    DEF                USE                          CODE
      CK (2);
    }

    void
    jsr (char *addr)
    {
      //    DEF                USE                          CODE

      static char tmp[16];
      // take care of return address:
      // (It is not possible with the current code to be executing a JSR from RAM.  We would
      //  need to add an interpreter in order to support that. So the assumption that NextPC
      //  is a compile-time constant is currently valid.)
      //sprintf(tmp, "0x%04x", NextPC&0xffff);

      //dumpf(R_Always|R_PC,              0,                           "PC = 0x%04x;", NextPC&0xffff);

      //push_stack16 ("S", tmp); // unclean!  unclean!
      //push_stack16 ("S", "PC");  // cleaner...
      sprintf(tmp, "0x%02x", NextPC&0xff);
      push_stack8 ("S", tmp);
      sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
      push_stack8 ("S", tmp); // cleanest...

      if (!VECTREX) {
        if (Gen_Switch || (strncmp(addr, "0x", 2) != 0)) {
          dumpf(R_Always|R_PC,              R_lookup(addr),      "PC = %s;", addr);
          post(); dumpf(R_Always,  R_FLUSH,                    "JUMP;"); Accessible = FALSE;
        } else {
          if ((HTOI(addr+2) >= rom_start) && (HTOI(addr+2) < rom_end)) {
            // REDUNDANT? dumpf(R_Always|R_PC,              R_lookup(addr),      "PC = %s;", addr);
            // BUG! Only works for hex addresses, does not look up label as in disassembler...:
            if (Single_Step) {
              dumpf(R_Always|R_PC,              R_lookup(addr),      "PC = %s;", addr);
              post(); dumpf(R_Always,  R_FLUSH,                    "JUMP;"); Accessible = FALSE;
            } else {
              post();
              // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
              dumpf(R_Always,          R_FLUSH & ~R_HNZVC, "goto L%s;", UC(addr+2)); Accessible = FALSE;
            }
          } else {
            dumpf(R_Always|R_PC,              R_lookup(addr),      "PC = %s;", addr);
            post(); dumpf(R_Always,  R_FLUSH,                    "JUMP;"); Accessible = FALSE;
          }
        }
      } else {    // Not a vectrex program so don't use Vectrex optimisations...
        int rc;
        unsigned int call;

        dumpf(R_Always|R_PC,              R_lookup(addr),              "PC = %s;", addr);
        rc = sscanf(addr, "%x", &call); // could have used the "0x" test?
        if (Single_Step || (rc != 1)) {       // not a constant jsr target.  Eg "JSR [,X++]"  PC has been assigned above.
                             // Unless this is a known address in the translated code, it will require
                             // support from a fallback interpreter.
          call = 0;
          // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
          post(); dumpf(R_Always,          R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
        } else if ((call&0xffff) >= 0x8000U) {
          if (*Name[call] != '\0') {        // We know this jsr target in the Vectrex BIOS
            // TO DO: If DP is known at this point, could we also check it against the expected value and warn if there is a mismatch?
            post(); dumpf(R_Always|Out[call], R_PC|In[call], "JUMP;"); Accessible = FALSE;

            //dumpf(Trash[call], 0, "// This should not be inserted! Someone is using a trashed register?");
            // if anyone tries to access a register that was trashed by this call,
            // the string above will be inserted.

            //if ((Out[call] & R_DP) != 0) {
              // If we ever do a high level emulation and avoid interpreting the vectrex BIOS, then we can use
              // this data to assign to DP where-ever the BIOS would have:
              //dumpf(R_Always, 0, "DP = 0x%02x << 8; memory_DP = &memory[DP];", DP_Out[call]);
              // Note that this has to be inserted after the return, and the return doesn't
              // happen exactly here - it happens after the label which is planted at the start
              // of the following instruction.  So that will have to be dealt with eventually...
            //}

          } else {
            // A *very few* roms use deeply internal calls in the BIOS that have not been documented
            // as externally usable interfaces.  The warning below is more of a call to document the
            // interface than anything critical to this translation.
            fprintf(stderr, "Warning: undetected bios call to %04x from %04x\n", call, iPC);
            // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
            post(); dumpf(R_Always,          R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
          }
        } else {
          // internal call - we could probably work out the regs that are required, trashed, or ignores using a transitive closure...
          // for now we'll err on the side of safety by requiring all registers except the flags... (fairly sure there's
          // nothing in the BIOS that takes flag bits as input parameters; and I think there's maybe only one that sets
          // a flag as a return value).
          // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
          post(); dumpf(R_Always,          R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE; // remove the exceptions!
        }
      }
    }

    void
    jmp (char *addr)
    {
      //    DEF                USE                          CODE
      if (Gen_Switch || (strncmp(addr, "0x", 2) != 0)) {
        dumpf(R_Always|R_PC,     R_lookup(addr),            "PC = %s;", addr);
        post(); dumpf(R_Always,  R_FLUSH,                   "JUMP;"); Accessible = FALSE;
      } else {
        // REDUNDANT?  dumpf(R_Always|R_PC,     R_lookup(addr),              "PC = %s;", addr);
        // BUG! Only works for hex addresses, does not look up label as in disassembler...:
        if (HTOI(addr+2) >= rom_start && HTOI(addr+2) < rom_end) {
          if (Single_Step) {
            dumpf(R_Always|R_PC,     R_lookup(addr),            "PC = %s;", addr);
            post(); dumpf(R_Always,  R_FLUSH,                   "JUMP;"); Accessible = FALSE;
          } else {
            post(); dumpf(R_Always,  R_FLUSH,                   "goto L%s;", UC(addr+2)); Accessible = FALSE;
          }
        } else {
          // Jumping out of this block - label cannot exist, so use raw address...
          dumpf(R_Always|R_PC,     R_lookup(addr),            "PC = %s;", addr);
          post(); dumpf(R_Always,  R_FLUSH,                   "JUMP;"); Accessible = FALSE;
        }
      }
    }

    void
    rti (void)
    {
      //    DEF                USE                          CODE
      CK (6);
      dumpf(R_RES,    R_E, "res = E;");  // The conditional removal of extra registers from the stack depends on the *original* E,
      pop_stack8 ("S", "CC"); Individual_Flags_from_CC_Reg (); // not the popped value of E...
      // (pop_stack8 had better not use 'res'...)
      dumpf(R_Always, R_RES, "if (res) {");
        CK (9);
        pop_stack8 ("S", "A");
        pop_stack8 ("S", "B");
        pop_stack8 ("S", "DP"); dumpf(R_DP, R_DP, "DP = DP << 8; checkDP();"); dumpf(R_Always, R_DP, "// memory_DP = &memory[DP];");
        pop_stack16 ("S", "X");
        pop_stack16 ("S", "Y");
        pop_stack16 ("S", "U");
      dumpf(R_Always, 0, "}");
      pop_stack16 ("S", "PC"); // needs special handling
      dumpf(R_Always,           R_FLUSH,                    "JUMP;"); Accessible = FALSE;
    }

    void
    rts (void)
    {
      //    DEF                USE                          CODE
      CK (5);
      pop_stack16 ("S", "PC");
      dumpf(R_Always,           R_FLUSH,                    "JUMP;"); Accessible = FALSE;
    }

    void
    swi (void)
    {
      //    DEF                USE                          CODE
      CK (19);

      static char tmp[16];

      //dumpf(R_Always|R_PC, 0, "PC = 0x%04x", NextPC&0xffff);

      //sprintf(tmp, "0x%04x", NextPC&0xffff);
      //push_stack16 ("S", tmp); // unclean!  unclean!
      //push_stack16 ("S", "PC");// cleaner
      sprintf(tmp, "0x%02x", NextPC&0xff);
      push_stack8 ("S", tmp);
      sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
      push_stack8 ("S", tmp); // cleanest...

      push_stack16 ("S", "U");
      push_stack16 ("S", "Y");
      push_stack16 ("S", "X");
      push_stack8 ("S", "(DP >> 8)"); // be careful
      push_stack8 ("S", "B");
      push_stack8 ("S", "A");
      CC_Reg_from_Individual_Flags(); push_stack8 ("S", "CC");

      dumpf(R_Always|R_PC, R_FLUSH, "PC = (memory[0xfffa]<<8)|memory[0xfffb];");
      // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
      dumpf(R_Always, R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
    }

    void
    swi2 (void)
    {
      //    DEF                USE                          CODE
      CK (20);

      static char tmp[16];

      //dumpf(R_Always|R_PC, 0, "PC = 0x%04x", NextPC&0xffff);

      //sprintf(tmp, "0x%04x", NextPC&0xffff);
      //push_stack16 ("S", tmp); // unclean!  unclean!
      //push_stack16 ("S", "PC");// cleaner
      sprintf(tmp, "0x%02x", NextPC&0xff);
      push_stack8 ("S", tmp);
      sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
      push_stack8 ("S", tmp); // cleanest...

      push_stack16 ("S", "U");
      push_stack16 ("S", "Y");
      push_stack16 ("S", "X");
      push_stack8 ("S", "(DP >> 8)"); // be careful
      push_stack8 ("S", "B");
      push_stack8 ("S", "A");
      CC_Reg_from_Individual_Flags(); push_stack8 ("S", "CC");
      dumpf(R_Always|R_PC, R_FLUSH, "PC = (memory[0xfff4]<<8)|memory[0xfff5];");
      // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
      dumpf(R_Always, R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
    }

    void
    swi3 (void)
    {
      //    DEF                USE                          CODE
      CK (20);

      static char tmp[16];

      //dumpf(R_Always|R_PC, 0, "PC = 0x%04x", NextPC&0xffff);

      //sprintf(tmp, "0x%04x", NextPC&0xffff);
      //push_stack16 ("S", tmp); // unclean!  unclean!
      //push_stack16 ("S", "PC");// cleaner
      sprintf(tmp, "0x%02x", NextPC&0xff);
      push_stack8 ("S", tmp);
      sprintf(tmp, "0x%02x", (NextPC>>8)&0xff); // cleanest...
      push_stack8 ("S", tmp); // cleanest...

      push_stack16 ("S", "U");
      push_stack16 ("S", "Y");
      push_stack16 ("S", "X");
      push_stack8 ("S", "(DP >> 8)");
      push_stack8 ("S", "B");
      push_stack8 ("S", "A");
      CC_Reg_from_Individual_Flags();  push_stack8 ("S", "CC");
      dumpf(R_Always|R_PC, R_FLUSH, "PC = (memory[0xfff2]<<8)|memory[0xfff3];");
      // TO DO: check if 'optimisation' (& ~R_HNZVC) is valid. It may be like the 'branch()' problem that Malban found.
      dumpf(R_Always, R_FLUSH & ~R_HNZVC, "JUMP;"); Accessible = FALSE;
    }

    void
    cwai (void)
    {
      //    DEF                USE                          CODE
      dumpf(R_Always,          R_FLUSH,                     "// CWAI - not suported yet!");
      //exit(1);
    }

    void
    sync (void)
    {
      //    DEF                USE                          CODE
      CK (4);
      dumpf(R_Always,          R_FLUSH,                     "// SYNC - not suported yet!");
      //exit(1);
    }

    void
    orcc (void)
    {
      //    DEF                USE                          CODE
      int tmp = imm_byte ();

      if ((tmp&E_FLAG) != 0) dumpf(R_Always|R_E, 0, "E = 1;");
      if ((tmp&F_FLAG) != 0) dumpf(R_Always|R_F, 0, "F = 1;");
      if ((tmp&I_FLAG) != 0) dumpf(R_Always|R_I, 0, "I = 1;");

      // use our conventions for TRUE and FALSE
      if ((tmp&H_FLAG) != 0) dumpf(R_Always|R_H, 0, "H = 0x10;");
      if ((tmp&N_FLAG) != 0) dumpf(R_Always|R_N, 0, "N = 0x80;");
      if ((tmp&Z_FLAG) != 0) dumpf(R_Always|R_Z, 0, "Z = 0;");
      if ((tmp&V_FLAG) != 0) dumpf(R_Always|R_V, 0, "V = 0x80;");
      if ((tmp&C_FLAG) != 0) dumpf(R_Always|R_C, 0, "C = 1;");

      CK (3);
    }

    void
    andcc (void)
    {
      //    DEF                USE                          CODE
      int tmp = imm_byte ();

      if ((tmp&E_FLAG) == 0) dumpf(R_Always|R_E, 0, "E = 0;");
      if ((tmp&F_FLAG) == 0) dumpf(R_Always|R_F, 0, "F = 0;");
      if ((tmp&I_FLAG) == 0) dumpf(R_Always|R_I, 0, "I = 0;");

      // use our conventions for TRUE and FALSE
      if ((tmp&H_FLAG) == 0) dumpf(R_Always|R_H, 0, "H = 0;");
      if ((tmp&N_FLAG) == 0) dumpf(R_Always|R_N, 0, "N = 0;");
      if ((tmp&Z_FLAG) == 0) dumpf(R_Always|R_Z, 0, "Z = 1;");
      if ((tmp&V_FLAG) == 0) dumpf(R_Always|R_V, 0, "V = 0;");
      if ((tmp&C_FLAG) == 0) dumpf(R_Always|R_C, 0, "C = 0;");

      CK (3);
    }

    /* Branch Instructions */

    #define cond_HI() "(Z && (!C))"
    #define cond_LS() "((!Z) || C)"
    #define cond_HS() "(!C)"
    #define cond_LO() "(C)"
    #define cond_NE() "(Z)"
    #define cond_EQ() "(!Z)"
    #define cond_VC() "((SINT8)V >= 0)"
    #define cond_VS() "((SINT8)V <  0)"
    #define cond_PL() "((SINT8)N >= 0)"
    #define cond_MI() "((SINT8)N <  0)"
    #define cond_GE() "((SINT8)(N^V) >= 0)"
    #define cond_LT() "((SINT8)(N^V) <  0)"
    #define cond_GT() "(((SINT8)(N^V) >= 0) && Z)"
    // #define cond_GT() "(" cond_GE() " && " cond_NE() ")"
    #define cond_LE() "(((SINT8)(N^V) <  0) || (!Z))"
    // #define cond_LE() "(" cond_LT() " || " cond_EQ() ")"

    signed short SEX8(UINT8 tmp) {
      if (tmp & 0x80) return 0xff00 | tmp;
      return tmp & 0x00ff;
    }

    void
    bra (int TWEAKED_FLUSH)
    {
      int IGNORE = R_HNZVC & ~TWEAKED_FLUSH;
      //    DEF                USE                          CODE
      if (Single_Step || Gen_Switch) {
        dumpf(R_Always|R_PC,     0,                         "PC = 0x%04x;", (NextPC+SEX8(imm_byte()))&0xffff );
        post(); dumpf(R_Always,  R_FLUSH & ~IGNORE,         "JUMP;"); Accessible = FALSE;
      } else {
        // REDUNDANT? dumpf(R_Always|R_PC,     0,           "PC = 0x%04x;", (NextPC+SEX8(imm_byte()))&0xffff );
        // BUG! Only works for hex addresses, does not look up label as in disassembler...:
        post(); dumpf(R_Always,  R_FLUSH & ~IGNORE,         "goto L%04X;", (NextPC+SEX8(imm_byte()))&0xffff); Accessible = FALSE;
      }
    }

    void brn(void)
    {
    }

    void
    branch (char *cond)
    {
      //    DEF                USE                          CODE
      dumpf(R_Always,          R_FLUSH,                     "// temp fix to ensure flags synched");
      dumpf(R_Always,          R_lookup(cond),              "if %s {", cond);
      bra(R_lookup(cond));
      dumpf(R_Always,          0,                           "}"); Accessible = TRUE;

      CK (3);
    }

    void
    long_bra (void)
    {
      //    DEF                USE                          CODE
      if (Single_Step || Gen_Switch) {
        dumpf(R_Always|R_PC,     0,                         "PC = 0x%04x;", (NextPC+imm_word())&0xffff);
        post(); dumpf(R_Always,  R_FLUSH,                   "JUMP;"); Accessible = FALSE;
      } else {
        // REDUNDANT? dumpf(R_Always|R_PC,     0,           "PC = 0x%04x;", (NextPC+imm_word())&0xffff);
        // BUG! Only works for hex addresses, does not look up label as in disassembler...:
        post(); dumpf(R_Always,  R_FLUSH,                   "goto L%04X;", (NextPC+imm_word())&0xffff); Accessible = FALSE;
      }
    }

    void long_brn(void)
    {
    }

    void
    long_branch (char *cond)
    {
      //    DEF                USE                          CODE
      CK (5);
      dumpf(R_Always,          R_lookup(cond),              "if %s {", cond);
      CK (1);
      long_bra ();
      dumpf(R_Always,          R_lookup(cond),              "}"); Accessible = TRUE;
    }

    void
    long_bsr (void)
    {
      //    DEF                USE                          CODE

      static char tmp[16];
      //sprintf(tmp, "0x%04x", NextPC&0xffff);
      //push_stack16 ("S", tmp); // unclean!  unclean!
      //push_stack16 ("S", "PC");// cleaner
      sprintf(tmp, "0x%02x", NextPC&0xff);
      push_stack8 ("S", tmp);
      sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
      push_stack8 ("S", tmp); // cleanest...

      CK (9);
      if (Single_Step || Gen_Switch) {
        dumpf(R_Always|R_PC,     0,                         "PC = 0x%04x;", (NextPC+imm_word())&0xffff);
        post(); dumpf(R_Always,  R_FLUSH,                   "JUMP;"); Accessible = FALSE;
      } else {
        // REDUNDANT? dumpf(R_Always|R_PC,     0,           "PC = 0x%04x;", (NextPC+imm_word())&0xffff);
        // BUG! Only works for hex addresses, does not look up label as in disassembler...:
        post(); dumpf(R_Always,  R_FLUSH,                   "goto L%04X;", (NextPC+imm_word())&0xffff); Accessible = FALSE;
      }
    }

    void
    bsr (void)
    {
      //    DEF                USE                          CODE

      static char tmp[16];
      //sprintf(tmp, "0x%04x", NextPC&0xffff);
      //push_stack16 ("S", tmp); // unclean!  unclean!
      //push_stack16 ("S", "PC");// cleaner
      sprintf(tmp, "0x%02x", NextPC&0xff);
      push_stack8 ("S", tmp);
      sprintf(tmp, "0x%02x", (NextPC>>8)&0xff);
      push_stack8 ("S", tmp); // cleanest...

      CK (7);
      if (Single_Step || Gen_Switch) {
        dumpf(R_Always|R_PC,     0,                         "PC = 0x%04x;", (NextPC+SEX8(imm_byte()))&0xffff);
        post(); dumpf(R_Always,  R_FLUSH,                   "JUMP;"); Accessible = FALSE;
      } else {
        // REDUNDANT? dumpf(R_Always|R_PC,     0,           "PC = 0x%04x;", (NextPC+SEX8(imm_byte()))&0xffff);
        // BUG! Only works for hex addresses, does not look up label as in disassembler...:
        post(); dumpf(R_Always,  R_FLUSH,                   "goto L%04X;", (NextPC+SEX8(imm_byte()))&0xffff); Accessible = FALSE;
      }
    }

    void set(char *reg, char *val)
    {
      //    DEF                USE                          CODE
      // since it is only X Y, S and U we can skip the cast...
      if (strncmp(val, "(UINT16)", strlen("(UINT16)")) == 0) val = val+strlen("(UINT16)");
      dumpf(R_Always|R_lookup(reg),     R_lookup(val),      "%s = %s;", reg, val);
      if (strcmp(reg, "X") == 0 || strcmp(reg, "Y") == 0) {
        dumpf(R_Z,             R_lookup(reg),               "Z = %s;", reg);
      }
    }

    /* translate 6809 code */

    void translate6809 (int iPC, int NextPCParam, int cycles)
    {
      // https://atjs.mbnet.fi/mc6809/Information/6809.htm for checking cycle counts
      /*
         The caller is responsible for identifying code areas and stepping over them. CodePtr is updated here
       in order to point at the second instruction byte if present, then to the start of the operands if present;
       it does *not* step over the operands, which are more reliably found by stepping *back* from NextPC ...
       */
      unsigned opcode;
    #define S "S"
    #define U "U"
    #define X "X"
    #define Y "Y"
    #define A "A"
    #define B "B"

      /* NextPC points to the byte following this instruction. Relative branches etc
         are relative to that address, not to the address of this instruction.
         NextPC is not modified by the opcode fetching process. */
      NextPC = NextPCParam;
      /* CodePTR initially points to the first byte of the current instruction, and
         is moved forward as the instruction and its operands are decoded.  It should
         be equal to NextPC by the time this instruction is fully translated (though
         might not be if the address part is skipped, eg in the "brn" instruction?)
      */
      CodePTR = iPC;

      opcode = memory[CodePTR++];

      // if we want the PC to always be valid (eg for diags) then force it here:
      //dumpf(R_PC, 0, "PC = 0x%04x; /* optional? */", NextPC);
      switch (opcode) {
        case 0x00: CK(4); neg(RDMEM(direct())); break; /* NEG direct */
        case 0x03: CK(4); com(RDMEM(direct())); break; /* COM direct */
        case 0x04: CK(4); lsr(RDMEM(direct())); break; /* LSR direct */
        case 0x06: CK(4); ror(RDMEM(direct())); break; /* ROR direct */
        case 0x07: CK(4); asr(RDMEM(direct())); break; /* ASR direct */
        case 0x08: CK(4); asl(RDMEM(direct())); break; /* ASL direct */
        case 0x09: CK(4); rol(RDMEM(direct())); break; /* ROL direct */
        case 0x0a: CK(4); dec(RDMEM(direct())); break; /* DEC direct */
        case 0x0c: CK(4); inc(RDMEM(direct())); break; /* INC direct */
        case 0x0d: CK(4); tst(RDMEM(direct())); break; /* TST direct */
        case 0x0e: CK(3); jmp(direct());        break; /* JMP direct */
        case 0x0f: CK(4); clr(RDMEM(direct())); break; /* CLR direct */

        case 0x10: {
          opcode = memory[CodePTR++];

          switch (opcode) {
          case 0x21:                        CK(5);      /* PC += 2; */                               break; // lbrn
            case 0x22:                                    long_branch( cond_HI() );                  break;
            case 0x23:                                    long_branch( cond_LS() );                  break;
            case 0x24:                                    long_branch( cond_HS() );                  break;
            case 0x25:                                    long_branch( cond_LO() );                  break;
            case 0x26:                                    long_branch( cond_NE() );                  break;
            case 0x27:                                    long_branch( cond_EQ() );                  break;
            case 0x28:                                    long_branch( cond_VC() );                  break;
            case 0x29:                                    long_branch( cond_VS() );                  break;
            case 0x2a:                                    long_branch( cond_PL() );                  break;
            case 0x2b:                                    long_branch( cond_MI() );                  break;
            case 0x2c:                                    long_branch( cond_GE() );                  break;
            case 0x2d:                                    long_branch( cond_LT() );                  break;
            case 0x2e:                                    long_branch( cond_GT() );                  break;
            case 0x2f:                                    long_branch( cond_LE() );                  break;
            case 0x3f:                                    swi2();                                    break;
            case 0x83:                        CK(5);      cmp16(get_d(),imm_word_s());               break;
            case 0x8c:                        CK(5);      cmp16(Y,imm_word_s());                     break;
            case 0x8e:                        CK(4);      ld16(Y, imm_word_s());                     break;
            case 0x93:                        CK(5);      cmp16(get_d(),RDMEM16(direct())); CK(1);   break;
            case 0x9c:                        CK(5);      cmp16(Y, RDMEM16(direct()));      CK(1);   break;
            case 0x9e:                        CK(5);      ld16(Y, RDMEM16(direct()));                break;
            case 0x9f:                        CK(5);      st16(Y, direct());                         break;
            case 0xa3: CK(1);                             cmp16(get_d(),RDMEM16(indexed())); post(); CK(1);  break;
            case 0xac: CK(1);                             cmp16(Y,RDMEM16(indexed())); post();       CK(1);  break;
            case 0xae: CK(1);                             ld16(Y, RDMEM16(indexed())); post();       break;
            case 0xaf: CK(1);                             st16(Y, indexed()); post();                break;
            case 0xb3:                        CK(6);      cmp16(get_d(),RDMEM16(extended())); CK(1); break;
            case 0xbc:                        CK(6);      cmp16(Y,RDMEM16(extended()));       CK(1); break;
            case 0xbe:                        CK(6);      ld16(Y, RDMEM16(extended()));              break;
            case 0xbf:                        CK(6);      st16(Y, extended());                       break;
            case 0xce:                        CK(4);      ld16(S, imm_word_s());                     break;
            case 0xde:                        CK(5);      ld16(S, RDMEM16(direct()));                break;
            case 0xdf:                        CK(5);      st16(S, direct());                         break;
            case 0xee: CK(1);                             ld16(S, RDMEM16(indexed())); post();       break;
            case 0xef: CK(1);                             st16(S, indexed()); post();                break;
            case 0xfe: CK(6);                             ld16(S, RDMEM16(extended()));              break;
            case 0xff:                        CK(6);      st16(S, extended());                       break;
            default:   printf("%X: invalid opcode $10%02X\n", iPC, opcode);
                       // exit(1);
          }
        }
        break;

        case 0x11: {
          opcode = memory[CodePTR++];

          switch (opcode) {
            case 0x3f:        swi3();                        break;
            case 0x83: CK(5); cmp16(U,imm_word_s());         break;
            case 0x8c: CK(5); cmp16(S,imm_word_s());         break;
            case 0x93: CK(6); cmp16(U,RDMEM16(direct()));    break;
            case 0x9c: CK(6); cmp16(S,RDMEM16(direct()));    break;
            case 0xa3: CK(2); cmp16(U,RDMEM16(indexed())); post();   break;
            case 0xac: CK(2); cmp16(S,RDMEM16(indexed())); post();   break;
            case 0xb3: CK(7); cmp16(U,RDMEM16(extended()));  break;
            case 0xbc: CK(7); cmp16(S,RDMEM16(extended()));  break;
            default:   printf("%X: invalid opcode $11%02X\n",iPC,opcode);
                       // exit(1);
          }
        }
        break;

        case 0x12: nop();                    break;
        case 0x13: sync();                   break;
        case 0x16: long_bra(); CK(5);        break;
        case 0x17: long_bsr();               break;
        case 0x19: daa();                    break;
        case 0x1a: orcc();                   break;
        case 0x1c: andcc();                  break;
        case 0x1d: sex();                    break;
        case 0x1e: exg();                    break;
        case 0x1f: tfr();                    break;

        case 0x20: bra(R_FLUSH);         CK(3);       break;
        case 0x21: brn();                CK(3);       break; // brn
        case 0x22: branch( cond_HI() );               break;
        case 0x23: branch( cond_LS() );               break;
        case 0x24: branch( cond_HS() );               break;
        case 0x25: branch( cond_LO() );               break;
        case 0x26: branch( cond_NE() );               break;
        case 0x27: branch( cond_EQ() );               break;
        case 0x28: branch( cond_VC() );               break;
        case 0x29: branch( cond_VS() );               break;
        case 0x2a: branch( cond_PL() );               break;
        case 0x2b: branch( cond_MI() );               break;
        case 0x2c: branch( cond_GE() );               break;
        case 0x2d: branch( cond_LT() );               break;
        case 0x2e: branch( cond_GT() );               break;
        case 0x2f: branch( cond_LE() );               break;

        case 0x30: set(X, indexed()); post(); break; /* LEAX indexed */  // set_X and set_Y should also set Z
        case 0x31: set(Y, indexed()); post(); break; /* LEAY indexed */
        case 0x32: set(S, indexed()); post(); break; /* LEAS indexed */
        case 0x33: set(U, indexed()); post(); break; /* LEAU indexed */
        case 0x34: pshs();            break; /* PSHS implied */
        case 0x35: puls();            break; /* PULS implied */
        case 0x36: pshu();            break; /* PSHU implied */
        case 0x37: pulu();            break; /* PULU implied */
        case 0x39: rts();             break; /* RTS implied  */
        case 0x3a: abx();             break; /* ABX implied  */
        case 0x3b: rti();             break; /* RTI implied  */
        case 0x3c: cwai();            break; /* CWAI implied */
        case 0x3d: mul();             break; /* MUL implied  */
        case 0x3f: swi();             break; /* SWI implied  */

        case 0x40: neg(A); break; /* NEGA implied */
        case 0x43: com(A); break; /* COMA implied */
        case 0x44: lsr(A); break; /* LSRA implied */
        case 0x46: ror(A); break; /* RORA implied */
        case 0x47: asr(A); break; /* ASRA implied */
        case 0x48: asl(A); break; /* ASLA implied */
        case 0x49: rol(A); break; /* ROLA implied */
        case 0x4a: dec(A); break; /* DECA implied */
        case 0x4c: inc(A); break; /* INCA implied */
        case 0x4d: tst(A); break; /* TSTA implied */
        case 0x4f: clr(A); break; /* CLRA implied */

        case 0x50: neg(B); break; /* NEGB implied */
        case 0x53: com(B); break; /* COMB implied */
        case 0x54: lsr(B); break; /* LSRB implied */
        case 0x56: ror(B); break; /* RORB implied */
        case 0x57: asr(B); break; /* ASRB implied */
        case 0x58: asl(B); break; /* ASLB implied */
        case 0x59: rol(B); break; /* ROLB implied */
        case 0x5a: dec(B); break; /* DECB implied */
        case 0x5c: inc(B); break; /* INCB implied */
        case 0x5d: tst(B); break; /* TSTB implied */
        case 0x5f: clr(B); break; /* CLRB implied */

        case 0x60:        neg(RDMEM(indexed())); post(); break; /* NEG indexed */
        case 0x63:        com(RDMEM(indexed())); post(); break; /* COM indexed */
        case 0x64:        lsr(RDMEM(indexed())); post(); break; /* LSR indexed */
        case 0x66:        ror(RDMEM(indexed())); post(); break; /* ROR indexed */
        case 0x67:        asr(RDMEM(indexed())); post(); break; /* ASR indexed */
        case 0x68:        asl(RDMEM(indexed())); post(); break; /* ASL indexed */
        case 0x69:        rol(RDMEM(indexed())); post(); break; /* ROL indexed */
        case 0x6a:        dec(RDMEM(indexed())); post(); break; /* DEC indexed */
        case 0x6c:        inc(RDMEM(indexed())); post(); break; /* INC indexed */
        case 0x6d:        tst(RDMEM(indexed())); post(); break; /* TST indexed */
        case 0x6e: CK(1); jmp(indexed());                break; /* JMP indexed */   // post() is handled in jmp() code so it comes before JUMP
        case 0x6f:        clr(RDMEM(indexed())); post(); break; /* CLR indexed */

        case 0x70: CK(5); neg(RDMEM(extended())); break; /* NEG extended */
        case 0x73: CK(5); com(RDMEM(extended())); break; /* COM extended */
        case 0x74: CK(5); lsr(RDMEM(extended())); break; /* LSR extended */
        case 0x76: CK(5); ror(RDMEM(extended())); break; /* ROR extended */
        case 0x77: CK(5); asr(RDMEM(extended())); break; /* ASR extended */
        case 0x78: CK(5); asl(RDMEM(extended())); break; /* ASL extended */
        case 0x79: CK(5); rol(RDMEM(extended())); break; /* ROL extended */
        case 0x7a: CK(5); dec(RDMEM(extended())); break; /* DEC extended */
        case 0x7c: CK(5); inc(RDMEM(extended())); break; /* INC extended */
        case 0x7d: CK(5); tst(RDMEM(extended())); break; /* TST extended */
        case 0x7e: CK(4); jmp(extended());        break; /* JMP extended */
        case 0x7f: CK(5); clr(RDMEM(extended())); break; /* CLR extended */

        case 0x80: CK(2); sub(A, imm_byte_s());  break; // TO DO: all these recipients of imm_byte_s need to be checked that they handle signedness correctly
        case 0x81: CK(2); cmp(A, imm_byte_s());  break;
        case 0x82: CK(2); sbc(A, imm_byte_s());  break;
        case 0x83: CK(4); subd(imm_word_s());    break;
        case 0x84: CK(2); and(A, imm_byte_s());  break;
        case 0x85: CK(2); bit(A, imm_byte_s());  break;
        case 0x86: CK(2); ld(A, imm_byte_s());   break;
        case 0x88: CK(2); eor(A, imm_byte_s());  break;
        case 0x89: CK(2); adc(A, imm_byte_s());  break;
        case 0x8a: CK(2); or(A, imm_byte_s());   break;
        case 0x8b: CK(2); add(A, imm_byte_s());  break;
        case 0x8c: CK(4); cmp16(X, imm_word_s());break;
        case 0x8d:        bsr();                 break;
        case 0x8e: CK(3); ld16(X, imm_word_s()); break;

        case 0x90: CK(4); sub(A, RDMEM(direct()));     break;
        case 0x91: CK(4); cmp(A, RDMEM(direct()));     break;
        case 0x92: CK(4); sbc(A, RDMEM(direct()));     break;
        case 0x93: CK(5); subd(RDMEM16(direct()));     break;
        case 0x94: CK(4); and(A, RDMEM(direct()));     break;
        case 0x95: CK(4); bit(A, RDMEM(direct()));     break;
        case 0x96: CK(4); ld(A, RDMEM(direct()));      break;
        case 0x97: CK(4); st(A, direct());             break;
        case 0x98: CK(4); eor(A, RDMEM(direct()));     break;
        case 0x99: CK(4); adc(A, RDMEM(direct()));     break;
        case 0x9a: CK(4); or(A, RDMEM(direct()));      break;
        case 0x9b: CK(4); add(A, RDMEM(direct()));     break;
        case 0x9c: CK(5); cmp16(X, RDMEM16(direct())); break;
        case 0x9d: CK(7); jsr(direct());               break;
        case 0x9e: CK(4); ld16(X, RDMEM16(direct()));  break;
        case 0x9f: CK(4); st16(X, direct());           break;

        case 0xa0:        sub(A, RDMEM(indexed())); post();            break;
        case 0xa1:        cmp(A, RDMEM(indexed())); post();            break;
        case 0xa2:        sbc(A, RDMEM(indexed())); post();            break;
        case 0xa3: CK(1); subd(RDMEM16(indexed())); post();            break;
        case 0xa4:        and(A, RDMEM(indexed())); post();            break;
        case 0xa5:        bit(A, RDMEM(indexed())); post();            break;
        case 0xa6:        ld(A, RDMEM(indexed())); post();             break;
        case 0xa7:        st(A, indexed()); post();                    break;
        case 0xa8:        eor(A, RDMEM(indexed())); post();            break;
        case 0xa9:        adc(A, RDMEM(indexed())); post();            break;
        case 0xaa:        or(A, RDMEM(indexed())); post();             break;
        case 0xab:        add(A, RDMEM(indexed())); post();            break;
        case 0xac: CK(1); cmp16(X, RDMEM16(indexed())); post();        break;
        case 0xad: CK(3); jsr(indexed());                              break;   // AHA!: post() is handled in jsr() code so it comes before JUMP
        case 0xae:        ld16(X, RDMEM16(indexed())); post();         break;
        case 0xaf:        st16(X, indexed()); post();                  break;

        case 0xb0: CK(5); sub(A, RDMEM(extended()));            break;
        case 0xb1: CK(5); cmp(A, RDMEM(extended()));            break;
        case 0xb2: CK(5); sbc(A, RDMEM(extended()));            break;
        case 0xb3: CK(6); subd(RDMEM16(extended()));            break;
        case 0xb4: CK(5); and(A, RDMEM(extended()));            break;
        case 0xb5: CK(5); bit(A, RDMEM(extended()));            break;
        case 0xb6: CK(5); ld(A, RDMEM(extended()));             break;
        case 0xb7: CK(5); st(A, extended());                    break;
        case 0xb8: CK(5); eor(A, RDMEM(extended()));            break;
        case 0xb9: CK(5); adc(A, RDMEM(extended()));            break;
        case 0xba: CK(5); or(A, RDMEM(extended()));             break;
        case 0xbb: CK(5); add(A, RDMEM(extended()));            break;
        case 0xbc: CK(6); cmp16(X, RDMEM16(extended()));        break;
        case 0xbd: CK(8); jsr(extended());                      break;
        case 0xbe: CK(5); ld16(X, RDMEM16(extended()));         break;
        case 0xbf: CK(5); st16(X, extended());                  break;

        case 0xc0: CK(2); sub(B, imm_byte_s());    break;
        case 0xc1: CK(2); cmp(B, imm_byte_s());    break;
        case 0xc2: CK(2); sbc(B, imm_byte_s());    break;
        case 0xc3: CK(4); addd(imm_word_s());      break;
        case 0xc4: CK(2); and(B, imm_byte_s());    break;
        case 0xc5: CK(2); bit(B, imm_byte_s());    break;
        case 0xc6: CK(2); ld(B, imm_byte_s());     break;
        case 0xc8: CK(2); eor(B, imm_byte_s());    break;
        case 0xc9: CK(2); adc(B, imm_byte_s());    break;
        case 0xca: CK(2); or(B, imm_byte_s());     break;
        case 0xcb: CK(2); add(B, imm_byte_s());    break;
        case 0xcc: CK(3); ldd_imm_word();          break;  // ldd_imm_word() == ldd(imm_word_s())
        case 0xce: CK(3); ld16(U, imm_word_s());   break;

        case 0xd0: CK(4); sub(B, RDMEM(direct()));     break;
        case 0xd1: CK(4); cmp(B, RDMEM(direct()));     break;
        case 0xd2: CK(4); sbc(B, RDMEM(direct()));     break;
        case 0xd3: CK(5); addd(RDMEM16(direct()));     break;
        case 0xd4: CK(4); and(B, RDMEM(direct()));     break;
        case 0xd5: CK(4); bit(B, RDMEM(direct()));     break;
        case 0xd6: CK(4); ld(B, RDMEM(direct()));      break;
        case 0xd7: CK(4); st(B, direct());             break;
        case 0xd8: CK(4); eor(B, RDMEM(direct()));     break;
        case 0xd9: CK(4); adc(B, RDMEM(direct()));     break;
        case 0xda: CK(4); or(B, RDMEM(direct()));      break;
        case 0xdb: CK(4); add(B, RDMEM(direct()));     break;
        case 0xdc: CK(4); ldd_direct();                break;  // ldd_direct() == ldd(RDMEM16(direct()))
        case 0xdd: CK(4); std(direct());               break;
        case 0xde: CK(4); ld16(U, RDMEM16(direct()));  break;
        case 0xdf: CK(4); st16(U, direct());           break;

        case 0xe0:        sub(B, RDMEM(indexed())); post();     break;
        case 0xe1:        cmp(B, RDMEM(indexed())); post();     break;
        case 0xe2:        sbc(B, RDMEM(indexed())); post();     break;
        case 0xe3: CK(1); addd(RDMEM16(indexed())); post();     break;
        case 0xe4:        and(B, RDMEM(indexed())); post();     break;
        case 0xe5:        bit(B, RDMEM(indexed())); post();     break;
        case 0xe6:        ld(B, RDMEM(indexed())); post();      break;
        case 0xe7:        st(B, indexed()); post();             break;
        case 0xe8:        eor(B, RDMEM(indexed())); post();     break;
        case 0xe9:        adc(B, RDMEM(indexed())); post();     break;
        case 0xea:        or(B, RDMEM(indexed())); post();      break;
        case 0xeb:        add(B, RDMEM(indexed())); post();     break;
        case 0xec:        ldd_indexed_post();                   break; // ldd_indexed_post() == ldd(RDMEM16(indexed())); post();ldd(RDMEM16(indexed())); post()
        case 0xed:        std(indexed()); post();               break;
        case 0xee:        ld16(U, RDMEM16(indexed())); post();  break;  // TO DO: NEED TO CHECK THINGS LIKE "LDU 3,U++"
        case 0xef:        st16(U, indexed()); post();           break;

        case 0xf0: CK(5); sub(B, RDMEM(extended()));     break;
        case 0xf1: CK(5); cmp(B, RDMEM(extended()));     break;
        case 0xf2: CK(5); sbc(B, RDMEM(extended()));     break;
        case 0xf3: CK(6); addd(RDMEM16(extended()));     break;
        case 0xf4: CK(5); and(B, RDMEM(extended()));     break;
        case 0xf5: CK(5); bit(B, RDMEM(extended()));     break;
        case 0xf6: CK(5); ld(B, RDMEM(extended()));      break;
        case 0xf7: CK(5); st(B, extended());             break;
        case 0xf8: CK(5); eor(B, RDMEM(extended()));     break;
        case 0xf9: CK(5); adc(B, RDMEM(extended()));     break;
        case 0xfa: CK(5); or(B, RDMEM(extended()));      break;
        case 0xfb: CK(5); add(B, RDMEM(extended()));     break;
        case 0xfc: CK(5); ldd_extended();                break; // ldd_extended() == ldd(RDMEM16(extended()))
        case 0xfd: CK(5); std(extended());               break;
        case 0xfe: CK(5); ld16(U, RDMEM16(extended()));  break;
        case 0xff: CK(5); st16(U, extended());           break;
        default:   printf("%04X: invalid opcode $%02X\n",iPC,opcode); exit(1);
      }
    }