Calypsi C compiler toolchain for 6502 and 65816

Programming the 6502 microprocessor and its relatives in assembly and other languages.
User avatar
Proxy
Posts: 746
Joined: 03 Aug 2018
Location: Germany

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by Proxy »

hth313 wrote:
The startup code is provided with the product, both in precompiled form in the library and as source code. This code is short and cruical to get the system initialized properly before you enter the main() function. It is not only doing data copying, it is also clearing variables that are 0 initialized. There may also be multiple areas that needs to be initialized.
well i'm blind, i found the file in the obvious named "src" folder. thank you!
hth313 wrote:
What output format do you use? Many formats (e.g. ELF, intel-hex, S-records) contain a concept of an entry point that is supported. If you are doing Commodore PRG then https://github.com/hth313/Calypsi-6502-Commodore may offer some inspiration, though it is for 6502.
Just Raw Binary, the header is completely custom and programs are loaded via Serial
to be more specific my custom header is 32 bytes large, first 4 bytes are a hardwired magic number (0x65C81600 in this case), next is a 32-bit address that defines where in memory the program should be loaded into, next is the total amount of bytes to load (32-bit value), and the last one is a 32-bit address that specifies where execution should jump to after loading is done, in order to start the program.
hth313 wrote:
The linker error is because __program_start which is the first executable address must reside in bank 00 as the reset vector is 16 bits, but it seems you have placed it in bank 02. This should be section 'code'.
well that won't be possible, programs will only ever get loaded into "HiRAM" ie bank 2 and above.
there is no reason to have it load code into bank 0 since the boot ROM will automatically do a long jump to the header specified address after the whole program has been loaded into memory.
the startup code should never have to deal with any of the vectors at all, since they are hardwired in the boot ROM.

so i'll modify the startup code to just remove the whole reset section, and put the address of __program_start into the header, hopefully that works.
though the assembler syntax will take some getting used to. plus it's like 5 in the morning here and i haven't slept yet, so i'll do all of that after a quick nap.

thanks for helping me getting this running
hth313
Posts: 30
Joined: 07 Oct 2018

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by hth313 »

Proxy wrote:

so i'll modify the startup code to just remove the whole reset section, and put the address of __program_start into the header, hopefully that works.
though the assembler syntax will take some getting used to. plus it's like 5 in the morning here and i haven't slept yet, so i'll do all of that after a quick nap.

thanks for helping me getting this running
That should work fine, just remove the reset vector section. You may need to move the __program_root_section label next to __program_start label. It is used to mark the first section to be included by the linker.
User avatar
Proxy
Posts: 746
Joined: 03 Aug 2018
Location: Germany

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by Proxy »

I came across a problem where i was unable to use the assembler, no matter what input parameters or what was in the assembly file it would just silently crash with no error given or anything.
the only reason i knew it crashed was because my batch file checks for %errorlevel% being non 0 after calling AS65816.

I wrecked my brain on this trying to understand what is happening, until i treid it with the unmodified cstartup.s, which did work. but then i changed the file extension and it stopped working.
and i can't say that i'm a big fan of the assembler forcing a specific file extension to be used (also the fact that it doesn't even give the user an error message to work with).
For a compiler it makes sense, .c is always a C source file, .h always a header. But those are standardized... Assembly on the other hand comes in many different flavors, that's why i seperate them all with the file extensions. a65 for 65c02, a816 for 65816, a86k for 68k, arv for RISC-V, etc.
both cc65 and WDC's CC don't care what the files are, if you tell them to assemble some file they'll do it without question.

so please, just have the assembler ignore file extensions for convenience. leave that up to the user to handle.

on a different note how do you import a section into an assembly file?
i got this like in the header used to calculate how large the program is:

Code: Select all

.long (.sectionEnd far - .sectionStart farcode) - 17
but the assembler says that it cannot find the section named "far". the command i'm using is:

Code: Select all

AS65816 --code-model large --data-model large -o cstartup.o cstartup.s
hth313
Posts: 30
Joined: 07 Oct 2018

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by hth313 »

I know the Clang system (I use its preprocessor for the assembler) has some ideas of file extensions and that they change the meaning of the input. Also, as you say, assembler extensions are all over the place. I will see if I can make it less picky about the extension used for assembly language, as the assembler binary knows what to expect...

To "import" a section, you need to mention it, like ".section sectionName", that makes it known. You do not need to put anything into (below) it.
User avatar
Proxy
Posts: 746
Joined: 03 Aug 2018
Location: Germany

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by Proxy »

ah! so like the ".section stack" and such at the start of the cstartup file?

but now I'm getting this error:

Code: Select all

cstartup.s:35: expression cannot be represented in object file format
        .long (.sectionEnd far - .sectionStart farcode) - 17    ; How many Bytes to load (Total Program Size - 17)
Terminating due to errors
this should just calculate the size of the file and subtract 17 from it.
the scm file has this line:

Code: Select all

(section farcode code farswitch data_init_table cfar chuge far huge zfar zhuge cstack))
i assume segments are placed in the order of how you put them in this list, so "far" will be the last segment in the output file and "farcode" will be the first.
so to get the size of the output file i just toke the end address of the "far" segment and subtract the start of the "farcode" segment from it, but aparently that won't work?
what alternative way is there?

if it helps here is the full header:

Code: Select all

    .section farcode, noreorder
    .pubweak __program_root_section
__program_root_section:
    .byte 0x65, 0xC8, 0x16, 0x00                            ; Magic Number
    .long .sectionStart farcode                             ; Where to load the Program into Memory
    .long (.sectionEnd far - .sectionStart farcode) - 17    ; How many Bytes to load (Total Program Size - 17)
    .long __program_start                                   ; Entry point of the Program
immediately after the header is the __program_start label with the rest of the code.

also once i actually have an object file, do i have to replace the original cstartup.o in the standard library? i assume i can i just remove it from the library and include the object file in the linker manually for convenience.
sanny
Posts: 6
Joined: 01 Sep 2017

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by sanny »

Proxy wrote:
but after a quick round of google, the C standard just doesn't have binary notation because aparently "hex is good enough"
Octal and decimal are "good" as well....
hth313
Posts: 30
Joined: 07 Oct 2018

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by hth313 »

Proxy wrote:
ah! so like the ".section stack" and such at the start of the cstartup file?
Yes, that just makes it know to the assembler, it does not put anything into the stack or define its size in any way.
Quote:
but now I'm getting this error:

Code: Select all

cstartup.s:35: expression cannot be represented in object file format
        .long (.sectionEnd far - .sectionStart farcode) - 17    ; How many Bytes to load (Total Program Size - 17)
Terminating due to errors
The error you get is because the expression is too complex for the ELF format and the available relocations to describe it. The crucial thing here is that the relocation/expression refers to two different sections.

What you are trying to accomplish is better solved by having the linker generate an output file in the desired format. Does the format you use have a name, is it officially known? Then I can add it.

If not, there is PGX and PGZ formats used by Foenix that looks very similar. Would it be possible for you to use either of them? PGZ is currently supported by the linker while PGX is not. The difference is essentially that PGX has a single chunk of code while PGZ supports multiple chunks (if you have that) and PGZ works with both 65816 and 68000 while PGX is only for 65816.

In either case, I prefer that this is solved by an output format, as that is what it is about.
Quote:
also once i actually have an object file, do i have to replace the original cstartup.o in the standard library? i assume i can i just remove it from the library and include the object file in the linker manually for convenience.
When you replace the C startup module you can just add it to the command line. You do not need to remove it from the library and I do not know how to do that in an easy way. The linker is able to live with multiple C startup modules in libraries and there is a mechanism to pick a specified one. This is done using the run-time attribute which is called cstartup, in the original one it has the value "normal". You can put any suitable string there to name you variant and then tell the linker to use it on the command line with --rtattr cstartup=whatever.
User avatar
Proxy
Posts: 746
Joined: 03 Aug 2018
Location: Germany

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by Proxy »

hth313 wrote:
The error you get is because the expression is too complex for the ELF format and the available relocations to describe it. The crucial thing here is that the relocation/expression refers to two different sections.
wait so it's a limitation of the object file format? then what is cc65 using that it works there? or is it because cc65's section symbols are just regular old symbols that get defined at the linking stage, instead of special ".sectionxyz" directives?
hth313 wrote:
What you are trying to accomplish is better solved by having the linker generate an output file in the desired format. Does the format you use have a name, is it officially known? Then I can add it.

