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