6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Tue Apr 16, 2024 3:38 pm

All times are UTC




Post new topic Reply to topic  [ 31 posts ]  Go to page 1, 2, 3  Next
Author Message
PostPosted: Sun Sep 26, 2010 4:56 am 
Offline

Joined: Mon Oct 16, 2006 8:28 am
Posts: 106
This one's a bit philosophical. I was thinking about recent vs older CPUs, and it occurred to me that while software interrupts (eg INT on x86, SWI on ARM and TRAP in 68K) make a lot of sense in CPUs with user and system modes, I can't figure out why simpler CPUs with only one mode would need them for anything other than debugging. For many years I believed that BRK was added only for debugger support, when I discovered that the MC6809 actually has three equivalents for BRK (SWI, SWI2 and SW3, each with slightly different semantics) and OS9 uses SWI2 to expose its various services. Maybe it's my Commodore background, but I'd think that JSR with a consistent jump table is a much better way to provide OS services than interrupts in an 1970s era CPU. Come to think of it, why even use BRK for debugging when a debugger's "insert breakpoint" could just stash away three bytes instead of one and use a JSR? (let's forget for a moment the use of BRK for patching PROMs - clearly SWI, SWI2 and SWI3 were not designed with that in mind and I'm thinking of the bigger picture).

What are the thoughts of the more experienced 6502 hackers here?

EDIT: Hah! saved by Google's cache :)


Last edited by faybs on Mon Sep 27, 2010 7:50 pm, edited 4 times in total.

Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Sep 26, 2010 5:55 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10789
Location: England
A couple of thoughts:

- if you use BRK for OS calls, you don't have to finalise your JSR table.

- external hardware can detect BRK and allow a supervisory mode: such as changing memory map or allowing some peripheral registers to be writeable.

Interesting question!
Ed


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Sep 26, 2010 5:55 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8422
Location: Southern California
A couple of topics that came to memory that might be helpful (you'll have to read past the initial post(s) to get to the parts related to your question):
viewtopic.php?t=24 (3 pages)
viewtopic.php?t=1488 (2 pages)

Hardware interrupt performance is so important to me though that I would not want to be increasing the latency by using BRK instructions and having to check the B flag on the '02. Fortunately the '816 (which has more use for BRK anyway) has a separate BRK vector.

Note: edits in dark blue


Last edited by GARTHWILSON on Sun Sep 26, 2010 12:20 pm, edited 2 times in total.

Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Sep 26, 2010 7:39 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
I can no longer post the source, but I do recall reading that BRK was chosen to patch EPROMs (note, NOT EEPROMs). With an opcode of $00, it was guaranteed to overwrite any byte at the desired location in EPROM, providing a convenient means of fixing software in the field (provided your code to implement the breakpoint handler could fit either in another ROM or in RAM).

It wasn't until the 65816 when BRK earned its new use as a system call mechanism, providing an effectively mode-independent OS entry point, usable in emulation or native modes, from any bank.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Sep 26, 2010 9:30 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10789
Location: England
...and such a pity that FF isn't NOP.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Sep 26, 2010 6:03 pm 
Offline

Joined: Thu Jul 26, 2007 4:46 pm
Posts: 105
One advantage would be reduced code size (Obviously, 1 byte vs 3) for each call; though since BRK doesn't encode any operands and the minimum load operation would be two bytes... this is a pretty unconvincing case.

If it had been a two byte opcode, and additionally (say) loaded the operand byte into one of the registers, it would have been more useful. Though, actually, one could pluck an operand byte (or more!) out of the instruction stream immediately following it, for vectoring. Its notable that classic MacOS's "A Traps" (for accessing various toolbox services) were implemented in a similar manner (except using the 68k's defined undefined opcodes)

It is also interesting to note that, on modern CPUs, all system calls are often vectored through one mechanism anyway, with the call selector and all parameters passed in registers. E.G, ARM has an "SWI" instruction, which takes an immediate operand notionally used for selecting the call to take; everyone these days ignores it since fetching it out of the instruction stream is too much hassle.

Hmm. It seems this has become quite a bit of a ramble.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sun Sep 26, 2010 7:35 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8422
Location: Southern California
BRK is officially a 2-byte instruction, although I don't really see any obligation to use the second one. The program counter is incremented twice, so an RTI will return to the location after the second or signature byte, unless you decrement the return address in the ISR which is more trouble than it's worth.


Top
 Profile  
Reply with quote  
PostPosted: Sun Sep 26, 2010 8:21 pm 
Offline
User avatar

Joined: Fri Dec 12, 2003 7:22 am
Posts: 259
Location: Heerlen, NL
faybs wrote:
What are the thoughts of the more experienced 6502 hackers here?
The only 6502 system that I know that uses BRK as part of its program is the disk controller of the Commodore 9060/9090 hard disk.
FYI: the older floppy drives of Commodore had a 6504 and a 6502 or two 6502s on board. The (1st) 6502 took care of the communication with the computer and interpreting the commands and the 6504/2nd 6502 took care of dealing with the floppy like reading/writing data or moving the head. In case of the 90x0 the second 6502 communicated with the hard disk controller over a SASI interface. As long as things went right, branches had to be taken. If the branch wasn't taken due to an error, BRK was executed. This on its turn caused the 6502 to perform a soft reset.