If not, there is PGX and PGZ formats used by Foenix that looks very similar. Would it be possible for you to use either of them? PGZ is currently supported by the linker while PGX is not. The difference is essentially that PGX has a single chunk of code while PGZ supports multiple chunks (if you have that) and PGZ works with both 65816 and 68000 while PGX is only for 65816.
it is not an official header, i designed by and for myself to be the "simpliest universal executable header possible" since it's not specific to any one system or architecture.
looking at the ones you mentioned, PGX wouldn't be a good choice as it doesn't specify the size of the program, making it impossible to load from Serial as the CPU wouldn't know when to stop reading/waiting for more data.
PGZ seems good though, i'd like it more if the signature was larger so it can be used for CPU/System Identification. but whatever, it'll do.
hth313 wrote:
In either case, I prefer that this is solved by an output format, as that is what it is about.
any specific reason for that? wouldn't that just mean more work for you instead of just having the users do it themselves in assembly (when possible of course)?
hth313 wrote:
When you replace the C startup module you can just add it to the command line. You do not need to remove it from the library and I do not know how to do that in an easy way. The linker is able to live with multiple C startup modules in libraries and there is a mechanism to pick a specified one. This is done using the run-time attribute which is called cstartup, in the original one it has the value "normal". You can put any suitable string there to name you variant and then tell the linker to use it on the command line with --rtattr cstartup=whatever.
oh i see, so the librarian utility is just a bit bare bones at the moment, i assume someday in the future it will get some more features like adding/removing/extracting individual object files, listing all the symbols in the library, etc.
anyways doing that worked fine and i was actually able to generate an output file for once! milestone reached! :lol:

but the victory was short lived because the generated .pgz file is only 7 bytes large despite the linker's generated .lst file saying that the executable section is 494 bytes in size... so where is all of that code? (btw why is the startup code 490 bytes? that seems a little much)
the .pgz file consists of the "Z" signature byte (5A), a 24-bit address that points to some random location in the "cstack" section (E9 41 02 -> 0x0241E9), and lastly a 24-bit word that is just all 0's.
the c program i'm compiling is just just a main function with a "return 0" at the end.
here the command i used for the linker:

Code: Select all

LN65816 --hosted --rtattr cstartup=SBCv1 -l --output-format pgz -o test.pgz Config\SBC_816.scm Config\cstartup.o Temp\test.o clib-lc-ld-double64.a
and the generated lst file:

Code: Select all

####################
#                  #
# Memories summary #
#                  #
####################

Name       Range         Size    Used    Checksum  Largest unallocated
----------------------------------------------------------------------
DirectPage 000100-0001ff 000100    7.8%  none      0000ec
LoRAM      000200-00efff 00ee00   53.8%  none      006e00
IO         00f000-00f0ff 000100    0.0%  none      000100
VRAM       010000-01ffff 010000    0.0%  none      010000
HiRAM      020000-08ffff 070000    3.7%  none      06bd8e


####################
#                  #
# Sections summary #
#                  #
####################

Name            Range          Size    Memory     Fragments
-----------------------------------------------------------
registers       000100-000113  000014  DirectPage 1
stack           000200-0041ff  004000  LoRAM      1
heap            004200-0081ff  004000  LoRAM      1
cstack          020000-023fff  004000  HiRAM      1
zfar            024000-024083  000084  HiRAM      2
farcode         024084-024261  0001de  HiRAM      9
data_init_table 024262-02426b  00000a  HiRAM      1
farcode         02426c-024271  000006  HiRAM      3


##########################
#                        #
# Memory sizes (decimal) #
#                        #
##########################

Executable       (Text):   494 bytes
Data                   :   132 bytes
Non-initialized        : 49172 bytes
interestingly, when i change "--output-format pgz" to "--output-format raw" to see if it's just a problem with the format, the linker straight up crashes with this error:

Code: Select all

internal error: Translator\Linker\Output\Raw.hs:14:1-60: Non-exhaustive patterns in function dumpRawProgram
so am i doing something wrong here and the linker won't tell me, or is it just having a bad day?
hth313
Posts: 30
Joined: 07 Oct 2018

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by hth313 »

Proxy wrote:
wait so it's a limitation of the object file format? then what is cc65 using that it works there? or is it because cc65's section symbols are just regular old symbols that get defined at the linking stage, instead of special ".sectionxyz" directives?
Unresolved expressions are turned into relocations. What relocations are available depends on the target. In general, relocations are really simple and meant to resolve a single location with an offset. The result can be stored into the code stream in different ways, typically needed by the target at hand are provided.

