// IGenerator public void OutputOrgDirective(int offset, int address) { // 64tass separates the "compile offset", which determines where the output fits // into the generated binary, and "program counter", which determines the code // the assembler generates. Since we need to explicitly specify every byte in // the output file, the compile offset isn't very useful. We want to set it once // before the first line of code, then leave it alone. // // Any subsequent ORG changes are made to the program counter, and take the form // of a pair of ops (.logical <addr> to open, .here to end). Omitting the .here // causes an error. if (offset == 0) { // Set the "compile offset" to the initial address. OutputLine("*", "=", SourceFormatter.FormatHexValue(Project.AddrMap.Get(0), 4), string.Empty); } else { if (mNeedHereOp) { OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(HERE_PSEUDO_OP), string.Empty, string.Empty); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.OrgDirective), SourceFormatter.FormatHexValue(address, 4), string.Empty); mNeedHereOp = true; } }
// IGenerator public void OutputAsmConfig() { CpuDef cpuDef = Project.CpuDef; string cpuStr; if (cpuDef.Type == CpuDef.CpuType.Cpu65816) { cpuStr = "65816"; } else if (cpuDef.Type == CpuDef.CpuType.Cpu65C02) { cpuStr = "65c02"; } else if (cpuDef.Type == CpuDef.CpuType.CpuW65C02) { cpuStr = "w65c02"; } else if (cpuDef.Type == CpuDef.CpuType.Cpu6502 && cpuDef.HasUndocumented) { cpuStr = "6510"; } else { cpuStr = "6502"; } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp("!cpu"), cpuStr, string.Empty); }
/// <summary> /// Outputs formatted data in an unformatted way, because the code generator couldn't /// figure out how to do something better. /// </summary> private void OutputNoJoy(int offset, int length, string labelStr, string commentStr) { byte[] data = Project.FileData; Debug.Assert(length > 0); Debug.Assert(offset >= 0 && offset < data.Length); bool singleValue = true; byte val = data[offset]; for (int i = 1; i < length; i++) { if (data[offset + i] != val) { singleValue = false; break; } } if (singleValue && length > 1) { string opcodeStr = SourceFormatter.FormatPseudoOp(sDataOpNames.Fill); string operandStr = length + "," + SourceFormatter.FormatHexValue(val, 2); OutputLine(labelStr, opcodeStr, operandStr, commentStr); } else { OutputDenseHex(offset, length, labelStr, commentStr); } }
// IGenerator public void OutputOrgDirective(int offset, int address) { // Linear search for offset. List should be small, so this should be quick. int index = 0; foreach (AddressMap.AddressMapEntry ame in Project.AddrMap) { if (ame.Offset == offset) { break; } index++; } mLineBuilder.Clear(); TextUtil.AppendPaddedString(mLineBuilder, ";", 0); // using +1 to make it look like the comment ';' shifted it over TextUtil.AppendPaddedString(mLineBuilder, SourceFormatter.FormatPseudoOp(".segment"), mColumnWidths[0] + 1); TextUtil.AppendPaddedString(mLineBuilder, string.Format("\"SEG{0:D3}\"", index), mColumnWidths[0] + mColumnWidths[1] + 1); OutputLine(mLineBuilder.ToString()); OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.OrgDirective), SourceFormatter.FormatHexValue(address, 4), string.Empty); }
// IGenerator public void OutputLocalVariableTable(int offset, List <DefSymbol> newDefs, LocalVariableTable allDefs) { foreach (DefSymbol defSym in newDefs) { string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); } }
// IGenerator public void OutputLocalVariableTable(int offset, List <DefSymbol> newDefs, LocalVariableTable allDefs) { foreach (DefSymbol defSym in newDefs) { // Use an operand length of 1 so values are shown as concisely as possible. string valueStr = PseudoOp.FormatNumericOperand(SourceFormatter, Project.SymbolTable, null, defSym.DataDescriptor, defSym.Value, 1, PseudoOp.FormatNumericOpFlags.None); OutputLine(SourceFormatter.FormatVariableLabel(defSym.Label), SourceFormatter.FormatPseudoOp(sDataOpNames.VarDirective), valueStr, SourceFormatter.FormatEolComment(defSym.Comment)); } }
// IGenerator public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) { if (prevM != newM) { string mop = (newM == 0) ? ".al" : ".as"; OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(mop), string.Empty, string.Empty); } if (prevX != newX) { string xop = (newX == 0) ? ".xl" : ".xs"; OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(xop), string.Empty, string.Empty); } }
// IGenerator public void OutputRegWidthDirective(int offset, int prevM, int prevX, int newM, int newX) { // prevM/prevX may be ambiguous for offset 0, but otherwise everything // should be either 0 or 1. Debug.Assert(newM == 0 || newM == 1); Debug.Assert(newX == 0 || newX == 1); if (offset == 0 && newM == 1 && newX == 1) { // Assembler defaults to short regs, so we can skip this. return; } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(REG_WIDTH_DIRECTIVE), "%" + newM + newX, string.Empty); }
// IGenerator public void FlushArDirectives() { // Output pending directives. There will always be something to do here unless // we were in "relative" mode. Debug.Assert(mNextAddress >= 0 || mIsInRelative); if (mNextAddress >= 0) { OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), SourceFormatter.FormatHexValue(mNextAddress, 4), string.Empty); } mNextAddress = -1; mIsInRelative = false; }
// IGenerator public void OutputAsmConfig() { CpuDef cpuDef = Project.CpuDef; string cpuStr; if (cpuDef.Type == CpuDef.CpuType.Cpu65816) { cpuStr = "65816"; } else if (cpuDef.Type == CpuDef.CpuType.Cpu65C02) { cpuStr = "65c02"; } else if (cpuDef.Type == CpuDef.CpuType.CpuW65C02) { cpuStr = "w65c02"; } else if (cpuDef.Type == CpuDef.CpuType.Cpu6502 && cpuDef.HasUndocumented) { cpuStr = "6502i"; } else { cpuStr = "6502"; } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(".cpu"), '\"' + cpuStr + '\"', string.Empty); // C64 PETSCII and C64 screen codes are built in. Define ASCII if we also // need that. mCurrentEncoding = CharEncoding.Encoding.C64Petscii; CheckAsciiFormats(out bool hasAscii, out bool hasHighAscii); if (hasHighAscii) { OutputLine(string.Empty, ".enc", '"' + HIGH_ASCII_ENC_NAME + '"', string.Empty); OutputLine(string.Empty, ".cdef", "$20,$7e,$a0", string.Empty); mCurrentEncoding = CharEncoding.Encoding.HighAscii; } if (hasAscii) { OutputLine(string.Empty, ".enc", '"' + ASCII_ENC_NAME + '"', string.Empty); OutputLine(string.Empty, ".cdef", "$20,$7e,$20", string.Empty); mCurrentEncoding = CharEncoding.Encoding.Ascii; } }
// IGenerator public void OutputEquDirective(string name, string valueStr, string comment) { OutputLine(name, SourceFormatter.FormatPseudoOp(sDataOpNames.EquDirective), valueStr, SourceFormatter.FormatEolComment(comment)); }
// IGenerator public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) { // This is similar in operation to the AsmTass64 implementation. See comments there. Debug.Assert(mPcDepth >= 0); int nextAddress = change.Address; if (nextAddress == Address.NON_ADDR) { // Start non-addressable regions at zero to ensure they don't overflow bank. nextAddress = 0; } if (change.IsStart) { if (change.Region.HasValidPreLabel) { string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel); OutputLine(labelStr, string.Empty, string.Empty, string.Empty); } if (mPcDepth == 0 && mFirstIsOpen) { mPcDepth++; // Set the "real" PC for the first address change. If we're in "loadable" // mode, just set "*=". If we're in "streaming" mode, we set "*=" to zero // and then use a pseudo-PC. if (mOutputMode == OutputMode.Loadable) { OutputLine("*", "=", SourceFormatter.FormatHexValue(nextAddress, 4), string.Empty); return; } else { // set the real PC to address zero to ensure we get a full 64KB OutputLine("*", "=", SourceFormatter.FormatHexValue(0, 4), string.Empty); } } AddressMap.AddressRegion region = change.Region; string addrStr; if (region.HasValidIsRelative) { int diff = nextAddress - region.PreLabelAddress; string pfxStr; if (diff >= 0) { pfxStr = "*+"; } else { pfxStr = "*-"; diff = -diff; } addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4); } else { addrStr = SourceFormatter.FormatHexValue(nextAddress, 4); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), addrStr + " {", string.Empty); mPcDepth++; } else { mPcDepth--; if (mPcDepth > 0 || !mFirstIsOpen) { // close previous block OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArEndDirective), string.Empty, string.Empty); //";" + SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective)); } else { // mark initial "*=" region as closed, but don't output anything mFirstIsOpen = false; } } }
// IGenerator public void OutputOrgDirective(int address) { OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.OrgDirective), SourceFormatter.FormatHexValue(address, 4), string.Empty); }
// IGenerator public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) { // 64tass separates the "compile offset", which determines where the output fits // into the generated binary, and "program counter", which determines the code // the assembler generates. Since we need to explicitly specify every byte in // the output file, having a distinct compile offset isn't useful here. We want // to set it once before the first line of code, then leave it alone. // // Any subsequent ORG changes are made to the program counter, and take the form // of a pair of ops (".logical <addr>" to open, ".here" to end). Omitting the .here // causes an error. // // If this is a "streamable" file, meaning it won't actually load into 64K of RAM // without wrapping around, then we skip the "* = addr" (same as "* = 0") and just // start with ".logical" segments. // // The assembler's approach is best represented by having an address region that // spans the entire file, with one or more "logical" regions inside. In practice // (especially for multi-bank 65816 code) that may not be the case, but the // assembler is still expecting us to start with a "* =" and then fit everything // inside that. So we treat the first region specially, whether or not it wraps // the rest of the file. Debug.Assert(mPcDepth >= 0); int nextAddress = change.Address; if (nextAddress == Address.NON_ADDR) { // Start non-addressable regions at zero to ensure they don't overflow bank. nextAddress = 0; } if (change.IsStart) { if (change.Region.HasValidPreLabel) { string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel); OutputLine(labelStr, string.Empty, string.Empty, string.Empty); } if (mPcDepth == 0 && mFirstIsOpen) { mPcDepth++; // Set the "real" PC for the first address change. If we're in "loadable" // mode, just set "*=". If we're in "streaming" mode, we set "*=" to zero // and then use a pseudo-PC. if (mOutputMode == OutputMode.Loadable) { OutputLine("*", "=", SourceFormatter.FormatHexValue(nextAddress, 4), string.Empty); return; } else { // Set the real PC to address zero to ensure we get a full 64KB. The // assembler assumes this as a default, so it can be omitted. //OutputLine("*", "=", SourceFormatter.FormatHexValue(0, 4), string.Empty); } } AddressMap.AddressRegion region = change.Region; string addrStr; if (region.HasValidIsRelative) { int diff = nextAddress - region.PreLabelAddress; string pfxStr; if (diff >= 0) { pfxStr = "*+"; } else { pfxStr = "*-"; diff = -diff; } addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4); } else { addrStr = SourceFormatter.FormatHexValue(nextAddress, 4); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), addrStr, string.Empty); mPcDepth++; } else { mPcDepth--; if (mPcDepth > 0 || !mFirstIsOpen) { // close previous block OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArEndDirective), string.Empty, string.Empty); } else { // mark initial "*=" region as closed, but don't output anything mFirstIsOpen = false; } } }
// IGenerator public void OutputArDirective(CommonUtil.AddressMap.AddressChange change) { int nextAddress = change.Address; if (nextAddress == Address.NON_ADDR) { // Start non-addressable regions at zero to ensure they don't overflow bank. nextAddress = 0; } if (change.IsStart) { AddressMap.AddressRegion region = change.Region; if (region.HasValidPreLabel || region.HasValidIsRelative) { // Need to output the previous ORG, if one is pending. if (mNextAddress >= 0) { OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), SourceFormatter.FormatHexValue(mNextAddress, 4), string.Empty); } } if (region.HasValidPreLabel) { string labelStr = mLocalizer.ConvLabel(change.Region.PreLabel); OutputLine(labelStr, string.Empty, string.Empty, string.Empty); } if (region.HasValidIsRelative) { // Found a valid IsRelative. Switch to "relative mode" if not there already. mIsInRelative = true; } if (mIsInRelative) { // Once we see a region with IsRelative set, we output regions as we // find them until the next Flush. string addrStr; if (region.HasValidIsRelative) { int diff = nextAddress - region.PreLabelAddress; string pfxStr; if (diff >= 0) { pfxStr = "*+"; } else { pfxStr = "*-"; diff = -diff; } addrStr = pfxStr + SourceFormatter.FormatHexValue(diff, 4); } else { addrStr = SourceFormatter.FormatHexValue(nextAddress, 4); } OutputLine(string.Empty, SourceFormatter.FormatPseudoOp(sDataOpNames.ArStartDirective), addrStr, string.Empty); mNextAddress = -1; return; } } mNextAddress = nextAddress; }