From cc99faed165273fd1505a22ee016c82df2118a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Henrik=20Sk=C3=A5rstedt?= Date: Wed, 21 Oct 2015 22:34:01 -0700 Subject: [PATCH] 65C02 CPU added - Added SAV as a Merlin directive - cpu command line option to switch CPU target --- README.md | 29 ++--- x65.cpp | 354 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 290 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index ad43256..a980446 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ x65 [-DLabel] [-iIncDir] (source.s) (dest.prg) [-lst[=file.lst]] [-opcodes[=file x65 filename.s code.prg [options] * -i(path): Add include path * -D(label)[=(value)]: Define a label with an optional value (otherwise defined as 1) +* -cpu=6502/65c02: assemble with opcodes for a different cpu * -obj (file.x65): generate object file for later linking * -bin: Raw binary * -c64: Include load address (default) @@ -516,6 +517,10 @@ A variation of **INCLUDE** that applies an oddball set of filename rules. These In Merlin USR calls a function at a fixed address in memory, x65 safely avoids this. If there is a requirement for a user defined macro you've got the source code to do it in. +**SAV** + +SAV causes Merlin to save the result it has generated so far, which is somewhat similar to the [EXPORT](#export) directive. If the SAV name is different than the source name the section will have a different EXPORT name appended and exported to a separate binary file. + ## Expression syntax Expressions contain values, such as labels or raw numbers and operators including +, -, \*, /, & (and), | (or), ^ (eor), << (shift left), >> (shift right) similar to how expressions work in C. Parenthesis are supported for managing order of operations where C style precedence needs to be overrided. In addition there are some special characters supported: @@ -636,7 +641,7 @@ FindFirstSpace Currently the assembler is in an early revision and while features are tested individually it is fairly certain that untested combinations of features will indicate flaws and certain features are not in a complete state. **TODO** -* 65c02 +* 65C02 enabled through directives (PROCESSOR/CPU/XC) * 65816 * Macro parameters should replace only whole words instead of any substring * Add 'import' directive as a catch-all include/incbin/etc. alternative @@ -644,6 +649,7 @@ Currently the assembler is in an early revision and while features are tested in * boolean operators (==, <, >, etc.) for better conditional expressions **FIXED** +* 65c02 (currently only through command line, not as a directive) * Now accepts negative numbers, Merlin LUP and MAC keyword support * Merlin syntax fixes (no '!' in labels, don't skip ':' if first character of label), symbol file fix for included object files with resolved labels for relative sections. List output won't disassemble lines that wasn't built from source code. * Export full memory of fixed sections instead of a single section @@ -668,18 +674,9 @@ Currently the assembler is in an early revision and while features are tested in * TEXT directive converts ascii to petscii (respect uppercase or lowercase petscii) (simplistic) Revisions: -* 2015-10-20 Fixed negative numbers, Merlin macros and 'endmacro' alternative, merlin LUP directive -* 2015-10-18 Fixed exporting binary files -* 2015-10-18 Added list file output which is disassembly with inline source that generated the code. -* 2015-10-16 XDEF and file protected symbols added for better recursive object file assembling -* 2015-10-15 Object file reading, additional bugs debugged. -* 2015-10-10 Relative Sections and Link support, adding -merlin command line to clean up code -* 2015-10-06 Added ENUM and MERLIN / LISA assembler directives (EJECT, DUM, DEND, DS, DB, DFB, DDB, IF, ENDIF, etc.) -* 2015-10-05 Added INCDIR, some command line options (-D, -i, -vice) -* 2015-10-04 Added [REPT](#rept) directive -* 2015-10-04 Added [STRUCT](#struct) directive, sorted functions by grouping a bit more, bug fixes -* 2015-10-02 Cleanup hid an error (#else without #if), exit with nonzero if error was encountered -* 2015-10-02 General cleanup, wrapping [conditional assembly](#conditional) in functions -* 2015-10-01 Added [Label Pools](#pool) and conditional assembly -* 2015-09-29 Moved Asm6502 out of Struse Samples. -* 2015-09-28 First commit +* 6 - 65C02 support +* 5 - Merlin syntax +* 4 - Object files, relative sections and linking +* 3 - 6502 full support +* 2 - Moved file out of struse samples +* 1 - Built a sample of a simple assembler within struse diff --git a/x65.cpp b/x65.cpp index 1ad2f43..558b681 100644 --- a/x65.cpp +++ b/x65.cpp @@ -225,6 +225,7 @@ enum AssemblerDirective { AD_DUMMY_END, // DEND: End a dummy section AD_DS, // DS: Define section, zero out # bytes or rewind the address if negative AD_USR, // USR: MERLIN user defined pseudo op, runs some code at a hard coded address on apple II, on PC does nothing. + AD_SAV, // SAV: MERLIN version of export but contains full filename, not an appendable name }; // Operators are either instructions or directives @@ -264,7 +265,8 @@ typedef struct { } OP_ID; enum AddrMode { - AMB_ZP_REL_X, // 0 ($12,x) address mode bit index + // address mode bit index + AMB_ZP_REL_X, // 0 ($12,x) AMB_ZP, // 1 $12 AMB_IMM, // 2 #$12 AMB_ABS, // 3 $1234 @@ -282,7 +284,7 @@ enum AddrMode { AMB_FLIPXY = AMB_COUNT, // e AMB_BRANCH, // f - // address mode masks + // address mode masks AMM_NON = 1< allSections; std::vector externals; // external labels organized by object file MapSymbolArray map; + + // CPU target + struct mnem *opcode_table; + int opcode_count; // context for macros / include files ContextStack contextStack; @@ -819,6 +938,7 @@ public: int lastEvalValue; Reloc::Type lastEvalPart; + strref export_base_name; strref last_label; bool errorEncountered; bool list_assembly; @@ -889,7 +1009,7 @@ public: // Manage locals void MarkLabelLocal(strref label, bool scope_label = false); - void FlushLocalLabels(int scope_exit = -1); + StatusCode FlushLocalLabels(int scope_exit = -1); // Label pools LabelPool* GetLabelPool(strref pool_name); @@ -940,7 +1060,8 @@ public: char* LoadBinary(strref filename, size_t &size); // constructor - Asm() { Cleanup(); localLabels.reserve(256); loadedData.reserve(16); lateEval.reserve(64); } + Asm() : opcode_table(opcodes_6502), opcode_count(num_opcodes_6502) { + Cleanup(); localLabels.reserve(256); loadedData.reserve(16); lateEval.reserve(64); } }; // Clean up work allocations @@ -1554,14 +1675,12 @@ StatusCode Asm::BuildMacro(Macro &m, strref arg_list) macexp.replace(param, a); } contextStack.push(m.source_name, macexp.get_strref(), macexp.get_strref()); - FlushLocalLabels(); - return STATUS_OK; + return FlushLocalLabels(); } else return ERROR_OUT_OF_MEMORY_FOR_MACRO_EXPANSION; } contextStack.push(m.source_name, m.source_file, macro_src); - FlushLocalLabels(); - return STATUS_OK; + return FlushLocalLabels(); } @@ -2153,9 +2272,10 @@ StatusCode Asm::CheckLateEval(strref added_label, int scope_end) switch (i->type) { case LateEval::LET_BRANCH: value -= i->address+1; - if (value<-128 || value>127) + if (value<-128 || value>127) { + i = lateEval.erase(i); return ERROR_BRANCH_OUT_OF_RANGE; - if (trg >= allSections[sec].size()) + } if (trg >= allSections[sec].size()) return ERROR_SECTION_TARGET_OFFSET_OUT_OF_RANGE; allSections[sec].SetByte(trg, value); break; @@ -2288,8 +2408,9 @@ void Asm::MarkLabelLocal(strref label, bool scope_reserve) } // find all local labels or up to given scope level and remove them -void Asm::FlushLocalLabels(int scope_exit) +StatusCode Asm::FlushLocalLabels(int scope_exit) { + StatusCode status = STATUS_OK; // iterate from end of local label records and early out if the label scope is lower than the current. std::vector::iterator i = localLabels.end(); while (i!=localLabels.begin()) { @@ -2297,6 +2418,9 @@ void Asm::FlushLocalLabels(int scope_exit) if (i->scope_depth < scope_depth) break; strref label = i->label; + StatusCode this_status = CheckLateEval(label); + if (this_status>FIRST_ERROR) + status = this_status; if (!i->scope_reserve || i->scope_depth<=scope_exit) { unsigned int index = FindLabelIndex(label.fnv1a(), labels.getKeys(), labels.count()); while (index=FIRST_ERROR) + status = this_status; + } + return status; } // include symbols listed from a .sym file or all if no listing @@ -2822,6 +2952,7 @@ DirectiveName aDirectiveNames[] { { "DS", AD_DS }, // MERLIN { "LUP", AD_REPT }, // MERLIN { "MAC", AD_MACRO }, // MERLIN + { "SAV", AD_SAV }, // MERLIN }; static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]); @@ -3121,6 +3252,13 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc case AD_USR: line.clear(); break; + case AD_SAV: + line.trim_whitespace(); + if (line.has_prefix(export_base_name)) + line.skip(export_base_name.get_len()); + if (line) + CurrSection().export_append = line.split_label(); + break; case AD_TEXT: { // text: add text within quotes // for now just copy the windows ascii. TODO: Convert to petscii. // https://en.wikipedia.org/wiki/PETSCII @@ -3349,13 +3487,13 @@ int sortHashLookup(const void *A, const void *B) { return _A->op_hash > _B->op_hash ? 1 : -1; } -int BuildInstructionTable(OP_ID *pInstr, int maxInstructions) +int BuildInstructionTable(OP_ID *pInstr, int maxInstructions, struct mnem *opcodes, int count) { // create an instruction table (mnemonic hash lookup) int numInstructions = 0; - for (int i = 0; i < num_opcodes_6502; i++) { + for (int i = 0; i < count; i++) { OP_ID &op = pInstr[numInstructions++]; - op.op_hash = strref(opcodes_6502[i].instr).fnv1a_lower(); + op.op_hash = strref(opcodes[i].instr).fnv1a_lower(); op.index = i; op.type = OT_MNEMONIC; } @@ -3364,7 +3502,6 @@ int BuildInstructionTable(OP_ID *pInstr, int maxInstructions) for (int d=0; d=0 && value<0x100 && error != STATUS_RELATIVE_SECTION) { switch (addrMode) { case AMB_ABS: - if (opcodes_6502[index].modes & AMM_ZP) + if (validModes & AMM_ZP) addrMode = AMB_ZP; break; case AMB_ABS_X: - if (opcodes_6502[index].modes & AMM_ZP_X) + if (validModes & AMM_ZP_X) addrMode = AMB_ZP_X; break; default: @@ -3487,35 +3644,61 @@ StatusCode Asm::AddOpcode(strref line, int index, strref source_file) } } - CODE_ARG codeArg = CA_NONE; - unsigned char opcode = opcodes_6502[index].aCodes[addrMode]; + bool valid_addressing_mode = !!(validModes & (1 << addrMode)); - if (!(opcodes_6502[index].modes & (1 << addrMode))) - error = ERROR_INVALID_ADDRESSING_MODE; - else { - if (opcodes_6502[index].modes & AMM_BRANCH) + if (!valid_addressing_mode) { + if (addrMode==AMB_ZP_REL_X && (validModes & AMM_REL_X)) { + addrMode = AMB_REL_X; + valid_addressing_mode = true; + } else if (addrMode==AMB_REL && (validModes & AMM_ZP_REL)) { + addrMode = AMB_ZP_REL; + valid_addressing_mode = true; + } else + error = ERROR_INVALID_ADDRESSING_MODE; + } + + // Add the instruction and argument to the code + if (error == STATUS_OK || error == STATUS_RELATIVE_SECTION) { + unsigned char opcode = opcode_table[index].aCodes[addrMode]; + CheckOutputCapacity(4); + AddByte(opcode); + + CODE_ARG codeArg = CA_NONE; + if (validModes & AMM_BRANCH) codeArg = CA_BRANCH; else if (addrMode == AMB_ABS || addrMode == AMB_REL || addrMode == AMB_ABS_X || addrMode == AMB_ABS_Y) codeArg = CA_TWO_BYTES; + else if (addrMode == AMB_ZP_ABS) + codeArg = CA_BYTE_BRANCH; else if (addrMode != AMB_NON && addrMode != AMB_ACC) codeArg = CA_ONE_BYTE; - } - // Add the instruction and argument to the code - if (error == STATUS_OK || error == STATUS_RELATIVE_SECTION) { - CheckOutputCapacity(4); + switch (codeArg) { + case CA_BYTE_BRANCH: { + if (evalLater) + AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE); + else if (error == STATUS_RELATIVE_SECTION) { + CurrSection().AddReloc(target_section_offs, CurrSection().DataOffset(), target_section, + target_section_type == Reloc::HI_BYTE ? Reloc::HI_BYTE : Reloc::LO_BYTE); + } + AddByte(value); + struct EvalContext etx(CurrSection().GetPC()-2, scope_address[scope_depth], -1, SectionId()); + error = EvalExpression(line, etx, value); + if (error==STATUS_NOT_READY) + AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], line, source_file, LateEval::LET_BRANCH); + else if (((int)value - (int)CurrSection().GetPC() - 1) < -128 || ((int)value - (int)CurrSection().GetPC() - 1) > 127) + error = ERROR_BRANCH_OUT_OF_RANGE; + AddByte(error == STATUS_NOT_READY ? 0 : (unsigned char)((int)value - (int)CurrSection().GetPC()) - 1); + break; + } case CA_BRANCH: - AddByte(opcode); if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BRANCH); - else if (((int)value - (int)CurrSection().GetPC()-1) < -128 || ((int)value - (int)CurrSection().GetPC()-1) > 127) { + else if (((int)value - (int)CurrSection().GetPC()-1) < -128 || ((int)value - (int)CurrSection().GetPC()-1) > 127) error = ERROR_BRANCH_OUT_OF_RANGE; - break; - } AddByte(evalLater ? 0 : (unsigned char)((int)value - (int)CurrSection().GetPC()) - 1); break; case CA_ONE_BYTE: - AddByte(opcode); if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_BYTE); else if (error == STATUS_RELATIVE_SECTION) @@ -3524,7 +3707,6 @@ StatusCode Asm::AddOpcode(strref line, int index, strref source_file) AddByte(value); break; case CA_TWO_BYTES: - AddByte(opcode); if (evalLater) AddLateEval(CurrSection().DataOffset(), CurrSection().GetPC(), scope_address[scope_depth], expression, source_file, LateEval::LET_ABS_REF); else if (error == STATUS_RELATIVE_SECTION) { @@ -3535,7 +3717,6 @@ StatusCode Asm::AddOpcode(strref line, int index, strref source_file) AddWord(value); break; case CA_NONE: - AddByte(opcode); break; } } @@ -3780,10 +3961,10 @@ bool Asm::List(strref filename) unsigned char addrmode[256]; memset(mnemonic, 255, sizeof(mnemonic)); memset(addrmode, 255, sizeof(addrmode)); - for (int i = 0; i < num_opcodes_6502; i++) { + for (int i = 0; i < opcode_count; i++) { for (int j = 0; j < AMB_COUNT; j++) { - if (opcodes_6502[i].modes & (1 << j)) { - unsigned char op = opcodes_6502[i].aCodes[j]; + if (opcode_table[i].modes & (1 << j)) { + unsigned char op = opcode_table[i].aCodes[j]; mnemonic[op] = i; addrmode[op] = j; } @@ -3835,23 +4016,25 @@ bool Asm::List(strref filename) unsigned char *buf = si->output + lst.address; unsigned char op = mnemonic[*buf]; unsigned char am = addrmode[*buf]; - if (op != 255 && am != 255) { + if (op != 255 && am != 255 && am<(sizeof(aAddrModeFmt)/sizeof(aAddrModeFmt[0]))) { const char *fmt = aAddrModeFmt[am]; - if (opcodes_6502[op].modes & AMM_FLIPXY) { + if (opcode_table[op].modes & AMM_FLIPXY) { if (am == AMB_ZP_X) fmt = "%s $%02x,y"; else if (am == AMB_ABS_X) fmt = "%s $%04x,y"; } - if (opcodes_6502[op].modes & AMM_BRANCH) - out.sprintf_append(fmt, opcodes_6502[op].instr, (char)buf[1] + lst.address + si->start_address + 2); + if (opcode_table[op].modes & AMM_ZP_ABS) + out.sprintf_append(fmt, opcode_table[op].instr, buf[1], (char)buf[2] + lst.address + si->start_address + 3); + else if (opcode_table[op].modes & AMM_BRANCH) + out.sprintf_append(fmt, opcode_table[op].instr, (char)buf[1] + lst.address + si->start_address + 2); else if (am == AMB_NON || am == AMB_ACC) - out.sprintf_append(fmt, opcodes_6502[op].instr); - else if (am == AMB_ABS || am == AMB_ABS_X || am == AMB_ABS_Y || am == AMB_REL) - out.sprintf_append(fmt, opcodes_6502[op].instr, buf[1] | (buf[2] << 8)); + out.sprintf_append(fmt, opcode_table[op].instr); + else if (am == AMB_ABS || am == AMB_ABS_X || am == AMB_ABS_Y || am == AMB_REL || am == AMB_REL_X) + out.sprintf_append(fmt, opcode_table[op].instr, buf[1] | (buf[2] << 8)); else - out.sprintf_append(fmt, opcodes_6502[op].instr, buf[1]); + out.sprintf_append(fmt, opcode_table[op].instr, buf[1]); } } - out.append_to(' ', 30); + out.append_to(' ', 33); strref line = lst.code.get_skipped(lst.line_offs).get_line(); line.clip_trailing_whitespace(); strown<128> line_fix(line); @@ -3881,21 +4064,24 @@ bool Asm::AllOpcodes(strref filename) return false; opened = true; } - for (int i = 0; i < num_opcodes_6502; i++) { + for (int i = 0; i < opcode_count; i++) { for (int a = 0; a < AMB_COUNT; a++) { - if (opcodes_6502[i].modes & (1 << a)) { + if (opcode_table[i].modes & (1 << a)) { const char *fmt = aAddrModeFmt[a]; - if (opcodes_6502[i].modes & AMM_BRANCH) - fprintf(f, "%s *+%d", opcodes_6502[i].instr, 5); + fputs("\t", f); + if (opcode_table[i].modes & AMM_BRANCH) + fprintf(f, "%s *+%d", opcode_table[i].instr, 5); + else if (a==AMB_ZP_ABS) + fprintf(f, "%s $%02x,*+%d", opcode_table[i].instr, 0x23, 13); else { - if (opcodes_6502[i].modes & AMM_FLIPXY) { + if (opcode_table[i].modes & AMM_FLIPXY) { if (a == AMB_ZP_X) fmt = "%s $%02x,y"; else if (a == AMB_ABS_X) fmt = "%s $%04x,y"; } - if (a==AMB_ABS || a==AMB_ABS_X || a==AMB_ABS_Y || a==AMB_REL) - fprintf(f, fmt, opcodes_6502[i].instr, 0x2120); + if (a == AMB_ABS || a == AMB_ABS_X || a == AMB_ABS_Y || a == AMB_REL || a == AMB_REL_X) + fprintf(f, fmt, opcode_table[i].instr, 0x2120); else - fprintf(f, fmt, opcodes_6502[i].instr, 0x21, 0x20, 0x1f); + fprintf(f, fmt, opcode_table[i].instr, 0x21, 0x20, 0x1f); } fputs("\n", f); } @@ -3910,7 +4096,7 @@ bool Asm::AllOpcodes(strref filename) void Asm::Assemble(strref source, strref filename, bool obj_target) { OP_ID *pInstr = new OP_ID[256]; - int numInstructions = BuildInstructionTable(pInstr, 256); + int numInstructions = BuildInstructionTable(pInstr, 256, opcode_table, opcode_count); StatusCode error = STATUS_OK; contextStack.push(filename, source, source); @@ -4365,6 +4551,7 @@ int main(int argc, char **argv) const strref listing("lst"); const strref allinstr("opcodes"); const strref endmacro("endm"); + const strref cpu("cpu"); int return_value = 0; bool load_header = true; bool size_header = false; @@ -4409,6 +4596,15 @@ int main(int argc, char **argv) } else if (arg.has_prefix(allinstr) && (arg.get_len() == allinstr.get_len() || arg[allinstr.get_len()] == '=')) { gen_allinstr = true; allinstr_file = arg.after('='); + } else if (arg.has_prefix(cpu) && (arg.get_len() == cpu.get_len() || arg[cpu.get_len()] == '=')) { + arg.split_token_trim('='); + if (arg.same_str("6502")) { + assembler.opcode_table = opcodes_6502; + assembler.opcode_count = num_opcodes_6502; + } else if (arg.same_str("65c02")) { + assembler.opcode_table = opcodes_65C02; + assembler.opcode_count = num_opcodes_65C02; + } } else if (arg.same_str("sym") && (a + 1) < argc) sym_file = argv[++a]; else if (arg.same_str("obj") && (a + 1) < argc) @@ -4428,6 +4624,7 @@ int main(int argc, char **argv) " x65 filename.s code.prg [options]\n" " * -i(path) : Add include path\n" " * -D(label)[=] : Define a label with an optional value(otherwise defined as 1)\n" + " * -cpu=6502/65c02: assemble with opcodes for a different cpu\n" " * -obj(file.x65) : generate object file for later linking\n" " * -bin : Raw binary\n" " * -c64 : Include load address(default)\n" @@ -4446,6 +4643,9 @@ int main(int argc, char **argv) if (source_filename) { size_t size = 0; strref srcname(source_filename); + + assembler.export_base_name = strref(binary_out_name).after_last_or_full('/', '\\').before_or_full('.'); + if (char *buffer = assembler.LoadText(srcname, size)) { // if source_filename contains a path add that as a search path for include files assembler.AddIncludeFolder(srcname.before_last('/', '\\'));