6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun May 12, 2024 5:46 am

All times are UTC




Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Mon Nov 13, 2023 7:31 pm 
Offline

Joined: Sat Feb 12, 2022 11:03 pm
Posts: 39
I want to make a "getch" function in C for my sym-1, and I think I can get it by doing:
Code:
__asm__ ("JSR $8A5A")


However, I'm not really quite sure how to pull the value from the A register to return it? I know that the return value gets placed in A/X/sreg, but
if I put it in a void function, would this work by doing
Code:
void getch()
{
    __asm__ ("JSR $8A5A");
}
...
char c;
getch();
__asm__ ("STA %b", c);


I don't seem to be able to get this to work.


Top
 Profile  
Reply with quote  
PostPosted: Mon Nov 13, 2023 8:37 pm 
Offline
User avatar

Joined: Tue Feb 28, 2023 11:39 pm
Posts: 146
Location: Texas
GinDiamond wrote:
I want to make a "getch" function in C for my sym-1, and I think I can get it by doing:
Code:
__asm__ ("JSR $8A5A")



Not sure what would exactly lie at address $8A5A for you, but I wouldn't rely on a hard coded memory address like this. There's no telling where the assembler/compiler might move a function to when you compile the next time. If this is a function written in assembly that you're calling from C, it would probably be a whole lot easier to export that function as a symbol the C compiler can see and then just call that.

Quote:
However, I'm not really quite sure how to pull the value from the A register to return it? I know that the return value gets placed in A/X/sreg, but
if I put it in a void function, would this work by doing
Code:
void getch()
{
    __asm__ ("JSR $8A5A");
}
...
char c;
getch();
__asm__ ("STA %b", c);


I don't seem to be able to get this to work.


So the thing here is that the compiler assumes that outside of your inline assembly it can do whatever it wants with the registers (A or otherwise). Looking in the documentation it doesn't appear that you can define a naked function which would strip out the function preamble/cleanup; the way that it looks like they suggest you get around this is to use a variable to temporarily hold it.

Code:
char c;

void getch(void)
{
    __asm__("JSR $8A5A");
    __asm__("STA %b", c);
}


This looks more like what you're probably trying to do, however, this seems less than ideal to me. Esp if you've already written the function in an assembly file. Tell the CA65 assembler to export that symbol as getch and then import from your c-code:

In ASM
Code:
.export getch

getch:
   ; Your code for getch() here.
   ; Leave the character in the A register before return, that's where the compiler will expect the return value
   rts


In C:
Code:
int getch(); // Define a prototype, the linker will find this for you now that it's exported from ASM

char c = getch();


Hope this helps!

Edit:
C sometimes like to put an underscore on function names. So you may need to define it as _getch in the assembly.


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 3:34 am 
Offline

Joined: Sat Feb 12, 2022 11:03 pm
Posts: 39
Hey, I'm sorry I wasn't as clear as I could be.
$8A5A is a subroutine in the SYM-1 ROM that waits and grabs an incoming byte from the serial port and puts it in the Accumulator.

I also am not quite sure what you mean about making an asm file with my function in it, however, this seems like what I should do for sure. How would I link that with a command?


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 5:09 am 
Offline
User avatar

Joined: Tue Feb 28, 2023 11:39 pm
Posts: 146
Location: Texas
GinDiamond wrote:
Hey, I'm sorry I wasn't as clear as I could be.
$8A5A is a subroutine in the SYM-1 ROM that waits and grabs an incoming byte from the serial port and puts it in the Accumulator.

I also am not quite sure what you mean about making an asm file with my function in it, however, this seems like what I should do for sure. How would I link that with a command?


Okay, getting clearer picture here. Didn't realize you were using a prebuilt ROM; in that case the above method could work fine, though I would ditch the global variable:

Code:
int getch(void)
{
    char c;
    __asm__ ("JSR $8A5A");
    __asm__ ("STA %b", c);
    return c;
}


Still not what I'd consider the most ideal, but you're using a C compiler, there's going to be a lot of boiler plate code that the compiler adds.

With something like the SYM-1 (which I just gave myself a crash course on), you can't grantee that every ROM function is written to interface with the CC65 compiler. getch() is such a simple function you might be able to just link it in with some fancy stuff with the linker, but I don't know how to do that off the top of my head.


As to the question of how to link assembly with C:

You can create an assembly file (usually the extension is .asm or .s) and then assemble it using the CA65 assembler which comes with the CC65 compiler.

You would join multiple files together with a linker step, most people wrap this up with a Makefile or some other such system for building multiple files.

A simple Makefile might look like so:
Code:
OBJS=asm1.o asm2.o cfile1.o cfile2.o

# Assemble .s files into .o files
.s.o:
    ca65 --cpu 65c02 -I include -o $@ $<

