Example #1
0
        private static void GenerateInstruction(IGenerator gen, StreamWriter sw,
                                                LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles)
        {
            DisasmProject proj      = gen.Project;
            Formatter     formatter = gen.SourceFormatter;

            byte[]   data = proj.FileData;
            Anattrib attr = proj.GetAnattrib(offset);

            string labelStr = string.Empty;

            if (attr.Symbol != null)
            {
                labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label);
            }

            OpDef op       = proj.CpuDef.GetOpDef(data[offset]);
            int   operand  = op.GetOperand(data, offset, attr.StatusFlags);
            int   instrLen = op.GetLength(attr.StatusFlags);

            OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
            if (op.IsWidthPotentiallyAmbiguous)
            {
                wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
            }
            if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None &&
                (op.AddrMode == OpDef.AddressMode.DP ||
                 op.AddrMode == OpDef.AddressMode.DPIndexX) ||
                op.AddrMode == OpDef.AddressMode.DPIndexY)
            {
                // Could be a forward reference to a direct-page label.  For ACME, we don't
                // care if it's forward or not.
                if ((gen.Quirks.SinglePassNoLabelCorrection && IsLabelReference(gen, offset)) ||
                    IsForwardLabelReference(gen, offset))
                {
                    wdis = OpDef.WidthDisambiguation.ForceDirect;
                }
            }

            string opcodeStr = formatter.FormatOpcode(op, wdis);

            string formattedOperand = null;
            int    operandLen       = instrLen - 1;

            PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix;
            bool isPcRelBankWrap = false;

            // Tweak branch instructions.  We want to show the absolute address rather
            // than the relative offset (which happens with the OperandAddress assignment
            // below), and 1-byte branches should always appear as a 4-byte hex value.
            if (op.AddrMode == OpDef.AddressMode.PCRel)
            {
                Debug.Assert(attr.OperandAddress >= 0);
                operandLen = 2;
                opFlags   |= PseudoOp.FormatNumericOpFlags.IsPcRel;
            }
            else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
                     op.AddrMode == OpDef.AddressMode.StackPCRelLong)
            {
                opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel;
            }
            else if (op.AddrMode == OpDef.AddressMode.Imm ||
                     op.AddrMode == OpDef.AddressMode.ImmLongA ||
                     op.AddrMode == OpDef.AddressMode.ImmLongXY)
            {
                opFlags |= PseudoOp.FormatNumericOpFlags.HasHashPrefix;
            }
            if ((opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) != 0)
            {
                int branchDist = attr.Address - attr.OperandAddress;
                isPcRelBankWrap = branchDist > 32767 || branchDist < -32768;
            }

            // 16-bit operands outside bank 0 need to include the bank when computing
            // symbol adjustment.
            int operandForSymbol = operand;

            if (attr.OperandAddress >= 0)
            {
                operandForSymbol = attr.OperandAddress;
            }

            // Check Length to watch for bogus descriptors.  (ApplyFormatDescriptors() should
            // now be screening bad descriptors out, so we may not need the Length test.)
            if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length)
            {
                FormatDescriptor dfd = gen.ModifyInstructionOperandFormat(offset,
                                                                          attr.DataDescriptor, operand);

                // Format operand as directed.
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    // Special handling for the double-operand block move.
                    string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                  gen.Localizer.LabelMap, dfd, operand >> 8, 1,
                                                                  PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
                    string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                  gen.Localizer.LabelMap, dfd, operand & 0xff, 1,
                                                                  PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
                    if (gen.Quirks.BlockMoveArgsReversed)
                    {
                        string tmp = opstr1;
                        opstr1 = opstr2;
                        opstr2 = tmp;
                    }
                    string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
                    formattedOperand = hash + opstr1 + "," + hash + opstr2;
                }
                else
                {
                    if (attr.DataDescriptor.IsStringOrCharacter)
                    {
                        gen.UpdateCharacterEncoding(dfd);
                    }
                    formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                     lvLookup, gen.Localizer.LabelMap, dfd,
                                                                     offset, operandForSymbol, operandLen, opFlags);
                }
            }
            else
            {
                // Show operand value in hex.
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    int arg1, arg2;
                    if (gen.Quirks.BlockMoveArgsReversed)
                    {
                        arg1 = operand & 0xff;
                        arg2 = operand >> 8;
                    }
                    else
                    {
                        arg1 = operand >> 8;
                        arg2 = operand & 0xff;
                    }
                    string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
                    formattedOperand = hash + formatter.FormatHexValue(arg1, 2) + "," +
                                       hash + formatter.FormatHexValue(arg2, 2);
                }
                else
                {
                    if (operandLen == 2)
                    {
                        // This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
                        // when outside bank zero.  The bank is included in the operand address,
                        // but we don't want to show it here.
                        operandForSymbol &= 0xffff;
                    }
                    formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
                }
            }
            string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);

            if (gen.Quirks.StackIntOperandIsImmediate && op.AddrMode == OpDef.AddressMode.StackInt)
            {
                // COP $02 is standard, but some require COP #$02
                operandStr = '#' + operandStr;
            }

            string eolComment = proj.Comments[offset];

            if (doAddCycles)
            {
                bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
                int  cycles      = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
                                                         branchCross);
                if (cycles > 0)
                {
                    if (!string.IsNullOrEmpty(eolComment))
                    {
                        eolComment = cycles.ToString() + "  " + eolComment;
                    }
                    else
                    {
                        eolComment = cycles.ToString();
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(eolComment))
                    {
                        eolComment = (-cycles).ToString() + "+ " + eolComment;
                    }
                    else
                    {
                        eolComment = (-cycles).ToString() + "+";
                    }
                }
            }
            string commentStr = formatter.FormatEolComment(eolComment);

            string replMnemonic = gen.ModifyOpcode(offset, op);

            if (attr.Length != instrBytes)
            {
                // This instruction has another instruction inside it.  Throw out what we
                // computed and just output as bytes.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap)
            {
                // Some assemblers have trouble generating PC-relative operands that wrap
                // around the bank.  Output as raw hex.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (op.AddrMode == OpDef.AddressMode.BlockMove &&
                     gen.Quirks.BlockMoveArgsReversed)
            {
                // On second thought, just don't even output the wrong thing.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (replMnemonic == null)
            {
                // No mnemonic exists for this opcode.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (replMnemonic != string.Empty)
            {
                // A replacement mnemonic has been provided.
                opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis);
            }
            gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr);

            // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the
            // wrong thing if we're in emulation mode.  Force flags back to short.
            if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm)
            {
                if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1)
                {
                    gen.OutputRegWidthDirective(offset, 0, 0, 1, 1);
                }
            }
        }
