// 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);
}
}