From 109a8caafabc765348ea5c93bb1079ec24fdb1f5 Mon Sep 17 00:00:00 2001 From: Bobbi Webber-Manners Date: Sat, 12 Oct 2019 19:16:41 -0400 Subject: [PATCH] Initial commit of z80as (and hex2bin) source. --- z80as/LICENSE | 27 ++ z80as/Makefile | 16 ++ z80as/as.h | 168 ++++++++++++ z80as/as0.c | 131 +++++++++ z80as/as1.c | 686 ++++++++++++++++++++++++++++++++++++++++++++++++ z80as/as2.c | 244 +++++++++++++++++ z80as/as3.c | 260 ++++++++++++++++++ z80as/as4.c | 121 +++++++++ z80as/as5.c | 77 ++++++ z80as/as6.c | 137 ++++++++++ z80as/basic.bin | Bin 0 -> 5522 bytes z80as/basic.hex | 174 ++++++++++++ z80as/hex2bin.c | 218 +++++++++++++++ 13 files changed, 2259 insertions(+) create mode 100644 z80as/LICENSE create mode 100644 z80as/Makefile create mode 100644 z80as/as.h create mode 100644 z80as/as0.c create mode 100644 z80as/as1.c create mode 100644 z80as/as2.c create mode 100644 z80as/as3.c create mode 100644 z80as/as4.c create mode 100644 z80as/as5.c create mode 100644 z80as/as6.c create mode 100644 z80as/basic.bin create mode 100644 z80as/basic.hex create mode 100644 z80as/hex2bin.c diff --git a/z80as/LICENSE b/z80as/LICENSE new file mode 100644 index 0000000..6b103c4 --- /dev/null +++ b/z80as/LICENSE @@ -0,0 +1,27 @@ +Copyright © 1977-1995 by Robert Swartz. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" +and any express or implied warranties, including, but not limited to, the +implied warranties of merchantability and fitness for a particular purpose +are disclaimed. In no event shall the copyright holder or contributors be +liable for any direct, indirect, incidental, special, exemplary, or +consequential damages (including, but not limited to, procurement of +substitute goods or services; loss of use, data, or profits; or business +interruption) however caused and on any theory of liability, whether in +contract, strict liability, or tort (including negligence or otherwise) +arising in any way out of the use of this software, even if advised of the +possibility of such damage. diff --git a/z80as/Makefile b/z80as/Makefile new file mode 100644 index 0000000..4f45a89 --- /dev/null +++ b/z80as/Makefile @@ -0,0 +1,16 @@ +CC = cc +CFLAGS = -O2 -Wall + +all: z80as hex2bin + +z80as: as0.o as1.o as2.o as3.o as4.o as5.o as6.o + $(CC) -o z80as.x86 as0.o as1.o as2.o as3.o as4.o as5.o as6.o + +hex2bin: hex2bin.c + $(CC) -o hex2bin hex2bin.c + +install: + su root cp z80as.x86 /usr/local/bin/z80as + +clean: + rm -f z80as.x86 hex2bin *.o diff --git a/z80as/as.h b/z80as/as.h new file mode 100644 index 0000000..43d29aa --- /dev/null +++ b/z80as/as.h @@ -0,0 +1,168 @@ +/* + * Z-80 assembler. + * Header file, used by all + * parts of the assembler. + */ +#include +#include +#include +#ifdef vax +#include stsdef +#include ssdef +#endif + +/* + * Table sizes, etc. + */ +#define NCPS 8 /* # of characters in symbol */ +#define NHASH 64 /* # of hash buckets */ +#define HMASK 077 /* Mask for above */ +#define NFNAME 32 /* # of characters in filename */ +#define NERR 10 /* Size of error buffer */ +#define NCODE 128 /* # of characters in code buffer */ +#define NINPUT 128 /* # of characters in input line */ +#define NLPP 60 /* # of lines on a page */ +#define XXXX 0 /* Unused value */ + +/* + * Exit codes. + */ +#ifdef vax +#define GOOD (SS$_NORMAL) +#define BAD (STS$M_INHIB_MSG|SS$_ABORT) +#else +#define GOOD 0 +#define BAD 1 +#endif + +/* + * Listing modes. + */ +#define NLIST 0 /* No list */ +#define ALIST 1 /* Address only */ +#define BLIST 2 /* Byte format */ +#define WLIST 3 /* Word format */ +#define SLIST 4 /* Source text only */ + +/* + * Types. These are used + * in both symbols and in address + * descriptions. Observe the way the + * symbol flags hide in the register + * field of the address. + */ +#define TMREG 0x00FF /* Register code */ +#define TMMDF 0x0001 /* Multidef */ +#define TMASG 0x0002 /* Defined by "=" */ +#define TMMODE 0xFF00 /* Mode */ +#define TMINDIR 0x8000 /* Indirect flag in mode */ + +#define TNEW 0x0000 /* Virgin */ +#define TUSER 0x0100 /* User name */ +#define TBR 0x0200 /* Byte register */ +#define TWR 0x0300 /* Word register */ +#define TSR 0x0400 /* Special register (I, R) */ +#define TDEFB 0x0500 /* defb */ +#define TDEFW 0x0600 /* defw */ +#define TDEFS 0x0700 /* defs */ +#define TDEFM 0x0800 /* defm */ +#define TORG 0x0900 /* org */ +#define TEQU 0x0A00 /* equ */ +#define TCOND 0x0B00 /* conditional */ +#define TENDC 0x0C00 /* end conditional */ +#define TNOP 0x0F00 /* nop */ +#define TRST 0x1000 /* restarts */ +#define TREL 0x1100 /* djnz, jr */ +#define TRET 0x1200 /* ret */ +#define TJMP 0x1300 /* call, jp */ +#define TPUSH 0x1400 /* push, pop */ +#define TIM 0x1500 /* im */ +#define TIO 0x1600 /* in, out */ +#define TBIT 0x1700 /* set, res, bit */ +#define TSHR 0x1800 /* sl, sr et al */ +#define TINC 0x1900 /* inc, dec */ +#define TEX 0x1A00 /* ex */ +#define TADD 0x1B00 /* add, adc, sbc */ +#define TLD 0x1C00 /* ld */ +#define TCC 0x1D00 /* condition code */ +#define TSUB 0x1E00 /* sub et al */ + +/* + * Registers. + */ +#define B 0 +#define C 1 +#define D 2 +#define E 3 +#define H 4 +#define L 5 +#define M 6 +#define A 7 +#define IX 8 +#define IY 9 + +#define BC 0 +#define DE 1 +#define HL 2 +#define SP 3 +#define AF 4 +#define AFPRIME 5 + +#define I 0 +#define R 1 + +/* + * Condition codes. + */ +#define CNZ 0 +#define CZ 1 +#define CNC 2 +#define CC 3 +#define CPO 4 +#define CPE 5 +#define CP 6 +#define CM 7 + +typedef unsigned int VALUE; /* For symbol values */ + +/* + * Address description. + */ +typedef struct ADDR { + int a_type; /* Type */ + VALUE a_value; /* Index offset, etc */ +} ADDR; + +/* + * Symbol. + */ +typedef struct SYM { + struct SYM *s_fp; /* Link in hash */ + char s_id[NCPS]; /* Name */ + int s_type; /* Type */ + VALUE s_value; /* Value */ +} SYM; + +/* + * External variables. + */ +extern char *cp; +extern char *ep; +extern char *ip; +extern char cb[]; +extern char eb[]; +extern char ib[]; +extern FILE *ifp; +extern FILE *ofp; +extern FILE *lfp; +extern int line; +extern int lmode; +extern VALUE laddr; +extern SYM sym[]; +extern int pass; +extern SYM *phash[]; +extern SYM *uhash[]; +extern int lflag; +extern jmp_buf env; +extern VALUE dot; +extern SYM *lookup(); diff --git a/z80as/as0.c b/z80as/as0.c new file mode 100644 index 0000000..31184c3 --- /dev/null +++ b/z80as/as0.c @@ -0,0 +1,131 @@ +/* + * Z-80 assembler. + * Command line processing + * and main driver. + */ +#include "as.h" + +FILE *ifp; +FILE *ofp; +FILE *lfp; +char cb[NCODE]; +char eb[NERR]; +char ib[NINPUT]; +char *cp; +char *ep; +char *ip; +int lflag; +VALUE laddr; +int lmode; +VALUE dot; +SYM *phash[NHASH]; +SYM *uhash[NHASH]; +int pass; +int line; +jmp_buf env; + +main(argc, argv) +char *argv[]; +{ + register char *ifn; + register char *p; + register int i; + register int c; + char fn[NFNAME]; + + ifn = NULL; + for (i=1; is_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); +} diff --git a/z80as/as2.c b/z80as/as2.c new file mode 100644 index 0000000..5532994 --- /dev/null +++ b/z80as/as2.c @@ -0,0 +1,244 @@ +/* + * Z-80 assembler. + * Symbol table routines + * and error handling. + */ +#include "as.h" + +/* + * Given a pointer to a + * sumbol, compute the hash bucket + * number. The "add all the characters + * and take the result modulo the table + * size" algorithm is used. + */ +symhash(id) +register char *id; +{ + register int hash; + register int n; + + hash = 0; + n = NCPS; + do { + hash += *id++; + } while (--n); + return (hash&HMASK); +} + +/* + * Handle an error. + * If no listing file, write out + * the error directly. Otherwise save + * the error in the error buffer. + */ +err(c) +{ + if (pass != 0) { + if (lflag != 0) + storerror(c); + else + printf("%04d %c\n", line, c); + } + if (c == 'q') + longjmp(env, 1); +} + +/* + * This routine is like + * "err", but it has the "u" + * code screwed into it, and it + * prints the name of the identifier + * "id" in the message. + */ +uerr(id) +char *id; +{ + if (pass != 0) { + if (lflag != 0) + storerror('u'); + else + printf("%04d u %.*s\n", line, NCPS, id); + } +} + +/* + * The "a" error is common. + */ +aerr() +{ + err('a'); +} + +/* + * Ditto the "q" error. + */ +qerr() +{ + err('q'); +} + +/* + * Put the error code + * "c" into the error buffer. + * Check that it is not already + * there. + */ +storerror(c) +register int c; +{ + register char *p; + + p = &eb[0]; + while (p < ep) + if (*p++ == c) + return; + if (p < &eb[NERR]) { + *p++ = c; + ep = p; + } +} + +/* + * Read identifier. + * The character "c" is the first + * character of the name. + */ +getid(id, c) +register int c; +char *id; +{ + register char *p; + + if (c < 0) { + c = getnb(); + if (isalpha(c) == 0) + qerr(); + } + p = &id[0]; + do { + if (p < &id[NCPS]) { + if (isupper(c)) + c = tolower(c); + *p++ = c; + } + if ((c = *ip) != '\n') + ++ip; + } while (c=='\'' || isalnum(c)!=0); + if (c != '\n') + --ip; + while (p < &id[NCPS]) + *p++ = 0; +} + +/* + * Lookup symbol in + * hash table "htable". + * If not there, and "cf" is + * true, create it. + */ +SYM * +lookup(id, htable, cf) +char *id; +SYM *htable[]; +{ + register SYM *sp; + register int hash; + + hash = symhash(id); + sp = htable[hash]; + while (sp != NULL) { + if (symeq(id, sp->s_id)) + return (sp); + sp = sp->s_fp; + } + if (cf != 0) { + if ((sp=(SYM *)malloc(sizeof(SYM))) == NULL) { + fprintf(stderr, "No memory\n"); + exit(BAD); + } + sp->s_fp = htable[hash]; + htable[hash] = sp; + sp->s_type = TNEW; + sp->s_value = 0; + symcopy(sp->s_id, id); + } + return (sp); +} + +/* + * Compare two names. + * Each are blocks of "NCPS" + * bytes. True return if the names + * are exactly equal. + */ +symeq(p1, p2) +register char *p1; +register char *p2; +{ + register int n; + + n = NCPS; + do { + if (*p1++ != *p2++) + return (0); + } while (--n); + return (1); +} + +/* + * Copy the characters + * that make up the name of a + * symbol. + */ +symcopy(p1, p2) +register char *p1; +register char *p2; +{ + register int n; + + n = NCPS; + do { + *p1++ = *p2++; + } while (--n); +} + +/* + * Get the next character + * from the input buffer. Do not + * step past the newline. + */ +get() +{ + register int c; + + if ((c = *ip) != '\n') + ++ip; + return (c); +} + +/* + * Get the next non + * whitespace character from + * the input buffer. Do not step + * past the newline. + */ +getnb() +{ + register int c; + + while ((c = *ip)==' ' || c=='\t') + ++ip; + if (c != '\n') + ++ip; + return (c); +} + +/* + * Put a character back. + */ +unget(c) +{ + if (c != '\n') + --ip; +} diff --git a/z80as/as3.c b/z80as/as3.c new file mode 100644 index 0000000..2d952e2 --- /dev/null +++ b/z80as/as3.c @@ -0,0 +1,260 @@ +/* + * Z-80 assembler. + * Read in expressions in + * address fields. + */ +#include "as.h" + +#define LOPRI 0 +#define ADDPRI 1 +#define MULPRI 2 +#define HIPRI 3 + +/* + * Read in an address + * descriptor, and fill in + * the supplied "ADDR" structure + * with the mode and value. + * Exits directly to "qerr" if + * there is no address field or + * if the syntax is bad. + */ +getaddr(ap) +register ADDR *ap; +{ + register int reg; + register int c; + + if ((c=getnb()) != '(') { + unget(c); + expr1(ap, LOPRI, 0); + return; + } + expr1(ap, LOPRI, 1); + if (getnb() != ')') + qerr(); + reg = ap->a_type&TMREG; + switch (ap->a_type&TMMODE) { + case TBR: + if (reg != C) + aerr(); + ap->a_type |= TMINDIR; + break; + case TSR: + case TCC: + aerr(); + break; + case TUSER: + ap->a_type |= TMINDIR; + break; + case TWR: + if (reg == HL) + ap->a_type = TBR|M; + else if (reg==IX || reg==IY) + ap->a_type = TBR|reg; + else if (reg==AF || reg==AFPRIME) + aerr(); + else + ap->a_type |= TMINDIR; + } +} + +/* + * Expression reader, + * real work, part I. Read + * operators and resolve types. + * The "lpri" is the firewall operator + * priority, which stops the scan. + * The "paren" argument is true if + * the expression is in parentheses. + */ +expr1(ap, lpri, paren) +register ADDR *ap; +{ + register int c; + register int opri; + ADDR right; + + expr2(ap); + while ((c=getnb())=='+' || c=='-' || c=='*' || c=='/') { + opri = ADDPRI; + if (c=='*' || c=='/') + opri = MULPRI; + if (opri <= lpri) + break; + expr1(&right, opri, paren); + switch (c) { + case '+': + if ((ap->a_type&TMMODE) != TUSER) + istuser(&right); + else + ap->a_type = right.a_type; + isokaors(ap, paren); + ap->a_value += right.a_value; + break; + case '-': + istuser(&right); + isokaors(ap, paren); + ap->a_value -= right.a_value; + break; + case '*': + istuser(ap); + istuser(&right); + ap->a_value *= right.a_value; + break; + case '/': + istuser(ap); + istuser(&right); + ap->a_value /= right.a_value; + } + } + unget(c); +} + +/* + * Expression reader, + * real work, part II. Read + * in terminals. + */ +expr2(ap) +register ADDR *ap; +{ + register int c; + register SYM *sp; + register int mode; + char id[NCPS]; + + c = getnb(); + if (c == '[') { + expr1(ap, LOPRI); + if (getnb() != ']') + qerr(); + return; + } + if (c == '-') { + expr1(ap, HIPRI); + istuser(ap); + ap->a_value = -ap->a_value; + return; + } + if (c == '~') { + expr1(ap, HIPRI); + istuser(ap); + ap->a_value = ~ap->a_value; + return; + } + if (c == '\'') { + ap->a_type = TUSER; + ap->a_value = get(); + while ((c=get()) != '\'') { + if (c == '\n') + qerr(); + ap->a_value = (ap->a_value<<8) + c; + } + return; + } + if (c>='0' && c<='9') { + expr3(ap, c); + return; + } + if (isalpha(c)) { + getid(id, c); + if ((sp=lookup(id, uhash, 0)) == NULL + && (sp=lookup(id, phash, 0)) == NULL) + sp = lookup(id, uhash, 1); + mode = sp->s_type&TMMODE; + if (mode==TBR || mode==TWR || mode==TSR || mode==TCC) { + ap->a_type = mode|sp->s_value; + ap->a_value = 0; + return; + } + if (mode == TNEW) + uerr(id); + ap->a_type = TUSER; + ap->a_value = sp->s_value; + return; + } + qerr(); +} + +/* + * Read in a constant. The argument + * "c" is the first character of the constant, + * and has already been validated. The number is + * gathered up (stopping on non alphanumeric). + * The radix is determined, and the number is + * converted to binary. + */ +expr3(ap, c) +register ADDR *ap; +register int c; +{ + register char *np1; + register char *np2; + register int radix; + register VALUE value; + char num[40]; + + np1 = &num[0]; + do { + if (isupper(c)) + c = tolower(c); + *np1++ = c; + c = *ip++; + } while (isalnum(c)); + --ip; + switch (*--np1) { + case 'h': + radix = 16; + break; + case 'o': + case 'q': + radix = 8; + break; + case 'b': + radix = 2; + break; + default: + radix = 10; + ++np1; + } + np2 = &num[0]; + value = 0; + while (np2 < np1) { + if ((c = *np2++)>='0' && c<='9') + c -= '0'; + else if (c>='a' && c<='f') + c -= 'a'-10; + else + err('n'); + if (c >= radix) + err('n'); + value = radix*value + c; + } + ap->a_type = TUSER; + ap->a_value = value; +} + +/* + * Make sure that the + * mode and register fields of + * the type of the "ADDR" pointed to + * by "ap" can participate in an addition + * or a subtraction. + */ +isokaors(ap, paren) +register ADDR *ap; +{ + register int mode; + register int reg; + + mode = ap->a_type&TMMODE; + if (mode == TUSER) + return; + if (mode==TWR && paren!=0) { + reg = ap->a_type&TMREG; + if (reg==IX || reg==IY) + return; + } + aerr(); +} diff --git a/z80as/as4.c b/z80as/as4.c new file mode 100644 index 0000000..aec47d8 --- /dev/null +++ b/z80as/as4.c @@ -0,0 +1,121 @@ +/* + * Z-80 assembler. + * Output Intel compatable + * hex files. + */ +#include "as.h" + +#define NHEX 32 /* Longest record */ + +VALUE hexla; +VALUE hexpc; +char hexb[NHEX]; +char *hexp = &hexb[0]; + +/* + * Output a word. Use the + * standard Z-80 ordering (low + * byte then high byte). + */ +outaw(w) +{ + outab(w); + outab(w >> 8); +} + +/* + * Output an absolute + * byte to the code and listing + * streams. + */ +outab(b) +{ + if (pass != 0) { + if (cp < &cb[NCODE]) + *cp++ = b; + outbyte(b); + } + ++dot; +} + +/* + * Put out the end of file + * hex item at the very end of + * the object file. + */ +outeof() +{ + outflush(); + fprintf(ofp, ":00000001FF\n"); +} + +/* + * Output a hex byte. Flush + * the buffer if no room. Store the + * byte in the buffer, for future + * checksumming. Remember the load + * address for flushing. + */ +outbyte(b) +{ + if (hexp>=&hexb[NHEX] || hexpc!=dot) { + outflush(); + hexp = &hexb[0]; + } + if (hexp == &hexb[0]) { + hexla = dot; + hexpc = dot; + } + *hexp++ = b; + ++hexpc; +} + +/* + * Flush out a block of + * code to the hex file. Figure + * out the length word and the + * checksum byte. + */ +outflush() +{ + register char *p; + register int b; + register int c; + + if ((b = hexp-&hexb[0]) != 0) { + putc(':', ofp); + outhex(b); + outhex(hexla >> 8); + outhex(hexla); + outhex(0); + c = b + (hexla>>8) + hexla; + p = &hexb[0]; + while (p < hexp) { + b = *p++; + outhex(b); + c += b; + } + outhex(-c); + putc('\n', ofp); + } +} + +/* + * Put out "b", as a + * two character hex value. + * We cannot use printf because + * of case problems on VMS. + * Upper case ascii. + */ +outhex(b) +{ + static char hex[] = { + '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' + }; + + putc(hex[(b>>4)&0x0F], ofp); + putc(hex[b&0x0F], ofp); +} diff --git a/z80as/as5.c b/z80as/as5.c new file mode 100644 index 0000000..ee44a6a --- /dev/null +++ b/z80as/as5.c @@ -0,0 +1,77 @@ +/* + * Z-80 assembler. + * Build up lines for the + * listing file. + */ +#include "as.h" + +/* + * Copy the data in the listing + * code buffer to the listing file. + * Produce no file if "lfp" is NULL. + * Honour the listing mode stored + * in the "lmode". + */ +list() +{ + register char *wp; + register int nb; + + if (lfp==NULL || lmode==NLIST) + return; + while (ep < &eb[NERR]) + *ep++ = ' '; + fprintf(lfp, "%.10s", eb); + if (lmode == SLIST) { + fprintf(lfp, "%31s %5d %s", "", line, ib); + return; + } + fprintf(lfp, " %04x", laddr); + if (lmode == ALIST) { + fprintf(lfp, "%24s %5d %s", "", line, ib); + return; + } + wp = cb; + nb = cp - cb; + list1(wp, nb, 1); + fprintf(lfp, " %5d %s", line, ib); + while ((nb -= 8) > 0) { + wp += 8; + fprintf(lfp, "%17s", ""); + list1(wp, nb, 0); + fprintf(lfp, "\n"); + } +} + +/* + * Copy out a partial line + * to the listing. Used for the first + * and the extra lines in BLIST and + * WLIST mode. + */ +list1(wp, nb, f) +register char *wp; +register int nb; +{ + register int d; + register int i; + + if (nb > 8) + nb = 8; + for (i=0; is_id); + sp->s_fp = phash[hash]; + phash[hash] = sp; + ++sp; + } +} diff --git a/z80as/basic.bin b/z80as/basic.bin new file mode 100644 index 0000000000000000000000000000000000000000..1b201eea1aa3810a6b0d311a46359d0fc91a7062 GIT binary patch literal 5522 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 +#include +#include + +#undef DEBUG +#define FNAMELEN 15 + +/* Convert hex character to value + * Returns value or 127 on error */ +char hexchar(char c) { + c = toupper(c); + if ((c >= '0') && (c <= '9')) return c - '0'; + if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10; + return 127; +} + +/* Do the actual work of converting a HEX file to a BIN file + * Returns 0 on success, 1 on error, 2 on EOF */ +char hextobin(FILE *in, FILE *out) { + int c, datalen, addr, rectype, i; + char byte; + static int endaddr = -1; + + /* Each line starts with a colon */ + c = fgetc(in); + if (c == -1) return 1; + if (c != ':') { + puts("Expected :"); + return 1; + } + + /* Then two hex digits representing the data length */ + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + datalen = c * 16; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + datalen += c; + +#ifdef DEBUG + printf("datalen=%d\n", datalen); +#endif + + /* Then four hex digits representing the address */ + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + addr = c * 16 * 16 * 16; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + addr += c * 16 * 16; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + addr += c * 16; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + addr += c; + +#ifdef DEBUG + printf("addr=%d\n", addr); +#endif + + /* Then two more hex digits representing the record type */ + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + rectype = c * 16; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + rectype += c; + +#ifdef DEBUG + printf("rectype=%d\n", rectype); +#endif + + /* We only support record type 0 (data) and 1 (EOF) */ + if ((rectype != 0) && (rectype != 1)) { + printf("Unsupported record type %d\n", rectype); + return 1; + } + + /* Handle EOF record */ + if (rectype == 1) { + return 2; + } + + /* Initialize endaddr on first call */ + if (endaddr == -1) endaddr = addr; + + /* Check for overlapping addresses */ + if (addr < endaddr) { + puts("Overlap in data!"); + return 1; + } + + /* Zero fill gaps */ + if (addr > endaddr) { + for (i = 0; i < addr - endaddr; ++i) { + puts("FILL"); + fputc(0, out); + } + } + + endaddr = addr + datalen; + + /* Now datalen bytes stored as 2*datalen hex digits */ + for (i = 0; i < datalen; ++i) { + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + byte = c * 16; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + byte += c; + fputc(byte, out); + } + + /* Finally two checksum hex digits (ignored) */ + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + c = fgetc(in); + if (c == -1) return 1; + c = hexchar(c); + if (c == 127) return 1; + + /* Now eat the newline */ + c = fgetc(in); + + return 0; +} + +int main(argc, argv) +int argc; +char *argv[]; +{ + char len, ret; + char inname[FNAMELEN+1], outname[FNAMELEN+1]; + FILE *in, *out; + + if (argc != 2) { + puts("usage: hex2bin hexfile"); + return 1; + } + + /* Strip off .HEX extension, if provided */ + len = strlen(argv[1]); + if (len > 4) { + if (((argv[1][len-1] == 'x') && (argv[1][len-2] == 'e') && + (argv[1][len-3] == 'h') && (argv[1][len-4] == '.')) || + ((argv[1][len-1] == 'X') && (argv[1][len-2] == 'E') && + (argv[1][len-3] == 'H') && (argv[1][len-4] == '.'))) + argv[1][len-4] = '\0'; + } + + strcpy(inname, argv[1]); + strcat(inname, ".hex"); + strcpy(outname, argv[1]); + strcat(outname, ".bin"); + printf("%s -> %s\n", inname, outname); + + in = fopen(inname, "r"); + if (!in) { + printf("Can't open %s for reading\n", inname); + goto done; + } + + out = fopen(outname, "w"); + if (!out) { + printf("Can't open %s for writing\n", outname); + goto done; + } + + while (!feof(in)) { + ret = hextobin(in, out); + putchar('.'); + if (ret == 1) { + printf("Error parsing %s\n", inname); + goto done; + } + if (ret == 2) { + puts("Done."); + goto done; + } + } + return 0; + +done: + if (in) fclose(in); + if (out) fclose(out); + return 0; +} + +