diff --git a/Asm65/CpuDef.cs b/Asm65/CpuDef.cs index fc8c8d3..1d9ae8e 100644 --- a/Asm65/CpuDef.cs +++ b/Asm65/CpuDef.cs @@ -146,7 +146,8 @@ namespace Asm65 { /// Set to true if "undocumented" opcodes should /// be included in the definition. /// Best CpuDef. - public static CpuDef GetBestMatch(CpuType type, bool includeUndocumented) { + public static CpuDef GetBestMatch(CpuType type, bool includeUndocumented, + bool twoByteBrk) { // Many 65xx variants boil down to a 6502, 65C02, or 65816, at least as far as // a disassembler needs to know. These do not, and would need full definitions: // @@ -190,6 +191,16 @@ namespace Asm65 { cpuDef = stripped; } + // If we want two-byte BRKs, replace the entry with the StackInt form. Copy the rest. + if (twoByteBrk) { + CpuDef bigBrkDef = new CpuDef(cpuDef); + bigBrkDef.mOpDefs[0] = OpDef.OpBRK_StackInt; + for (int i = 1; i < 256; i++) { + bigBrkDef.mOpDefs[i] = cpuDef.mOpDefs[i]; + } + cpuDef = bigBrkDef; + } + cpuDef.HasUndocumented = includeUndocumented; return cpuDef; diff --git a/Asm65/Formatter.cs b/Asm65/Formatter.cs index f836dc3..e96e702 100644 --- a/Asm65/Formatter.cs +++ b/Asm65/Formatter.cs @@ -733,7 +733,7 @@ namespace Asm65 { case AddressMode.DP: case AddressMode.PCRel: case AddressMode.PCRelLong: // BRL - case AddressMode.StackInt: // COP + case AddressMode.StackInt: // COP and two-byte BRK case AddressMode.StackPCRelLong: // PER case AddressMode.WDM: fmt = wdisStr + "{0}"; diff --git a/Asm65/OpDef.cs b/Asm65/OpDef.cs index aa995f9..4cbd2ce 100644 --- a/Asm65/OpDef.cs +++ b/Asm65/OpDef.cs @@ -1478,15 +1478,16 @@ namespace Asm65 { AddrMode = AddressMode.Unknown }; - public static readonly OpDef OpBRK_Implied = new OpDef(OpBRK) { + public static readonly OpDef OpBRK_Implied = new OpDef(OpBRK) { // 1-byte form Opcode = 0x00, - // There should arguably be OpBRK_Implied for 6502/65C02 and OpBRK_StackInt for - // 65816, but in practice hardly any assemblers prefer (or even allow) it to be - // a two-byte instruction. The BRK does *act* like a two-byte instruction, but - // code rarely reflects this usage. AddrMode = AddressMode.Implied, CycDef = 7 | (int)(CycleMod.OneIfE0) }; + public static readonly OpDef OpBRK_StackInt = new OpDef(OpBRK) { // 2-byte form + Opcode = 0x00, + AddrMode = AddressMode.StackInt, + CycDef = 7 | (int)(CycleMod.OneIfE0) + }; public static readonly OpDef OpORA_DPIndexXInd = new OpDef(OpORA) { Opcode = 0x01, AddrMode = AddressMode.DPIndexXInd, diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 750a190..ff2b347 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -83,8 +83,9 @@ namespace PluginCommon { /// The file data is only guaranteed to hold the BRK opcode byte. /// /// Offset of the BRK instruction. + /// True if the CPU is configured for two-byte BRKs. /// Set to true if the BRK doesn't actually return. - void CheckBrk(int offset, out bool noContinue); + void CheckBrk(int offset, bool isTwoBytes, out bool noContinue); } /// diff --git a/PluginCommon/PlSymbol.cs b/PluginCommon/PlSymbol.cs index 33765c4..d3c96f4 100644 --- a/PluginCommon/PlSymbol.cs +++ b/PluginCommon/PlSymbol.cs @@ -123,7 +123,8 @@ namespace PluginCommon { dict.Add(ps.Value, ps); } catch (ArgumentException) { appRef.DebugLog("WARNING: GenerateValueList: multiple entries with " + - "value " + ps.Value.ToString("x4")); + "value " + ps.Value.ToString("x4") + ": " + dict[ps.Value].Label + + " and " + ps.Label); } } } diff --git a/PluginCommon/Util.cs b/PluginCommon/Util.cs index 42ada4e..d8f2161 100644 --- a/PluginCommon/Util.cs +++ b/PluginCommon/Util.cs @@ -58,5 +58,31 @@ namespace PluginCommon { public static uint ComputeBufferCRC(byte[] data) { return CRC32.OnBuffer(0, data, 0, data.Length); } + + /// + /// Formats the byte that follows a BRK instruction. How we do this depends on + /// whether the system is configured for two-byte BRKs. + /// + /// + /// We can actually apply the format both ways and let the app ignore the one it + /// doesn't like, but this is cleaner. + /// + /// Reference to application object. + /// True if BRKs are handled as two-byte instructions. + /// Offset of BRK instruction. + /// Data type to apply. + /// Data sub-type to apply. + /// Label, for subType=Symbol. + public static void FormatBrkByte(IApplication appRef, bool twoByteBrk, int brkOffset, + DataSubType subType, string label) { + if (twoByteBrk) { + // Two-byte BRK, so we want to apply the format to the instruction itself. + appRef.SetOperandFormat(brkOffset, subType, label); + } else { + // Single-byte BRK, so we want to format the byte that follows the + // instruction as inline data. + appRef.SetInlineDataFormat(brkOffset + 1, 1, DataType.NumericLE, subType, label); + } + } } } diff --git a/SourceGen/AsmGen/AsmAcme.cs b/SourceGen/AsmGen/AsmAcme.cs index 173d0f6..44aa8a0 100644 --- a/SourceGen/AsmGen/AsmAcme.cs +++ b/SourceGen/AsmGen/AsmAcme.cs @@ -314,8 +314,8 @@ namespace SourceGen.AsmGen { return null; } } - if (op == OpDef.OpWDM_WDM) { - // ACME doesn't like this to have an operand. Output as hex. + if (op == OpDef.OpWDM_WDM || op == OpDef.OpBRK_StackInt) { + // ACME doesn't like these to have an operand. Output as hex. return null; } return string.Empty; // indicate original is fine diff --git a/SourceGen/AsmGen/AsmCc65.cs b/SourceGen/AsmGen/AsmCc65.cs index f9ab61c..3965883 100644 --- a/SourceGen/AsmGen/AsmCc65.cs +++ b/SourceGen/AsmGen/AsmCc65.cs @@ -325,8 +325,9 @@ namespace SourceGen.AsmGen { // IGenerator public string ModifyOpcode(int offset, OpDef op) { - if ((op == OpDef.OpWDM_WDM) && mAsmVersion < V2_18) { + if (op == OpDef.OpBRK_StackInt || (op == OpDef.OpWDM_WDM && mAsmVersion < V2_18)) { // cc65 v2.17 doesn't support WDM, and assembles BRK to opcode $05. + // cc65 v2.18 only supports two-byte BRK on 65816 code. // https://github.com/cc65/cc65/issues/715 // https://github.com/cc65/cc65/issues/716 return null; diff --git a/SourceGen/CodeAnalysis.cs b/SourceGen/CodeAnalysis.cs index 7129a4d..4b98f6f 100644 --- a/SourceGen/CodeAnalysis.cs +++ b/SourceGen/CodeAnalysis.cs @@ -672,7 +672,7 @@ namespace SourceGen { // On first visit, check for BRK inline call. if (firstVisit) { - if (op == OpDef.OpBRK_Implied) { + if (op == OpDef.OpBRK_Implied || op == OpDef.OpBRK_StackInt) { bool noContinue = CheckForInlineCall(op, offset, !doContinue); if (!noContinue) { // We're expected to continue execution past the BRK. @@ -951,8 +951,10 @@ namespace SourceGen { } else if (op == OpDef.OpJSR_AbsLong && (mPluginCaps[i] & PluginCap.JSL) != 0) { ((IPlugin_InlineJsl)script).CheckJsl(offset, out bool noCont); noContinue |= noCont; - } else if (op == OpDef.OpBRK_Implied && (mPluginCaps[i] & PluginCap.BRK) != 0) { - ((IPlugin_InlineBrk)script).CheckBrk(offset, out bool noCont); + } else if ((op == OpDef.OpBRK_Implied || op == OpDef.OpBRK_StackInt) && + (mPluginCaps[i] & PluginCap.BRK) != 0) { + ((IPlugin_InlineBrk)script).CheckBrk(offset, op == OpDef.OpBRK_StackInt, + out bool noCont); noContinue &= noCont; } } catch (PluginException plex) { diff --git a/SourceGen/DisasmProject.cs b/SourceGen/DisasmProject.cs index 82080f7..e98442b 100644 --- a/SourceGen/DisasmProject.cs +++ b/SourceGen/DisasmProject.cs @@ -239,6 +239,7 @@ namespace SourceGen { // system definition. ProjectProps.CpuType = CpuDef.CpuType.Cpu65816; ProjectProps.IncludeUndocumentedInstr = false; + ProjectProps.TwoByteBrk = false; UpdateCpuDef(); } @@ -281,7 +282,8 @@ namespace SourceGen { public void ApplySystemDef(SystemDef sysDef) { CpuDef.CpuType cpuType = CpuDef.GetCpuTypeFromName(sysDef.Cpu); bool includeUndoc = SystemDefaults.GetUndocumentedOpcodes(sysDef); - CpuDef tmpDef = CpuDef.GetBestMatch(cpuType, includeUndoc); + bool twoByteBrk = SystemDefaults.GetTwoByteBrk(sysDef); + CpuDef tmpDef = CpuDef.GetBestMatch(cpuType, includeUndoc, twoByteBrk); // Store the best-matched CPU in properties, rather than whichever was originally // requested. This way the behavior of the project is the same for everyone, even @@ -289,6 +291,7 @@ namespace SourceGen { // originally-specified CPU. ProjectProps.CpuType = tmpDef.Type; ProjectProps.IncludeUndocumentedInstr = includeUndoc; + ProjectProps.TwoByteBrk = twoByteBrk; UpdateCpuDef(); ProjectProps.AnalysisParams.DefaultTextScanMode = @@ -325,7 +328,7 @@ namespace SourceGen { public void UpdateCpuDef() { CpuDef = CpuDef.GetBestMatch(ProjectProps.CpuType, - ProjectProps.IncludeUndocumentedInstr); + ProjectProps.IncludeUndocumentedInstr, ProjectProps.TwoByteBrk); } /// diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index 8f2590c..7aa0f9a 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -188,6 +188,7 @@ namespace SourceGen { public class SerProjectProperties { public string CpuName { get; set; } public bool IncludeUndocumentedInstr { get; set; } + public bool TwoByteBrk { get; set; } public int EntryFlags { get; set; } public string AutoLabelStyle { get; set; } public SerAnalysisParameters AnalysisParams { get; set; } @@ -199,6 +200,7 @@ namespace SourceGen { public SerProjectProperties(ProjectProperties props) { CpuName = Asm65.CpuDef.GetCpuNameFromType(props.CpuType); IncludeUndocumentedInstr = props.IncludeUndocumentedInstr; + TwoByteBrk = props.TwoByteBrk; EntryFlags = props.EntryFlags.AsInt; AutoLabelStyle = props.AutoLabelStyle.ToString(); AnalysisParams = new SerAnalysisParameters(props.AnalysisParams); @@ -495,6 +497,7 @@ namespace SourceGen { // Deserialize ProjectProperties: misc items. proj.ProjectProps.CpuType = Asm65.CpuDef.GetCpuTypeFromName(spf.ProjectProps.CpuName); proj.ProjectProps.IncludeUndocumentedInstr = spf.ProjectProps.IncludeUndocumentedInstr; + proj.ProjectProps.TwoByteBrk = spf.ProjectProps.TwoByteBrk; proj.ProjectProps.EntryFlags = Asm65.StatusFlags.FromInt(spf.ProjectProps.EntryFlags); if (Enum.TryParse(spf.ProjectProps.AutoLabelStyle, out AutoLabel.Style als)) { diff --git a/SourceGen/ProjectProperties.cs b/SourceGen/ProjectProperties.cs index 2a1f976..80556d9 100644 --- a/SourceGen/ProjectProperties.cs +++ b/SourceGen/ProjectProperties.cs @@ -80,6 +80,11 @@ namespace SourceGen { /// public bool IncludeUndocumentedInstr { get; set; } + /// + /// Should we treat BRK instructions as 2 bytes? + /// + public bool TwoByteBrk { get; set; } + /// /// Initial status flags at entry points. /// @@ -129,6 +134,7 @@ namespace SourceGen { public ProjectProperties(ProjectProperties src) : this() { CpuType = src.CpuType; IncludeUndocumentedInstr = src.IncludeUndocumentedInstr; + TwoByteBrk = src.TwoByteBrk; EntryFlags = src.EntryFlags; AutoLabelStyle = src.AutoLabelStyle; diff --git a/SourceGen/RuntimeData/Apple/SOS.cs b/SourceGen/RuntimeData/Apple/SOS.cs index 3804902..187892a 100644 --- a/SourceGen/RuntimeData/Apple/SOS.cs +++ b/SourceGen/RuntimeData/Apple/SOS.cs @@ -54,7 +54,7 @@ namespace RuntimeData.Apple { mFunctionList = PlSymbol.GeneratePlatformValueList(plSyms, SOS_MLI_TAG, appRef); } - public void CheckBrk(int offset, out bool noContinue) { + public void CheckBrk(int offset, bool twoByteBrk, out bool noContinue) { noContinue = true; if (offset + 4 >= mFileData.Length) { // ran off the end @@ -75,8 +75,7 @@ namespace RuntimeData.Apple { if (!mFunctionList.TryGetValue(req, out sym)) { return; } - mAppRef.SetInlineDataFormat(offset + 1, 1, DataType.NumericLE, - DataSubType.Symbol, sym.Label); + Util.FormatBrkByte(mAppRef, twoByteBrk, offset, DataSubType.Symbol, sym.Label); mAppRef.SetInlineDataFormat(offset + 2, 2, DataType.NumericLE, DataSubType.Address, null); diff --git a/SourceGen/RuntimeData/Apple/SOS.sym65 b/SourceGen/RuntimeData/Apple/SOS.sym65 index 219c475..e4bb56a 100644 --- a/SourceGen/RuntimeData/Apple/SOS.sym65 +++ b/SourceGen/RuntimeData/Apple/SOS.sym65 @@ -2,8 +2,9 @@ ; See the LICENSE.txt file for distribution terms (Apache 2.0). ; ; Source: SOS programmer's guide +; SOS Reference Manual, Volume 2 (Apple 1982) -*SYNOPSIS SOS constants. +*SYNOPSIS Apple /// Sophisticated Operating System constants. ; SOS MLI function codes. *TAG SOS-MLI-Functions @@ -15,11 +16,17 @@ SOS_GET_SEG_INFO = $43 SOS_GET_SEG_NUM = $44 SOS_RELEASE_SEG = $45 +SOS_SET_FENCE = $60 +SOS_GET_FENCE = $61 +SOS_SET_TIME = $62 +SOS_GET_TIME = $63 +SOS_GET_ANALOG = $64 +; SOS ref: "TERMINATE" +; SOS ref: "No errors are possible. This is an excellent call for beginners." SOS_QUIT = $65 SOS_READBLOCK = $80 SOS_WRITEBLOCK = $81 -SOS_GET_TIME = $82 SOS_D_STATUS = $82 SOS_D_CONTROL = $83 SOS_GET_DEV_NUM = $84 @@ -35,7 +42,9 @@ SOS_SET_PREFIX = $C6 SOS_GET_PREFIX = $C7 SOS_OPEN = $C8 SOS_NEWLINE = $C9 +; SOS ref: "READ" SOS_READFILE = $CA +; SOS ref: "WRITE" SOS_WRITEFILE = $CB SOS_CLOSE = $CC SOS_FLUSH = $CD diff --git a/SourceGen/RuntimeData/Help/codegen.html b/SourceGen/RuntimeData/Help/codegen.html index aa2c651..20b9f95 100644 --- a/SourceGen/RuntimeData/Help/codegen.html +++ b/SourceGen/RuntimeData/Help/codegen.html @@ -196,7 +196,7 @@ code, but also needs to know how to handle the corner cases.

outside bank zero cannot be assembled. SourceGen currently deals with this by outputting the entire file as a hex dump.
  • Undocumented opcode $AB (LAX #imm) generates an error.
  • -
  • WDM is not allowed to have an operand.
  • +
  • BRK and WDM are not allowed to have operands.
  • Quirks:

    @@ -231,6 +231,7 @@ code, but also needs to know how to handle the corner cases.

    Bugs: