Jump to content
  • 1
SerErris

Some CC65 Questions

Question

Hi, I am working on a larger project and need to understand better how the structuring in files and the placing in memory works.

The project will be a pure assembler project and also will not use any BASIC operation.

Question 1:

I will need to define a lot of variables inside ZP. As far as I understood I can use $02-$7F and $A9-$AF. How do I need to setup the cx16.cfg ZP directive, that it does automatically assign the bytes to the allowed windows?
 

Quote

ZP:       file = "", start = $0002,  size = $FE, define = yes;

This does work (e.g compiles) to enable more ZP bytes then the normal $20-$7F ... but it obviously would just overwrite $80- $A8, which is unsave for the Kernal and might crash the machine (makes kernel functions unusable).

Question 2:

Maybe related to the first one: As I do not know how many variables I will have and may want to change them during the development process and they all have different length, I want to use the .res statement to define the length of each variable and using labels to get the symbols for the addresses of each ZP.

I did something like that:
 

Quote
.exportzp ZP,RAND,TRTB
 
 
.segment  "ZEROPAGE": zeropage
ZP:
 .res 0                 ; The start of the zero page workspace.
 
RAND:
 .res 4                 ; Four 8-bit seeds for the random number generation
                        ; system implemented in the DORND routine
 
TRTB:
 .res 2                 ; This is set by elite-loader.asm to point to the MOS
                        ; keyboard translation table, which is used by the TT217
                        ; routine to translate internal key values to ASCII

As my ZP segement starts @$02 ZP will point to $02 as well as RAND and TRPTB will point to $06. My issue with that is, that the compiler will actually create code and place it into memory after loading. Is there any other way of automatically (in opossition to calculate everything manually) define some symbols (e.g. memory addresses) but not get it actually compiled into the source code?

I can see the EXTZP segment in the cx16-asm.cfg ... but what does it mean? It has not been defined.

Also I do not understand how to  load things like LOADADR correctly. It is getting imported... from where?

That brings me to my question 3:

I like to have a main PRG file (e.g. loader) and some .bin files that keep individual parts of the real program, such as models or math library or main loop etc.

From the file structure I do understand how to create multiple source files and exporting symbols I want to use from outside. However how do I instruct the assembler/linker to create a .bin file out of a given object file? 

Lets assume I want to have the following three files out of the input files:
Loader.s => LOADER.PRG
math.s => GAME1.BIN
models.s => GAME2.BIN
mainloop.s => GAME3.BIN

Question 4:
Also I want to instruct the linker to put the GAME2.BIN @$A000 to be able to use it in a BANK. How can I use multiple BIN files using the same address space, as they are all sitting in BANKS (so all starting with $A000?)