Example #2
0
        private void UpdateControls()
        {
            CpuItem item = (CpuItem)cpuSelectionComboBox.SelectedItem;

            if (item == null)
            {
                // initializing
                return;
            }

            // Push current choice to settings.
            AppSettings.Global.SetEnum(AppSettings.INSTCH_MODE, typeof(CpuDef.CpuType),
                                       (int)item.Type);
            AppSettings.Global.SetBool(AppSettings.INSTCH_SHOW_UNDOC, mShowUndocumented);

            // Populate the items source.
            InstructionItems.Clear();
            CpuDef cpuDef = CpuDef.GetBestMatch(item.Type, true, false);

            for (int opc = 0; opc < 256; opc++)
            {
                OpDef op = cpuDef[opc];
                if (!mShowUndocumented && op.IsUndocumented)
                {
                    continue;
                }

                int    opLen       = op.GetLength(StatusFlags.AllIndeterminate);
                string sampleValue = "$12";
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    sampleValue = "#$12,#$34";
                }
                else if (opLen == 3)
                {
                    sampleValue = "$1234";
                }
                else if (opLen == 4)
                {
                    sampleValue = "$123456";
                }
                string instrSample = mFormatter.FormatMnemonic(op.Mnemonic,
                                                               OpDef.WidthDisambiguation.None) + " " +
                                     mFormatter.FormatOperand(op, sampleValue, OpDef.WidthDisambiguation.None);


                StringBuilder     flags         = new StringBuilder(8);
                const string      FLAGS         = "NVMXDIZC";
                Asm65.StatusFlags affectedFlags = op.FlagsAffected;
                for (int fl = 0; fl < 8; fl++)
                {
                    if (affectedFlags.GetBit((StatusFlags.FlagBits)(7 - fl)) >= 0)
                    {
                        flags.Append(FLAGS[fl]);
                    }
                    else
                    {
                        flags.Append("-");
                    }
                }

                string         cycles = op.Cycles.ToString();
                OpDef.CycleMod mods   = cpuDef.GetOpCycleMod(opc);
                if (mods != 0)
                {
                    cycles += '+';
                }

                InstructionItems.Add(new InstructionItem(mFormatter.FormatHexValue(opc, 2),
                                                         instrSample, flags.ToString(), cycles,
                                                         mOpDesc.GetShortDescription(op.Mnemonic),
                                                         mOpDesc.GetAddressModeDescription(op.AddrMode),
                                                         op.IsUndocumented));
            }
        }
Example #3
0
        private static void GenerateInstruction(IGenerator gen, StreamWriter sw, int offset,
                                                int instrBytes, bool doAddCycles)
        {
            DisasmProject proj      = gen.Project;
            Formatter     formatter = gen.SourceFormatter;

            byte[]   data = proj.FileData;
            Anattrib attr = proj.GetAnattrib(offset);

            string labelStr = string.Empty;

            if (attr.Symbol != null)
            {
                labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label);
            }

            OpDef op       = proj.CpuDef.GetOpDef(data[offset]);
            int   operand  = op.GetOperand(data, offset, attr.StatusFlags);
            int   instrLen = op.GetLength(attr.StatusFlags);

            OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
            if (op.IsWidthPotentiallyAmbiguous)
            {
                wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
            }

            string replMnemonic = gen.ReplaceMnemonic(op);
            string opcodeStr    = formatter.FormatOpcode(op, wdis);

            string formattedOperand = null;
            int    operandLen       = instrLen - 1;
            bool   isPcRel          = false;
            bool   isPcRelBankWrap  = false;

            // Tweak branch instructions.  We want to show the absolute address rather
            // than the relative offset (which happens with the OperandAddress assignment
            // below), and 1-byte branches should always appear as a 4-byte hex value.
            if (op.AddrMode == OpDef.AddressMode.PCRel)
            {
                Debug.Assert(attr.OperandAddress >= 0);
                operandLen = 2;
                isPcRel    = true;
            }
            else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
                     op.AddrMode == OpDef.AddressMode.StackPCRelLong)
            {
                isPcRel = true;
            }
            if (isPcRel)
            {
                int branchDist = attr.Address - attr.OperandAddress;
                isPcRelBankWrap = branchDist > 32767 || branchDist < -32768;
            }

            // 16-bit operands outside bank 0 need to include the bank when computing
            // symbol adjustment.
            int operandForSymbol = operand;

            if (attr.OperandAddress >= 0)
            {
                operandForSymbol = attr.OperandAddress;
            }

            // Check Length to watch for bogus descriptors (?)
            if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length)
            {
                // Format operand as directed.
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    // Special handling for the double-operand block move.
                    string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                  gen.Localizer.LabelMap, attr.DataDescriptor, operand >> 8, 1, false);
                    string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                  gen.Localizer.LabelMap, attr.DataDescriptor, operand & 0xff, 1, false);
                    if (gen.Quirks.BlockMoveArgsReversed)
                    {
                        string tmp = opstr1;
                        opstr1 = opstr2;
                        opstr2 = tmp;
                    }
                    formattedOperand = opstr1 + "," + opstr2;
                }
                else
                {
                    formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                     gen.Localizer.LabelMap, attr.DataDescriptor,
                                                                     operandForSymbol, operandLen, isPcRel);
                }
            }
            else
            {
                // Show operand value in hex.
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    int arg1, arg2;
                    if (gen.Quirks.BlockMoveArgsReversed)
                    {
                        arg1 = operand & 0xff;
                        arg2 = operand >> 8;
                    }
                    else
                    {
                        arg1 = operand >> 8;
                        arg2 = operand & 0xff;
                    }
                    formattedOperand = formatter.FormatHexValue(arg1, 2) + "," +
                                       formatter.FormatHexValue(arg2, 2);
                }
                else
                {
                    if (operandLen == 2)
                    {
                        // This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
                        // when outside bank zero.  The bank is included in the operand address,
                        // but we don't want to show it here.
                        operandForSymbol &= 0xffff;
                    }
                    formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
                }
            }
            string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);

            string eolComment = proj.Comments[offset];

            if (doAddCycles)
            {
                bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
                int  cycles      = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
                                                         branchCross);
                if (cycles > 0)
                {
                    eolComment = cycles.ToString() + "  " + eolComment;
                }
                else
                {
                    eolComment = (-cycles).ToString() + "+ " + eolComment;
                }
            }
            string commentStr = formatter.FormatEolComment(eolComment);

            if (attr.Length != instrBytes)
            {
                // This instruction has another instruction inside it.  Throw out what we
                // computed and just output as bytes.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap)
            {
                // Some assemblers have trouble generating PC-relative operands that wrap
                // around the bank.  Output as raw hex.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (op.AddrMode == OpDef.AddressMode.BlockMove &&
                     gen.Quirks.BlockMoveArgsReversed)
            {
                // On second thought, just don't even output the wrong thing.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (replMnemonic == null)
            {
                // No mnemonic exists for this opcode.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (replMnemonic != string.Empty)
            {
                // A replacement mnemonic has been provided.
                opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis);
            }
            gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr);

            // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the
            // wrong thing if we're in emulation mode.  Force flags back to short.
            if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm)
            {
                if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1)
                {
                    gen.OutputRegWidthDirective(offset, 0, 0, 1, 1);
                }
            }
        }
Example #4
0
        private static void GenerateInstruction(IGenerator gen, StreamWriter sw,
                                                LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles)
        {
            DisasmProject proj      = gen.Project;
            Formatter     formatter = gen.SourceFormatter;

            byte[]   data = proj.FileData;
            Anattrib attr = proj.GetAnattrib(offset);

            string labelStr = string.Empty;

            if (attr.Symbol != null)
            {
                labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label);
            }

            OpDef op       = proj.CpuDef.GetOpDef(data[offset]);
            int   operand  = op.GetOperand(data, offset, attr.StatusFlags);
            int   instrLen = op.GetLength(attr.StatusFlags);

            OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None;
            if (op.IsWidthPotentiallyAmbiguous)
            {
                wdis = OpDef.GetWidthDisambiguation(instrLen, operand);
            }
            if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None &&
                (op.AddrMode == OpDef.AddressMode.DP ||
                 op.AddrMode == OpDef.AddressMode.DPIndexX) ||
                op.AddrMode == OpDef.AddressMode.DPIndexY)
            {
                // Could be a forward reference to a direct-page label.  For ACME, we don't
                // care if it's forward or not.
                if ((gen.Quirks.SinglePassNoLabelCorrection && IsLabelReference(gen, offset)) ||
                    IsForwardLabelReference(gen, offset))
                {
                    wdis = OpDef.WidthDisambiguation.ForceDirect;
                }
            }
            if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe &&
                gen.Quirks.SinglePassAssembler &&
                IsForwardLabelReference(gen, offset))
            {
                // Assemblers like cc65 can't tell if a symbol reference is Absolute or
                // Long if they haven't seen the symbol yet.  Irrelevant for ACME, which
                // doesn't currently handle 65816 outside bank 0.
                wdis = OpDef.WidthDisambiguation.ForceLong;
            }

            string opcodeStr = formatter.FormatOpcode(op, wdis);

            string formattedOperand = null;
            int    operandLen       = instrLen - 1;

            PseudoOp.FormatNumericOpFlags opFlags =
                PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix;
            bool isPcRelBankWrap = false;

            // Tweak branch instructions.  We want to show the absolute address rather
            // than the relative offset (which happens with the OperandAddress assignment
            // below), and 1-byte branches should always appear as a 4-byte hex value.
            // Unless we're outside bank 0 on 65816, in which case most assemblers require
            // them to be 6-byte hex values.
            if (op.AddrMode == OpDef.AddressMode.PCRel ||
                op.AddrMode == OpDef.AddressMode.DPPCRel)
            {
                Debug.Assert(attr.OperandAddress >= 0);
                operandLen = 2;
                opFlags   |= PseudoOp.FormatNumericOpFlags.IsPcRel;
            }
            else if (op.AddrMode == OpDef.AddressMode.PCRelLong ||
                     op.AddrMode == OpDef.AddressMode.StackPCRelLong)
            {
                opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel;
            }
            else if (op.AddrMode == OpDef.AddressMode.Imm ||
                     op.AddrMode == OpDef.AddressMode.ImmLongA ||
                     op.AddrMode == OpDef.AddressMode.ImmLongXY)
            {
                opFlags |= PseudoOp.FormatNumericOpFlags.HasHashPrefix;
            }
            if ((opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) != 0)
            {
                int branchDist = attr.Address - attr.OperandAddress;
                isPcRelBankWrap = branchDist > 32767 || branchDist < -32768;
            }
            if (op.IsAbsolutePBR)
            {
                opFlags |= PseudoOp.FormatNumericOpFlags.IsAbsolutePBR;
            }
            if (gen.Quirks.BankZeroAbsPBRRestrict)
            {
                // Hack to avoid having to define a new FormatConfig.ExpressionMode for 64tass.
                // Get rid of this 64tass gets its own exp mode.
                opFlags |= PseudoOp.FormatNumericOpFlags.Is64Tass;
            }

            // 16-bit operands outside bank 0 need to include the bank when computing
            // symbol adjustment.
            int operandForSymbol = operand;

            if (attr.OperandAddress >= 0)
            {
                operandForSymbol = attr.OperandAddress;
            }

            // Check Length to watch for bogus descriptors.  (ApplyFormatDescriptors() should
            // now be screening bad descriptors out, so we may not need the Length test.)
            if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length)
            {
                FormatDescriptor dfd = gen.ModifyInstructionOperandFormat(offset,
                                                                          attr.DataDescriptor, operand);

                // Format operand as directed.
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    // Special handling for the double-operand block move.
                    string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                  gen.Localizer.LabelMap, dfd, operand >> 8, 1,
                                                                  PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
                    string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                  gen.Localizer.LabelMap, dfd, operand & 0xff, 1,
                                                                  PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix);
                    if (gen.Quirks.BlockMoveArgsReversed)
                    {
                        string tmp = opstr1;
                        opstr1 = opstr2;
                        opstr2 = tmp;
                    }
                    string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
                    formattedOperand = hash + opstr1 + "," + hash + opstr2;
                }
                else if (op.AddrMode == OpDef.AddressMode.DPPCRel)
                {
                    // Special handling for double-operand BBR/BBS.  The instruction generally
                    // behaves like a branch, so format that first.
                    string branchStr = PseudoOp.FormatNumericOperand(formatter,
                                                                     proj.SymbolTable, gen.Localizer.LabelMap, dfd,
                                                                     operandForSymbol, operandLen, opFlags);
                    string dpStr = formatter.FormatHexValue(operand & 0xff, 2);
                    formattedOperand = dpStr + "," + branchStr;
                }
                else
                {
                    if (attr.DataDescriptor.IsStringOrCharacter)
                    {
                        gen.UpdateCharacterEncoding(dfd);
                    }
                    formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable,
                                                                     lvLookup, gen.Localizer.LabelMap, dfd,
                                                                     offset, operandForSymbol, operandLen, opFlags);
                }
            }
            else
            {
                // Show operand value in hex.
                if (op.AddrMode == OpDef.AddressMode.BlockMove)
                {
                    int arg1, arg2;
                    if (gen.Quirks.BlockMoveArgsReversed)
                    {
                        arg1 = operand & 0xff;
                        arg2 = operand >> 8;
                    }
                    else
                    {
                        arg1 = operand >> 8;
                        arg2 = operand & 0xff;
                    }
                    string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#";
                    formattedOperand = hash + formatter.FormatHexValue(arg1, 2) + "," +
                                       hash + formatter.FormatHexValue(arg2, 2);
                }
                else if (op.AddrMode == OpDef.AddressMode.DPPCRel)
                {
                    formattedOperand = formatter.FormatHexValue(operand & 0xff, 2) + "," +
                                       formatter.FormatHexValue(operandForSymbol, operandLen * 2);
                }
                else
                {
                    if (operandLen == 2 && !(op.IsAbsolutePBR && gen.Quirks.Need24BitsForAbsPBR) &&
                        (opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) == 0)
                    {
                        // This is necessary for 16-bit operands, like "LDA abs" and "PEA val",
                        // when outside bank zero.  The bank is included in the operand address,
                        // but we don't want to show it here.  We may need it for JSR/JMP though,
                        // and the bank is required for relative branch instructions.
                        operandForSymbol &= 0xffff;
                    }
                    formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2);
                }
            }
            string operandStr = formatter.FormatOperand(op, formattedOperand, wdis);

            if (gen.Quirks.StackIntOperandIsImmediate &&
                op.AddrMode == OpDef.AddressMode.StackInt)
            {
                // COP $02 is standard, but some require COP #$02
                operandStr = '#' + operandStr;
            }

            // The BBR/BBS/RMB/SMB instructions include a bit index (0-7).  The standard way is
            // to make it part of the mnemonic, but some assemblers make it an argument.
            if (gen.Quirks.BitNumberIsArg && op.IsNumberedBitOp)
            {
                // Easy way: do some string manipulation.
                char bitIndex = opcodeStr[opcodeStr.Length - 1];
                opcodeStr  = opcodeStr.Substring(0, opcodeStr.Length - 1);
                operandStr = bitIndex.ToString() + "," + operandStr;
            }

            string eolComment = proj.Comments[offset];

            if (doAddCycles)
            {
                bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00);
                int  cycles      = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken,
                                                         branchCross);
                if (cycles > 0)
                {
                    if (!string.IsNullOrEmpty(eolComment))
                    {
                        eolComment = cycles.ToString() + "  " + eolComment;
                    }
                    else
                    {
                        eolComment = cycles.ToString();
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(eolComment))
                    {
                        eolComment = (-cycles).ToString() + "+ " + eolComment;
                    }
                    else
                    {
                        eolComment = (-cycles).ToString() + "+";
                    }
                }
            }
            string commentStr = formatter.FormatEolComment(eolComment);

            string replMnemonic = gen.ModifyOpcode(offset, op);

            if (attr.Length != instrBytes)
            {
                // This instruction has another instruction inside it.  Throw out what we
                // computed and just output as bytes.
                // TODO: in some odd situations we can split something that doesn't need
                //   to be split (see note at end of #107).  Working around the problem at
                //   this stage is a little awkward because I think we need to check for the
                //   presence of labels on one or more later lines.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap)
            {
                // Some assemblers have trouble generating PC-relative operands that wrap
                // around the bank.  Output as raw hex.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (op.AddrMode == OpDef.AddressMode.BlockMove &&
                     gen.Quirks.BlockMoveArgsReversed)
            {
                // On second thought, just don't even output the wrong thing.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (replMnemonic == null)
            {
                // No mnemonic exists for this opcode.
                gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr);
            }
            else if (replMnemonic != string.Empty)
            {
                // A replacement mnemonic has been provided.
                opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis);
            }
            gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr);

            // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the
            // wrong thing if we're in emulation mode.  Force flags back to short.
            if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm)
            {
                if ((operand & 0x30) != 0 && attr.StatusFlags.IsEmulationMode)
                {
                    gen.OutputRegWidthDirective(offset, 0, 0, 1, 1);
                }
            }
        }