9

On the 6502, the brk instruction is a software interrupt. Like any other interrupt, it pushes the status word to the stack and then the program counter, before transferring control to an interrupt routine.

But the weird thing is, the processor sets a bit in the status word as it's pushed to the stack. This bit is not present in the status word; it only is set in the copy on the stack as it's written to memory. This bit is 0 if the interrupt came from hardware, and 1 if the interrupt came from software by executing the brk instruction. I think the idea is that then the interrupt service routine can, in a most awkward and time-consuming way, test this bit to check why the interrupt happened (this would involve needing to save and restore A and X, an ALU operation and a conditional branch as well, not exactly the kind of faff you want in an interrupt handler or syscall handler).

But the 6502 already has the ability to read from three different vectors when the interrupt happens; it feels like the obvious thing to do is to have one more interrupt vector so that hardware interrupts read from a different vector than the software interrupts. This would obviate the need to set bit 5 as it's pushed, and intuitively, supporting four vectors is no harder than supporting three (according to the Hanson's block diagram the hardware to do this is already present in the open drain mosfets on the ADL bus).

What I am proposing here is what the Motorola 6800 does, which the 6502 designers are known to have been very well familiar with. So I'm guessing they had a reason for making the 6502 interrupts work in this way.

6
  • 2
    Your question, I take it, is why the 6502 doesn't assign BRK its own interrupt. That's something I've often wondered myself, given how painful it is to have an IRQ handler check for a BRK before servicing the peripheral that actually needs attention, and given the difficulties that arise if an IRQ or NMI arrives while code is about to execute a BRK instruction. A somewhat better formulation of the question might be whether any interviews with the designer or other such sources indicate that an approach using separate vectors was rejected for some reason.
    – supercat
    Apr 24 at 20:29
  • 1
    I wouldn't be terribly surprised if what actually happened was that the logic to jam the fetched opcode latch to all zeroes, and have an all-zeroes opcode latch value trigger an interrupt sequence, were designed before there was any thought given to a BRK instruction, and the decision to make all-bits-zero be a useful instruction was made after the logic was already designed; having the push-processor-status logic put the state of an internal latch onto the data bus was a trivial change compared with reworking interrupt vectors.
    – supercat
    Apr 24 at 20:39
  • My Leventhal says about the BRK instruction: "... the Break status is set to 1 and then ... pushed onto the stack" Is it incorrect? Apr 24 at 22:21
  • 1
    @WeatherVane, yes, that's either technically true but misleading, or incorrect, depending on how you see it. There is no "Break status" bit in the CPU. It exists only on the byte that's pushed to the stack, which contains the other status bits. Apr 26 at 5:39
  • I see, and there's no instruction to test the B in the status register itself anyway. But about your "save and restore A and X ... not exactly the kind of faff you want in an interrupt handler" – an interrupt handler will almost certainly want to do that if it has any functionality. Apr 26 at 18:53

3 Answers 3

15

TL;DR: Clearing Two Bits At Once Needs More Logic

Remember, the 6502 is all about shedding whatever could be removed from the 6800. Never spend on hardware if it could be done in software. In this case vector generation and handling. Especially if it's about debugging. Production systems don't debug and BRK is only there to help with debugging. Debugger are already bloaty by nature, slowing down execution, no need to spend hardware on easing a fringe case like that.


Operational Details

As Justme already mentioned, all interrupts use the very same microcode, only some modify a few generated bits. In fact it's quite simple, but needs close attention.

But the 6502 already has the ability to read from three different vectors when the interrupt happens;

Almost, but different, and very cleverly made to reduce the needed hardware to almost zero.

and intuitively, supporting four vectors is no harder than supporting three

It is, as generating addresses does need some logic. Spoofing a default address does not, or much less.

