#define SEGMENT_OVERFLOW 26
#define DATA_IN_ZP 27
#define REQUIRE_Z180 28
+#define SEGMENT_CLASH 29
#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"
#define MULPRI 2
#define HIPRI 3
+#ifndef SEGMENT_LIMIT
+#define SEGMENT_LIMIT 0
+#endif
+
/*
* Address description.
*/
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;
/*
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);
--- /dev/null
+/*
+ * 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;
+}
+
/*
- * 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"
{
off_t base = sizeof(obh);
int i;
+
if (pass == 1) {
/* Lay the file out */
for (i = 1; i < NSEGMENT; i++) {
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
}
}
*/
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)
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) {
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);
}
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 */
}
--- /dev/null
+/*
+ * 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);
+}
"constant out of range",
"data in BSS",
"segment overflow",
- "Z180 instruction"
+ "Z180 instruction",
+ "segment conflict"
};
/*