Exposing Variable from CA65 to CC65

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Post Reply
User avatar
Goose2k
Posts: 207
Joined: Wed Dec 11, 2019 9:38 pm
Contact:

Exposing Variable from CA65 to CC65

Post by Goose2k » Tue Dec 01, 2020 6:28 pm

I have a variable that I want to be able to access in both my C code, and my Assembly code.

Can someone provide an example of how to do this?

Everything i have tried either ends up with unresolved external symbols, redefinition of the variable, or 2 separate variables which give different values depending on if they are accessed in C or assembly.

User avatar
rainwarrior
Posts: 8002
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Exposing Variable from CA65 to CC65

Post by rainwarrior » Tue Dec 01, 2020 6:33 pm

Anything in C has a _ on the front in assembly. Anything in assembly with an _ on the front can be exported from assembly with .export and "imported" with extern in C.

When I want to use something in both, I usually make an alias with a _ to export to C:

Code: Select all

; assembly

.segment "BSS" ; (it can be any segment)
variable: .res 1

_variable = variable ; alias with a _
.export _variable

// C

extern char variable;
And going the other way...

Code: Select all

// C

char variable;

; assembly

.import _variable
variable = _variable ; alias without the _
Also, if you want to be able to take advantage of zero page optimizations, you can allocate a variable in assembly and use a pragma to tell the C code it's on the zero page.

Code: Select all

; assembly

.segment "ZEROPAGE"
_i: .res 2
.exportzp _i

// C

extern int i;
#pragma zpsym("i");
If you'd like some examples, my NESert Golfing project goes back and forth between C and assembly a lot. See dgolf.c and dgolf.s.

This document might also help if you're trying to write functions in assembly that you can call from C: cc65-intern

User avatar
dougeff
Posts: 2820
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Exposing Variable from CA65 to CC65

Post by dougeff » Tue Dec 01, 2020 6:47 pm

2 ways.

In the C declare the variable extern and in the asm declare it with _ prefix and write a .export _blah at the top of the file declaring it

or

In the C declare as normal. In the asm write a .import _blah in the file that uses it (with _ prefix in asm files)
nesdoug.com -- blog/tutorial on programming for the NES

User avatar
Goose2k
Posts: 207
Joined: Wed Dec 11, 2019 9:38 pm
Contact:

Re: Exposing Variable from CA65 to CC65

Post by Goose2k » Tue Dec 01, 2020 6:54 pm

I must be missing something (I am trying the "exporting from ASM" version that rainmaker demonstrated).

It compiles, but when I look at the resulting assembly, they are pointing to 2 different spots in memory.

Code: Select all

; assembly
.segment "BSS" ; (it can be any segment)
CREDITS_QUEUED: .res 1

_CREDITS_QUEUED = CREDITS_QUEUED ; alias with a _
.export _CREDITS_QUEUED

// c header

#pragma bss-name(push, "BSS")
extern char CREDITS_QUEUED;

Code incrementing the assembly version:

Code: Select all

;	inc <CREDITS_QUEUED
  INC $1B         


Code checking the value in C:

Code: Select all

;if (CREDITS_QUEUED > 0)
L1970:                   
  LDA $041B                
  BEQ L0DDC           
Last edited by Goose2k on Tue Dec 01, 2020 7:00 pm, edited 1 time in total.

User avatar
rainwarrior
Posts: 8002
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Exposing Variable from CA65 to CC65

Post by rainwarrior » Tue Dec 01, 2020 6:57 pm

I can't tell you what's wrong given your description. What you've written looks okay, so you'd have to share more of the program to try and diagnose it.

Code: Select all

#pragma bss-name(push, "BSS")
This line is meaningless in this context. The bss-name determines where new variables declared in C are allocated. Ones that are extern (imported) are already assigned their own segment elsewhere and not affected by this pragma.

User avatar
Goose2k
Posts: 207
Joined: Wed Dec 11, 2019 9:38 pm
Contact:

Re: Exposing Variable from CA65 to CC65

Post by Goose2k » Tue Dec 01, 2020 7:04 pm

Oh I see the issue now that I wrote it in the post above. I had:

inc <CREDITS_QUEUED

But it should have been changed to

inc CREDITS_QUEUED

I think because I moved it out of ZP? (trying to match your example)

User avatar
rainwarrior
Posts: 8002
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Exposing Variable from CA65 to CC65

Post by rainwarrior » Tue Dec 01, 2020 7:06 pm

Goose2k wrote:
Tue Dec 01, 2020 6:54 pm
Code incrementing the assembly version:

Code: Select all

;	inc <CREDITS_QUEUED
  INC $1B         

Code checking the value in C:

Code: Select all

;if (CREDITS_QUEUED > 0)
L1970:                   
  LDA $041B                
  BEQ L0DDC           
Your assembly code is incorrect. You have force-discarded the high byte of its address with <. I would generally recommend avoid using this operator this way in ca65.

So you're probably used to seeing this in NESASM:

Code: Select all

inc <CREDITS_QUEUED
However, NESASM has an unfortunate notation where it uses < to choose the instruction type. In ca65 < is used simply to select the low byte of a value, which is not quite the same, and as you've found out generates erroneous code in this case. If you want to safely ensure something is emitting ZP code, you can do this in ca65:

Code: Select all

inc z:CREDITS_QUEUED
In this case, it would give you an error telling you that CREDITS_QUEUED is not actually on the zero page. (Of course the correct thing here is not to use a ZP instruction, since CREDITS_QUEUED is not a ZP variable.)

Most of the time z: is unnecessary in ca65, because it will automatically use ZP instructions as long as the variable is declared above its usage.
Last edited by rainwarrior on Tue Dec 01, 2020 7:09 pm, edited 1 time in total.

User avatar
Goose2k
Posts: 207
Joined: Wed Dec 11, 2019 9:38 pm
Contact:

Re: Exposing Variable from CA65 to CC65

Post by Goose2k » Tue Dec 01, 2020 7:09 pm

Great explanation thanks!

Funny enough, it's used like that throughout shiru's neslib, which is where I picked it up from.

User avatar
rainwarrior
Posts: 8002
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Exposing Variable from CA65 to CC65

Post by rainwarrior » Tue Dec 01, 2020 7:13 pm

Replied while I was editing, but I just wanted to point out that in ca65 you probably don't want to use z: under normal circumstances. ca65 automatically selects ZP instructions, so it's usually unnecessary. z: is only needed if you want the compiler to make sure the variable was on the ZP.

In cc65:

Code: Select all

lda <var ; "I know for sure this is on the ZP. Don't check."

lda z:var ; "I think this is on the ZP. Please make sure."

lda var ; "I'll let the assembler figure it out."
NESASM doesn't auto-select, so the < notation it uses becomes mandatory any time you want ZP access. A lot of people coming from NESASM to ca65 have a habit of using < everywhere they use ZP.

Pokun
Posts: 1752
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Exposing Variable from CA65 to CC65

Post by Pokun » Thu Dec 03, 2020 3:38 am

Question: Does ca65 properly use zero-page addressing if the "z:" operator is used on an instruction which argument is memory outside the zero-page area?

User avatar
rainwarrior
Posts: 8002
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Exposing Variable from CA65 to CC65

Post by rainwarrior » Thu Dec 03, 2020 3:41 am

Pokun wrote:
Thu Dec 03, 2020 3:38 am
Question: Does ca65 properly use zero-page addressing if the "z:" operator is used on an instruction which argument is memory outside the zero-page area?
It will always generate a zero-page instruction if you use z: but if the argument isn't actually on the zero-page, it will give an error and it will fail to build.

If you want to force a zero-page instruction on something that is not on the zero-page, you can use < to intentionally discard the high bytes of the address.