The section start, end and size are just a name mangled symbol in Calypsi that is deciphered by the linker. This is of course non-standard, but it was the only way I could see that could do it. I limit my extension to a single section, no arbitrary expressions are allowed in ELF, which is why it failed. It could potentially be done in a different (non-standard) way, but that is how I implemented it.
Quote:
it is not an official header, i designed by and for myself to be the "simpliest universal executable header possible" since it's not specific to any one system or architecture.
looking at the ones you mentioned, PGX wouldn't be a good choice as it doesn't specify the size of the program, making it impossible to load from Serial as the CPU wouldn't know when to stop reading/waiting for more data.
PGZ seems good though, i'd like it more if the signature was larger so it can be used for CPU/System Identification. but whatever, it'll do.
PGZ is defined for 65816 and 68000, well, any byte oriented target that usues either 24 and 32 bit address ranges.
Quote:
hth313 wrote:
In either case, I prefer that this is solved by an output format, as that is what it is about.
any specific reason for that? wouldn't that just mean more work for you instead of just having the users do it themselves in assembly (when possible of course)?
Well, you can see from all the hoops you are trying to do that it takes time. It is that "when possible" and trying to figure out how to do it. The header and metadata also exists in memory when done this way, when it really just is supposed to be in the file.

The PGZ format is about 20 lines of code for me, it is a simple format. Adding another (simple) format is not all that expensive, but there is of course long term maintenance of it. I think the best is to use an existing format, if there are limitations with them, then I think the second best is to add a new format to take care of that use-case. It may also be possible to write a simple converter tool.
Quote:
oh i see, so the librarian utility is just a bit bare bones at the moment, i assume someday in the future it will get some more features like adding/removing/extracting individual object files, listing all the symbols in the library, etc.
The file format used is one of the two that is common on Unix, you should be able to use other (existing) tools to manipulate the library archives.
Quote:
anyways doing that worked fine and i was actually able to generate an output file for once! milestone reached! :lol:

but the victory was short lived because the generated .pgz file is only 7 bytes large despite the linker's generated .lst file saying that the executable section is 494 bytes in size... so where is all of that code? (btw why is the startup code 490 bytes? that seems a little much)

[...]

interestingly, when i change "--output-format pgz" to "--output-format raw" to see if it's just a problem with the format, the linker straight up crashes with this error:

Code: Select all

internal error: Translator\Linker\Output\Raw.hs:14:1-60: Non-exhaustive patterns in function dumpRawProgram
so am i doing something wrong here and the linker won't tell me, or is it just having a bad day?
The internal error is caused by a bug. The Raw format only supports one "progbits" memory and there is a guard that checks if there are multiple. In this case it is none and that error guard did not trigger, but the code later did as it expected one. I need to add another error guard for this case.

The list file looks a bit fishy in that it seems like it has managed to mix zfar and farcode in the same memory, which is not allowed. zfar is bss (no bits) and farcode has bits.

What version are you using of Calypsi 65816? Is your project available on some hosting service so that I can take a look at it?

I would like to see the .scm file.

Rather than discussing here, maybe you could file an issue at https://github.com/hth313/Calypsi-tool-chains
User avatar
Proxy
Posts: 746
Joined: 03 Aug 2018
Location: Germany

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by Proxy »

hth313 wrote:
The list file looks a bit fishy in that it seems like it has managed to mix zfar and farcode in the same memory, which is not allowed. zfar is bss (no bits) and farcode has bits.
I'll open an issue on that Internal bug in a minute, but i just want to quickly jump on this bit here.
i thought we already solved that a page ago or something with the "--hosted" commandline parameter?

EDIT: well just removing all the sections seems to work for that just fine.
arrow201
Posts: 8
Joined: 03 Jul 2020

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by arrow201 »

Hi,

I'd like to try this compiler on a home brewed 65C02 from the late 1980s. The memory mapping is straight forward:
RAM - $0000-$7FFF (User program space: $0300-$7FFF)
I/O - $8000-$BFFF
EEPROM - $C000-$DFFF
EPROM (BIOS/Monitor) - $E000-$FFFF

I don't see a generic section in the manual for homebrew/custom boards and trying to figure out exactly what I need to do to get this to work. I would like to run programs in ram(ie. starting at $0300). To download the program to the board, I'd compile for raw output.

My guess for the .scm file would be something like:

Code: Select all

(define memories
  '((memory program
            (address (#x300 . #x6fff))
            (section (programStart #x300) (startup #x300)
                     code switch data cdata data_init_table izpage idata))
    (memory zeroPage
            (address (#x0 . #xf0))
            (section registers zzpage zpage))
    (memory stack
            (address (#x100 . #x1ff))
            (section stack))
    (memory data
            (address (#x7000 . #x7fff))
            (section cstack data zdata heap))
    (block cstack (size #x400))               ; C stack size
    (block stack  (size #x100))               ; machine stack size
    (block heap   (size #x200))               ; heap size
    ))
I really have no idea what my startup.s should look like. The simplest one I found was at:
https://github.com/hth313/Calypsi-6502- ... 64f074d24c
for the C64. I've commented out what I think I don't need? Maybe I need to add some other code?

startup.s

Code: Select all

              ;; Variant, change attribute value if you make your own
              .rtmodel cstartup ;;,"c64"
              ;; External declarations
              .section cstack
              .section data_init_table
              .extern main, exit
              .extern _Zp, _Vsp, _Vfp

              .section programStart, root ; to be at address 0x300
              .public __program_root_section, __program_start

              .section programStart, root ; to be at address 0x300
__program_root_section:
;;for C64?
;;              .word   nextLine
;;              .word   10            ; line number
;;              .byte   0x9e, " 2062", 0 ; SYS 2062
;;nextLine:     .word   0             ; end of program

              .section startup, noreorder
              .public __calypsi_entry, __program_start

__calypsi_entry:
              .section startup, root, noreorder
__program_start:
              lda     #.byte0(.sectionEnd cstack)
              sta     zp:_Vsp
              lda     #.byte1(.sectionEnd cstack)
              sta     zp:_Vsp+1
;;; Initialize data sections if needed.
              .section startup, noreorder
              .public __data_initialization_needed
              .extern __initialize_sections
__data_initialization_needed:
              lda     #.byte0 (.sectionStart data_init_table)
              sta     zp:_Zp
              lda     #.byte1 (.sectionStart data_init_table)
              sta     zp:_Zp+1
              lda     #.byte0 (.sectionEnd data_init_table)
              sta     zp:_Zp+2
              lda     #.byte1 (.sectionEnd data_init_table)
              sta     zp:_Zp+3
              jsr     __initialize_sections
              .section startup, noreorder
              .public __call_initialize_global_streams
              .extern __initialize_global_streams
__call_initialize_global_streams:
              jsr     __initialize_global_streams
              .section startup, root, noreorder
              tsx
              stx     _InitialStack ; for exit()
              lda     #0            ; argc = 0
              sta     zp:_Zp
              sta     zp:_Zp+1
              jsr     main
              jmp     exit
;;; ***************************************************************************
;;;
;;; Keep track of the initial stack pointer so that it can be restores to make
;;; a return back on exit().
;;;
;;; ***************************************************************************
              .section zdata, bss
              .public _InitialStack
_InitialStack:
              .space  1
I don't know how to handle the exit. If I decide to exit my C program, I'd want to jump to the monitor prompt, which is at $E003. Would I use the stub? ie:

Code: Select all

// exit program
void _Stub_exit(int exitCode)
{
	myExit();
}
...then put in some .s file (or part of startup.s?):

Code: Select all

directive:
.section code
.public myExit ; export myExit
myExit: jmp 0xE003
The next item is interrupts. When my board boots, IRQ location $FFFE in EPROM points to $00FD and puts an RTI instruction there. So, when writing in assembler, I'd put a JMP at $00FD, and the IRQ method address in $00FE/$00FF. So, if writing a C program with interrupts, would I initialize with a JMP at $00FD(in startup.s?), then in my C program:

Code: Select all

__attribute__((interrupt(0x00fe)))
MyIRQ()
{
...
}
...and lastly I'd need to figure out the command line options for the compiler and linker. Whew!

Any help appreciated, thanks!
User avatar
BigDumbDinosaur
Posts: 9426
Joined: 28 May 2009
Location: Midwestern USA (JB Pritzker’s dystopia)
Contact:

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by BigDumbDinosaur »

arrow201 wrote:
I don't see a generic section in the manual for homebrew/custom boards and trying to figure out exactly what I need to do to get this to work. I would like to run programs in ram(ie. starting at $0300)...Any help appreciated, thanks!

The OP for this topic hasn’t been on for over four months. He may not be monitoring this anymore.
x86?  We ain't got no x86.  We don't NEED no stinking x86!
User avatar
Proxy
Posts: 746
Joined: 03 Aug 2018
Location: Germany

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by Proxy »

the tool chain github is where the dev is mostly spending their time to answer questions and resolve issues, so i'd recommend you ask over there: https://github.com/hth313/Calypsi-tool-chains

but i can answer some stuff.
arrow201 wrote:
I don't see a generic section in the manual for homebrew/custom boards and trying to figure out exactly what I need to do to get this to work.
when you download the utility it should come with an "example" folder that has a minimal linker setup, and in the "src\lib\lowlevel" folder there is the default cstartup.s that you can either modify directly or copy and then modify for whatever system you have.
though i agree that it should be made much easier to set up a custom environment. so a template example with better commented startup code would be very useful. (you can also ask for that on the github)
arrow201 wrote:
I don't know how to handle the exit. If I decide to exit my C program, I'd want to jump to the monitor prompt, which is at $E003.
in C, exiting from the main function is handled like with any other function, meaning it just returns control to whatever code called it. in the startup code you can see the "JSR main" that calls the main function, and once it finishes/returns it continues with the instructions following it. in the case of your startup code it will execute a "JMP" to the label named "exit"
so it you want it to jump to your monitor you could just define "exit" to equal $E003, or replace the "JMP exit" with "JMP $E003".
arrow201 wrote:
The next item is interrupts. When my board boots, IRQ location $FFFE in EPROM points to $00FD and puts an RTI instruction there. So, when writing in assembler, I'd put a JMP at $00FD, and the IRQ method address in $00FE/$00FF
I wouldn't recommend jumping execution to Zeropage. use an indirect jump instead, which gets it's target address (the address that it jumps to) from memory where the operand address (the one you write in the assembler) points to.
so for example "JMP ($00FE)" reads 2 bytes from address $00FE and $00FF and let's say that the value at the first address was $99 and at the second address $44, meaning that execution will jump to address $4499 and continue there.
this leaves your Zeropage nice and tidy without having to store any code there.

as for Interrupt code in C... it's somewhat complicated. your startup code has to get the address of the function (assembly or C) you want to call and write it to where your IRQ ROM code jumps to. the problem with doing it in C is that any temporary varibles the C environment uses (which are pretty much just in the Zeropage) have to be copied to somewhere else before you can actually call your C interrupt function, which adds a lot of latency to the IRQ function.
maybe also ask the dev about a simple example for interrupt handling since i've honestly never bothered with it myself and all my Interrupt code has been in pure assembly to avoid having to save any C memory values.

on a final note, i recommend including this header file i call "MemAccess.h" that i made that adds some macros for accessing memory and setting/clearing/checking individual bits of varibles/memory locations. which makes accessing memory a lot more readable IMO.

Code: Select all

#ifndef MEMACCESS
	#define MEMACCESS
	
	#include <stdint.h>
	
	#define mread8(addr)			*((uint8_t*)addr)
	#define mread16(addr)			*((uint16_t*)addr)
	#define mread32(addr)			*((uint32_t*)addr)
	
	#define mwrite8(addr, val)		*((uint8_t*)addr) = val
	#define mwrite16(addr, val)		*((uint16_t*)addr) = val
	#define mwrite32(addr, val)		*((uint32_t*)addr) = val
	
	#define bitread(var, bit)		(((var) >> (bit)) & 1U)
	#define bitset(var, bit)		((var) |= (1U << (bit)))
	#define bitclr(var, bit)		((var) &= ~(1U << (bit)))
	
	#define bitwrite(var, bit, val)	((val) ? bitset(var, bit) : bitclr(var, bit))
#endif
I also recommend learning the "stdint" sytax for variables. so "uint16_t" instead of "unsigned int", "int8_t" instead of "char", etc. on a CPU like a 65C02 keeping track of your vairable's widths can save you a fair amount of execution time and memory.

Either way, I hope this was atleast somewhat helpful.
arrow201
Posts: 8
Joined: 03 Jul 2020

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by arrow201 »

Proxy, thank you very much for your reply! I didn't know there were minimal examples in the install. I'll d/l and install the utility and see how far I get. :) Sorry, I didn't explain the IRQ setting well. I meant to say how I'd initialize the IRQ vector. It would be called from an ASM program as you said, JMP ($00FE). Thank you for the MemAccess idea and example. Yes, it makes code much more readable. Also, thanks for the "int8_t", etc. idea as well. I use this syntax in microcontroller code and makes sense to continue with it here to keep things clear.

Thanks again for all the information!
arrow201
Posts: 8
Joined: 03 Jul 2020

Re: Calypsi C compiler toolchain for 6502 and 65816

Post by arrow201 »

Following up to my previous post, I have a home brewed 65C02 from the late 1980s. Memory mapping:
RAM - $0000-$7FFF (User program space: $0300-$7FFF)
I/O - $8000-$BFFF
EEPROM - $C000-$DFFF
EPROM (BIOS/Monitor) - $E000-$FFFF

The purpose is to run small experimental programs in RAM. Another objective is to have the code execute at the first loaded address, ie. if I load(raw format) between $1000-10FF, I want to do a run at $1000. If not configured right, the start of execution could be anywhere between the start/end addresses. I don't know if I did this the best way, but I have it working the way I want(so far, blinking a LED from a VIA(6522)) doing the following:

linker.scm

Code: Select all

(define memories
  '((memory program
            (address (#x1000 . #x6fff))
            (section (startup #x1000)
                     code switch data cdata data_init_table izpage idata reset))
    (memory zeroPage
            (address (#x0 . #xbf))		; $C0-$FF - used by monitor, IRQ, NMI
            (section registers zzpage zpage))
    (memory stack
            (address (#x100 . #x1ff))
            (section stack))
    (memory data
            (address (#x7000 . #x7fff))
            (section cstack data zdata heap))
    (block cstack (size #x400))               ; C stack size
    (block stack  (size #x100))               ; machine stack size (page 1)
    (block heap   (size #x200))               ; heap size
    ))

cstartup.s

Code: Select all

; cstartup.s
;
	; External declarations
	.section stack			  
	.section cstack
	.section data_init_table

	.extern main
	.extern _Zp, _Vsp, _Vfp
		  
	.section startup, root, noreorder
	ldx     #.byte0(.sectionEnd stack)
	txs
	lda     #.byte0(.sectionEnd cstack)
	sta     zp:_Vsp
	lda     #.byte1(.sectionEnd cstack)
	sta     zp:_Vsp+1

	lda     #0			; argc = 0
	sta     zp:_Zp
	sta     zp:_Zp+1
	jsr     main
	jmp     0xE000		; MBII monitor

Next is interrupts. I don't know, but it seems setting up the IRQ will not work the way I want it to using the attribute commands or modifying the interrupt.h(which appears to have been made for the C64?) In summary, I want my IRQ routine to be in RAM, in the same program space. How this board works is the BIOS IRQ points to page 0(EPROM - $FFFE=$FD, $FFFF=$00). In page 0, you would then do a JMP to your IRQ in program space.
ie:
Program lives at $1000-$10FF and lets say the IRQ routine compiles to be at $1034
When an IRQ comes in:
- $FFFE/$FFFF --> jumps to $00FD
- then the 3 p.0 locations would have to be: $00FD=$4C(JMP), $00FE=$34, $00FF=$10 --> JMP $1034
The way I got this to work is:

main.h

Code: Select all

struct IRQ
{
	uint8_t		Jmp;
	uint16_t	*Addr;
};
#define IRQ_VECTOR (*(volatile struct IRQ*) 0x00FD)
main.c

Code: Select all

int main ()
{
	__disable_interrupts();

	InitIRQ();
...	
	__disable_interrupts();
	return (0);
}

void InitIRQ()
{
	IRQ_VECTOR.Jmp = 0x4C;	// JMP
	IRQ_VECTOR.Addr = (uint16_t	*)&MyIRQ;
	
	__enable_interrupts();	
}

void MyIRQ()
{
	MBII_StartINT();	// asm routine to push regs
...
	MBII_EndINT();		// asm routine to restore regs
	
//	__asm("rti");	// compiler doesn't produce code!
}
I noticed the "__asm" command doesn't do anything. There's no compile/link error, but it also doesn't produce any code when looking at the .lst file. I've put a "RTI" in my MBII_EndINT(); but that doesn't satisfy the final JSR and my guess is it will eventually crash(?)
Post Reply