/* 6502 Assembler for C02 Compiler * * Uses DASM Syntax but supports * * 65C02 and Sweet 16 Opcodes */ #include #include #include #include #include "a02.h" #define DEBUG FALSE int debug; //Ouput Debug Info #define INCLEN 255 char incpath[INCLEN]; enum otypes {BINFIL, APLFIL, ATRFIL, INTFIL, MSRFIL, PRGFIL}; //Object File Types int obin[] = {TRUE, FALSE, TRUE, FALSE, FALSE, TRUE}; //Is File Type Binary int olen[] = {0, 8, 32, 0, 0}; //Hex Bytes per Line char objtyp; //Object File Type int objbin; //Binary Object File Flag int objlen; //Text Object File Line Length int objcnt; //Text Object File Byte Count int chksum; //Text Object File Checksum int orgadr; //Origin Address int curadr; //Current Address int endadr; //Address after Last Byte int exeadr; //Execution Start Address int lstadr; //List Address char hdrtxt[MAXSTR]; //SREC Header Record Text struct sym {int block; char name[MAXLBL+1]; int bytes, value, refrd;}; struct sym symbol; //Current Symbol struct sym symtbl[MAXSYM]; //Global Symbol Table int symcnt; //Number of Global Labels int blkcnt; //Number of Block Labels Generated int blknum; //Local Label Block Number (0 = Global) char label[MAXSTR]; //Assembly Line Label char mnmnc[MAXSTR]; //Opcode Mnemonic char oprnd[MAXSTR]; //Operand Text char cmmnt[MAXSTR]; //Assembly Line Comment char mcode[MAXSTR]; //Generated Bytes char strng[MAXSTR]; //Parsed String int opridx; //Index into Operand char cpdchr; //Last Character Copied into oprnd unsigned char token, opmod; //OpCode Token, Modifier unsigned int amode; //Addressing Modes int zpage, opval; //ZeroPage Flag, Operand Value char hexadr[6]; //Current Address in Hexadecimal char bytstr[5]; //String Representation of Byte char inplin[MAXSTR]; //Input Buffer char *linptr; //Pointer into Input Buffer int lineno; //Input File Line Number int savlno; //Line Number (Saved) int passno; //Assembler Pass Number (1 or 2) int endasm; //End Assembly Flag char prgnam[256]; //Assembler Path and Name (from Command Line) char inpnam[256]; //Input File Name char outnam[256]; //Output File Name char lstnam[256]; //List File Name char incnam[256]; //Include File Name FILE *inpfil; //Input File Pointer FILE *outfil; //Output File Pointer FILE *lstfil; //List File Pointer FILE *incfil; //Include File Pointer /* Print Usage Info and Exit */ void usage(char* appnam) { printf("Usage: %s [opts] asmfile objfile [lstfile]\n", appnam); printf(" Opts: -d - Output debug info\n"); printf(" -h - SREC header record text\n"); printf(" -a - Apple-1 monitor format\n"); printf(" -p - Commodore PRG format\n"); printf(" -s - Motorola SREC format\n"); printf(" -x - Intel HEX format"); printf(" \n"); exit(EXIT_FAILURE); } /* Print Symbol Table */ void prtsym(void) { fprintf(lstfil, "\n%s Symbol Table\nBlock Name Size Value Rfd\n", "Global"); for (int i=0; i 0xFF) ? 2 : 1; symbol.refrd = FALSE; if (debug) printf("Set Symbol %s in block %d to value %d, size %d, referred=False\n", symbol.name, symbol.block, symbol.bytes, symbol.value); } /* Add Character to Beginning of String */ void pfxstr(char c, char* s) { for (int i=strlen(s)+1; i; i--) s[i] = s[i-1]; //Copy All Characters to the Right s[0] = c; //Insert Character at Beginning } /* Parse Label from Input Line * Sets: label * Updates: linptr * Returns: Label Found (TRUE/FALSE) */ int plabel(void) { if (debug) puts("Parsing Label"); int block = (skpchr('.')) ? blknum : 0; //Local Label Block Number int found = pword(FALSE, label); //Parse Word without Skipping Spaces if (debug) { if (found) printf("Found Label %s\n", label); else puts("No Label Found"); } skpchr(':'); //Skip Optional Label Terminator if (found && passno == 1) { if (label[0] && fndsym(block, label)) xerror("Duplicate Label %s Encountered\n", label); if (debug) printf("Initializing Symbol %s\n", label); symbol.block = block; if (strlen(label) > MAXLBL) xerror("Label %s Too Long\n", label); strcpy(symbol.name, label); setsym(curadr, 0); } if (block) pfxstr('.', label); skpspc(); //Skip to Mnemonic, Comment, or EOL return found; } /* Append to Operand */ void aoprnd(int c) { if (opridx < MAXSTR) oprnd[opridx++] = toupper(c); } /* Copy Character to Operand and Increment */ int cpychr(int c) { if (c && toupper(*linptr) != c) return FALSE; cpdchr = *linptr; aoprnd(cpdchr); linptr++; return TRUE; } /* Evaluate Binary Number */ int evlbin() { int result = 0; cpychr('%'); while (isdigit(*linptr)) { if (*linptr > '1') break; result = (result << 1) + *linptr - '0'; cpychr(0); } return result; } /* Evaluate Character Constant */ int evlchr() { int result = 0; cpychr('\''); result = *linptr; cpychr(0); cpychr('\''); return result; } /* Evaluate Decimal Number */ int evldec() { int result = 0; while (isdigit(*linptr)) { result = result * 10 + *linptr - '0'; cpychr(0); } return result; } /* Evaluate Hexadecimal Number */ int evlhex() { int result = 0; cpychr('$'); while (isxdigit(*linptr)) { int digit = toupper(*linptr) - '0'; if (digit > 9) digit = digit - 7; result = (result << 4) + digit; cpychr(0); } return result; } /* Evaluate Symbol */ struct sym *evlsym() { char name[MAXSTR]; int block = (cpychr('.')) ? blknum : 0; pword(TRUE, name); for (int i=0; name[i]; i++) if (opridxrefrd = TRUE; //Symbol was Referenced return result; } /* Evaluate Term in Operand */ int evltrm() { int result; skpspc(); if (isalpha(*linptr) || *linptr == '.') { struct sym *target = evlsym(); result = (target) ? target->value : 0x100; } else if (isdigit(*linptr)) result = evldec(); else switch(*linptr) { case '$': result = evlhex(); break; case '%': result = evlbin(); break; case '\'': result = evlchr(); break; default: result = -1; } skpspc(); if (debug) printf("Term Evaluated to %d\n", result); return result; } /* Evaluate Operand */ int evlopd(int maxsiz) { int result = 0; int hilo = 0; //Return LSB (1) or MSB (2) int prns; //Optional Parenthesis after Hi/Low Operator if (debug) puts("Evaluating Operand"); skpspc(); if (cpychr('<')) hilo = 1; else if (cpychr('>')) hilo = 2; if (hilo) prns = cpychr('('); result = evltrm(); if (result >= 0) while (cpychr('+') || cpychr('-')) { int opertr = cpdchr; int opdval = evltrm(); if (opdval < 0) break; if (opertr == '+') result += opdval; else result -= opdval; } if (hilo) { if (result < 0) xerror("Hi/Low Operator Requires Operand", ""); if (prns) cpychr(')'); // switch (hilo) { case 1: result = result & 0xFF; break; //LSB case 2: result = result >> 8; break; //MSB } } if (debug) printf("Operand Evaluated to %d\n", result); if (result > maxsiz) xerror("Operand Value too Large\n", ""); return result; } /* Write Hex and Update Checksum */ void outhex(char prefix, int b) { if (prefix) fputc(prefix, outfil); fprintf(outfil, "%02X", b); chksum += b; } \ /* Write Hex Address */ void outadr(int w) { outhex(0, w >> 8); outhex(0, w & 0xFF); } /* Write Hex File Line Header */ void outhdr(int addr) { chksum = 0; int linlen = __min(objlen, endadr - curadr + 1); if (debug) printf("outhdr: curadr=%04x, endadr=%04x, linlen=%04x\n", curadr, endadr, linlen); switch (objtyp) { case INTFIL: outhex(':', linlen); outadr(addr); outhex(0, 0); break; default: outadr(addr); //Write Address } if (objtyp == APLFIL) fputc(':', outfil); } /* Write Checksum */ void outchk(void) { if (objtyp == INTFIL) outhex(0, -chksum & 0xFF); } /* Write Hex File Line End */ void outend(void) { if (objtyp = INTFIL) outchk(); //Write Checksum fputs("\n", outfil); objcnt = 0; } /* Write Hex File End of File */ void outeot(void) { if (objcnt) outend(); if (objtyp = INTFIL); fputs(":00000001FF\n", outfil); } /* Write Byte to Hex Object File */ void outtxt(int b) { if (objcnt == 0) outhdr(curadr - 1); switch (objtyp) { case APLFIL: outhex(' ', b); break; default: outhex(0, b); } objcnt++; if (objcnt == objlen) outend(); } /* Write Byte to Output File */ void outbyt(int b) { if (curadr > -1) curadr++; if (passno != 2) return; if (objbin) fputc(b & 0xFF, outfil); else outtxt(b); sprintf(bytstr, "%02X ", b); if (strlen(mcode) < 9) strcat(mcode, bytstr); } /* Write Word to Output File */ void outwrd(int w) { outbyt(w & 0xff); outbyt(w >> 8); } /* Write Atari 8-Bit EXE Block Header */ void atrhdr(int saddr, int eaddr) { outwrd(0xFFFF); //Binary File Indicator outwrd(saddr); //Start Address outwrd(eaddr-1); //End Address } /* Write Atari 8-Bit EXE Ending Block */ void atrend(void) { atrhdr(0x02E0, 0x02E2); outbyt(0x4C); //JMP outwrd(exeadr); //start address } /* Lookup Opcode */ int lkpopc(struct opc opl[]) { if (debug) printf("Looking up Mnemonic %s\n", mnmnc); token = 0xFF; //Set Token to Invalid char mne[5]; strncpy(mne, mnmnc, 4); mne[4] = 0; //Truncate Mnemonic to Four Characters for (int i=0; opl[i].name[0]; i++) { if (strcmp(opl[i].name, mne)) continue; token = opl[i].token; amode = opl[i].amode; if (debug) printf("Found token %02X, amode %04X\n", token, amode); return TRUE; } return FALSE; } /* Assemble BYTE Pseudo-Op */ void asmbyt(void) { if (debug) puts("Assembling BYTE Pseudo-Op"); do { if (cpychr('"')) { //String Operand while (!cpychr('"')) {outbyt(*linptr); cpychr(0); } skpspc(); } else outbyt(evlopd(0xFF)); //Evaluate Operand } while (cpychr(',')); } /* Assemble HEX Pseudo-Op */ void asmhex(void) { if (debug) puts("Assembling HEX Pseudo-Op"); do {outbyt(evlhex(0xFF)); } while (cpychr(',')); } /* Assemble WORD Pseudo-Op */ void asmwrd(void) { do { outwrd(evlopd(0xFFFF)); //Evaluate Operand } while (cpychr(',')); } /* Assemble ALIGN Pseudo-Op */ void asmaln(void) { if (debug) puts("Assembling ALIGN Pseudo-Op"); int size = evlopd(0xFFFF); if (size < 2) return; if (debug) printf("Aligning to %d Bytes\n", size); int fill = size - (curadr % size); if (fill == size) return; if (debug) printf("Filling %d Bytes\n", fill); for (int i=0; ivalue - curadr - ofsadj); } else if (cpychr('+')) offset = evlopd(0xFF); else if (cpychr('-')) offset = -evlopd(0xFF); else { opval = evlopd(0xFFFF); if (opval < 0) xerror("Illegal Branch Operand\n", ""); offset = opval - curadr - 2; } if (debug) printf("Calculated Branch Offset of %d\n", offset); if ((offset > 127 || offset < -128) && passno == 2) xerror("Branch Out of Range\n", ""); if (debug) printf("Branch Offset %d\n", offset); opval = offset & 0xFF; } /* Assemble Zero Page, Relative Opcode */ void asmzpr(void) { int bitno = -1; if (debug) printf("Assembling ZeroPage (Relative) Opcode Token 0x%02X\n", token); if (strlen(mnmnc) < 4) {opmod = evlopd(7) << 4; cpychr(','); skpspc();} //Set Modifier to Bit Position int zpval = evlopd(0xFF); cpychr(','); skpspc();//Get ZeroPage Operand if (zpval < 0) xerror ("Instruction %s requires Multiple Operands\n", mnmnc); if (amode == 0x0004) {zpage = TRUE; opval = zpval;} //RMB, SMB - Zero Page Operand else {asmbrn(FALSE); opval = opval << 8 | zpval;} //BBR, BBS - Combine Operanda } /* Assemble Immediate Mode Instruction */ void asmimd(void) { if (debug) printf("Assembling Immediate Opcode Token 0x%02X\n", token); opval = evlopd(0xFF); zpage = TRUE; opmod = 0x08; //Immediate } /* Assemble Indirect Mode Instruction */ void asmind(void) { if (debug) puts("Assembling Indirect Mode Instruction"); zpage = TRUE; opval = evlopd(0xFFFF); if (cpychr(',') && cpychr('X') && chkmod(INDCX)) cpychr(')'); ////(Indirect,X) opmod=0 else if (cpychr(')')) { if (cpychr(',') && cpychr('Y') && chkmod(INDCY)) opmod = 0x10; //(Indirect),Y else if (chkmod(INDCT)) opmod = 0x11; //(Indirect) if (token == 0x4C) zpage = FALSE; //JMP (Indirect Absolute) } else chkmod(0); //Illegal Addressing Mode if (zpage && opval > 0x00FF) xerror("Operand Value too Large\n", ""); } /* Assemble Implied/Accumulator/Absolute/ZeroPage Mode Instruction */ void asmiaz(void) { opval = evlopd(0xFFFF); if (opval < 0) { if (amode != IMPLD) //Implied if (chkmod(ACMLT)) opmod = 0x08; //Accumulator return; } if (debug) printf("Assembling Absolute/ZeroPage 0x%02X\n", token); zpage = (opval <= 0xff) ? TRUE : FALSE; if (zpage && chkmod(ZPAGE)) opmod = 0x04; //ZeroPage else if (chkmod(ABSLT)) opmod = 0x0C; //Absolute if (cpychr(',')) { if (cpychr('X')) { if (zpage && chkmod(ZPAGX)) opmod = 0x14; //ZeroPage,X else if (chkmod(ABSLX)) opmod = 0x1C; //Absolute,X } else if (cpychr('Y')) { if (zpage && (token == 0x82 || token == 0xA2)) opmod = 0x14; //ZeroPage,Y else {zpage = FALSE; opmod = 0x18;} //Absolute,Y } else chkmod(0); //Illegal Addressing Mode } } /* Fix Opcode (if needed) */ unsigned char fixopc(void) { if (debug) printf("Fixing OpCode $%02X+$%02X\n", token, opmod); for (int i=0; opfix[i].token; i++) if (opfix[i].token == token && opfix[i].opmod == opmod) return opfix[i].opcode; return token + opmod; } /* Ouput Opcode debug Info */ void dbgopc(void) { printf("token=$%02X, opmod=$%02X, Address Mode: ", token, opmod); if (amode == 0x1004) puts("ZeroPage, Relative"); else if (amode == 0x0004) puts("ZeroPage"); else switch (opmod) { case 0x00: if (amode == IMPLD) puts("Implied"); else puts("(Indirect,X)"); break; case 0x08: if (opval < 0) puts("Accumulator"); else puts("#Immediate"); break; case 0x10: puts("(Indirect),Y"); break; case 0x11: puts("(Indirect)"); break; case 0x04: puts("ZeroPage"); break; case 0x0C: puts("Absolute"); break; case 0x14: if ((token == 0x82 || token == 0xA2)) puts("ZeroPage,Y"); else puts("ZeroPage,X"); break; case 0x1C: puts("Absolute,X"); break; case 0x18: puts("Absolute,Y"); break; default: puts("UNKOWN"); } if (opval < 0) puts("No Operand"); else { printf("Operand Value %d, ", opval); if (zpage) puts("Zero Page"); else puts("Absolute"); } } /* Evaluate Register Operand * * Returns: Register Number 0-15 */ int evlreg(void) { char name[MAXSTR]; int result; if (debug) puts("Evaluating Register Argument"); int rel = cpychr('@'); if (debug) printf("Set rel to %d\n", rel); if (amode == INDCT && !rel) xerror("Indirect Argument Required\n", ""); if (amode == RGIND && rel) token = token + 0x20; //LD or ST relative char pfx = 'R'; if (!cpychr(pfx)) pfx = 0; if (debug) puts("Evaulating Register Number"); if (pfx && isalpha(*linptr)) return evlhex(0x0F); if (isdigit(*linptr)) return evldec(0x0F); if (!pword(FALSE, name)) xerror("Register Argument Required\n", ""); for (int i=0; name[i]; i++) if (opridx= 0) {if (amode == RELTV) outbyt(opval); else outwrd(opval);} return TRUE; } /* Assemble Opcode */ int asmopc(int dot) { opmod = 0; if (asmpso(dot)) return TRUE; //Check For/Assemble Pseudo-Op if (asmswo()) return TRUE; //Check for/Assemble Sweet-16 Instruction if (lkpopc(opclst) == FALSE) xerror("Invalid Mnemonic %s\n", mnmnc); if (debug) printf("Assembling Opcode Token 0x%02X, ", token); if (debug) printf("Addressing Mode Mask 0x%04X\n", amode); skpspc(); if (amode == RELTV) asmbrn(TRUE); //Branch (Relative) Instruction else if (amode == 0x0004 || amode == 0x1004) asmzpr(); //Branch ZP (Relative) else if (cpychr('#')) asmimd(); //Assemble Immediate Instruction else if (cpychr('(')) asmind(); //Assemble Indirect Instruction else asmiaz(); //Assemble Implied/Accumulator/Absolute/ZeroPage Instruction if (debug) dbgopc(); int opcode = fixopc(); if (debug) printf("Writing OpCode $%02X\n", opcode); outbyt(opcode); if (debug) printf("Writing %s Operand %d\n", zpgabs[-zpage], opval); if (opval >= 0) { if (zpage) outbyt(opval); //Byte Operand else outwrd(opval); //Word Operand } return TRUE; } /* Parse Opcode Mnemonic from Input Line * Sets: mnmnc * Updates: linptr * Returns: Label Found (TRUE/FALSE) */ int pmnmnc(void) { if (debug) puts("Parsing Mnemonic"); int dot = cpychr('.'); //Optional Dot before Pseudo-Op int found = pword(TRUE, mnmnc); opridx = 0; //Initialize Operand Index if (found) asmopc(dot); oprnd[opridx] = 0; //Terminate Operand String return found; } /* Parse Comment from Input Line * Sets: cmmnt * Updates: linptr */ void pcmmnt(void) { skpspc(); int i = 0; while (*linptr >= ' ') cmmnt[i++] = *linptr++; cmmnt[i] = 0; //Terminate Comment if (debug) {if (i) printf("Comment: %s\n", cmmnt); else puts("No Comment Found");} } /* Add Label to Symbol Table */ void addsym() { if (symbol.value<0) xerror("Origin Not Set", ""); memcpy(&symtbl[symcnt++], &symbol, sizeof(symbol)); if (debug) printf("Added symbol %s in block %d to Symbol Table\n", symbol.name, symbol.block); } /* Open Include File */ void opninc(void) { if (debug) printf("Opening Include File %s\n", incnam); if (lstfil) fputs("\n", lstfil); incfil = opnfil(incnam, "r", incpath); savlno = lineno; lineno = 1; } /* Close Include File */ void clsinc(void) { if (debug) printf("Closing Include File %s\n", incnam); if (lstfil) fputs("\n", lstfil); fclose(incfil); incfil = NULL; incnam[0] = 0; lineno = savlno; endasm = FALSE; //Clear End Flag for Return to Main File } /* Close Object File */ void clsobj() { if (objbin) {if (objtyp == ATRFIL) atrend();} else {if (objcnt) outeot();} fclose(outfil); } /* Assemble Input File (Two Pass) * * Args: pass - Assembly Pass (1 or 2) * * Requires: inpfil - Input File Pointer * * Uses: inplin - Input Line Buffer * * lineno - Input File Line Number */ void asmfil(int pass) { endasm = FALSE; //Reset End Assembly Flag passno = pass; //Assembly Pass Number if (debug) printf("Assembling Pass %d\n", pass); lineno = 1; //Initialize Input File Line Number blkcnt = 0; //Initialize Local Block Count blknum = 0; // and Local Block Number orgadr = -1; //Origin Address Not Set curadr = orgadr; //Set Current Address to Origin exeadr = -1; //Execution Address Not Set objcnt = 0; //Initialize Object File Byte Count if (debug) printf("Rewinding Input File\n"); rewind(inpfil); //Start at Beginning of Input File while (TRUE) { if (incfil) linptr = fgets(inplin, MAXSTR, incfil); else linptr = fgets(inplin, MAXSTR, inpfil); if (endasm || linptr == NULL) {if (incfil) {clsinc(); continue;} else break;} if (debug) printf("%1d %05d %04X: %s", passno, lineno, curadr, inplin); lstadr = curadr; //Set List Address mcode[0] = 0; //Clear Generated Macbine Code plabel(); //Parse Label pmnmnc(); //Parse Mnemonic pcmmnt(); //Parse Comment if (passno == 1 && label[0]) addsym(); //Add Label to Table if (passno == 2) { if (lstadr < 0) hexadr[0] = 0; else sprintf(hexadr, "%04X", lstadr); fprintf(lstfil, "%05d %-4s %-9s%-7s %-5s %-16s %s\n", lineno, hexadr, mcode, label, mnmnc, oprnd, cmmnt ); fflush(lstfil); //Flush Output Buffer in case of Segmentation Fault } lineno++; if (incnam[0] && incfil == NULL) opninc(); //Open Include File } endadr = curadr; //Set End Address for Second Pass if (debug) printf("End address set to %04x\n", endadr); } /* Parse Command Line Option */ int pcoptn(char *argval) { if (argval[0] != '-') return FALSE; char option = toupper(argval[1]); if (debug) printf(" Option '%c'\n", option); switch(option) { case 'D': debug = TRUE; break; //Enable debug Output case 'A': objtyp = APLFIL; break; //Apple-1 Monitor Entry File case 'E': objtyp = ATRFIL; break; //Atari 8-Bit EXE File case 'P': objtyp = PRGFIL; break; //Commodore PRG File case 'S': objtyp = MSRFIL; break; //Motorola SREC File case 'X': objtyp = INTFIL; break; //Intel HEX File default: xerror("Illegal Command Line Option %s\n", argval); } if (debug && objtyp) printf("Object type set to %d\n", objtyp); return TRUE; } /* Parse Command Line Arguments */ void pcargs(int argc, char *argv[]) { int argnum = 0; for (int arg = 0; arg