mirror of
https://github.com/bobbimanners/Zapple-II.git
synced 2024-09-28 17:55:16 +00:00
687 lines
12 KiB
C
687 lines
12 KiB
C
/*
|
|
* Z-80 assembler.
|
|
* Assemble one line of input.
|
|
* Knows all the dirt.
|
|
*/
|
|
#include "as.h"
|
|
|
|
#define OPDJNZ 0x10 /* Opcode: djnz */
|
|
#define OPADD 0x80 /* Opcode: add */
|
|
#define OPDAD 0x09 /* Opcode: dad */
|
|
#define OPADC 0x88 /* Opcode: adc */
|
|
#define OPADCW 0x4A /* Opcode: adc hl */
|
|
#define OPSBCW 0x42 /* Opcode: sbc hl */
|
|
#define OPSUBI 0xC6 /* Opcode: make immediate */
|
|
#define OPXCHG 0xEB /* Opcode: xchg */
|
|
#define OPXTHL 0xE3 /* Opcode: xthl */
|
|
#define OPEXAF 0x08 /* Opcode: ex af,af' */
|
|
#define OPRST 0xC7 /* Opcode: rst 0 */
|
|
#define OPINCRP 0x03 /* Opcode: inc rp */
|
|
#define OPDECRP 0x0B /* Opcode: dec rp */
|
|
#define OPINC 0x04 /* Opcode: inc */
|
|
#define OPIIN 0x40 /* Opcode: indirect in */
|
|
#define OPIOUT 0x41 /* Opcode: indirect out */
|
|
#define OPIN 0xDB /* Opcode: in */
|
|
#define OPIM 0x46 /* Opcode: im */
|
|
#define OPPCHL 0xE9 /* Opcode: jp (hl) */
|
|
#define OPJP 0xC3 /* Opcode: jp cc base */
|
|
#define OPJR 0x20 /* Opcode: jr cc base */
|
|
#define OPRET 0xC0 /* Opcode: ret cc base */
|
|
|
|
/*
|
|
* Assemble one line.
|
|
* The line in in "ib", the "ip"
|
|
* scans along it. The code is written
|
|
* right out, and also stashed in the
|
|
* "cb" for the listing.
|
|
*/
|
|
asmline()
|
|
{
|
|
register SYM *sp;
|
|
register int c;
|
|
register int opcode;
|
|
register int disp;
|
|
register int reg;
|
|
register int srcreg;
|
|
register int cc;
|
|
register VALUE value;
|
|
register int delim;
|
|
register SYM *sp1;
|
|
char id[NCPS];
|
|
char id1[NCPS];
|
|
ADDR a1;
|
|
ADDR a2;
|
|
|
|
laddr = dot;
|
|
lmode = SLIST;
|
|
loop:
|
|
if ((c=getnb())=='\n' || c==';')
|
|
return;
|
|
if (isalpha(c) == 0)
|
|
qerr();
|
|
getid(id, c);
|
|
if ((c=getnb()) == ':') {
|
|
sp = lookup(id, uhash, 1);
|
|
if (pass == 0) {
|
|
if ((sp->s_type&TMMODE) != TNEW
|
|
&& (sp->s_type&TMASG) == 0)
|
|
sp->s_type |= TMMDF;
|
|
sp->s_type &= ~TMMODE;
|
|
sp->s_type |= TUSER;
|
|
sp->s_value = dot;
|
|
} else {
|
|
if ((sp->s_type&TMMDF) != 0)
|
|
err('m');
|
|
if (sp->s_value != dot)
|
|
err('p');
|
|
}
|
|
lmode = ALIST;
|
|
goto loop;
|
|
}
|
|
/*
|
|
* If the first token is an
|
|
* id and not an operation code,
|
|
* assume that it is the name in front
|
|
* of an "equ" assembler directive.
|
|
*/
|
|
if ((sp=lookup(id, phash, 0)) == NULL) {
|
|
getid(id1, c);
|
|
if ((sp1=lookup(id1, phash, 0)) == NULL
|
|
|| (sp1->s_type&TMMODE) != TEQU) {
|
|
err('o');
|
|
return;
|
|
}
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
sp = lookup(id, uhash, 1);
|
|
if ((sp->s_type&TMMODE) != TNEW
|
|
&& (sp->s_type&TMASG) == 0)
|
|
err('m');
|
|
sp->s_type &= ~TMMODE;
|
|
sp->s_type |= TUSER|TMASG;
|
|
sp->s_value = a1.a_value;
|
|
laddr = a1.a_value;
|
|
lmode = ALIST;
|
|
goto loop;
|
|
}
|
|
unget(c);
|
|
lmode = BLIST;
|
|
opcode = sp->s_value;
|
|
switch (sp->s_type&TMMODE) {
|
|
case TORG:
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
lmode = ALIST;
|
|
laddr = dot = a1.a_value;
|
|
break;
|
|
|
|
case TDEFB:
|
|
do {
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
outab(a1.a_value);
|
|
} while ((c=getnb()) == ',');
|
|
unget(c);
|
|
break;
|
|
|
|
case TDEFW:
|
|
lmode = WLIST;
|
|
do {
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
outaw(a1.a_value);
|
|
} while ((c=getnb()) == ',');
|
|
unget(c);
|
|
break;
|
|
|
|
case TDEFM:
|
|
if ((delim=getnb()) == '\n')
|
|
qerr();
|
|
while ((c=get()) != delim) {
|
|
if (c == '\n')
|
|
qerr();
|
|
outab(c);
|
|
}
|
|
break;
|
|
|
|
case TDEFS:
|
|
laddr = dot;
|
|
lmode = ALIST;
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
dot += a1.a_value;
|
|
break;
|
|
|
|
case TNOP:
|
|
if ((opcode&0xFF00) != 0)
|
|
outab(opcode >> 8);
|
|
outab(opcode);
|
|
break;
|
|
|
|
case TRST:
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
if (a1.a_value < 8) {
|
|
outab(OPRST|(a1.a_value<<3));
|
|
break;
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TREL:
|
|
getaddr(&a1);
|
|
if ((cc=ccfetch(&a1)) >= 0) {
|
|
if (opcode==OPDJNZ || cc>=CPO)
|
|
aerr();
|
|
opcode = OPJR | (cc<<3);
|
|
comma();
|
|
getaddr(&a1);
|
|
}
|
|
istuser(&a1);
|
|
disp = a1.a_value-dot-2;
|
|
if (disp<-128 || disp>127)
|
|
aerr();
|
|
outab(opcode);
|
|
outab(disp);
|
|
break;
|
|
|
|
case TRET:
|
|
unget(c = getnb());
|
|
if (c!='\n' && c!=';') {
|
|
getaddr(&a1);
|
|
if ((cc=ccfetch(&a1)) < 0)
|
|
aerr();
|
|
opcode = OPRET | (cc<<3);
|
|
}
|
|
outab(opcode);
|
|
break;
|
|
|
|
case TJMP:
|
|
getaddr(&a1);
|
|
if ((cc=ccfetch(&a1)) >= 0) {
|
|
opcode = (opcode&0x00C6) | (cc<<3);
|
|
comma();
|
|
getaddr(&a1);
|
|
}
|
|
if ((a1.a_type&TMMODE) == TBR) {
|
|
reg = a1.a_type&TMREG;
|
|
if (reg==M || reg==IX || reg==IY) {
|
|
if (opcode != OPJP)
|
|
aerr();
|
|
outop(OPPCHL, &a1);
|
|
break;
|
|
}
|
|
}
|
|
istuser(&a1);
|
|
outab(opcode);
|
|
outaw(a1.a_value);
|
|
break;
|
|
|
|
case TPUSH:
|
|
getaddr(&a1);
|
|
if ((a1.a_type&TMMODE) == TWR) {
|
|
reg = a1.a_type&TMREG;
|
|
switch (reg) {
|
|
case IX:
|
|
outab(0xDD);
|
|
reg = HL;
|
|
break;
|
|
case IY:
|
|
outab(0xFD);
|
|
reg = HL;
|
|
break;
|
|
case AF:
|
|
reg = SP;
|
|
break;
|
|
case SP:
|
|
case AFPRIME:
|
|
aerr();
|
|
}
|
|
outab(opcode|(reg<<4));
|
|
break;
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TIM:
|
|
getaddr(&a1);
|
|
istuser(&a1);
|
|
if ((value=a1.a_value) > 2)
|
|
aerr();
|
|
else if (value != 0)
|
|
++value;
|
|
outab(0xED);
|
|
outab(OPIM|(value<<3));
|
|
break;
|
|
|
|
case TIO:
|
|
getaddr(opcode==OPIN ? &a1 : &a2);
|
|
comma();
|
|
getaddr(opcode==OPIN ? &a2 : &a1);
|
|
if (a1.a_type==(TBR|A) && a2.a_type==(TUSER|TMINDIR)) {
|
|
outab(opcode);
|
|
outab(a2.a_value);
|
|
break;
|
|
}
|
|
if ((a1.a_type&TMMODE)==TBR && a2.a_type==(TBR|TMINDIR|C)) {
|
|
reg = a1.a_type&TMREG;
|
|
if (reg==M || reg==IX || reg==IY)
|
|
aerr();
|
|
outab(0xED);
|
|
if (opcode == OPIN)
|
|
opcode = OPIIN; else
|
|
opcode = OPIOUT;
|
|
outab(opcode|(reg<<3));
|
|
break;
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TBIT:
|
|
getaddr(&a1);
|
|
comma();
|
|
getaddr(&a2);
|
|
if ((a1.a_type&TMMODE) == TUSER
|
|
&& a1.a_value < 8
|
|
&& (a2.a_type&TMMODE) == TBR) {
|
|
if ((reg=a2.a_type&TMREG)==IX || reg==IY)
|
|
reg = M;
|
|
outop(opcode|(a1.a_value<<3)|reg, &a2);
|
|
break;
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TSHR:
|
|
getaddr(&a1);
|
|
if ((a1.a_type&TMMODE) == TBR) {
|
|
if ((reg=a1.a_type&TMREG)==IX || reg==IY)
|
|
reg = M;
|
|
outop(opcode|reg, &a1);
|
|
break;
|
|
}
|
|
aerr();
|
|
|
|
case TINC:
|
|
getaddr(&a1);
|
|
if ((a1.a_type&TMMODE) == TWR) {
|
|
reg = a1.a_type&TMREG;
|
|
switch (reg) {
|
|
case IX:
|
|
outab(0xDD);
|
|
reg = HL;
|
|
break;
|
|
case IY:
|
|
outab(0xFD);
|
|
reg = HL;
|
|
break;
|
|
case AF:
|
|
case AFPRIME:
|
|
aerr();
|
|
}
|
|
if (opcode == OPINC)
|
|
opcode = OPINCRP; else
|
|
opcode = OPDECRP;
|
|
outab(opcode|(reg<<4));
|
|
break;
|
|
}
|
|
if ((a1.a_type&TMMODE) == TBR) {
|
|
if ((reg=a1.a_type&TMREG)==IX || reg==IY)
|
|
reg = M;
|
|
outop(opcode|(reg<<3), &a1);
|
|
break;
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TEX:
|
|
getaddr(&a1);
|
|
comma();
|
|
getaddr(&a2);
|
|
if (a1.a_type==(TWR|AF) && a2.a_type==(TWR|AFPRIME)) {
|
|
outab(OPEXAF);
|
|
break;
|
|
}
|
|
if (a1.a_type == (TWR|DE))
|
|
opcode = OPXCHG;
|
|
else if (a1.a_type == (TWR|TMINDIR|SP))
|
|
opcode = OPXTHL;
|
|
else
|
|
aerr();
|
|
if (a2.a_type == (TWR|HL))
|
|
outab(opcode);
|
|
else if (a2.a_type == (TWR|IX)) {
|
|
outab(0xDD);
|
|
outab(opcode);
|
|
} else if (a2.a_type == (TWR|IY)) {
|
|
outab(0xFD);
|
|
outab(opcode);
|
|
} else
|
|
aerr();
|
|
break;
|
|
|
|
case TSUB:
|
|
getaddr(&a1);
|
|
if (a1.a_type == TUSER) {
|
|
outab(opcode | OPSUBI);
|
|
outab(a1.a_value);
|
|
break;
|
|
}
|
|
if ((a1.a_type&TMMODE) == TBR) {
|
|
if ((reg=a1.a_type&TMREG)==IX || reg==IY)
|
|
reg = M;
|
|
outop(opcode|reg, &a1);
|
|
break;
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TADD:
|
|
getaddr(&a1);
|
|
comma();
|
|
getaddr(&a2);
|
|
if (a1.a_type == (TBR|A)) {
|
|
if (a2.a_type == TUSER) {
|
|
outab(opcode | OPSUBI);
|
|
outab(a2.a_value);
|
|
break;
|
|
}
|
|
if ((a2.a_type&TMMODE) == TBR) {
|
|
if ((reg=a2.a_type&TMREG)==IX || reg==IY)
|
|
reg = M;
|
|
outop(opcode|reg, &a1);
|
|
break;
|
|
}
|
|
}
|
|
if ((a1.a_type&TMMODE) == TWR) {
|
|
switch(reg = a1.a_type&TMREG) {
|
|
case IX:
|
|
if (opcode != OPADD)
|
|
aerr();
|
|
outab(0xDD);
|
|
opcode = OPDAD;
|
|
srcreg = IX;
|
|
break;
|
|
case IY:
|
|
if (opcode != OPADD)
|
|
aerr();
|
|
outab(0xFD);
|
|
opcode = OPDAD;
|
|
srcreg = IY;
|
|
break;
|
|
case HL:
|
|
if (opcode == OPADD)
|
|
opcode = OPDAD;
|
|
else {
|
|
outab(0xED);
|
|
if (opcode == OPADC)
|
|
opcode = OPADCW;
|
|
else
|
|
opcode = OPSBCW;
|
|
}
|
|
srcreg = HL;
|
|
break;
|
|
default:
|
|
aerr();
|
|
}
|
|
if ((a2.a_type&TMMODE) == TWR) {
|
|
reg = a2.a_type&TMREG;
|
|
if (reg==BC || reg==DE || reg==SP) {
|
|
outab(opcode|(reg<<4));
|
|
break;
|
|
}
|
|
if (reg == srcreg) {
|
|
outab(opcode|(HL<<4));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
aerr();
|
|
break;
|
|
|
|
case TLD:
|
|
asmld();
|
|
break;
|
|
|
|
default:
|
|
err('o');
|
|
}
|
|
goto loop;
|
|
}
|
|
|
|
/*
|
|
* Handle the dreaded "ld" instruction,
|
|
* with its dozens of forms, formats and special
|
|
* encodings. The "getldaddr" routine performs most
|
|
* of the special stuff for index registers and for
|
|
* indexing. This layer just screens out the many
|
|
* cases, and emits the correct bytes.
|
|
*/
|
|
asmld()
|
|
{
|
|
int mdst;
|
|
int rdst;
|
|
int msrc;
|
|
int rsrc;
|
|
ADDR *indexap;
|
|
ADDR dst;
|
|
ADDR src;
|
|
ADDR *getldaddr();
|
|
|
|
indexap = NULL;
|
|
indexap = getldaddr(&dst, &mdst, &rdst, indexap);
|
|
comma();
|
|
indexap = getldaddr(&src, &msrc, &rsrc, indexap);
|
|
if (dst.a_type == (TBR|A)) {
|
|
if (msrc == TSR) {
|
|
if (rsrc == I)
|
|
outaw(0x57ED); /* ld a,i */
|
|
else
|
|
outaw(0x5FED); /* ld a,r */
|
|
return;
|
|
}
|
|
if (msrc == (TMINDIR|TUSER)) {
|
|
outab(0x3A); /* lda */
|
|
outaw(src.a_value);
|
|
return;
|
|
}
|
|
if (msrc == (TMINDIR|TWR)) {
|
|
if (rsrc==BC || rsrc==DE) {
|
|
outab(0x0A|(rsrc<<4)); /* ldax */
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (src.a_type == (TBR|A)) {
|
|
if (mdst == TSR) {
|
|
if (rdst == I)
|
|
outaw(0x47ED); /* ld i,a */
|
|
else
|
|
outaw(0x4FED); /* ld r,a */
|
|
return;
|
|
}
|
|
if (mdst == (TMINDIR|TUSER)) {
|
|
outab(0x32); /* sta */
|
|
outaw(dst.a_value);
|
|
return;
|
|
}
|
|
if (mdst == (TMINDIR|TWR)) {
|
|
if (rdst==BC || rdst==DE) {
|
|
outab(0x02|(rdst<<4)); /* stax */
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (dst.a_type==(TWR|SP) && msrc==TWR) {
|
|
if (rsrc == HL) {
|
|
outab(0xF9); /* sphl */
|
|
return;
|
|
}
|
|
}
|
|
if (msrc == TUSER) {
|
|
if (mdst == TBR) {
|
|
outab(0x06|(rdst<<3)); /* mvi */
|
|
if (indexap != NULL)
|
|
outab(indexap->a_value);
|
|
outab(src.a_value);
|
|
return;
|
|
}
|
|
if (mdst == TWR) {
|
|
outab(0x01|(rdst<<4)); /* lxi */
|
|
outaw(src.a_value);
|
|
return;
|
|
}
|
|
}
|
|
if (mdst==TWR && msrc==(TMINDIR|TUSER)) {
|
|
if (rdst == HL)
|
|
outab(0x2A); /* lhld */
|
|
else
|
|
outaw(0x4BED|(rdst<<12)); /* ld rp,(ppqq) */
|
|
outaw(src.a_value);
|
|
return;
|
|
}
|
|
if (mdst==(TMINDIR|TUSER) && msrc==TWR) {
|
|
if (rsrc == HL)
|
|
outab(0x22); /* shld */
|
|
else
|
|
outaw(0x43ED|(rsrc<<12)); /* ld (ppqq),rp */
|
|
outaw(dst.a_value);
|
|
return;
|
|
}
|
|
if (mdst==TBR && msrc==TBR && (rdst!=M || rsrc!=M)) {
|
|
outab(0x40|(rdst<<3)|rsrc);
|
|
if (indexap != NULL)
|
|
outab(indexap->a_value);
|
|
return;
|
|
}
|
|
aerr();
|
|
}
|
|
|
|
/*
|
|
* Read in addresses for "ld"
|
|
* instructions. Split off the mode
|
|
* and the register name. Adjust the register
|
|
* name to correctly deal with the index registers
|
|
* and for indexed addressing modes. Return the address
|
|
* pointer "ap" if indexing is required, otherwise just
|
|
* pass the "iap" through.
|
|
*/
|
|
ADDR *
|
|
getldaddr(ap, modep, regp, iap)
|
|
ADDR *ap;
|
|
int *modep;
|
|
int *regp;
|
|
ADDR *iap;
|
|
{
|
|
register int mode;
|
|
register int reg;
|
|
|
|
getaddr(ap);
|
|
mode = ap->a_type&TMMODE;
|
|
reg = ap->a_type&TMREG;
|
|
switch (ap->a_type) {
|
|
case TBR|IX:
|
|
outab(0xDD);
|
|
reg = M;
|
|
iap = ap;
|
|
break;
|
|
|
|
case TBR|IY:
|
|
outab(0xFD);
|
|
reg = M;
|
|
iap = ap;
|
|
break;
|
|
|
|
case TWR|IX:
|
|
outab(0xDD);
|
|
reg = HL;
|
|
break;
|
|
|
|
case TWR|IY:
|
|
outab(0xFD);
|
|
reg == HL;
|
|
break;
|
|
|
|
case TWR|AF:
|
|
case TWR|AFPRIME:
|
|
aerr();
|
|
reg = HL;
|
|
}
|
|
*modep = mode;
|
|
*regp = reg;
|
|
return (iap);
|
|
}
|
|
|
|
/*
|
|
* Output an opcode, surrounded
|
|
* by the index prefix bytes and the
|
|
* index displacement byte. Look at
|
|
* the address mode to see if the bytes
|
|
* are needed.
|
|
*/
|
|
outop(op, ap)
|
|
register int op;
|
|
register ADDR *ap;
|
|
{
|
|
register int needisp;
|
|
|
|
needisp = 0;
|
|
if (ap->a_type == (TBR|IX)) {
|
|
outab(0xDD);
|
|
needisp = 1;
|
|
} else if (ap->a_type == (TBR|IY)) {
|
|
outab(0xFD);
|
|
needisp = 1;
|
|
}
|
|
if ((op&0xFF00) != 0) {
|
|
outab(op>>8);
|
|
if (needisp != 0) {
|
|
outab(ap->a_value);
|
|
needisp = 0;
|
|
}
|
|
}
|
|
outab(op);
|
|
if (needisp != 0)
|
|
outab(ap->a_value);
|
|
}
|
|
|
|
/*
|
|
* The next character
|
|
* in the input must be a comma
|
|
* or it is a fatal error.
|
|
*/
|
|
comma()
|
|
{
|
|
if (getnb() != ',')
|
|
qerr();
|
|
}
|
|
|
|
/*
|
|
* Check if the mode of
|
|
* an ADDR is TUSER. If not, give
|
|
* an error.
|
|
*/
|
|
istuser(ap)
|
|
register ADDR *ap;
|
|
{
|
|
if ((ap->a_type&TMMODE) != TUSER)
|
|
aerr();
|
|
}
|
|
|
|
/*
|
|
* Try to interpret an "ADDR"
|
|
* as a condition code name. Return
|
|
* the condition, or "-1" if it cannot
|
|
* be interpreted as a condition. The
|
|
* "c" condition is a pain.
|
|
*/
|
|
ccfetch(ap)
|
|
register ADDR *ap;
|
|
{
|
|
if (ap->a_type == (TBR|C))
|
|
return (CC);
|
|
if ((ap->a_type&TMMODE) == TCC)
|
|
return (ap->a_type&TMREG);
|
|
return (-1);
|
|
}
|