From: Alan Cox Date: Tue, 31 Oct 2017 20:48:32 +0000 (+0000) Subject: as further work X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=f673a9bc744bbfb1e63be97f38252d80aee35eb7;p=FUZIX.git as further work - Initial core support for word addressed machines - Sketch out a compilable DG Nova PoC for this - PC relative addressing --- diff --git a/Applications/MWC/cmd/asz80/as.h b/Applications/MWC/cmd/asz80/as.h index ad053e43..cb8c766f 100644 --- a/Applications/MWC/cmd/asz80/as.h +++ b/Applications/MWC/cmd/asz80/as.h @@ -152,6 +152,7 @@ typedef uint16_t VALUE; /* For symbol values */ #define SEGMENT_OVERFLOW 26 #define DATA_IN_ZP 27 #define REQUIRE_Z180 28 +#define SEGMENT_CLASH 29 @@ -251,7 +252,98 @@ typedef uint16_t VALUE; /* For symbol values */ #define DATA_IN_BSS 25 #define SEGMENT_OVERFLOW 26 #define DATA_IN_ZP 27 +#define SEGMENT_CLASH 28 +#elif TARGET_DGNOVA + +#define TARGET_WORD_MACHINE + +/* 16 bit machine but we need to track in 32bits to allow for the fact we + can be dealing with 2^16 words */ +typedef uint32_t VALUE; /* For symbol values */ + +#define SEGMENT_LIMIT 0x10000 /* bytes */ + +#define NSEGMENT 5 /* # of segments */ + +#define ARCH OA_DGNOVA +#define ARCH_FLAGS 0 + + +/* + * 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 0x000F /* Register code */ +#define TMMDF 0x0001 /* Multidef */ +#define TMASG 0x0002 /* Defined by "=" */ +#define TMMODE 0xFF00 /* Mode */ +#define TMINDIR 0x8000 /* Indirect flag in mode */ +#define TPUBLIC 0x0080 /* Exported symbol */ + +#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 TSEGMENT 0x0D00 /* segments by number */ +#define TEXPORT 0x0E00 /* symbol export */ +#define TCC 0x0F00 +/* CPU specific codes */ +#define TCPUOPT 0x1100 +#define TMEMORY 0x1200 +#define TALU 0x1300 +#define TIO 0x1400 +#define TDEV 0x1500 +#define TAC 0x1600 +#define TIMPL 0x1700 +#define TBYTE 0x1800 +#define TTRAP 0x1900 + +#define TPCREL 0x0010 + +/* + * Error message numbers + */ + +#define BRACKET_EXPECTED 1 +#define MISSING_COMMA 2 +#define SQUARE_EXPECTED 3 +#define PERCENT_EXPECTED 4 +#define UNEXPECTED_CHR 10 +#define PHASE_ERROR 11 +#define MULTIPLE_DEFS 12 +#define SYNTAX_ERROR 13 +#define MUST_BE_ABSOLUTE 14 +#define MISSING_DELIMITER 15 +#define INVALID_CONST 16 +#define BRA_RANGE 17 +#define CONDCODE_ONLY 18 +#define INVALID_REG 19 +#define ADDR_REQUIRED 20 +#define INVALID_ID 21 +#define BADMODE 22 +#define DIVIDE_BY_ZERO 23 +#define CONSTANT_RANGE 24 +#define DATA_IN_BSS 25 +#define SEGMENT_OVERFLOW 26 +#define DATA_IN_ZP 27 +#define BAD_ACCUMULATOR 28 +#define NEED_ZPABS 29 +#define BADDEVICE 30 +#define BAD_PCREL BRA_RANGE +#define SEGMENT_CLASH 31 #else #error "Unknown target" @@ -275,6 +367,10 @@ typedef uint16_t VALUE; /* For symbol values */ #define MULPRI 2 #define HIPRI 3 +#ifndef SEGMENT_LIMIT +#define SEGMENT_LIMIT 0 +#endif + /* * Address description. */ @@ -294,8 +390,9 @@ typedef struct SYM { char s_id[NCPS]; /* Name */ int s_type; /* Type */ VALUE s_value; /* Value */ + uint16_t s_number; /* Symbol number 1..n, also usable for + tokens as extra data */ int s_segment; /* Segment this symbol is relative to */ - uint16_t s_number; /* Symbol number 1..n */ } SYM; /* @@ -352,6 +449,7 @@ extern void outab(uint8_t); extern void outabchk(uint16_t); extern void outraw(ADDR *); extern void outrab(ADDR *); +extern void outrabrel(ADDR *); extern void outeof(void); extern void outbyte(uint8_t); extern void outflush(void); diff --git a/Applications/MWC/cmd/asz80/as1-nova.c b/Applications/MWC/cmd/asz80/as1-nova.c new file mode 100644 index 00000000..8db69097 --- /dev/null +++ b/Applications/MWC/cmd/asz80/as1-nova.c @@ -0,0 +1,414 @@ +/* + * DG Nova assembler. + * Assemble one line of input. + * Knows all the dirt. + */ +#include "as.h" + +/* + * 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. + */ +void getaddr(ADDR *ap) +{ + int reg; + int c; + + /* We only have one addressing format we ever use.. an address. + Quite how we encode it is another saga because our memory ops + use register relative or pc relative */ + c = getnb(); + if (c != '#') + unget(c); + expr1(ap, LOPRI, 1); + switch (ap->a_type&TMMODE) { + case TUSER: + break; + default: + qerr(SYNTAX_ERROR); + } + if (c == '@') /* Indirect */ + ap->a_value |= 0x8000; +} + +static int accumulator(void) +{ + int c = getnb(); + if (c < '0' || c > '3') { + aerr(BAD_ACCUMULATOR); + unget(c); + return 0; + } + return c - '0'; +} + +static int carryop(char c) +{ + c = toupper(c); + if (c == 'Z') + return 1; + if (c == 'O') + return 2; + if (c == 'C') + return 3; + return 0; +} + +static int postop(char c) +{ + c = toupper(c); + if (c == 'L') + return 1; + if (c == 'R') + return 2; + if (c == 'S') + return 3; + return 0; +} +/* + * Assemble one line. + * The line in in "ib", the "ip" + * scans along it. The code is written + * right out. + */ +void asmline(void) +{ + SYM *sp; + int c; + int acs, acd; + int opcode; + int disp; + int reg; + int srcreg; + int cc; + VALUE value; + int delim; + SYM *sp1; + char id[NCPS]; + char id1[NCPS]; + char iid[4]; + ADDR a1; + ADDR a2; + +loop: + if ((c=getnb())=='\n' || c==';') + return; + if (isalpha(c) == 0 && c != '_' && c != '.') + qerr(UNEXPECTED_CHR); + 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; + /* Word machine so half the byte count */ + sp->s_value = dot[segment] >> 1; + sp->s_segment = segment; + } else { + if ((sp->s_type&TMMDF) != 0) + err('m', MULTIPLE_DEFS); + if (sp->s_value != dot[segment]) + err('p', PHASE_ERROR); + } + goto loop; + } + /* We have to do ugly things here because the Nova instruction set + merges the opcode and flags */ + memcpy(iid, id, 4); + iid[3] = 0; + + /* + * 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 && + (sp = lookup(iid, phash, 0)) == NULL) { + getid(id1, c); + if ((sp1=lookup(id1, phash, 0)) == NULL + || (sp1->s_type&TMMODE) != TEQU) { + err('o', SYNTAX_ERROR); + return; + } + getaddr(&a1); + istuser(&a1); + sp = lookup(id, uhash, 1); + if ((sp->s_type&TMMODE) != TNEW + && (sp->s_type&TMASG) == 0) + err('m', MULTIPLE_DEFS); + sp->s_type &= ~(TMMODE|TPUBLIC); + sp->s_type |= TUSER|TMASG; + sp->s_value = a1.a_value; + sp->s_segment = a1.a_segment; + /* FIXME: review .equ to an external symbol/offset and + what should happen */ + goto loop; + } + unget(c); + opcode = sp->s_value; + + switch (sp->s_type&TMMODE) { + case TORG: + getaddr(&a1); + istuser(&a1); + if (a1.a_segment != ABSOLUTE) + qerr(MUST_BE_ABSOLUTE); + segment = 0; + dot[segment] = a1.a_value * 2; /* dot is in bytes */ + /* Tell the binary generator we've got a new absolute + segment. */ + outabsolute(dot[segment]); + break; + + case TEXPORT: + getid(id, getnb()); + sp = lookup(id, uhash, 1); + sp->s_type |= TPUBLIC; + break; + /* .code etc */ + case TSEGMENT: + segment = sp->s_value; + /* Tell the binary generator about a segment switch to a non + absolute segnent */ + outsegment(segment); + break; + + case TDEFW: + do { + getaddr(&a1); + istuser(&a1); + outraw(&a1); + } while ((c=getnb()) == ','); + unget(c); + break; + + case TDEFM: + if ((delim=getnb()) == '\n') + qerr(MISSING_DELIMITER); + while ((c=get()) != delim) { + if (c == '\n') + qerr(MISSING_DELIMITER); + outab(c); + } + /* Word machine - pad the end */ + if (dot[segment] & 1) + dot[segment]++; + break; + + case TDEFS: + getaddr(&a1); + istuser(&a1); + /* Write out the words. The BSS will deal with the rest */ + for (value = 0 ; value < a1.a_value; value++) + outaw(0); + break; + + case TCPUOPT: + cpu_flags |= sp->s_value; + break; + + case TMEMORY: + { + int indirect = 0; + /* + * Memory operations are either + * 0,disp Word 0-255 (zero page) + * 1,signed disp PC relative + * 2.disp ac2 + offset + * 3,disp ac3 + offset + * + * We don't enforce any rules on ac2/ac3 but encode + * them on the basis the user knows what they are doing + * + * 0,disp is encoded is an 8 bit relocation for ZP + * 1,disp FIXME needs to be encoded as an 8bit PCREL + * + * There is *no* immediate load nor is there anyway + * to load arbitrary addresses. + * + * FIXME: we need some Nova specific meta ops as a + * result + * + * .nomodify - don't sneak in data words + * .modify - allowed to sneak in data words + * .local - local data word + * .flushlocal - write locals out here + * + * local data words are placed in a queue with their + * relocation address. During pass 0 we try to place them + * by adding ,skp to TALU instructions and putting one + * after it. If we reach the point it won't fit we add a + * JMP around the data and load with data. Likewise we + * can fill after a jump. + * + * This has its own fun... a jump is itself pcrel or + * constrained. Fortunately however we can encode an + * arbitrary jump as JMP #.+1 and a word. We can't do + * this ourself with JSR but compilers can jmp 3,1 + * and do it. + * + * For A2/A3 the same way to write stuff is likely to be + * + * ; a2 is loaded with foo + * + * LDA 1,bar-foo,2 + * + * and the assembler with turn bar-foo into an ABSOLUTE + */ + acd = accumulator(); + comma(); + c = get(); + if (c == '@') + indirect = 1; + else { + unget(c); + indirect = 0; + } + getaddr(&a1); + c = get(); + disp = 0; + if (c == ',') + acs = accumulator(); + else + unget(c); + /* ,0 means zero page */ + if (acs == 0) { + if (a1.a_segment == UNKNOWN) + a1.a_segment = ZP; + if (a1.a_segment != ZP && a1.a_segment != ABSOLUTE) + aerr(NEED_ZPABS); + } + /* ,2 + are indexes so we can't really police them for sanity */ + /* ,1 is PC relative so must be in this segment */ + if (acs == 1) { + if (a1.a_segment == UNKNOWN) + a1.a_segment = segment; + else if (a1.a_segment != ABSOLUTE && a1.a_segment != segment) + aerr(BAD_PCREL); + if (a1.a_segment != ABSOLUTE) + a1.a_type |= TPCREL; + } + /* Insert the accumulators */ + opcode |= (acd << 13); + opcode |= (acs << 8); + if (indirect) + opcode |= 0x0400; + if (acs) + outrabrel(&a1); /* Signed */ + else if (acs == 0) + outrab(&a1); /* Unsigned */ + outab(opcode >> 8); + break; + } + + case TALU: + { + char *p = id + 3; + SYM *skp = NULL; + int drop; + int cf; + int sh; + + cf = carryop(*p); + if (cf) + p++; + sh = postop(*p); + if (sh) + p++; + c = get(); + if (c == '#') + drop = 1; + else { + unget(c); + drop = 0; + } + acs = accumulator(); + comma(); + acd = accumulator(); + c = getnb(); + if (c == ',') { + getid(id1,getnb()); + skp = lookup(id1, phash, 0); + if (skp == NULL || skp->s_type != TCC) + err('s',SYNTAX_ERROR); + } else + unget(c); + opcode |= (acs << 13); + opcode |= (acd << 11); + opcode |= (sh << 6); + opcode |= (cf << 4); + opcode |= (drop << 3); + if (skp) + opcode |= skp->s_value; + outaw(opcode); + break; + } + + case TIO: + acs = accumulator(); + comma(); + getaddr(&a1); + istuser(&a1); + if (a1.a_value > 63) + err('d', BADDEVICE); + opcode |= (acs << 1); + opcode |= a1.a_value; + outaw(opcode); + break; + + case TDEV: + getaddr(&a1); + istuser(&a1); + if (a1.a_value > 63) + err('d', BADDEVICE); + opcode |= a1.a_value; + outaw(opcode); + break; + + case TAC: + acs = accumulator(); + opcode |= (acs << 11); + outaw(opcode); + break; + + case TIMPL: + outaw(opcode); + break; + + case TBYTE: + acs = accumulator(); + comma(); + acd = accumulator(); + opcode |= (acd << 11); + opcode |= (acs << 6); + outaw(opcode); + break; + + case TTRAP: + acs = accumulator(); + comma(); + acd = accumulator(); + comma(); + getaddr(&a1); + /* We can't relocate these yet FIXME */ + istuser(&a1); + opcode |= (acs << 15); + opcode |= (acd << 13); + opcode |= (a1.a_value << 4); + outaw(opcode); + break; + default: + aerr(SYNTAX_ERROR); + } + goto loop; +} + diff --git a/Applications/MWC/cmd/asz80/as4.c b/Applications/MWC/cmd/asz80/as4.c index e4550590..a2164c74 100644 --- a/Applications/MWC/cmd/asz80/as4.c +++ b/Applications/MWC/cmd/asz80/as4.c @@ -1,7 +1,12 @@ /* - * Z-80 assembler. - * Output Intel compatable - * hex files. + * Output FUZIX object files for 8/16bit machines. + * + * Currently we understand little endian 8 and 16bit formats along with + * PC relative. + * + * FIXME: We need to manage dot[segment] properly for word addressed machines + * so that we track and write in words not bytes. Right now word addressing + * is broken */ #include "as.h" @@ -18,6 +23,7 @@ void outpass(void) { off_t base = sizeof(obh); int i; + if (pass == 1) { /* Lay the file out */ for (i = 1; i < NSEGMENT; i++) { @@ -36,15 +42,6 @@ void outpass(void) obh.o_dbgbase = 0; /* for now */ /* Number the symbols for output */ numbersymbols(); - outsegment(1); -#ifdef TARGET_WORDMACHINE - outbyte(REL_WORDMACHINE); - outbyte(BYTES_PER_ADDRESS); -#elif TARGET_BIGENDIAN - outbyte(REL_BIGENDIAN); -#else - outbyte(REL_LITTLEENDIAN); -#endif } } @@ -74,8 +71,13 @@ void outsegment(int seg) */ void outaw(uint16_t w) { +#ifdef TARGET_BE + outab(w >> 8); + outab(w); +#else outab(w); outab(w >> 8); +#endif } static void check_store_allowed(uint8_t segment, uint16_t value) @@ -86,6 +88,10 @@ static void check_store_allowed(uint8_t segment, uint16_t value) err('z', DATA_IN_ZP); } +/* + * Symbol numbers and relocatios are always written little endian + * for simplicity. + */ void outraw(ADDR *a) { if (a->a_segment != ABSOLUTE) { @@ -120,7 +126,7 @@ void outab(uint8_t b) outbyte(REL_REL); ++dot[segment]; ++truesize[segment]; - if (truesize[segment] == 0 || dot[segment] == 0) + if (truesize[segment] == SEGMENT_LIMIT || dot[segment] == SEGMENT_LIMIT) err('o', SEGMENT_OVERFLOW); } @@ -137,10 +143,12 @@ void outrabrel(ADDR *a) check_store_allowed(segment, a->a_value); if (a->a_sym) { outbyte(REL_ESC); - outbyte((0 << 4 ) | PCREL); + outbyte((0 << 4 ) | REL_PCREL); outbyte(a->a_sym->s_number & 0xFF); outbyte(a->a_sym->s_number >> 8); - outaw(a->a_value); + outbyte(a->a_value); + outab(a->a_value >> 8); + return; } /* relatives without a symbol don't need relocation */ } diff --git a/Applications/MWC/cmd/asz80/as6-nova.c b/Applications/MWC/cmd/asz80/as6-nova.c new file mode 100644 index 00000000..5745eba9 --- /dev/null +++ b/Applications/MWC/cmd/asz80/as6-nova.c @@ -0,0 +1,148 @@ +/* + * DG Nova assembler. + * + * Tables + */ + +#include "as.h" + +SYM sym[] = { + { 0, "skp", TCC, 1 }, + { 0, "szc", TCC, 2 }, + { 0, "snc", TCC, 3 }, + { 0, "szr", TCC, 4 }, + { 0, "snr", TCC, 5 }, + { 0, "sez", TCC, 6 }, + { 0, "sbn", TCC, 7 }, + + { 0, "defw", TDEFW, XXXX }, + { 0, "defs", TDEFS, XXXX }, + { 0, "defm", TDEFM, XXXX }, + { 0, "org", TORG, XXXX }, + { 0, "equ", TEQU, XXXX }, + { 0, "export", TEXPORT, XXXX }, + { 0, ".word", TDEFW, XXXX }, + { 0, ".blkw", TDEFS, XXXX }, + { 0, ".ascii", TDEFM, XXXX }, + { 0, ".org", TORG, XXXX }, + { 0, ".equ", TEQU, XXXX }, + { 0, ".export", TEXPORT, XXXX }, + { 0, "code", TSEGMENT, CODE }, + { 0, "data", TSEGMENT, DATA }, + { 0, "bss", TSEGMENT, BSS }, + { 0, "zp", TSEGMENT, ZP }, + { 0, ".code", TSEGMENT, CODE }, + { 0, ".data", TSEGMENT, DATA }, + { 0, ".bss", TSEGMENT, BSS }, + { 0, ".zp", TSEGMENT, ZP }, + + { 0, "jmp", TMEMORY, 0x0000 }, + { 0, "jsr", TMEMORY, 0x0800 }, + { 0, "isz", TMEMORY, 0x1000 }, + { 0, "dsz", TMEMORY, 0x1800 }, + { 0, "lda", TMEMORY, 0x2000 }, + { 0, "inc", TMEMORY, 0x3000 }, + { 0, "sta", TMEMORY, 0x4000 }, + + { 0, "com", TALU, 0x8000 }, + { 0, "neg", TALU, 0x8100 }, + { 0, "mov", TALU, 0x8200 }, + { 0, "adc", TALU, 0x8400 }, + { 0, "sub", TALU, 0x8500 }, + { 0, "add", TALU, 0x8600 }, + { 0, "and", TALU, 0x8700 }, + + { 0, "dia", TIO, 0x6100 }, + { 0, "doa", TIO, 0x6200 }, + { 0, "dib", TIO, 0x6300 }, + { 0, "dob", TIO, 0x6400 }, + { 0, "dic", TIO, 0x6500 }, + { 0, "doc", TIO, 0x6600 }, + { 0, "skip", TDEV, 0x6700 }, + + + /* Then the post NOVA 1 operations that are less elegant being + device ops */ + { 0, "mtfp", TAC, 0x6001 }, + { 0, "mffp", TAC, 0x6081 }, + { 0, "mtsp", TAC, 0x6201 }, + { 0, "mfsp", TAC, 0x6281 }, + { 0, "psha", TAC, 0x6301 }, + { 0, "popa", TAC, 0x6381 }, + { 0, "sav", TIMPL, 0x6501 }, + { 0, "ret", TIMPL, 0x6581 }, + /* Undocumented SAV n ? */ + + { 0, "trap", TTRAP, 0x8008 }, + { 0, "ldb", TBYTE, 0x6101 }, + { 0, "stb", TBYTE, 0x6401 }, + + { 0, "div", TIMPL, 0x7641 }, + { 0, "mul", TIMPL, 0x76C1 }, + { 0, "divs", TIMPL, 0x7E01 }, + { 0, "muls", TIMPL, 0x7E81 }, +}; + +/* + * Set up the symbol table. + * Sweep through the initializations + * of the "phash", and link them into the + * buckets. Because it is here, a + * "sizeof" works. + */ +void syminit(void) +{ + SYM *sp; + int hash; + + sp = &sym[0]; + while (sp < &sym[sizeof(sym)/sizeof(SYM)]) { + hash = symhash(sp->s_id); + sp->s_fp = phash[hash]; + phash[hash] = sp; + ++sp; + } +} + +char *etext[] = { + "unexpected character", + "phase error", + "multiple definitions", + "syntax error", + "must be absolute", + "missing delimiter", + "invalid constant", + "relative out of range", + "skip condition required", + "invalid register for operation", + "address required", + "invalid id", + "bad addressing mode", + "divide by 0", + "constant out of range", + "data in BSS", + "segment overflow", + "data in zero page", + "bad accumulator", + "must be zero page or absolute", + "bad device", + "segment conflict" +}; + +/* + * 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. + */ +void isokaors(ADDR *ap, int paren) +{ + int mode; + int reg; + + mode = ap->a_type&TMMODE; + if (mode == TUSER) + return; + aerr(ADDR_REQUIRED); +} diff --git a/Applications/MWC/cmd/asz80/as6.c b/Applications/MWC/cmd/asz80/as6.c index acb4dcbf..33372c5b 100644 --- a/Applications/MWC/cmd/asz80/as6.c +++ b/Applications/MWC/cmd/asz80/as6.c @@ -180,7 +180,8 @@ char *etext[] = { "constant out of range", "data in BSS", "segment overflow", - "Z180 instruction" + "Z180 instruction", + "segment conflict" }; /*