Otherwise without a prefix it will correctly pick the zero-page instruction in most cases. As long as enough information is given on a line before that instruction, it will know it belongs there. (Either the label's segment is marked with : zeropage, or called "ZEROPAGE", or it was imported with .importzp, or it is otherwise a constant < 256.) If for some reason you have to define the variable on a line after the instruction, in that case you might want to put z: on it to let it know ahead of time, but if you make a habit of declaring variables at the top of the file it will just do it all automatically for you.

Pokun
Posts: 1752
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Exposing Variable from CA65 to CC65

Post by Pokun » Thu Dec 03, 2020 4:40 am

I see, thanks.
The "<" operator seems to be the only way to use direct-page addressing on page 1 when programming SPC700 with the SPC700 macro-pack. Ca65 really doesn't seem to like that you mark segments outside page 0 as zero-/direct-page. I guess the 65816 mode might have similar problems with direct-page addressing outside zero-page.

User avatar
rainwarrior
Posts: 8002
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Exposing Variable from CA65 to CC65

Post by rainwarrior » Thu Dec 03, 2020 5:09 am

Yes, the assembler doesn't really know anything about direct page. That's a frequently requested thing.

Personally I haven't found it that much of a problem in practice, though. I've tried both of the following:

1. For variables are used exclusively in DP: put each page of DP in its own zero page segment. Ensure there's an equivalent space in memory reserved somewhere.

2. For variables that are used both ways: Make a set of DP aliases for variables that you want to access from both places. Something like:

Code: Select all

.segment "RAM"
v1: .res 2
v2: .res 2
v3: .res 2

; direct page aliases
dvseg = v1
dv1 = <(v1 - dvseg)
dv2 = <(v2 - dvseg)
dv3 = <(v3 - dvseg)

; example
.segment "CODE"
lda #dvseg
tcd
lda dv1 ; should automatically be a DP instruction
lda z:dv1 ; if you want to be 100% certain it's a DP instruction
You might make a convenient macro for the aliasing if that looks too ugly. Note that the aliases don't have to be declared in a segment since they're just assignments = and the assembler is smart enough to know from the < that they will always be 8 bit constants.
Last edited by rainwarrior on Mon Dec 07, 2020 4:39 pm, edited 1 time in total.

User avatar
aquasnake
Posts: 207
Joined: Fri Sep 13, 2019 11:22 pm

Re: Exposing Variable from CA65 to CC65

Post by aquasnake » Fri Dec 04, 2020 8:59 pm

in asm file:

Code: Select all

	.export         _SELECTED_GAME
	
	
	
	.segment        "ZEROPAGE"

	; zero page
_SELECTED_GAME:      .res 2

in c file:

Code: Select all

extern s16 SELECTED_GAME;
#pragma zpsym("SELECTED_GAME")
Note that the first two bytes on page 0 are not occupied and may be ignored by the compiler.

In *.cfg file:

ZP: start = $0002, size = $0040, define = yes;


The start address tells the compiler how much space the zero page is actually allocated, because sometimes the first few bytes are used for purposes that the user does not know. It is recommended that the start address of zero page in your own cfg file should be greater than or equal to the original address in the cc65 template.

Similarly, the last a few addresses of 0 page space should not be used. cc65 may uses these as pseudo registers or other stack. It is also recommended not to allocate more than $80 $60.

In "nes.inc" file, cc65 itself takes up a lot of zero page space, as follows:

Code: Select all

;; FIXME: optimize zeropage usage

SCREEN_PTR      = $62           ;2
CRAM_PTR        = $64           ;2
CHARCOLOR       = $66
BGCOLOR         = $67
RVS             = $68
CURS_X          = $69
CURS_Y          = $6a

tickcount       = $6b           ;2

VBLANK_FLAG     = $70

ringbuff        = $0200
ringwrite       = $71
ringread        = $72
ringcount       = $73

ppuhi           = $74
ppulo           = $75
ppuval          = $76

screenrows      = (30-1)
charsperline    = 32
xsize           = charsperline

Post Reply