_________________
Code:
    ___
   / __|__
  / /  |_/     Groetjes, Ruud
  \ \__|_\
   \___|       URL: www.baltissen.org



Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Sep 27, 2010 4:28 am 
Offline

Joined: Sat Aug 21, 2010 7:52 am
Posts: 231
Location: Arlington VA
I'm writing a Forth for the Commodore PET 2001 and a few days ago I stumbled on Wozniak's Sweet-16 stuff. After checking it out, I decided to use it. I didn't like how the package had to be invoked with JSR SW16, since the whole point of having it is to squeeze bytes. Instead I pointed the break vector (on the PET at $0092) to SW16.

Since I'm writing a Forth, I also made a few other mods, like adding PUSH and PULL as ops $0D and $0E to Sweet16 (to manipulate the Forth data stack). Sweet-16's R0-R15 overlaps my zero page mapping in Forth, with TOS at R1, the "N" 8-byte work area at N2-N5, and UP at N6. It was trivial to write JSR TOFORTH and a ">6502" Forth primitive to allow for inline assembler. The idea was to be able to seamlessly hop between high level Forth <--> 6502 <--> Sweet-16

Here's what I did to the first several instructions in Sweet16 to get it to work
Code:
entry from BRK instruction

SW16    ldy $0106,x    ;INIT SWEET16 PC
        lda $0105,x    ;ADDRESS
        sec
        sbc #2
        bcs sw16a
        dey
sw16a   sty R15H
        sta R15L

changes to RTN instruction

RTNZ    pla
        pla
        tsx
        lda R15L
        sta $0105,x
        lda R15H
        sta $0106,x
        pla
        tay
        pla
        tax
        pla
        rti


Here's some code demonstrating the technique

Code:
;--------------------------------------------------------------
;
;       DHASH!   ( LFAnew hash1 -- )
;
; links the current LFA onto the top of this hash' thread
;
; When we're done, the word whose LFAnew is on the stack will become
; the new top of this hash thread, and the new word's LFA will link
; to the previous top of the chain.
;
; sweet16 saved 51 bytes vs. 6502
;
;dhashstorelfa  .word $adde
;               .byt (dhashstore-*-1)|bit7
;               .asc "DHASH","!"|bit7
dhashstore      ldy #1
                jsr setup               ; LFAnew -> N0
                brk
                .byt set  | R11
                .word lfalist
                .byt sub  | ACC
                .byt st   | N2          ; 0 -> N2
                .byt ld   | R11
                .byt add  | TOS
                .byt add  | TOS
                .byt st   | R11         ; R11+hash1*2 -> R11
                .byt ldd  | R11
                .byt popd | R11         ; back up the pointer
                .byt bnz, <(dhashstore01-*-2)
                .byt set  | ACC
                .word rootlfa
                .byt std  | R11
                .byt popd | R11
dhashstore01    .byt st   | N1          ; head -> N1
                .byt rtn
                ldy #2
                lda (n),y
                and #$1f
                sta n+7                 ; length of name being inserted
dhashstore02    lda (n+2),y
                and #$1f
                sta n+6
                lda n+7
                cmp n+6                 ; compare lengths
                bcc dhashstore05
                bne dhashstore04
                ;sec
                adc #1
                tay                     ; last char in names
dhashstore03    lda (n),y
                eor (n+2),y
                asl                     ; compare char ignoring bit7
                bne dhashstore04
                dey
                cpy #2
                bne dhashstore03        ; stop at first char
                dey
                brk
                .byt set  | ACC
                .word $adde
                .byt std  | N1
                .byt rtn
dhashstore04    brk
                .byt ld   | N1
                .byt st   | N2          ; keep track of where we've just been
                .byt ldd  | N1
                .byt st   | N1          ; (N1) -> N1
                .byt rtn
                ldy #2
                bne dhashstore02
dhashstore05    brk
                .byt ld   | N2
                .byt bnz, <(dhashstore06-*-2)
                .byt ld   | R11         ; empty list, insert at the head
                .byt st   | N2          ; head -> N2
dhashstore06    .byt ld   | N0
                .byt std  | N2          ; N0 -> (N2)
                .byt ld   | N1
                .byt std  | N0          ; N1 -> (N0)
                .byt rtn
                jmp pops                ; toss the hash


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 27, 2010 11:00 am 
Offline

Joined: Thu Sep 02, 2010 12:34 am
Posts: 36
Location: Salzburg, Austria
Ruud wrote:
The only 6502 system that I know that uses BRK as part of its program is the disk controller of the Commodore 9060/9090 hard disk.

Another one for the list: the Atari 850 serial/parallel interface.

