|
Post by 0x8bitdev on May 30, 2022 16:25:01 GMT
A stupid question...
I have the following data in ASM file:
... _Lev0_Layout:
.word _Lev0Scr0 .byte bank(_Lev0Scr0) .word _Lev0Scr1 .byte bank(_Lev0Scr1) .word _Lev0Scr2 .byte bank(_Lev0Scr2) .word _Lev0Scr3 .byte bank(_Lev0Scr3) .word _Lev0Scr4 .byte bank(_Lev0Scr4) .word _Lev0Scr5 .byte bank(_Lev0Scr5) .word _Lev0Scr6 .byte bank(_Lev0Scr6) .word _Lev0Scr7 .byte bank(_Lev0Scr7) .word _Lev0Scr8 .byte bank(_Lev0Scr8) .word _Lev0Scr9 .byte bank(_Lev0Scr9) .word _Lev0Scr10 .byte bank(_Lev0Scr10) ... ... .. .
_Lev0Scr0: ...
_Lev0Scr1: ... _Lev0Scr...
...
_mpd_MapsArr: .word _Lev0_Layout .byte bank(_Lev0_Layout)
Is there any trick/way to avoid repeating '.byte bank(_Lev0ScrN)' to get data successfully?
|
|
|
Post by elmer on May 30, 2022 19:06:53 GMT
Is there any trick/way to avoid repeating '.byte bank(_Lev0ScrN)' to get data successfully? Not that I know of, sorry. You can make it shorter to type ... ".db ^_Lev0ScrN" but it's basically the same thing. There isn't a ".farptr" or anything like that (yet).
|
|
|
Post by turboxray on May 30, 2022 19:50:35 GMT
A stupid question... I have the following data in ASM file: ... _Lev0_Layout:
.word _Lev0Scr0 .byte bank(_Lev0Scr0) .word _Lev0Scr1 .byte bank(_Lev0Scr1) .word _Lev0Scr2 .byte bank(_Lev0Scr2) .word _Lev0Scr3 .byte bank(_Lev0Scr3) .word _Lev0Scr4 .byte bank(_Lev0Scr4) .word _Lev0Scr5 .byte bank(_Lev0Scr5) .word _Lev0Scr6 .byte bank(_Lev0Scr6) .word _Lev0Scr7 .byte bank(_Lev0Scr7) .word _Lev0Scr8 .byte bank(_Lev0Scr8) .word _Lev0Scr9 .byte bank(_Lev0Scr9) .word _Lev0Scr10 .byte bank(_Lev0Scr10) ... ... .. .
_Lev0Scr0: ...
_Lev0Scr1: ... _Lev0Scr...
...
_mpd_MapsArr: .word _Lev0_Layout .byte bank(_Lev0_Layout)
Is there any trick/way to avoid repeating '.byte bank(_Lev0ScrN)' to get data successfully? Like a 24bit long address? Yeah, just make a macro for that sort of thing it would look something like: defLongAddress .macro
.dw \1 .db bank(\1)
.endm
And then you'd have something like: _Lev0_Layout: defLongAddress _Lev0Scr0 defLongAddress _Lev0Scr1 defLongAddress _Lev0Scr2 defLongAddress _Lev0Scr3 . . .
Or are you looking for something that is like variadic parameters in C? Where you just give it an infinite number of arguments and it creates it all with one macro call?
|
|
|
Post by 0x8bitdev on May 31, 2022 7:14:37 GMT
elmer and turboxray Thanks for your answers! I'll describe the problem a bit detailed. There is a general code that generates ASM data for NES/SMS/ZX/PCE/SMD... And everything works well! But there is a HuC with its farptrs... that breaks the versatility. So the question is, how to save the versatility for HuC data? One way I use is indirect farptrs in ASM data. For example: _mpd_Props: <-- this is a farptr for HuC, but ASM data doesn't know anything about HuC and farptrs. so I can use the same code with/without HuC. .incbin "_tilemap_Props.bin"
_mpd_PropsOffs: <-- just an offsets array; for accessing the '_mpd_Props' data I use a routine that calculates a farptr by data offset: farptr += data_offset .word 0 ; (chr0) .word 900 ; (chr1) .word 1524 ; (chr2)
So I just wanted to know, may be someone is using similar technique(s) to avoid or minimizing 'bank(...)' declarations in ASM data? Now I'm moving ASM blocks from .H file to .ASM to save more memory for code and ran into a problems like with an array of farptrs... [upd]: It seems like I've found the solution for farptrs array:... _Lev0_Layout: .word _Lev0Scr0 .word _Lev0Scr1 .word _Lev0Scr2 .word _Lev0Scr3 .word _Lev0Scr4 .word _Lev0Scr5 .word _Lev0Scr6 .word _Lev0Scr7 .word _Lev0Scr8 .word _Lev0Scr9 .word _Lev0Scr10 ... ... .. .
_Lev0Scr0: ... _Lev0Scr1: ... _Lev0Scr...
... _mpd_MapsArr: .word _Lev0_Layout .byte bank(_Lev0_Layout) 1. Using _Lev0_Layout and bank(_Lev0_Layout) I can map _Lev0_Layout data. 2. Then I can get an address of _Lev0ScrN = ( ( word )_Lev0_Layout[ N ] ) 3. I know the bank and address: _Lev0_Layout + I know data offset: address _Lev0ScrN - address _Lev0_Layout 4. Using the bank and address of _Lev0_Layout + _Lev0ScrN data offset, I can map _Lev0ScrN data...
|
|
|
Post by elmer on May 31, 2022 14:24:45 GMT
[upd]: It seems like I've found the solution:... 4. Using the bank and address of _Lev0_Layout + _Lev0ScrN data offset, I can map _Lev0ScrN data... Sure, 24-bit starting-point + 16-bit offset will work ... I just didn't understand what you were asking for. To confirm what you're seeing, HuC and PCEAS don't change the written-order of your data (only of your code in procedures), so if you add a list of data items you can be sure that they'll be put one after the other in the ROM/CD and so you can use a 16-bit offset from a starting-point (if that's large enough for your data). HOWEVER ... be warned that I have seen PCEAS mess with the page number (i.e. the top 3 bits of a 16-bit address) sometimes when items cross bank boundaries, so calculating the offset math inside generated asm-code rather than just doing an incbin MAY end up failing at some point, unless you hide the calculation inside a macro which converts the addresses to linear form before calculating the offset. I wish that PCEAS's behavior in this regard was a bit clearer to me, but I'm afraid that it's currently not. PCEAS's ability to change .PAGE within a bank can be very useful for assembling code, but it's rarely what you want to see happen in data (which IMHO should just be a linear offset from the start of the ROM/CD).
|
|
|
Post by turboxray on May 31, 2022 14:37:57 GMT
elmer and turboxray Thanks for your answers! I'll describe the problem a bit detailed. There is a general code that generates ASM data for NES/SMS/ZX/PCE/SMD... And everything works well! But there is a HuC with its farptrs... that breaks the versatility. So the question is, how to save the versatility for HuC data? One way I use is indirect farptrs in ASM data. For example: _mpd_Props: <-- this is a farptr for HuC, but ASM data doesn't know anything about HuC and farptrs. so I can use the same code with/without HuC. .incbin "_tilemap_Props.bin"
_mpd_PropsOffs: <-- just an offsets array; for accessing the '_mpd_Props' data I use a routine that calculates a farptr by data offset: farptr += data_offset .word 0 ; (chr0) .word 900 ; (chr1) .word 1524 ; (chr2)
So I just wanted to know, may be someone is using similar technique(s) to avoid or minimizing 'bank(...)' declarations in ASM data? Now I'm moving ASM blocks from .H file to .ASM to save more memory for code and ran into a problems like with an array of farptrs... [upd]: It seems like I've found the solution for farptrs array:... _Lev0_Layout: .word _Lev0Scr0 .word _Lev0Scr1 .word _Lev0Scr2 .word _Lev0Scr3 .word _Lev0Scr4 .word _Lev0Scr5 .word _Lev0Scr6 .word _Lev0Scr7 .word _Lev0Scr8 .word _Lev0Scr9 .word _Lev0Scr10 ... ... .. .
_Lev0Scr0: ... _Lev0Scr1: ... _Lev0Scr...
... _mpd_MapsArr: .word _Lev0_Layout .byte bank(_Lev0_Layout) 1. Using _Lev0_Layout and bank(_Lev0_Layout) I can map _Lev0_Layout data. 2. Then I can get an address of _Lev0ScrN = ( ( word )_Lev0_Layout[ N ] ) 3. I know the bank and address: _Lev0_Layout + I know data offset: address _Lev0ScrN - address _Lev0_Layout 4. Using the bank and address of _Lev0_Layout + _Lev0ScrN data offset, I can map _Lev0ScrN data... Okay, so it looks like your issue is with data structures and not specifically redundant syntax. I mean at that point, you'd have to give specifics why you didn't like the current example (the extra byte, or needing multiple of 3 instead of power of 2 for indexing, etc). With your WORD offset, you're still deriving the bank of the dependency data right? As in, base_long_address + offset -> bank:page -> address / $2000:(address & 0x1fff | PAGE2), etc. (Not that exact formula, obviously, but deriving the bank for the offset each time).
|
|
|
Post by 0x8bitdev on May 31, 2022 15:41:24 GMT
HOWEVER ... be warned that I have seen PCEAS mess with the page number (i.e. the top 3 bits of a 16-bit address) sometimes when items cross bank boundaries, so calculating the offset math inside generated asm-code rather than just doing an incbin MAY end up failing at some point, unless you hide the calculation inside a macro which converts the addresses to linear form before calculating the offset. Thanks for reminding me! I had a problem with reading a word between banks. So I had to align the data by word. Now the main thing is not to forget about it! [upd]: Can't understand the problem... Why macro?.. A far pointer uses a linear address in its address part? Okay, so it looks like your issue is with data structures and not specifically redundant syntax. I mean at that point, you'd have to give specifics why you didn't like the current example (the extra byte, or needing multiple of 3 instead of power of 2 for indexing, etc). Actually the problem was with the redundant syntax. I don't want to use the bank() everywhere for each farpointer. If you mean that ' .byte bank(_Lev0_Layout)', that is an unfortunate example. Don't pay attention to it. I'm using a .word in real generated data. That example is handwritten, not copy/pasted. With your WORD offset, you're still deriving the bank of the dependency data right? As in, base_long_address + offset -> bank:(address & 0x1fff | PAGE2), etc. You are right and what are the alternatives?
|
|
|
Post by turboxray on Jun 1, 2022 6:42:43 GMT
You are right and what are the alternatives? Highly dependent on the data structure. I mean if you're able to map like up to 3 banks into the logical address range, and the entire level map contents fits within that area, then you don't need to derive the bank for each _Lev0Scr<n> calculation - just the base address and page. Otherwise, I usually just keep the trade off of an extra byte over the cost to calculate it. But I almost always splitting long pointers into three arrays; one for bank, one for low address and one for high address. That way it's one index to address them all. But like I said, highly dependent on the data structure and how I'm accessing it.
|
|
|
Post by 0x8bitdev on Jun 1, 2022 7:20:40 GMT
You are right and what are the alternatives? Highly dependent on the data structure. I mean if you're able to map like up to 3 banks into the logical address range, and the entire level map contents fits within that area, then you don't need to derive the bank for each _Lev0Scr<n> calculation - just the base address and page. Otherwise, I usually just keep the trade off of an extra byte over the cost to calculate it. But I almost always splitting long pointers into three arrays; one for bank, one for low address and one for high address. That way it's one index to address them all. But like I said, highly dependent on the data structure and how I'm accessing it. Reading your answer I understand that it is a good solution for a pure ASM project where you can make everything, but for the HuC/ASM interaction... Correct me, if I'm wrong.
|
|
|
Post by elmer on Jun 1, 2022 16:16:35 GMT
[upd]: Can't understand the problem... Why macro?.. A far pointer uses a linear address in its address part? No, a far-pointer is what you'd expect ... a 16-bit address followed by an 8-bit bank number. The cautions come in with the top 3-bits of the 16-bit address, the nominal "page" number 0..7 (i.e. $0xxx, $2xxx, $4xxx, etc). What you *seem* to be doing is to use a fixed bank number, determined from the label at the start of the data structure, and then just use the 16-bit address of the individual "screen" elements within your data structure. How do you detect and cope with the data structure crossing a bank boundary? Or even worse, two bank boundaries? I don't know what the alignment and size limitations are that you're putting on your data structure, and so I don't know if this is going to be a problem for you or not. Reading your answer I understand that it is a good solution for a pure ASM project where you can make everything, but for the HuC/ASM interaction... Correct me, if I'm wrong. Well, I don't want to put words in turboxray 's mouth, but I think that both of us would kinda assume that any library code would do as much of the heavy-lifting of address-manipulation or data-structure access in either pure-asm or HuC inline-asm sections in order to be both fast and easy-to-write (for an asm-capable library author). HuC's ability to drop into a bit of hand-written assembly in the middle of a C function allows you to do a bunch of useful work that still behaves as though it is just C code to your HuC userbase. turboxray 's recent additions of #incasmlabel and fastcall macros to the HuC language make it even cleaner to hide the "gnarly" behind-the-scenes-details of library functionality behind an easy-to-use interface for the HuC audience.
|
|
|
Post by 0x8bitdev on Jun 1, 2022 17:17:20 GMT
How do you detect and cope with the data structure crossing a bank boundary? Or even worse, two bank boundaries? I don't know what the alignment and size limitations are that you're putting on your data structure, and so I don't know if this is going to be a problem for you or not. I found a glitch when working with the farpeekw. Sometimes it returns a half-word data. But after aligning data by word the glitch disappeared. I've taken a look at the farpeekw and this routine works with one bank data. And now, when I've moved all data to ASM file, I have mixed .byte/.word and can't align them so that it always works correctly with the farpeekw. So this is a problem... turboxray 's recent additions of #incasmlabel and fastcall macros to the HuC language make it even cleaner to hide the "gnarly" behind-the-scenes-details of library functionality behind an easy-to-use interface for the HuC audience. I've found the both #incasmlabel and fastcall macros in the unit tests. But I didn't understand how the #incasmlabel works. Is there any description? When you are working with a pure ASM project, you can manage memory as you need... But HuC does its own memory management, and this moment isn't clear to me...
|
|
|
Post by elmer on Jun 1, 2022 19:27:52 GMT
But I didn't understand how the #incasmlabel works. Is there any description? It's just the same as #incasm, except that it puts a HuC label at the start of the asm file that HuC users can then pass to library code. I found a glitch when working with the farpeekw. Sometimes it returns a half-word data. But after aligning data by word the glitch disappeared. Yes, apparently it was written to wrap around from the end of the bank to the beginning, which makes sense, but then it doesn't actually increment the bank number the way it should ... which is absolutely idiotic! Well, it can be fixed to read the word value correctly, even when the word crosses a bank, but you're still only putting-off the problem, because calculating the address to pass to farpeekw() is still going to have to deal with crossing a bank boundary. Honestly, HuC was never designed to do this kind of work efficiently in C, it was supposed to make the high-level stuff easier and quicker to write, while gently leading new developers into using assembly-language for the slow/difficult low-level stuff. As someone capable of writing asm code, you're just making your life more difficult by trying to write this kind of low-level functionality in C code. But HuC does its own memory management, and this moment isn't clear to me... I'm sure that it's documented somewhere, but yes, it took me a while to figure out how the HuC environment was setup, too. Here's how it uses memory ... MPR0 ($0000-$1FFF) : PCE hardware MPR1 ($2000-$3FFF) : PCE RAM with ZP & Stack (BSS segment) MPR2 ($4000-$5FFF) : HuC constants (permanently mapped) MPR3 ($6000-$7FFF) : Freely mapped HuC data (DATA segment) MPR4 ($8000-$9FFF) : HuC procedure trampolines (permanently mapped) MPR5 ($A000-$BFFF) : HuC Banked Code (LIB2_BANK) and compiled HuC procedures MPR6 ($C000-$DFFF) : HuC System Code (LIB1_BANK) MPR7 ($E000-$FFFF) : HuC System Code (LIB1_BANK) or System Card BIOS
The only region that you can freely change in HuC is MPR3. Library code written in assembly-language can also use MPR4 and potentially MPR2, but they must restore the original banks before returning to HuC code. Does that help at all? FWIW, asm code often looks pretty similar, with MPR0, MPR1 and MPR7 being almost-universally used in that way. MPR3 is common choice for "freely mapped data" because it is so easy to detected when you increment a pointer outside the bank (the pointer goes from +ve to -ve). But it's not the *universal* choice for mapping data, there are plenty of programs that use a different page.
|
|
|
Post by 0x8bitdev on Jun 2, 2022 12:00:00 GMT
But I didn't understand how the #incasmlabel works. Is there any description? It's just the same as #incasm, except that it puts a HuC label at the start of the asm file that HuC users can then pass to library code. Actually that unit test example so non-obvious... There are still questions: extern void abort(void);
#incasmlabel(test1, "./tests/20200415_asm1.asm"); #incasmlabel(test2, "./tests/20200415_asm2.asm", 3); <--- 3 what is this? #incasmlabel(test3, "./tests/20200415_asm3.asm", 2000); <--- 2000 what is this?
int main() { char aa = 1; if (aa == 1) exit(0);
abort();
exit(0); }
How to use the 'test1, 2, 3' ?... As labels to jump to in ASM blocks? Well, it can be fixed to read the word value correctly, even when the word crosses a bank, but you're still only putting-off the problem, because calculating the address to pass to farpeekw() is still going to have to deal with crossing a bank boundary. Of course, I know. Here is my ASM routine I use for calculating a far-pointer by an offset: ; *** farptr += offset *** ; ; IN: ; ; __ax - offset ; __bl - bank number ; __si - address ; _mpd_farptr_add_offset:
; add an offset
clc lda <__ax adc <__si sta <__si lda <__ax+1 adc <__si+1
tay
; increment a bank number
lsr a lsr a lsr a lsr a lsr a clc adc <__bl sta <__bl
; save high byte of an address
tya and #$1f sta <__si+1 rts
This code works with data that exceeds 8K.
Where I can fail?.. MPR3 is common choice for "freely mapped data" because it is so easy to detected when you increment a pointer outside the bank (the pointer goes from +ve to -ve). But it's not the *universal* choice for mapping data, there are plenty of programs that use a different page. The question is how many banks can I map starting with MPR3 in my user routine? And where is my routine will be? For example I need to make something like this: void tiles_data_processing() { lock_data(); <-- here I'll map data banks
// data processing without far jumps... //...
unlock_data(); <-- here I'll restore data banks }
|
|
|
Post by elmer on Jun 2, 2022 15:13:18 GMT
#incasmlabel(test1, "./tests/20200415_asm1.asm"); #incasmlabel(test2, "./tests/20200415_asm2.asm", 3); <--- 3 what is this? #incasmlabel(test3, "./tests/20200415_asm3.asm", 2000); <--- 2000 what is this?
How to use the 'test1, 2, 3' ?... As labels to jump to in ASM blocks? You may have missed me saying this previously, but I don't ever actually use HuC, so I only find out about things when someone reports a bug. I'm sorry, but that's the reality of the situation, and so there's a limited amount of help that I can give you about exactly how HuC works. Because you're asm-capable, the fastest way to find out about things like this is to just try them in a piece a C code, and then take a look at the .s source file that HuC generates. You can also look at the compiler source. Anyway, having said that, here's what the compiler outputs in the .s file for that particular test ... .code .page 2 _test1: .include "./tests/20200415_asm1.asm"
.code .page 3 _test2: .include "./tests/20200415_asm2.asm"
.code .page 0 _test3: .include "./tests/20200415_asm3.asm"
You can see the asm labels, with the leading underscore so that HuC can refer to them. The optional 3rd parameter to #incasmlabel is the ".page" number (0..7). You've looked at a testsuite example, so the "2000" value in there is illegal, and is specifically there to cause a warning message to be output. Hmmmm ... you can also see that #incasmlabel pseudo-op includes the asm source within the .code section, wheras the #incasm pseudo-op includes the asm source within the .data section, which is most-likely where you need it to be for your purposes. HuC can sometimes be a bit of a magical mystery, and in this case, you'd have to ask turboxray why #incasm and #incasmlabel behave differently, and what the different problems are that cause the need for the different behavior.
|
|
|
Post by elmer on Jun 2, 2022 15:33:54 GMT
This code works with data that exceeds 8K. Where I can fail?.. [upd]: It seems like I've found a bug in the routine... in a bank value calculation Yes, I was going to point out the bug, good for you for finding it first! You need to change lda <__ax+1 adc <__si+1
to lda <__si+1 and #$1F adc <__ax+1
And even then, you're left with a pointer which must still be remapped to the correct destination-page that you want to use, so change tya and #$1f sta <__si+1
to tya and #$1f ora #$60 sta <__si+1
The question is how many banks can I map starting with MPR3 in my user routine? And where is my routine will be? For example I need to make something like this: void tiles_data_processing() { lock_data(); <-- here I'll map data banks
// data processing without far jumps... //...
unlock_data(); <-- here I'll restore data banks } Well, if you're writing in C (maybe with some inline-asm), then your code will be in a procedure located in MPR5 ($A000-$BFFF). If you write tiles_data_processing in asm as a procedure, it would also run in MPR5. If you write tiles_data_processing in asm as a bit of code to run in LIB2_BANK or LIB3_BANK ... it would also run in MPR5. Typically, when multiple banks of data are needed, you use MPR3 and MPR4 ... BUT note that you absolutely cannot call any other procedures in HuC while you've got MPR4 mapped to a region of data, and that you MUST restore MPR4's original value before you return. FWIW, that exact restriction/limitation does not apply in the new KickC environment, but that's not particularly helpful to you at this time!
|
|