(according to the Hanson's block diagram the hardware to do this is already present in the open drain mosfets on the ADL bus).

Yep, that's exactly the place where the magic happens, so follow me closely, as this is really about every line, bit and transistor needed or not.

To start with, each and every interrupt executes the sequence for the opcode 00.

  1. Either because 00 was read as instruction from memory, or

  2. due 00 being 'inserted'.

Except the wording as 'insertion' is a bit misleading. But let's first look at case #1,when opcode 00 is read from memory

Executing BRK

It gets, like any other instruction,

  • loaded into the Predecode Register so the
  • Predecode Logic can analyze it to see if it's a single or multibyte instruction (*1),
  • When time is right (aka last instruction finished) it is
  • stored in the instruction register
  • enriched with a cycle counter and
  • used to step through the micro program:

enter image description here

For a BRK the sequence is

  1. store PCH to stack
  2. store PCL to stack
  3. store P to stack
  4. fetch new PCL
  5. fetch new PCH

and continue with instruction fetch.

At that point we may remember a few points which are true without further effort:

  • Bit 4 of the status Register is always 1, (there is no need to set it like the question posits) (*2)

  • All address lines are precharged to 1 during Phi2

enter image description here

For BRK having its vector at $FFFE/FF this means two things:

  1. It does not have to do anything to get the B flag set when pushing P in step 3.
  2. When loading the vector, the ADL/ADH is already preset to $FFFF during each cycle, thus BRK does not need to provide a vector address at all - just force ADL bit 0 to zero in step 4, which will be done by pulling 0/ADL0.

Executing Interrupts (including Reset)

As mentioned, all interrupts are just tweaked BRK. Tweaked and preceded by a hardware event which will set (at least) one flip-flop to note its occurrence.

enter image description here

With an interrupt or reset detected (the ***G flip-flops), the Predecode Logic (see above) will not forward the next instruction read, but deliver an all zero value ... exactly our beloved BRK opcode.

From there on execution will go on as with BRK, except for the Control Logic issuing a few tweaks during:

  • With any interrupt flag set, the bit 4 of the status register gets pulled to zero (*3)
  • Depending on interrupt A1 or A2 gets pulled during step 4 and 5
    • IRQ does not pull any (*4)
    • RESET pulls A1 using 0/ADL1
    • NMI pulls A2 using 0/ADL2

This is easily visible by looking at the address bits:

IRQ   $FFFE/F -> 11x
RESET $FFFC/D -> 10x
NMI   $FFFA/B -> 01x

Putting another vector at $FFF8/9, like the 6800 did(*5), would need a set of additional transistors. Having the vectors reshuffled and IRQ and BRK combined is one of the areas where the 6500 saved real estate - in interrupt management, execution and address generation.

Yeah, But Why Not Pulling A1 and A2 for IRQ?

Sure, but that would have required at least two additional OR gates, as either line would now be pulled in two situations. Maybe even more importantly, the way the various interrupt control bits are combined to manage priority and timing would have need some additional gates as well (*6).

Bottom Line: 6502 was never about playing by the book, but saving wherever it could be done - all debugging needs is a reliable hook, no bells and whistles.


*1 - The distinction is necessary to avoid the PC incrementing twice for single byte instructions. For that, the Predecode Logic considers $x0 instructions as two byte (or better 'not one byte'), so the PC gets incremented twice. Not an issue for RTI ($40) and RTS ($60), as they don't care about the result. But BRK does push that PC after being incremented.

Then again, from the developers' PoV BRK was always imagined only as debugging tool and a debugger will have to manipulate the PC anyway. No reason to waste precious transistors on making it nice and cosy.

*2 - Or more exactly, it doesn't exist at all. Like the address bus, the data bus is precharged to all ones. Unless any other signal pulls bit 4 low, it will end up as a 1 in memory (and ignored when reading back).

*3 - This is the only time and by a single input that it gets pulled low.

*4 - Not adding any circuitry is the most economic solution of all.

*5 - The 6800 has 4 vectors:

RESET       $FFFE/F -> 111x
NMI         $FFFC/D -> 110x
SWI (BRK)   $FFFA/B -> 101x
IRQ         $FFF8/9 -> 100x

*6 - For more details I suggest playing with the beautiful Visual6502 simulation.

8

Actually, BRK is not a special case of interrupts with B flag set, interrupts are a special case of BRK with B flag cleared.

The operation of BRK is the base case which executes code from vector with B flag set.

The operation of IRQ/NMI signal handling determines that interrupt is pending so it clears the instruction register to insert a BRK instruction to be executed.

B flag is likely just an internal flag for status if this BRK happened due to it actually being a fetched instruction or an inserted BRK instruction for IRQ/NMI.

NMI is slightly special case of IRQ as it uses extra logic to change the BRK/IRQ vector to the NMI vector.

If the CPU would have more vectors, it would need more logic to decode which vector to execute.

And the later 65C816 and 65C802 did have more vectors and separate BRK and IRQ vectors in native mode, but used the same vector in 6502 emulation mode. Hardware designers are also free to add hardware which supports multiple interrupt sources and makes the CPU to fetch a different vector based on which interrupt is active.

Anyway, as the BRK and IRQ did use the same vector, if you use BRK for patching code, debugging code or for system calls while using hardware interrupts, the vector being executed must know if it was a real hardware IRQ or BRK instruction in code to check if it must branch to handle the hardware interrupt or do something with the encountered BRK.

-2

Sorry about the SHOUTING but I was a little frustrated and trying to get my point across. Why don't we have a discussion about SNAPCOUNT?

New contributor
Well3 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
1
  • What even is snapcount and why do you want a discussion about it here? Go ahead and ask a separate question, and as commenters on your other post said, please try to make sense. 1 hour ago

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .