6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Apr 27, 2024 4:28 pm

All times are UTC




Post new topic Reply to topic  [ 30 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Wed Feb 21, 2024 7:48 pm 
Offline

Joined: Fri Mar 11, 2022 11:25 pm
Posts: 12
Can somebody please explain to me what happens to the stack when BRK is called? It may well be that I don't understand the stack itself.

Assume we have the code below:

Code:
$C000  BRK  ;
$C001  12   ; Padding byte


Let's also assume there is nothing on the stack at present (so SP=$00, top of stack=$100)

When BRK is called, does the stack look like this afterwards?

Code:
$0100  02
$0101  C0
$0102  <SR>


What's been confusing me is the BRK handler that's used in the BBC Micro. For some reason, that code tries to access $103+SP for the PC of the originating BRK. Does the stack always have 2 bytes at $100/$101 and the code above would cause the C002 to be placed at $102/$103 instead?

Where am I going wrong?


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 21, 2024 8:12 pm 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 745
Location: Germany
The stack works a bit differently than what you said.
the Stack Pointer decrements when Pushing bytes to the Stack and increments when Pulling bytes from the Stack, that's why the stack usually starts at $01FF. it also always points to the next free location on the stack, one above the last element.
so for example if SP = $80 then executing PHA will store the contents of A to $0180 and then update the SP to $7F.

so when the stack starts at $01FF and you execute BRK, the stack should end up at $01FC (after pushing 3 bytes, 2 for the PC and 1 for the SR)

because the SP points to the next free location if you want to access bytes on the stack you always need to add 1 to the address.
for example

Code:
    TSX
    LDA $0101,X

will load the top most element on the stack into A.
so in your case the BCC micro is accessing the third element on the stack (which is the status register after BRK). basically you can imagine it as accessing the stack with a 1-index instead of a 0-index. $0101,X = 1st element, $0102,X = 2nd element, $0109,X = 9th element, etc.


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 21, 2024 8:17 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
I see Proxy posted while I was writing.  I'll post this anyway.  As you add things to the stack, it grows downward, not upward.  Typically you'll want to initialize the stack pointer as $FF, so the first byte pushed goes to $1FF, the second one at $1FE, etc..  The stack pointer register S is decremented immediately after each byte is written onto the stack.  See the 6502 interrupts primer, and treatise on 6502 stacks (stacks plural, not just the page-1 hardware stack).

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 21, 2024 9:07 pm 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1398
Location: Scotland
As others have said; The stack pointer decrements as it pushes.

Seeing your thread on stardot (which I replied to over there too), maybe I can add a little more:

The OS accesses $103,x as it's a handy shortcut to pull bytes off the stack to get the address of the byte after the BRK so it can then get the error code and print the subsequent error message.

(Note that the X register has been pushed onto the stack too at the start of the BRK handler, so $103,x fetches the low byte of the stored PC. (Also note that the stored PC is the byte after the byte after the BRK - at least in my version - it's been a good while since I looked at the OS disassembly)

My BRK handler looks like this: (this is 65C02 code, some differences for the 6502 needed):

Code:
realBrk:
        phx                     ; Temp save X so we get get the return addres directly

; Get the address of the byte immediately after the BRK instruction by
;       fetching it directly out of the stack and subtracting 1 from it.

        tsx
        lda     $0103,x         ; PC Low
        sec
        sbc     #1
        sta     brkL
        lda     $0104,x         ; PC High
        sbc     #0
        sta     brkH

;brk2:
        stx     stkPtr          ; store stack pointer in &F0

; at this point brkL/H point to byte after BRK
;       In a BBC Micro ROMs are offered it via a service call...
;       (This is not a BBC Micro though)

        plx                     ; Restore X
        lda     irqA            ; .. and A

; Close any exec file

;       ldy     execFd
;       beq     :+
;       sty     sBufParam
;       lda     #CMD_CLOSE
;       jsr     fsCall
;       stz     execFd
;:

; and spool file

;       ldy     spoolFd
;       beq     :+
;       sty     sBufParam
;       lda     #CMD_CLOSE
;       jsr     fsCall
;       stz     spoolFd


; clear any Escape condition

        stz     escFlag
        cli                     ; (Re) Allow interrupts

        jmp     (brkV)


; _brkV:
;       Ruby default BRK handler - print the message and re-start RubyOS
;********************************************************************************

_brkV:
        aix8
        e6502

.ifdef  VERBOSE_BRK
        jsr     strout
        .byte   13,10,"[BRK ",0

        lda     brkH
        jsr     oHex8
        lda     brkL
        jsr     oHex8
        jsr     strout
        .asciiz ":"

; Now get the byte (error code) and print it

        lda     (brkL)
        jsr     oHex8
        jsr     strout
        .asciiz "] "
.endif

; Finally, print the (hopefully) zero-terminated error message

brkvLoop:
        inc     brkL            ; Pre-inc to skip over the code
        bne     :+
        inc     brkH
:       lda     (brkL)

; Make sure it's printable

        beq     doneBrkV
        cmp     #KEY_SPACE
        blt     doneBrkV
        cmp     #127
        bge     doneBrkV

; Print and loop

        jsr     osWrch
        bra     brkvLoop

doneBrkV:
        jsr     osNewl
        jmp     rubyOsWarm


Note that many optimisations are possible here, but I started trying to do the same as the Beeb does...

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 21, 2024 9:19 pm 
Offline

Joined: Fri Mar 11, 2022 11:25 pm
Posts: 12
drogon wrote:
As others have said; The stack pointer decrements as it pushes.
(Note that the X register has been pushed onto the stack too at the start of the BRK handler, so $103,x fetches the low byte of the stored PC. (Also note that the stored PC is the byte after the byte after the
-Gordon


Thanks everyone for the stack explanations - and Gordon for mentioning the X on the stack too. When I get a spare moment, I'll need to draw this out on paper (I'm at work at the moment but I have to reply while everybody else is not asleep :-) ). That X will obviously make a 1 byte difference (that's what page 466 of the Beeb User Guide is doing too).. and yes, I thought the PC would be the address of the next instruction rather than the BRK itself.


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 21, 2024 10:48 pm 
Offline

Joined: Wed Aug 21, 2019 6:10 pm
Posts: 217
SparkyNZ wrote:
... and yes, I thought the PC would be the address of the next instruction rather than the BRK itself.


That's common with JSR, where the address on the stack is the third byte of the JSR instruction.

It seems like incrementing the address bus after the first instruction byte is read happens in parallel with decoding the opcode, so by the time the 65C02 knows that it is executing a BRK, the address bus is already pointing to the address following BRK. [(Edit:) And then because the part of the IRQ/NMI cycle that is going to be used by BRK starts in cycle 3, it can't use the normal 1 byte code circuit that suppresses the PC increment at the end of cycle 2, because for all of the other one cycle opcodes, that is the final cycle of the opcode which triggers the next instruction fetch cycle.]

So rather than throw transistors and clock cycles at adjusting that, [Edit: with an alternative PC increment suppression circuit that leads to the 3rd cycle of the IRQ/NMI address cycle,] they declared that address to be holding a signature byte, making the BRK a two byte instruction that [Edit: reads, but] itself doesn't make use of the second byte.

Edit: And, as I'm reminded below, when an interrupt is executed, the instruction has completed, so the address of the next instruction is sitting in the address register, so RTI returns to the return stack vector address, not the following address.

Refreshing my memory from the 65C816 datasheet cycle by cycle address mode timing diagrams, what it may have been in the NMOS is that BRK is sharing execution circuitry with the original NMOS JSR routine for the first two cycles, and it is cycle 3 where it starts with clock cycle 3 of the original NMOS IRQ/NMI ... because in the 65816 the "signature" byte is on the bus in Cycle 2, and the BRK/COP addressing doesn't come into alignment with the IRQ/NMI clock cycle until clock cycle 3. (from the datasheet clock cycle specification, p. 36: 2b p. 41, 22a, and p. 42: 22j)

Now, that is not speculative due to a limit of extant information. I presume that the mask level analysis of the NMOS 6502 would be able to say that for sure. However, interpreting the mask level analysis is outside of my strike zone, where I can read the clock by clock specification in the 65C816 datasheet.


Last edited by BruceRMcF on Fri Feb 23, 2024 4:42 pm, edited 4 times in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 22, 2024 5:22 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3346
Location: Ontario, Canada
BruceRMcF wrote:
making the BRK a two byte instruction that itself doesn't make use of the second byte.
Yup, that's right. But although BRK is a two-byte instruction, I've seen documents -- including page B-8 of the MOS Programming Manual! -- which describe it as a one-byte instruction. :shock:

I suppose it could be argued that's valid. The one-byte viewpoint does works alright (and perhaps avoids confusion) in the scenario where the BRK is used simply to terminate the program (and there won't be any attempt to return to the code that follows the BRK).

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Last edited by Dr Jefyll on Thu Feb 22, 2024 2:45 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 22, 2024 5:51 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1927
Location: Sacramento, CA, USA
BruceRMcF wrote:
And then because RTS and RTI expect return vectors to point at the byte before the next instruction, a RTI will skip that byte following BRK.

Uhh, I'm pretty sure that RTI expects the exact return address on the stack, so PHP RTI will "return" one byte before RTS, all else being equal. Someone please correct me if I'm wrong.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 22, 2024 6:53 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
RTI does not increment the address.  RTS does.  The interrupt sequence stacks the address of the first byte of the next instruction, whereas JSR stacks the address of the last byte of the JSR instruction.  However, BRK uses a signature byte following the BRK op code ($00).  Using the signature byte is optional; but if you have multiple BRK instructions in your code, it can be useful to tell it what you wanted it to do.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 22, 2024 9:36 pm 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3346
Location: Ontario, Canada
barrym95838 wrote:
I'm pretty sure that RTI expects the exact return address on the stack
Yup. And -- compared with RTI -- RTS does have a different behavior (it expects to find the address minus one on stack). Subroutine details are interesting, but IMO they don't teach us much about BRK issues. Instead I'll offer the following useful though unsubstantiated narrative.

I believe the 65xx designers' original goal was for BRK to be truly just a simple, one-byte instruction. Managing without any signature byte was not an impediment. It wasn't part of their plan, and they didn't need it -- they were still able to satisfy their goals in regard to software debugging, and patching fuse-based PROMs.

Moreover, with BRK being just a simple, one-byte instruction, RTI would be its perfect complement. IOW, if the BRK handler needed to return, it could do so simply by using an RTI. But, as implemented, BRK does not play nicely with RTI, and if the BRK handler needs to return then it's obliged to include extra code to alter what's on stack before the RTI can be performed.

Under pressure for time, they had to either fix the bug or make it a permanent part of the specification. So.... the signature byte got re-cast it as a feature! :mrgreen:

Edit: yes MOS was forced to choose between altering the behavior vs. endorsing it and making it permanent. But I need to correct what I said about adjusting what's on stack before the RTI can be performed. That's not necessary if one embraces the as-is behavior and accepts BRK as a 2-byte instruction (albeit with the second byte devoid of any defined function).

To be clear, I don't deny that clever uses can be found for the signature byte. But I believe its existence is purely an accident. I don't know if my take on the designers' intentions can be verified. But at the very least it's an explanation that's coherent... and it's also one that's helpful when it comes to trying to remember BRK's behavioral quirks.

-- Jeff

PS -- regarding quirks, the Eyes & Lichty manual wrote:
Although BRK is a one-byte instruction, the program counter (which is pushed onto the stack by the
instruction) is incremented by two; this lets you follow the break instruction with a one-byte signature byte
indicating which break caused the interrupt. Even if a signature byte is not needed, either the byte following the
BRK instruction must be padded with some value or the break-handling routine must decrement the return
address on the stack to let an RTI (return from interrupt) instruction executed correctly.

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Last edited by Dr Jefyll on Sat Feb 24, 2024 3:35 pm, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Thu Feb 22, 2024 10:45 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8144
Location: Midwestern USA
Back in the days of patching PROMs, we used the signature to tell the BRK handler which patch was to be invoked.  I, for one, never thought of BRK’s double-increment of PC before pushing the register as a “bug”.  It was definitely a “feature”.  :D

I should note that COP in the 65C816 has the exact same behavior.  I’m guessing that if Bill Mensch thought the double-increment “feature” was a bug, he would have designed it out of COP, since there would have been no prior expectations regarding COP’s behavior.  That said, I also am guessing that the internal mechanism for handling a software interrupt is shared between BRK and COP, which would explain why COP has that “feature”.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 23, 2024 12:02 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3346
Location: Ontario, Canada
BigDumbDinosaur wrote:
Back in the days of patching PROMs, we used the signature to tell the BRK handler which patch was to be invoked.
Yup -- if you can alter the signature byte, that'll certainly work. But it's not the only way of identifying the necessary patch. At least one alternative means comes to mind instantly, and if I slept on it I could maybe come up with 2 or 3 more.

BigDumbDinosaur wrote:
It was definitely a “feature”. :D

I should note that COP in the 65C816 has the exact same behavior.
Noted and accepted -- I'm fine with all that. But none of it disproves my suggested narrative (if that's your intention; I really can't tell).

And BTW I did consider COP as I was writing my post. Eyes and Lichty note that "COP is much like BRK, with the program counter value pushed on the stack being incremented by two; this lets you follow the co-processor instruction with a signature byte."

Honestly, I do kind of like the signature byte! :) But a lot of confusion has resulted from documents like the MOS Programming Manual (which, as noted, lists BRK as a one-byte instruction). And I don't think anyone will deny that it really stinks having to increment the address on stack before an RTI can be performed following a BRK. :roll: This isn't something that would be put in place deliberately.

I suppose it could be argued that the signature byte *was* part of their original specification, and then they accidentally included a bug. But I find it more plausible the other way around (ie, the bug happened first). Only then did the signature byte become a thing (subsequently incorporated in the BRK and later the COP specification). Indeed, it's plausible that the apparent misprint in the MOS Programming Manual (dated 1976) is more than a meaningless typo, instead being formerly accurate but now outdated info that reveals the original intention.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Last edited by Dr Jefyll on Fri Feb 23, 2024 1:38 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 23, 2024 1:36 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8144
Location: Midwestern USA
Dr Jefyll wrote:
BigDumbDinosaur wrote:
It was definitely a “feature”. :D

I should note that COP in the 65C816 has the exact same behavior.

Noted and accepted -- I'm fine with all that. But none of it disproves my suggested narrative (if that's your intention; I really can't tell).

As Yakov Smirnoff would occasionally say during his comedy routine, “I make yoke!”

I have no reason to quibble with your theory, as I don’t know if the double-increment was accidental or intentional.  To use an overworked phrase, it is what it is.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 23, 2024 3:32 am 
Offline
User avatar

Joined: Fri Dec 11, 2009 3:50 pm
Posts: 3346
Location: Ontario, Canada
BigDumbDinosaur wrote:
it is what it is.
Yup, that's right. The die (so to speak :wink: ) was cast decades ago, and now we have to live with the result!

BTW, I had a brief memory lapse in regard to this business of overwriting BRK's as a means to alter the existing code in a PROM or EPROM. This trick works because PROM bits can always be changed from 1 to 0, which means you'll always be able to create a BRK opcode when you need one (because the opcode is all zeros). But the signature byte is apt to include some ones, meaning it'll frequently be impossible to create the desired signature byte.

In light of this I altered my previous post, awkwardly inserting the "if you can alter" clause in the first sentence. And I suspect you, too, suffered a memory lapse, BDD, when you said, Back in the days of patching PROMs, we used the signature to tell the BRK handler which patch was to be invoked.

Brain farts aside, I'll note in closing that the 1970s MOS design team may well have been attracted by the advantage of being able to patch an existing PROM. But this advantage reliably applies only to the BRK opcode, and not to the signature byte. In my view, this further weakens the already doubtful premise that the signature byte is something that was desirable and was deliberately planned.

-- Jeff

_________________
In 1988 my 65C02 got six new registers and 44 new full-speed instructions!
https://laughtonelectronics.com/Arcana/ ... mmary.html


Last edited by Dr Jefyll on Fri Feb 23, 2024 3:36 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Fri Feb 23, 2024 3:36 am 
Offline

Joined: Sun Feb 22, 2004 9:01 pm
Posts: 78
For Sparky, the full effect of BRK being executed is this. Assume the stack is empty, so S=&FF, and this code is executed:
before:
01FC ??
01FD ??
01FE ??
01FF ?? <--- S points here

2000 00 BRK <--- execute from here
2001 44 EQUB &44
2002 66 EQUB &66
2003 88 EQUB &88

The BRK causes a IRQ/BRK which results in the stack being:
01FC ?? <--- S now points here
01FD pp flags pushed with B bit set
01FE 02 low byte of address &2002
01FF 20 high byte of address &2002

The CPU then jumps to the address in the IRQ vector. NOTE! The flags register will have the B bit clear, the *pushed* flags register will have the B bit set. The only way to tell if an IRQ/BRK is an IRQ or a BRK is to examine the pushed flags, not the live flags. The live flags always say "not BRK". That's why BRK/IRQ handlers always start with examining the stack.


The IRQ/BRK handler shouldn't be closing files and tidying up, the handler the IRQ/BRK handler itself jumps to should be doing that, so that code can chose what to do.
Code:
realBrk:
        phx                     ; Temp save X so we get get the return addres directly
        tsx
        lda     $0103,x         ; PC Low
        sec
        sbc     #1
        sta     brkL
        lda     $0104,x         ; PC High
        sbc     #0
        sta     brkH
        stx     stkPtr          ; store stack pointer in &F0
        plx                     ; Restore X
        lda     irqA            ; .. and A
; Close any exec file
; and spool file
; clear any Escape condition
etc.
All the above is what the BRK handler should be doing, *not* what the IRQ/BRK dispatcher does. It should be done by whatever hangs off:
Code:
        jmp     (brkV)
The IRQ/BRK dispatcher doesn't know what the BRK handler will want to do, it mustn't preassume anything. It's up to the BRK handler to choose what to do.

_________________
--
JGH - http://mdfs.net


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 30 posts ]  Go to page 1, 2  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 26 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: