It is currently Fri Nov 24, 2017 11:45 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 89 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6  Next
Author Message
PostPosted: Mon Nov 09, 2015 8:57 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5838
Location: Canada
I just put all my RAM allocations in a single file, and import everything from there. Everything includes this file. When I assemble the file, I define RAM_EXPORT which causes all the allocations and exports to be made, but elsewhere it is included and just creates the necessary imports.

Code:
.macro RESZP label, size
   .ifdef RAM_EXPORT
      label: .res size
      .exportzp label
   .else
      .importzp label
   .endif
.endmacro

.macro RES label, size
   .ifdef RAM_EXPORT
      label: .res size
      .export label
   .else
      .import label
   .endif
.endmacro

.segment "ZEROPAGE"

RESZP  i,                     1
RESZP  j,                     1
RESZP  k,                     1

.segment "OAM"
RES    oam,                   256

.segment "RAM"
RES    collision,             256
RES    dog_data0,             16
RES    dog_data1,             16
RES    dog_data2,             16


If I am using temporary variables in functions, and I wanted a "helpful" name for them, I'd just alias them there:

Code:
.proc my_func
param_a = i
param_b = j
param_c = k
   lda param_a
   eor param_b
   clc
   adc param_c
   rts
.endproc


Your problem seems to stem specifically from wanting to use aliased zeropage variables before the definition of the function. I think you I'd just do a "forward declaration" for that variable if it ever came up, but really it hasn't for me. (Forward declarations are a common necessity in C/C++, so I'm used to doing that already.) The stuff I tend to alias is always in BSS, and I'm used to using the common temporaries for ZP, they're all named like i, j, k, l, etc. and usually I document which ones a function uses or which ones it doesn't because there's a lot of overlap-- so I'd rather know a common name for them than hide it from myself with an alias.

The BSS stuff that I do alias is usually to do with different types of object, all of which use the same memory areas to store their state, but each of them has different data needs, so I alias the various bytes to document their usage. A struct might have done the job well, if I wasn't using striped arrays. At any rate, not really an issue because it's not on the zeropage, so it's not subject to the one-pass problem.


Top
 Profile  
 
PostPosted: Tue Nov 10, 2015 5:27 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2981
Location: Tampere, Finland
I also never had the problem because of the include/module architecture I've been using.

You can force zeropage addressing with .lobyte or "<", but if you were ever to move the variable outside zeropage, the hibyte would silently get truncated out. There's also a bug in the current version that gives a "suspicious address expression" warning when you use "z:<foo": https://github.com/cc65/cc65/issues/194

Actually, come to think of it, in this case you should be able to force zeropage addressing with "z:", and also get an error if it's not a zeropage address:
Code:
lda z:foo ; OK
foo = $55
lda z:bar ; error
bar = $123

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Tue Nov 10, 2015 9:35 am 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
Ah, good to know there's a way to force ZP addressing. Since I only need that for accessing a subroutine's local variables from outside that subroutine, which is a very particular case, I might just do that.

Anyway, I just thought of another way to implement overlapping variables. The idea is to use a huge dummy segment as a counter, defining dummy labels there so you can calculate the offset from the beginning and then add the offset to the base label in the actual segment that's used for memory. First, you need to reserve the bytes in the actual memory segment:

Code:
.segment "ZEROPAGE": zeropage
Scratchpad: .res 32 ;reserve 32 bytes for scratchpad variables

Then you need a couple of macros:

Code:
.macro StartScratchpadPage
   .segment "SCRATCHPAD": zeropage ;switch to the dummy segment, which is way bigger than 32 bytes
   .align $20 ;move on to the next "page"
   ;(could optionally add padding here so variables begin after another block of variables)
.endmacro

.macro DefineScratchpadVariable Name, Size
   .local Dummy
   .segment "SCRATCHPAD": zeropage
   Dummy: .res Size
   .ident(.string(Name)) = Scratchpad + (Dummy & $1f) ;ignore the higher bits of the offset when adding to the base address
.endmacro

Now you can start defining variables:

Code:
StartScratchpadPage
DefineScratchpadVariable foo, 2
DefineScratchpadVariable bar, 2

StartScratchpadPage
DefineScratchpadVariable baz, 2
DefineScratchpadVariable qux, 2

Now both foo and baz point to $00, while both bar and qux point to $02.

Again, this feels a little hacky to me, but if adding offsets manually is acceptable, I don't see why calculating the offsets automatically before adding wouldn't be OK.

The biggest advantage over using structs is that you don't need anything fancy to use these variables, they're just like regular labels, as far as the assembler is concerned.

I haven't worked out the details yet, but I'm pretty sure this solution can also be applied to temporary variables in general, meaning I could have as many temporary modules as necessary without having to create new segments for each of them. They'd all use the same 8 dummy segments to calculate the offsets, and add the offsets to labels in the actual memory pages.


Top
 Profile  
 
PostPosted: Wed Nov 11, 2015 9:50 am 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
OK, I believe these are the final macros for creating scratchpad variables (please don't mind my long macro names):

Code:
;Starts a new block of scratchpad variables.
.macro Assembler_StartScratchpadVariables Offset
   .pushseg ;save the current segment
   .segment "SCRATCHPAD_RAM_BLOCKS" ;switch to a large dummy segment
   .align $20 ;move on to the next block
   : ;mark the start with a label
   .ifnblank Offset
      .res Offset ;skip the requested amount of bytes
   .endif
.endmacro

When calling this macro you can provide an offset, which is the number of bytes to skip before starting the new variables. This offset must be a constant number, which can be written manually or automatically generated from a previous scratchpad declaration.

Code:
;Declares a scratchpad variable in the current block.
.macro Assembler_DeclareScratchpadVariable VariableName, VariableSize
   .ident(.string(VariableName)) = Assembler_Scratchpad + <(* - :-) ;create the address by adding the base address to the offset from the start of the block
   .res VariableSize ;advance the amount of bytes used by the variable
.endmacro

"Assembler_Scratchpad" is a label in regular zero page, where 32 bytes are reserved.

Code:
;Ends the current block of scratchpad variables.
.macro Assembler_EndScratchpadVariables Offset
   .ifnblank Offset
      .ident(.string(Offset)) = <(* - :-) ;save the current offset in the specified symbol
   .endif
   .popseg ;restore the previous segment
.endmacro

When ending the declaration you can optionally save the current offset in a symbol, which you can use in a later declaration to have that block placed after this one.

Here's how the macros are used:

Code:
Assembler_StartScratchpadVariables
Assembler_DeclareScratchpadVariable Scratch0, 2
Assembler_DeclareScratchpadVariable Scratch1, 2
Assembler_EndScratchpadVariables

Assembler_StartScratchpadVariables
Assembler_DeclareScratchpadVariable Scratch2, 3
Assembler_DeclareScratchpadVariable Scratch3, 1
Assembler_DeclareScratchpadVariable Scratch4, 2
Assembler_EndScratchpadVariables ScratchBlock0Size

Assembler_StartScratchpadVariables ScratchBlock0Size
Assembler_DeclareScratchpadVariable Scratch5, 2
Assembler_DeclareScratchpadVariable Scratch6, 1
Assembler_EndScratchpadVariables

The final addresses will be:

Scratch0: $00
Scratch1: $02
Scratch2: $00
Scratch3: $03
Scratch4: $04
Scratch5: $06
Scratch6: $08

You have to admit, this is a pretty good alternative to manually typing offsets, and you end up with the exact same kinds of labels, so you don't even need any special tricks for using these variables once they're declared.

I'm now working on a similar set of macros to deal with the reusable general RAM areas.

EDIT: Now that I think of it, the .align is probably unnecessary, since the offsets are calculated with subtractions. If that's the case, I might even be able to use the same dummy segment for scratchpad variables and for general RAM, since it's basically a space I can fill linearly.


Top
 Profile  
 
PostPosted: Wed Nov 11, 2015 12:03 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
Here's what I believe to be the final scratchpad RAM macros, in case anyone is interested:

Code:
;Starts a new block of scratchpad variables.
.macro Assembler_StartScratchpadVariables Offset
   .pushseg
   .segment "DUMMY"
   :
   .ifnblank Offset
      .res Offset
   .endif
.endmacro

Code:
;Declares a scratchpad variable in the current block.
.macro Assembler_DeclareScratchpadVariable VariableName, VariableSize
   .local Variable
   Variable: .res VariableSize
   .ident(.string(VariableName)) = Assembler_Scratchpad + (Variable - :-)
   .assert .ident(.string(VariableName)) + VariableSize <= 32, ldwarning, "Scratchpad RAM overflow."
.endmacro

Code:
;Ends the current block of scratchpad variables.
.macro Assembler_EndScratchpadVariables Offset
   .ifnblank Offset
      .ident(.string(Offset)) = <(* - :-)
   .endif
   .popseg
.endmacro


Top
 Profile  
 
PostPosted: Wed Nov 11, 2015 1:22 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5838
Location: Canada
Yeah, once you get over the learning curve for ca65's advanced features, it becomes pretty apparenty just how powerful it is. ;)

Movax12 took a crack at making a "high level assembly" language out of macros for ca65: http://forums.nesdev.com/viewtopic.php?t=9272 / http://www.romhacking.net/documents/635/


Top
 Profile  
 
PostPosted: Wed Nov 11, 2015 1:47 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
rainwarrior wrote:
Yeah, once you get over the learning curve for ca65's advanced features, it becomes pretty apparenty just how powerful it is. ;)

Indeed. I'm still struggling a bit, but the time I spent working on these macros was essential for me to understand the basics. I won't lie though, I can't wait to finish these support macros so I can get back to coding 6502 assembly!

Quote:
Movax12 took a crack at making a "high level assembly" language out of macros for ca65: http://forums.nesdev.com/viewtopic.php?t=9272 / http://www.romhacking.net/documents/635/

I vaguely remember that, but since I didn't use ca65 at the time, I didn't pay much attention. Only now I'm catching up with the crazy stuff people have used this assembler for, like tepples reimplementing 6502 or blargg implementing Z80.


Top
 Profile  
 
PostPosted: Wed Nov 11, 2015 3:02 pm 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
Some documentation for the "highlevel macros" https://www.assembla.com/spaces/ca65hl/wiki

As far as reserving RAM, you can use .struct with macros to define your variables yourself, as constants at assemble time. This is not the intended use, but I do it.

Code:
resZP baz .word

; or, multiple declarations at once

resZP {   /
foo .byte,    /
bar .byte 4   /
}


Same thing for resBSS.

The downside of keeping track of RAM at assemble is that it won't work for multiple modules. So, at the end of the file I (hackily) output the current RAM usage to a file named for the module(s) that will be called from that file. The module loads its RAM offsets based on that file. That way, exclusive sections/states of code, that will never run at the same time, can use the same memory.

Hope that makes sense. I've been thinking about a way to make the assembler do this natively.


Top
 Profile  
 
PostPosted: Thu Nov 12, 2015 9:44 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
Movax12 wrote:
Hope that makes sense.

Actually... I'm pretty confused! I couldn't find anything about resZP or resBSS, not in the documentation you liked to nor in the SMB High Level Disassembly files... What am I missing?

Random ca65 question time:

Code:
.macro IncludeHidden FilePath
   .scope
      .include FilePath
   .endscope
.endmacro

Why doesn't this work?

Scoping an include normally works as expected (all identifiers are hidden), but not inside a macro, and I can't for the life of me understand why.


Top
 Profile  
 
PostPosted: Thu Nov 12, 2015 9:58 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5838
Location: Canada
ca65 macros are really strange. They're not a text-replacement macro like with C, they get turned into some sort of tokenized equivalent and ends up doing the substitution at some weird intermediate stage with a lot of funny rules. It's kind of unfortunate; I think there's equal probability that it's just a bug, or it was just somehow really hard to do scope + include inside a macro because of the internal implementation.


Top
 Profile  
 
PostPosted: Thu Nov 12, 2015 10:25 pm 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
I see... so the scope is probably being opened and closed before the include itself is executed, and it affects only identifiers created inside the macro itself (none, in this case)... bummer. Simple text-replacement would've been way more intuitive.

This would've helped me make my main file look cleaner, but now I'll have to .scope the shit out of it. :cry:

I guess I could maybe cheat and have this macro include files containing ".scope" and ".endscope" instead of using the actual commands in the macro, but that would be such a nasty thing to do... if it even works! EDIT: It doesn't work. I'm out of ideas.

EDIT: One more idea - I can create scoped versions of the files I need to scope, which contain nothing but the scope and an include of the original file. Still pretty crappy.


Top
 Profile  
 
PostPosted: Fri Nov 13, 2015 4:34 am 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
tokumaru wrote:
Movax12 wrote:
Hope that makes sense.

Actually... I'm pretty confused! I couldn't find anything about resZP or resBSS, not in the documentation you liked to nor in the SMB High Level Disassembly files... What am I missing?


These are separate macros just used as an example. They use .struct and track RAM usage. I was mostly trying to make the point that you can keep track of RAM yourself, but only if you have a single module.
When you use .struct you are creating constants starting at 0, .res will just mark the identifier with its address size, the linker assigns the actual address. It would be nice if there was a way to manage RAM yourself across multiple modules without having to get too crazy with linker configurations.


Top
 Profile  
 
PostPosted: Fri Nov 13, 2015 5:08 am 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2981
Location: Tampere, Finland
The .scope/.include thing seems like a bug to me, assuming that by "doesn't work" you meant that it executes but the identifiers appear in global scope.

There's an issue tracker over at https://github.com/cc65/cc65/issues

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Fri Nov 13, 2015 5:13 am 
Offline
User avatar

Joined: Sun Jan 02, 2011 11:50 am
Posts: 522
tokumaru wrote:
I see... so the scope is probably being opened and closed before the include itself is executed


I think this is basically what is happening.


Top
 Profile  
 
PostPosted: Fri Nov 13, 2015 5:49 am 
Online
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10118
Location: Rio de Janeiro - Brazil
thefox wrote:
The .scope/.include thing seems like a bug to me, assuming that by "doesn't work" you meant that it executes but the identifiers appear in global scope.

Yes, that's what happens.

Quote:
There's an issue tracker over at https://github.com/cc65/cc65/issues

I'm not really sure about how these things work, since I've never filed but reports before (I don't even have a GitHub account). Does anyone here feel like reporting this?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 89 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Bing [Bot], Majestic-12 [Bot], Sumez and 8 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:  
Powered by phpBB® Forum Software © phpBB Group