9

I am learning how to program the Atari 800 by examining some tutorial code that came with the IDE/Assembler I am using. I am using the MADS assembler for this.

putchar_ptr = $346
csrhinh     = 752
character = $80
rowcrs  = $54
colcrs  = $55
    org $2000

    .proc main
    mva #1 csrhinh
    mva #6 rowcrs
    mva #16 colcrs
    mva #0 character

next_character
    ldx character
    cpx #.len text
    beq stop
    lda text,x
    jsr putchar
    inc character
    jmp next_character

stop    jmp stop

    .proc putchar
    tax
    lda putchar_ptr+$1
    pha
    lda putchar_ptr
    pha
    txa
    rts
    .endp

    .local text
    .byte 'Hi there!',$9b,'new line'
    .endl

    .endp

    run main

I did reference the Atari 800 manual and the MADS-Assembler manual but I didn't find anything. The specific question I am asking is, in the putchar procedure, why is the accumulator pushed onto the stack? From what I can tell all it is loaded with is the location of the routine pointer on the first push and the put pointer on the second. A few possibilities I see are that I could be mistaken on what the routine actually is (the atari 800 manual wasn't very informative about that) or the push might point to something else other than the stack. I would say the latter is true but then we aren't pushing the character we are trying to print because of the txa instruction and the accumulator being reloaded.

9
  • The routine does an indirect jump to the contents of putchar_ptr, because rts jump to the contents just pushed on the stack. I don't know why they are doing it that way instead of using a plain indirect jump.
    – dirkt
    Oct 20, 2018 at 15:59
  • 1
    @dirkt Because the address is possibly stored in routine-1 form and ment to be used that way. An indirect Jump would require a non decremented one, So this is faster than loading and incrementing it for use with an indirect jump.
    – Raffzahn
    Oct 20, 2018 at 17:06
  • @Raffzahn: So why don't they just store it in addr instead of addr-1 form? It was certainly common to do that, e.g. COUT on the Apple II, and IIRC also in the C64 ROM.
    – dirkt
    Oct 20, 2018 at 17:27
  • @dirkt Because it's part of a list of addresses - or better list of IOCB. The Atari OS is a bit more sophisticated compared with the C64. It is based on an abstract, file based IO system called CIO, which used IO controll blocks for each open file. For BASIC there is a lisst of 8 IOCB predefined. Each has at offset 6 a pointer with the function to be called for output ($34*6* here). For outputing the routine is just called with the IOCB number times $10 in X, so instead of a fixed address, above LDA use (IOCB_list+6,X) to fetch whatever pointer is needed and uses the RTS mechanic.
    – Raffzahn
    Oct 20, 2018 at 17:34
  • 1
    @Raffzahn: Ah, so the real answer is that it's a not a single vector, but part of a data structure, and that's the reason the -1 form is used (by choice), because you can't use an indirect jump anyway, and you need to use pha/rts.
    – dirkt
    Oct 20, 2018 at 18:02

1 Answer 1

18

That's a usual way to an indirect JSR with a 6502. The 6502 does not support indirect subroutine calls (*1), so it has to be done in software. Indirect subroutine calls are a useful tool for function calls into OS/library functions which may change during runtime or by configuration - like when redirecting output to a different driver. By using a routine pointer for certain calls it's easy to overload/replace them by just changing that pointer (*2).

Lacking the indirect call the 6502 needs to emulate an indirect subroutine call in software by calling a subroutine which in turn pushes the pointer onto the stack (high first) and then jumping there by 'returning' to it. Adds some cycles, but also preserves the flexibility (*3).

In detail it works like this:

LDA ptr+1     * high byte of target routine pointer
PHA           * push down the stack
LDA ptr       * low  byte of target routine pointer
PHA           * push down the stack
RTS           * 'returning' to the address at TOS

The TAX/TXA around is just to preserve the parameter (character to be printed) around the stack handling code.

The NES-Dev Wiki offers a nice page about this topic.


Above routine is in itself a waste of time (23 cycles) and code (9 bytes) compared to a JSR pointing to an indirect jump. Just when the OS table is, like in this case, prepared for being executed using this, it will hold the routine addresses minus one, so an indirect jump won't work.


Further, I'm not so sure that just grabbing the routine from IOCB 0 is a great idea. While it should work, as IOCB 0 is usually associated with the screen, it's definitely not fault resistant. It might be way better to go thru the CIO first.

(Caveat: My Atari knowledge is only small and rather rusty)


*1 - One of the few really missing instructions that could have been added rather easily. And a major hint that the 6502 wasn't designed with a general purpose CPU in mind, but rather a microcontroller with its fixed address locations, where such redirection is done during compile/linkage time.

*2 - Always keep in mind to use target address minus one, as RTS will increment the address before fetching the next instruction.

*3 - Some OS did speed up this by putting a JMP-opcode in front of every callable pointer, allowing a user programm to just JSRing via the pointer-1 address, greatly reducing the overhead to 3 cycles.

5
  • However I wonder why no indirect JMP has been used here. Oct 21, 2018 at 4:52
  • 1
    @MartinRosenau Because the vector is from a structure where it is already prepared in address-1 format, ready for this kind of 'trick' ? The CIO is a great system offering I/O on an abstract level, usung IOCBs, above is a direct unchecked use of an IOCB used by BASIC. Not the way it is ment to work.
    – Raffzahn
    Oct 21, 2018 at 7:25
  • 1
    In your pseudocode you wrote "high byte" twice; the second (I think) should be "low byte". Oct 22, 2018 at 19:42
  • 1
    @user3570736 Right. Except, I wouldn't call ot pseudo code :)
    – Raffzahn
    Oct 22, 2018 at 20:14
  • While there are many instructions that the 6502 could and should have added very easily, I don't think the mechanism used for jmp (abs) could easily be used with jsr. If jmp (abs) had been designed to be processed just like jmp abs, except that the state machine would be advanced to a "behave as though one has just fetched a jmp opcode" rather than "fetch next instruction", the same mechanism might have been applicable to jsr, but the existing jmp (abs) mechanism would not be suitable in such fashion.
    – supercat
    Sep 23, 2022 at 20:41

You must log in to answer this question.

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