Question 5: How do I get all this together, that all the parts can be loaded to the corresponding RAM areas? (I am not talking about the Loader here, but how does the linker get the correct addresses to math.s and how do I get those addresses to get my loader right (e.g. prepare the right bank before loading it).

I would be interested in a good documentation, that goes into those details to understand much better the building process. I see you heavily using it for the KERNAL but even there I do not even understand were it starts and how it works.

The official documentation does not go into any detail on that - it is a good reference, but not very good to learn.

Edited by SerErris

Share this post


Link to post
Share on other sites

17 answers to this question

Recommended Posts

  • 0

I have improved two of the ld65 configure files ("cx16-asm.cfg" and "cx16-bank.cfg") because of your questions.  You should update your copy of the cc65 project's files.

Answer about the original question 1:
There are two available regions in the zero-page.  Each one should have its own definition in the MEMORY section of your .cfg file.  And, each one should have its own segment.  See how I did it in the new "cx16-asm.cfg" file.  You would start by putting your zero-page variables in the first segment.  When you tried to build your program, ld65 would complain that the memory area is too big; it would tell you how much too big.  Then, you would count that many bytes back from the end of your ZP variables, and insert a ".segment" line that names the other zero-page segment.

Answer about the original question 2:
You should use the ".res" method of defining those variables.  You should create a second file that has the same name as your zero-page source file, but with a suffix of ".inc" instead of ".asm".  That header file will use the .globalzp directive to export/import all of your ZP variables.  Your ZP source file will ".include" that header.  And, any other source file that needs to use those ZP variables will include that header.  That's one way that you can pass the names and locations from file to file.

Other answers:

"__LOADADDR__" and "__EXEHDR__" come from the system library.  (Their purpose is to force ld65 to link in some objects from that library.)  You don't write any code for them -- I already did that.  You tell ld65 about them by putting the library's name as the last one on the ld65 command line:

ld65 -C x16-asm.cfg -o HELLO.PRG hello.o cx16.lib

ld65 will find those names in that library, and it will link in the stuff for the main load address and the BASIC SYS stub.  Your main program should start with the "CODE" lines.  (As Ender wrote, cl65 gives the library name to ld65 for you; you don't need to remember it.)

You're correct about Matt's "hello.asm" file.  ".org" lines must not be there!!  And, the ".segment" lines aren't needed by you -- because you aren't using "cx16.cfg".

Yes, the cc65 project's "cx16-bank.cfg" is like "cx16.cfg".  It is written for C programs.  But, you can use it as an example template when you create the bank things in your .cfg file.

__BANKRAMADDR__, also, would be imported from the system library.  But, you might create your own version of the "bankramaddr.s" file.  Therefore, don't put that line into your .cfg file.

__HEADER_LAST__ and __ONCE_RUN__ are generated by ld65 because of "define = yes" attributes on some of the lines.  __HEADER_LAST__ is the address of the next byte after the actual end of the HEADER memory area.  Therefore, MAIN will start immediately after HEADER.  __ONCE_RUN__ is used as another way to create an overlay.  It is the address where the ONCE segment is put in RAM.  Therefore, BSS will be put on top of ONCE; that part of RAM will be reused.  The ONCE segment is useful for C program initiation, you don't need to care about it.

The BSS segment is used for ".res" variables that you don't put into the zero-page.  RODATA is for data that never is changed (messages, for example).  DATA is for ".byte", ".word", and similar variables that must have specific values when the file is loaded, but that will be changed by the program.  Again, that separation mainly is for C programs, but Assembly code can use them, too.  ca65 has convenient nicknames for those ".segment" directives: ".zeropage", ".code", ".rodata", ".data", and ".bss".

I recommend that you put the "__EXEHDR__" line in your .cfg file (if you haven't already).  (It will provide the BASIC SYS header.)

I saw a $D000 in your .cfg file.  That's for the C64, not the X16!  I use the symbol name __HIMEM__ (high memory) to set the proper address -- you should do it as I did it.

Edited by Greg King
  • Thanks 1

Share this post


Link to post
Share on other sites
  • 0
  1. I believe the best way to account for the KERNAL variables is to create two seperate memory areas, with two corresponding segments. The linker should warn you when one of the segments becomes too large. I would be careful using the virtual register area for zero page variables, as some of these (specifically r11-r15) are marked as a scratch area for KERNAL calls.
  2. If you add "type = zp" when you define a zeropage memory segment, the compiler should not try to write the code to a file. However, the empty file definition on the zeropage memory area should already stop it from outputting code. I believe LOADADDR and HEADER are only automatically imported if you are using the C compiler, so you should fill them with the load address and the BASIC loader in your main source file.
  3. cc65 is primarily designed to link multiple source files into one executable, instead of converting multiple source files into multiple loadable modules. Additionally, the Commander X16 does not perform any relocation when it loads code into memory, so the modules would need to be loaded into the same area of memory every time. It is much easier to load and unload data, because it does not rely on being loaded at a specific address to function. I would link all of the code into one executable, and load the data from BIN files when it is needed. If you want to create loadable code modules, there may be a way in the cc65 documentation (most of the documentation pertaining to linking and segments is under the ld65 section).
  4. I am assuming that according to the information given above, GAME2.BIN would contain model data. If models.s does not contain any code or references to labels, then it should not matter what address the assembler believes the data is located at. Simply compile it into a file named GAME2.BIN.
  5. I have never tried to create multiple code modules or loading code into banked memory. The KERNAL uses multiple cfg files to manage the complexity of splitting the code across multiple memory banks. It appears that creating multiple modules and loading code into banked memory, although possible, is very difficult. Therefore, I would recommend keeping all code in one PRG file as much as possible. If you need to have your program in multiple files, there may be other people who know how to do this.

Good luck with your program!

  • Thanks 1

Share this post


Link to post
Share on other sites
  • 0

I'll add on to Elektron72's post with a little more advice.  A good place to see how to use cc65 is to look at the X16 kernal's cfg setup in their github (it's basically how I learned cc65, in addition to the documentation).

https://github.com/commanderx16/x16-rom/tree/master/cfg

It uses the preprocessor of cc65 to process those cfgtpl files into cfg files. 

Like Elektron72 said, you probably don't want to define a code segment for banked RAM if you're just planning on loading data there.  The way people typically do it is to just compile them into their own data file, then in their code use the kernel load routine to load them into whichever memory location they need.  If you really want to understand it though, you can see in the kernal rom, the way it does banked memory is to simply define the $C000 segments multiple times, for example.  The bank it's assigned to is determined by the order they appear in the cfg file.  The way it works in the X16 rom in particular, they create a .bin file for each banked rom module, which all start at $C000, then they just concatenate the .bin files together to make the .rom file.

One more thing, you should take a look at cl65 if you haven't already. It's designed to make a lot of this stuff simpler, without having to worry about cfg files. That file you looked at, cx16-asm.cfg, cl65 uses that file for it's compilation.  With that you can simply use those predefined segments, such "ZEROPAGE", when you want to use the zeropage.

https://cc65.github.io/doc/cl65.html

 

Edited by Ender
  • Thanks 1

Share this post


Link to post
Share on other sites
  • 0

Answer 2:

In "cx16-asm.cfg", EXTZP was a "rollover" from "cx16.cfg".  Assembly code that needs some zero-page locations must use EXTZP if that code will be used in C programs that use dynamically loaded drivers.  (Those drivers need cc65's ZP variables to be in a guaranteed place.  But, it wouldn't be guaranteed if the Assembly code shared the ZEROPAGE segment.)  However, if a C program won't use dynamic drivers, then it won't matter whether the Asm. code uses ' .zeropage ' or ' .segment "EXTZP" '.

__LOADADDR__ automatically is imported from this library file.  __EXEHDR__ is imported from this library file -- if "cx16.cfg" (or "cx16-bank.cfg") is used, or if "-u __EXEHDR__" is used in a cl65 command line.

Answers 3, 4, and 5:

What you want to do can be done in the same way that overlays are done on the C64.  I created "cx16-bank.cfg" with that idea in mind.  You would copy it into your project, then modify it to suit your needs.  You need to put two lines in the "MEMORY" section, and two lines in the "SEGMENTS" section, for every bank that you want to load from a file -- make sure that you set the "bank =" attribute correctly for each bank.

You can set all of the load addresses in one file that looks like this library file.  Or, you can set each one in each source file that starts a bank binary file.  If you do them separately, for each file, then you should remove the "__BANKRAMADDR__:" line from your .cfg file.

When your code wants to access a labeled location in banked RAM, it will need to know which bank.  It can use ca65's ".bank()" function to get that info.

Edited by Greg King
Changed a verb to past-tense.
  • Thanks 1

Share this post


Link to post
Share on other sites
  • 0

Hi all, thanks for all your input. A lot of stuff to digest.

Sorry for that very long post. I really try to work my way into cc65 environment as it has been said to be the go to solution for development on X16. I have to admit that I do not understand a lot based on the documentation. It seems like none of the documentation is actually written with pure assembly in mind. I have now invested a lot of hours trying to learn how it works and even with your input here I cannot figure it out yet.

So please forgive me for those questions and the long wordings around it. I do not have any better way to ask them or to find my way through. 
 

If you have a better way to get this asked and answered or have a tutorial for CC65 specifically focussing on pure assembler (does not have to be X16 related) then please let me know. 

So now on to my questions

 

Regarding Question 1: the ZP assignment of the two segments we have in there:
I understood - there is no automatic way to let the linker assign the variables to addresses if you grow out of one of the two ZP areas. Doing it manual is obviously a way, but I thought there would be a automation. So then I can just setup the ZP as the full segment (e.g. $02-$FF) to define ZP addresses and manually do the split at the point when it runs out of the 1st ZP range. I am will put in a debug output to help to find that specific point in my source code ( .out sprintf("Symbol Name %04X",SYMBOL) ). 

Regarding Question 2: I will check again if it creates a file .. but as I have it in a source file on its own, I believe it would need to create at least an object file, otherwise the exporting of symbols would not work? The other question was, how I can define the Symbols in a way that the assembler/linker calculates corresponding addresses. I stumbled accross the include file for the KERNAL roms where it is managed by setting the first address (e.g.$02) and from there let the assembler calculate the variables - so no .res statement, but SymbolY=SymbolX+2. I would then just include that file into every source file to have those symbols available. This method would also remove the need to export all the variables. However I will continue to try the .res x method with labels to understand if that works (or maybe works better).

Regarding Question 3-5:
Got those points. I still quite do not understand all the stuff in the .cfg files esp the __SOMETHING__ directives or keywords ... 
 

Things I do not understand:

First:

Where do I get __LOADADDR__ from? I do not have any C in there and also do not use cc65 and want to be able to use ca65 and ld65 instead of cl65 ... so how or what do i need to put into my .asm files to get __LOADADDR__ populated and avoid that the linker complains about this unset symbol?

I tried
.org $A000
.export LOADADDR = *

But that did not remove the error from ld65 ... 

Second:

How to use the .segment statements in the .asm files?

I have seen the Hello-World.asm from Matt. 

.org $080D
.segment "STARTUP"
.segment "INIT"
.segment "ONCE"
.segment "CODE"

Questions:
1. Why do we have the .org instruction in there? Should the linker not automatically place it?
2. All that .segment statements ... What are they for? I simply cannot get my head around what the logic behind it. To my understanding the first three do nothing as the .segment control command just switches the segment. Correct? So the first three .segment statements just can go away? and the only relevant .segment is "CODE" ?

Third: @Greg Kingreading your cx16-bank.cfg I still see a lot of C stuff in there. And for some of the parameters I have no idea how to produce them:

Looking at this section from 

SYMBOLS {
  __LOADADDR__: type = import;
  __EXEHDR__: type = import;
  __BANKRAMADDR__: type = import;
  __STACKSIZE__: type = weak, value = $0800; # 2K stack
  __HIMEM__: type = weak, value = $9F00;
  __BANKRAMSTART__: type = export, value = $A000;
  __BANKRAMSIZE__: type = weak, value = $2000; # 8K banked RAM
  }
   


Similar to __LOADADDR__ how do I define __BANKRAMADDR__ and what is it used for? It is never referenced anywhere later on. Only __BANKRAMSTART__ and __BANKRAMSIZE__ are getting used later on. Also __EXEHDR__  What is it? Do I need it for assembler?

The next section is giving me questionmarks:
 

MEMORY {
  ZP: file = "", define = yes, start = $0022, size = $0080 - $0022;
  LOADADDR: file = %O, start = %S - 2, size = $0002;
  HEADER: file = %O, define = yes, start = %S, size = $000D;
  MAIN: file = %O, define = yes, start = __HEADER_LAST__, size = __HIMEM__ - __HEADER_LAST__;
 

BSS: file = "", start = __ONCE_RUN__, size = __HIMEM__ - __ONCE_RUN__ - __STACKSIZE__;

 

So MAIN is depending on __HEADER_LAST__ which is again not defined anywhere.
BSS what is that? And another symbol in here with no definition (not getting imported or anywhere else from)  __ONCE_RUN__ . 

And then the segments ... I do not even know where to start:

SEGMENTS {
  ZEROPAGE: load = ZP, type = zp;
  EXTZP: load = ZP, type = zp, optional = yes;
  LOADADDR: load = LOADADDR, type = ro;
  EXEHDR: load = HEADER, type = ro;
  STARTUP: load = MAIN, type = ro;
  LOWCODE: load = MAIN, type = ro, optional = yes;
  CODE: load = MAIN, type = ro;
  RODATA: load = MAIN, type = ro;
  DATA: load = MAIN, type = rw;
  INIT: load = MAIN, type = rw;
  ONCE: load = MAIN, type = ro, define = yes;
 

BSS: load = BSS, type = bss, define = yes;

 

I understand ZP and CODE, I understand DATA - it is just there to differentiate CODE from DATA in Memory blocks (not BANKs). I do not understand all the rest. Is it at all relevant for programming in Assembler, or can it get simply removed?

This is from x16-asm.cfg:

FEATURES {
  STARTADDRESS: default = $0801;
}
 
SYMBOLS {
 
  __LOADADDR__: type = import;
}
 
MEMORY {
 
  ZP: file = "", start = $0002, size = $00FE, define = yes;
  LOADADDR: file = %O, start = %S - 2, size = $0002;
  MAIN: file = %O, start = %S, size = $D000 - %S;
}
 
SEGMENTS {  
  ZEROPAGE: load = ZP, type = zp, optional = yes;
  LOADADDR: load = LOADADDR, type = ro;
  EXEHDR: load = MAIN, type = ro, optional = yes;
  CODE: load = MAIN, type = rw;
  RODATA: load = MAIN, type = ro, optional = yes;
  DATA: load = MAIN, type = rw, optional = yes;
  BSS: load = MAIN, type = bss, optional = yes, define = yes;
}
 

That looks much more assembler to me and I would assume I just need to add the Bank information there, correct? Bank switching is anyhow something I need to handle myself in my code.

Still I have BSS which I do not understand what it is - but I could ignore it as it is optional. 

So I just would need to place segments into banks and let the linker create multiple files (one for the CODE) and one for each Bank. 

Is that a correct understanding?

Share this post


Link to post
Share on other sites
  • 0

Okay I played around with it a little bit more.

I stripped down my .cfg to that (please find attached)  

x16-asm.cfg

I have setup the following code, that should from my understanding give me the right output:

.export __LOADADDR__ = $0801
 
.segment "LOADADDR"
;  .word $0801     ;this has been uncommented and was not there in the original file .. this is the workaround
.segment "EXEHDR"
  ;put BASIC start into program file (SYS 2061) $80E
  .byte $0b,$08,$0a,$00,$9e,$32,$30,$36,$31,$00,$00,$00
 
.segment "CODE"
  ldx #$00                    ;initialize index
 
loop:
  lda hellostring,x          ;Load character from string

I compiled it with:

ca65 -t cx16 hello.asm
ld65 -C x16-asm.cfg -o HELLO.PRG hello.o

However it does not .. (at least I found a way to set a Symbol that LD does not complain anymore).

HxD output of HELLO.PRG

The output looks like that (please see the first two bytes, that should read 01,08 ... however that is actually my first basic byte loader ($0b,$08...). The result is obviously, that the LOAD" command in X16 loads the code to $080b and not to $0801. 
The __LOADADDR__ is never used anywhere in the .cfg file - but needs to be there otherwise the ld complains about a missing symbol and the two bytes required in the header of the PRG file are simply not getting inserted.

I found this workaround which is putting the word $0801 simply in the code (uncommenting it). That will create the correct load address .. 

So what is going wrong? Should the __LOADADDR__ not have instructed the linker to add the two bytes to the file? It looks like the linker does not add the loading address (or Startaddress) to the PRG file at all.. IT links the code to the correct STARTADDRESS however.
 

Here for convenience the relevant sections of the x16-asm.cfg again (removed the banks for simplicity here):

FEATURES {
    STARTADDRESS: default = $0801;
}
SYMBOLS {
    __LOADADDR__: type = import;
    __BANKRAMSTART__: type = export, value = $A000;
    __BANKRAMSIZE__:  type = weak,   value = $2000; # 8K banked RAM
}
MEMORY {
    ZP:       file = ""start = $0002,  size = $FF-$02,      define = yes;
    LOADADDR: file = %O, start = %S - 2, size = $0002;
    MAIN:     file = %O, start = %S,     size = $D000 - %S;
 
}
SEGMENTS {
    ZEROPAGE: load = ZP,       type = zp,  optional = yes;
    LOADADDR: load = LOADADDR, type = ro;
    EXEHDR:   load = MAIN,     type = ro,  optional = yes;
    CODE:     load = MAIN,     type = rw;
    RODATA:   load = MAIN,     type = ro,  optional = yes;
    DATA:     load = MAIN,     type = rw,  optional = yes;
    BSS:      load = MAIN,     type = bss, optional = yes, define = yes;
 
}

 

Last point:

1911151469_Screenshot2020-09-21122954.jpg.86fa5de291255e67524c24a2f7b19b1d.jpg

If we look here again, you can see my "HELLO WOLRD!" String that starts from $1F. As you can see it is totally garbled and not PETSCII code. I tried .asciiz and .byte ... both lead to the same result. I used the -t cx16, which should lead to PETSCII code instead ... any Idea? 

/EDIT: Same issue with the RAM bank (e.g. my models) It is missing the two bytes of the loader address (should be $A000) to my understanding... But it is simply missing.

 

Edited by SerErris

Share this post


Link to post
Share on other sites
  • 0
4 hours ago, SerErris said:

Questions:
1. Why do we have the .org instruction in there? Should the linker not automatically place it?
2. All that .segment statements ... What are they for? I simply cannot get my head around what the logic behind it. To my understanding the first three do nothing as the .segment control command just switches the segment. Correct? So the first three .segment statements just can go away? and the only relevant .segment is "CODE" ?

This is what tells ca65 to create the BASIC SYS instruction at the beginning. Don't kill yourself to roll it on your own! Just use that in your assembly "main" and then you can link in other stuff from there.

4 hours ago, SerErris said:

want to be able to use ca65 and ld65 instead of cl65

I would still recommend using cl65 for the linking step. Using ld65 on its own doesn't really give you much. You can use ca65 to build all your object files, and then cl65 to link them together and get your PRG.

If you are letting the link automatically place all the object files where it wants in the CODE segment, you don't even need .segment statements in any other assembly file, as CODE is assumed. You can also have data put in the CODE segment in between subroutines - the 6502 police won't arrest you! 

  • Like 1

Share this post


Link to post
Share on other sites
  • 0
22 minutes ago, SlithyMatt said:

This is what tells ca65 to create the BASIC SYS instruction at the beginning. Don't kill yourself to roll it on your own! Just use that in your assembly "main" and then you can link in other stuff from there.

I would still recommend using cl65 for the linking step. Using ld65 on its own doesn't really give you much. You can use ca65 to build all your object files, and then cl65 to link them together and get your PRG.

If you are letting the link automatically place all the object files where it wants in the CODE segment, you don't even need .segment statements in any other assembly file, as CODE is assumed. You can also have data put in the CODE segment in between subroutines - the 6502 police won't arrest you! 

I still cannot figure out, why LD does not put the load address to the beginning of the output file. From verbose output it looks like cl65 is allways linking the cx16.lib to it (which most likely contains the startup routines ... )
ld65 -v -o LOADER.PRG -C cx16-bank.cfg loader.o ship.o cx16.lib  ... but again figuring out in the cx16.lib source code how it is done ... not possible for me.

/EDIT: But for now I am sticking to cl65 

I am coming more and more to the conclusion that cc65 is great for C and simply completely overshoot for assembler and does not add anything to ACME other you can have multiple files ... The later is somehow great as you can simply collect some functional stuff in individual files, however also risks to overshoot with to many files and the complete code becomes unreadable.

The only thing that is great is that you do not think about placing of stuff anywhere in the memory as that is automatically done and you do not waste a single byte... 

Edited by SerErris

Share this post


Link to post
Share on other sites
  • 0
3 minutes ago, SerErris said:

however also risks to overshoot with to many files and the complete code becomes unreadeable

I would contend that splitting up the code into multiple files is generally more readable than one big assembly file.

What I have done with most of my projects is rather than linking separate object files is to just have an include tree starting from the main file, and then you only need a single cl65 call to build the whole thing. This will preclude you from using any C code in your project, but if you are going all assembly it has some nice side effects. Most notably, the machine code listing file (generated with -l) for the main will contain everything in your include tree, showing effectively where every bit of code and inline data will be loaded in RAM.

  • Like 1

Share this post


Link to post
Share on other sites
  • 0
Quote

I still cannot figure out, why LD does not put the load address to the beginning of the output file. 

ld65 doesn't but cl65 does.  That's one of the reasons to use cl65, it does a lot of these things for you, whereas ld65 it's pretty much all manual.

  • Thanks 1

Share this post


Link to post
Share on other sites
  • 0

LOL another # issue (LDA #<address is different then LDA <address (which works btw) but obviously not with the right content.

 

Edited by SerErris

Share this post


Link to post
Share on other sites
  • 0
9 minutes ago, Greg King said:

You should use the ".res" method of defining those variables.  You should create a second file that has the same name as your zero-page source file, but with a suffix of ".inc" instead of ".asm".  That header file will use the .globalzp directive to export/import all of your ZP variables.  Your ZP source file will ".include" that header.  And, any other source file that needs to use those ZP variables will include that header.  That's one way that you can pass the names and locations from file to file.

That is very C like (header files describing the external interface). Now I need to see if I use include with constants (that avoids all the exporting/importing) or if I use the header file approach.

Thanks for all your input @Greg King I will integrate it into my thoughts about the proper layout. 
 

What I like about cc65 tool chain is, that you simply let the linker do its job an put everything in place. So you actually only need to ensure it fits into the memory (or think of ways of splitting it).  All the rest will be done by the linker. However it requires a little bit more of preparation around the individual files.

One last question regarding scopes. Is the scope inside a .s/.asm file automatically local? So any variable will be only local to the file until I export them or import others from outside?

Share this post


Link to post
Share on other sites
  • 0

Yes. Any variables or subroutines that need to be used in multiple files must be exported in one, and imported in another. Most of my assembly source files have a large amount of .import statements at the top.

  • Like 1

Share this post


Link to post
Share on other sites
  • 0
2 hours ago, SerErris said:

LDA <address (which works btw) but obviously not with the right content.

It is sort of a nonsense instruction, but it is valid syntax wise. Effectively, it will turn it into a LDA from the zero page, using the lower 8-bits of address as the ZP address.

So, you could have this:

CHROUT = $FFD2

.org $44

zp1: .byte "a"

zp2: .byte "b"

.org $80D

lda address

jsr CHROUT

lda <address

jsr CHROUT

lda >address

jsr CHROUT

; lda #address  -- Don't do this. The assembler will fail. You need to specify a single byte to load

lda #<address

jsr CHROUT

lda #>address

jsr CHROUT

.org $4445

address: .byte "c"

 

When you run, you will get the following output:

CBAED

We get this trickery by using addresses that can all be interpreted as ASCII/PETSCII characters

So, you can see that the actual data is:

$44: 41 ("A")

$45: 42 ("B")

$4445: 43 ("C")

And if we get rid of the "address" symbol and the operators, our LDA instructions (with CHROUT calls also removed) become:

lda $4445

lda $45

lda $44

lda #$45 (immediate "E")

lda #$44 (immediate "D")

 

Edited by SlithyMatt
typos
  • Like 1

Share this post


Link to post
Share on other sites
  • 0

The good thing is, that the assembler put out a warning about it. But I was puzzled at first as it looked goot - then finding out great: again a missing # ... 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...

Important Information

Please review our Terms of Use