# Compile .c files into .o files
.c.o:
    cc65 --cpu 65c02 -I include -o $@ $<

# Generate your program from the above created .o files.
myprogram.bin: $(OBJS)
    ld65 $(OBJS) -o myprogram.bin


CC65 doesn't come with Make on it's own, you'll have to get and install Make for whatever platform you're running on. And each flavor of Make has it's own little quarks. (I would recommend GNU Make simply because it has probably the most extensive documentation on the net)

If you look at the CC65 docs I believe they talk about how you can roll a Makefile that works with their tools.

If you want you can try dissecting the ROM I've been slowly coding up which has a Makefile, but it is purely in assembly, no C.
(It does use the CA65 assembler though)

https://github.com/Kelmar/Lemon

Good Luck!


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 9:19 am 
Offline

Joined: Sun May 07, 2017 3:59 pm
Posts: 20
See also here: Calling assembly functions from C.

Yuri wrote:
Code:
int getch(void)
{
    char c;
    __asm__ ("JSR $8A5A");
    __asm__ ("STA %b", c);
    return c;
}


That does not compile due to "%b" requiring a literal. Since "c" is a local variable, I don't think it can be accessed easily here. It should work by making it global:

Code:
int getch(void)
{
  static char c;
  __asm__ ("JSR $8A5A");
  __asm__ ("STA %v", c);
  return c;
}


This compiles to
Code:
; ---------------------------------------------------------------
; int __near__ getch (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _getch: near

.segment   "BSS"

L0002:
   .res   1,$00

.segment   "CODE"

   jsr     $8A5A
   sta     L0002
   ldx     #$00
   rts

.endproc


Note that X gets zeroed, as per the calling convention described in the documentation linked above - you'd have to do that in the external ASM function as well.

Of course the inline ASM is not optimal due to the useless STA and write-only variable.

Interestingly, when changing the return type to "char", the generated code contains an additional redundant load:
Code:
; ---------------------------------------------------------------
; unsigned char __near__ getch8 (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _getch8: near

.segment   "BSS"

L0009:
   .res   1,$00

.segment   "CODE"

   jsr     $8A5A
   sta     L0009
   ldx     #$00
   lda     L0009
   rts

.endproc


Top
 Profile  
Reply with quote  
PostPosted: Tue Nov 14, 2023 6:25 pm 
Offline
User avatar

Joined: Tue Feb 28, 2023 11:39 pm
Posts: 146
Location: Texas
hmn wrote:
See also here: Calling assembly functions from C.

That does not compile due to "%b" requiring a literal. Since "c" is a local variable, I don't think it can be accessed easily here. It should work by making it global:


Not surprising, I hadn't tried compiling it myself. Good to know a static variable works as well. Wouldn't be thread safe, but I doubt anyone is doing complex multi threaded code on the 6502.



Quote:
...

Note that X gets zeroed, as per the calling convention described in the documentation linked above - you'd have to do that in the external ASM function as well.


Correct, because the return is a 16-bit integer not a pure byte. Hence the -1 to indicate an end of file or other error for getch(), otherwise -1 would turn into 255, which is a valid character.

In my Lemon code I take advantage of being able to check the fact that I can check the status flags and use the carry bit as a quick way to indicate if a return value is valid or not.

Quote:
Of course the inline ASM is not optimal due to the useless STA and write-only variable.


Yep, exactly. Let alone all the other boiler plate stack build up and unwinding that the compiler may (or may not) add on top of that.

Quote:
Interestingly, when changing the return type to "char", the generated code contains an additional redundant load:
Code:
; ---------------------------------------------------------------
; unsigned char __near__ getch8 (void)
; ---------------------------------------------------------------

.segment   "CODE"

.proc   _getch8: near

.segment   "BSS"

L0009:
   .res   1,$00

.segment   "CODE"

   jsr     $8A5A
   sta     L0009
   ldx     #$00
   lda     L0009
   rts

.endproc


Correct, there's a lot of stuff the compiler just does that seems to not make sense from a human perspective. I'm sure from the context of how the compiler keeps track of what registers are in use and are not, it might make more sense, but yea, it seems to do weird stuff like that.

There's a discussion here were we go into some of the weird things the compilers sometimes does.

This is the reason why I decided to switch to pure ASM for Lemon, the compiler was just adding too much fluff, and my goal is to fit it into the 8KByte ROM I have.


Top
 Profile  
Reply with quote  
PostPosted: Thu Nov 16, 2023 7:49 am 
Offline

Joined: Sat Feb 12, 2022 11:03 pm
Posts: 39
This is all very very interesting, I'll have to use this in future projects!

Turns out, the default libraries of the sym-1 seem to be really really good to start with, so I'm not really creating anything groundbreaking or even faster/smaller by writing getchar() myself. However, it seems writing my own memset may be an interesting idea.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC


Who is online

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