I analyzed the source code and discovered they used BRK throughout the code to call a common subroutine - maybe to save a byte compared to JSR. The 4k ROM is really full and the 850 interface uses a 6507 so there are no other interrupt sources.

Code:
LASTAT  .MACRO
        BRK
        NOP
        .ENDM
...
;----------------------------------------------------------------
;       STATUS LATCH ROUTINE--CALLED VIA LASTAT
;----------------------------------------------------------------
;
LSTAT   = *
LSTHI   = *
        LDA     PORT1
        AND     HILOST          ; CHECK FOR HIGH-TO-LOW CHANGE
        STA     HILOST
LSTLO   = *
        LDA     PORT1
        ORA     LOHIST          ; CHECK LOW-TO-HIGH CHANGE
        STA     LOHIST
        RTI                     ; RETURN -- 33 CYCLES EXECUTION
...
        *=      $FFF8
HNDCHK  .BYTE   $C5                     ; HANDLER CHECKSUM
        .BYTE   VERSON
        .WORD   CHKSUM,RESET,LSTAT

so long,

Hias


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Sep 27, 2010 11:53 am 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
The BBC micro used BRK as a exception mechanism for errors in the BASIC (e.g. divide by zero etc.).

In my firmware I use BRK for accessing operating system functions, like multitasking. Its quite convenient having the registers pushed on the stack at the start of a task switch. I added an immediate mode to BRK in my assembler to allow commands like BRK #TASK_YIELD.

_________________
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Sep 27, 2010 12:59 pm 
Offline
User avatar

Joined: Mon Dec 08, 2008 6:32 pm
Posts: 143
Location: Brighton, England
The BBC Micro made considerable use of BRK as an error exception mechanism - not just in BASIC but throughout the machine.

Following a BRK instruction, an error code and an error message were expected - to allow the error handler to process the error.

On execution of a BRK, the operating system would inform all the paged ROMS of the fact, then jump into the currently selected language ROM via the BRK vector. (The language was expected to set up the vector on initialisation). The language was expected to handle the error condition - usually by printing out the error message and returning to the input prompt.

My assembler allows an immediate operand to be added to BRK instructions (and COPs if using the 65816, since COP s virtually identical to BRK)

In the computer I'm designing, I'm considering using BRK in a similar manner to the Beeb while COP is used for operating system calls.

_________________
Shift to the left,
Shift to the right,
Mask in, Mask Out,
BYTE! BYTE! BYTE!


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Sep 27, 2010 6:40 pm 
Offline
User avatar

Joined: Sun Feb 13, 2005 9:58 am
Posts: 85
GARTHWILSON wrote:
BRK is officially a 2-byte instruction, although I don't really see any obligation to use the second one. The program counter is incremented twice, so an RTI will return to the location after the second or signature byte, unless you decrement the return address in the ISR which is more trouble than it's worth.


in my opinion the second byte can be used as a parameter for a BRK routine, some kind of indexed o.s. call


Top
 Profile  
Reply with quote  
PostPosted: Mon Sep 27, 2010 7:51 pm 
Offline

Joined: Mon Oct 16, 2006 8:28 am
Posts: 106
Some interesting stuff, thanks all. I must admit I hadn't considered multitasking as a possibility, since 64K is not a whole lot of space for multiple programs to share. Banking provided by something like the C128's MMU could alleviate that, as could the 65816's larger address space. On the other hand, the way the 65816 organizes memory in banks, with some addressing modes hardcoded to use bank 0, makes it even worse from a programmer's point of view than real mode x86. I suspect that Chuck Peddle is a brilliant hardware designer but a so-so programmer.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Mon Sep 27, 2010 8:11 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
ptorric wrote:
in my opinion the second byte can be used as a parameter for a BRK routine, some kind of indexed o.s. call


That is precisely what "is more trouble than its worth," according to Garth. And, I'm inclined to agree. To gain access to that byte, you need to preserve all your registers, pop the address from the stack, decrement it by one, read the byte (which requires 8-bit registers, thus requiring you switch modes if you're on a 65816), and branch based on that byte.

Alternatively, you could just do this:

Code:
LDX #syscall_foo
BRK


and be done with it. Inside the BRK handler, then:

Code:
  JMP (table,X)
table:
  .dw func1, func2, ..., funcFoo, ... funcX


This code will work with 8- or 16-bit registers, provided your syscall_X constants are even values and start from 0.

At any rate, since BRK is a genuine interrupt, you're going to impose some additional amount of overhead compared to a normal JSR or JSL. I think JSR/JSL is preferable for nearly everything I can think of, except when having to deal with memory banking (since the processor's VP# signal can trigger external MMU hardware to a known-good, supervisory-compatible state) and thread-switching (since BRK pushes P onto the stack for you, thus saving you the 3 cycles you'd impose otherwise through PHP). Thus, BRK makes a great entry-point for a microkernel, but everything above the microkernel layer is, perhaps, better implemented through JSR/JSL calls.

Anyway, that's my take on it. :-)


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

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 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: