News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_roudoudou

Rasm Z80 assembler

Started by roudoudou, 08:58, 22 February 17

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

roudoudou

Quote from: zhulien on 07:16, 22 June 23If I choose the cpr models how do the calls between 16k roms work?

Each Z80 program manages banking at his convenience...

The banking system in Rasm let the assembly knows which bank a label is located (using prefix {bank} before a label)

It's up to the programm (your generator) to take care of banking (bis repetita)


My pronouns are RASM and ACE

zhulien

Quote from: roudoudou on 10:36, 22 June 23
Quote from: zhulien on 07:16, 22 June 23If I choose the cpr models how do the calls between 16k roms work?

Each Z80 program manages banking at his convenience...

The banking system in Rasm let the assembly knows which bank a label is located (using prefix {bank} before a label)

It's up to the programm (your generator) to take care of banking (bis repetita)



Do you have an example? I did read the rasm documentation but its a bit vague to me.

andycadley

Quote from: zhulien on 10:30, 22 June 23Actually I have a heap across banks too, but it is not yet used by the c compiler, only used by assembler code.  I plan to make it accessible to the c compiler though.  Also virtual memory so that 128k users can have 4mb of vm.
I clearly must be missing something.

Let's say I have some data stored in a heap in some arbitrary bank of RAM, let's call it A. I want to pass a pointer to it to a function that ends up in bank B, this will manipulate the data and store a pointer to it in a linked list that is in bank C.

How can I possibly do that in assembly, without using 24-bit pointers and without some advance knowledge of where that bank is and without helper methods elsewhere in RAM to read from it?

roudoudou

Quote from: zhulien on 11:04, 22 June 23Do you have an example? I did read the rasm documentation but its a bit vague to me.
I repeat myself but...

...it's not to the assembler to bother with that, so you have to read your machine/OS specifications

For the Amstrad CPC you can see something here https://www.cpcwiki.eu/index.php/Gate_Array#Register_3_-_RAM_Banking

Rasm offers some GA informations (prefix {page} or {pageset}) in snapshot usage but it's simplistic and designed for data, not code

So it's up to YOU to bank in #4000 or in #C000 or with 64K sets
My pronouns are RASM and ACE

zhulien

I know how ram banking works on cpc. I don't know how to use the cpr directive of rasm.

Let's say th le code being assembled is 32kb and to be a pair of roms. Does the address go from c000 to ffff twice?  These are assembly directives right? Not runtime banking commands.  

http://rasm.wikidot.com/keywords:memory

zhulien

Quote from: andycadley on 11:06, 22 June 23
Quote from: zhulien on 10:30, 22 June 23Actually I have a heap across banks too, but it is not yet used by the c compiler, only used by assembler code.  I plan to make it accessible to the c compiler though.  Also virtual memory so that 128k users can have 4mb of vm.
I clearly must be missing something.

Let's say I have some data stored in a heap in some arbitrary bank of RAM, let's call it A. I want to pass a pointer to it to a function that ends up in bank B, this will manipulate the data and store a pointer to it in a linked list that is in bank C.

How can I possibly do that in assembly, without using 24-bit pointers and without some advance knowledge of where that bank is and without helper methods elsewhere in RAM to read from it?
You can do it with 16bit packed far addresses. The reason the data for a packed address needs to be aligned is because... they are packed.  4mb ram on a z80 is a lot and the packing of addresses makes programming easier with very little loss in the overall scheme.  

roudoudou

Cartridge bank are 16K, it's up to you to split your code

QuoteBANK number
Selecting a bank number will set RASM to cartridge mode by default. If you want to access the banks of a snapshot or the ROMs, you must first declare your intention with the BUILDSNA or BUILDROM commands. The cartridge mode is basically limited to 32 ROMs. If you exceed ROM 31, RASM will automatically switch to extended cartridge mode like XPR (experimental and only supported under the hood ;)
Using BANK without parameter will open a new temporary space of 64K which will be "lost" except if you use SAVE directive obviously
WARNING: Select a BANK always open a 64K memory space whereas only 16K will be written in ROM/cartridge/snapshot mode. To let you compile at any address, the beginning of this 16K space will be the first written byte. I recommend you to read the output log to check the start address. If you need to start your code/data after the logical address of the ROM/RAM bank, you may output a NOP at the beginning (common values are 0, #4000,#8000, #C000 ...)
My pronouns are RASM and ACE

roudoudou

Quote from: zhulien on 11:54, 22 June 23
Quote from: andycadley on 11:06, 22 June 23
Quote from: zhulien on 10:30, 22 June 23Actually I have a heap across banks too, but it is not yet used by the c compiler, only used by assembler code.  I plan to make it accessible to the c compiler though.  Also virtual memory so that 128k users can have 4mb of vm.
I clearly must be missing something.

Let's say I have some data stored in a heap in some arbitrary bank of RAM, let's call it A. I want to pass a pointer to it to a function that ends up in bank B, this will manipulate the data and store a pointer to it in a linked list that is in bank C.

How can I possibly do that in assembly, without using 24-bit pointers and without some advance knowledge of where that bank is and without helper methods elsewhere in RAM to read from it?
You can do it with 16bit packed far addresses. The reason the data for a packed address needs to be aligned is because... they are packed.  4mb ram on a z80 is a lot and the packing of addresses makes programming easier with very little loss in the overall scheme. 
back a few pages ago, you can already tell your generator to use macro and align directives...

we came full circle, i give up :-X
My pronouns are RASM and ACE

andycadley

Quote from: zhulien on 11:54, 22 June 23
Quote from: andycadley on 11:06, 22 June 23
Quote from: zhulien on 10:30, 22 June 23Actually I have a heap across banks too, but it is not yet used by the c compiler, only used by assembler code.  I plan to make it accessible to the c compiler though.  Also virtual memory so that 128k users can have 4mb of vm.
I clearly must be missing something.

Let's say I have some data stored in a heap in some arbitrary bank of RAM, let's call it A. I want to pass a pointer to it to a function that ends up in bank B, this will manipulate the data and store a pointer to it in a linked list that is in bank C.

How can I possibly do that in assembly, without using 24-bit pointers and without some advance knowledge of where that bank is and without helper methods elsewhere in RAM to read from it?
You can do it with 16bit packed far addresses. The reason the data for a packed address needs to be aligned is because... they are packed.  4mb ram on a z80 is a lot and the packing of addresses makes programming easier with very little loss in the overall scheme. 
But something has to unpack/repack that pointer every time it is used and (unlike in a high level language) you just cannot possibly know when that is in assembly language.

F.e.

I store a value in HL and then CALL some code that does

LD, A (HL)
INC A
LD (HL), A
LD (some_global_variable), HL
RET

What is the assembler supposed to do with this code? Is that value in HL a far pointer? What if I call the code from some other routine in the same bank? Does everything that accesses some_global_variable now have to change to accomodate the fact what is stored there might be a far pointer?

zhulien

#334
Quote from: andycadley on 12:10, 22 June 23
Quote from: zhulien on 11:54, 22 June 23
Quote from: andycadley on 11:06, 22 June 23
Quote from: zhulien on 10:30, 22 June 23Actually I have a heap across banks too, but it is not yet used by the c compiler, only used by assembler code.  I plan to make it accessible to the c compiler though.  Also virtual memory so that 128k users can have 4mb of vm.
I clearly must be missing something.

Let's say I have some data stored in a heap in some arbitrary bank of RAM, let's call it A. I want to pass a pointer to it to a function that ends up in bank B, this will manipulate the data and store a pointer to it in a linked list that is in bank C.

How can I possibly do that in assembly, without using 24-bit pointers and without some advance knowledge of where that bank is and without helper methods elsewhere in RAM to read from it?
You can do it with 16bit packed far addresses. The reason the data for a packed address needs to be aligned is because... they are packed.  4mb ram on a z80 is a lot and the packing of addresses makes programming easier with very little loss in the overall scheme. 
But something has to unpack/repack that pointer every time it is used and (unlike in a high level language) you just cannot possibly know when that is in assembly language.

F.e.

I store a value in HL and then CALL some code that does

LD, A (HL)
INC A
LD (HL), A
LD (some_global_variable), HL
RET

What is the assembler supposed to do with this code? Is that value in HL a far pointer? What if I call the code from some other routine in the same bank? Does everything that accesses some_global_variable now have to change to accomodate the fact what is stored there might be a far pointer?
Of course it is a near pointer.  There is no difficulty in coding a multibabked program from the point of view,, I am not saying to not consider anything to do with addressing, but when assembling it would be nice if we could assemble a program eg that is very large and have the assembler work out the hard bit which is..  how many functions fit in a bank before resetting org for the next bank and identifying the cross bank calls and where so, substitute them for a defined macro.  If totally handcoding assembly the benefit is still there but if a high level language is generating assembly rather than machine code natively, then it is a lot harder to know what is going in each bank. 

For now even if I manually changed org to previous addresses I got warnings or errors so not sure what is going on there. 

I will experiment more along the lines of...

Manually setting or to 4000h, at multiple locations in the code, followed by 4000h, $ ???

Maybe I can make the assembler warn me if it exceeds 7fffh but then I have to manually fix it to make things fit, manually change every far call... so much for a 2 second assembly process from c (for example). as I will need to do that every time I compile / assemble.

Unless of course, I modify the compiler to generate actual binary... losing the optimisation which is a 2nd optional program that analysts the asm before rasm gets it.


andycadley


I think you're overlooking the big picture: Assembly Language has absolutely no concept of "a function" - everything is just code. You can jump into any point arbitrarily. You can manipulate things in ways that a high level language wouldn't ever do but the assembler has to be able to cope with, for example:

LD HL, r1
LD DE,r2
PUSH HL
PUSH DE
PUSH HL
PUSH HL
PUSH DE
PUSH DE
CALL r2
r1: INC A
RET NZ
r2: ADD A,A
JP Z, r1
RET

Is a completely valid bit of Z80 code that rasm has to be able to assemble. Where is the start and end of each "function"? Indeed how many functions are there? What do you expect rasm to do if it crosses a 16K boundary? What do you imagine the generated code looks like?

What you want is a linker, so that intermediate code can be generated without fixed addressing and it's code size known, so that your compiler can generate code as necessary and then the linker can figure out how to arrange the generated code optimally across multiple banks and fix up addressing to work (either using something like the firmware's RST scheme or by your code generator/linker agreeing a scheme of inserting NOPS where necessary to give fix up space.


zhulien

function, I called it that, it's a label.  Generated code would look almost the same whether catering for banked or not - only that rasm could detect which labels are called far or not and for far called ones in another pass, re-align them - it already knows the label address for everything, of course it does need another pass to recalculate after aligning far-called labels. It's not that hard - but from scratch it is a lot of work to create a brand new assembler and / or compiler.  You are assuming I am trying to make rasm do something too high level - I am not, only identify which labels are referenced between banks, and substitute them for packed labels and / or macros which can call a banking function which handles the packed addresses.  Your example code doesn't necessarily mean it has any far calls within it.  We could even have a directive to wrap around code segments to indicate keep them together, don't put them across 2 banks. but... as mentioned, you can almost instantly flow from one bank to another anyway on CPC - it is very straight forward.  You never created a linked list out of the banks and had the z80 automatically execute through all 512k of extended memory indefinitely?  To do that, nop all the banks, at the end of each bank, stick the bank switching code to the next bank - don't forget the jump in the newly switched bank to jump to the start of the bank for that one's execution...  For large code that is very valid, and yes, you can have global RAM heap and banked heap - which of course is not directly accessible.

In any case, if nobody has interest in such a feature than myself, that is fair enough.

andycadley

The problem is you keep thinking of the process in terms of generated assembly code. But an assembler has to be able to cope with hand written code that can undermine assumptions such as "only labels get called" e.g.

LD HL, somelabel
LD DE, 256
ADD HL,DE
PUSH HL
RET

Which is essentially CALL somelabel+256.

Or 

LD HL, somelabel
JP (HL)

Which is equivalent to the pseudocode:

LD HL, somelabel
CALL HL
RET

And that's just thinking about the entry point to a "function". Trying to determine where it might exit is, in the worst possible case, an NP complete problem 

Expecting an assembler to somehow be able to divine the high level meaning of code is just not practical nor desirable in most cases. Every platform under the sun has faced and solved this problem already with an assembler capable of generating relocatable code and a linker.

zhulien

#338
I never mentioned only labels called. I mentioned labels that are referenced out of the current bank.

It is not hard to determine which labels are referenced from another bank and pack them.

Please think positive. If you don't believe the feature would be useful to you, I am not asking that it is a permanently enabled feature.

I am not going to ask this, but as an example... what if I asked it to support z80 and 6502 together so it works on both CPUs natively?  Yep I have proven it can be done... 

andycadley

Given a file of assembly code how does RASM know where your "function" begins and ends? How does it know when one crosses into another bank?

What should RASM generate instead of the three bytes for a CALL instruction? Bearing in mind you only have three bytes, you can't move code again or assembling the code becomes a recursive function itself.

What should RASM generate instead of a JP instruction for a far pointer. What should RASM generate for a JR instruction that now refers to a far pointer.

roudoudou

I don't want to say but...

It's been 4 days of one-way dialogue

from my own experience i can say the generator would have been modified a long time ago to take account of those jumps :P

because from a generator point of view, it's easy and fast modifications. In fact, it's the easiest way to do
My pronouns are RASM and ACE

zhulien

Quote from: andycadley on 08:57, 24 June 23Given a file of assembly code how does RASM know where your "function" begins and ends? How does it know when one crosses into another bank?

What should RASM generate instead of the three bytes for a CALL instruction? Bearing in mind you only have three bytes, you can't move code again or assembling the code becomes a recursive function itself.

What should RASM generate instead of a JP instruction for a far pointer. What should RASM generate for a JR instruction that now refers to a far pointer.

For me I would use rst 6 followed by the packed address for far calls.  For far jumps i would probably throw a warning for coder to address them for this type of model.  If directives were provided to wrap the entry and last statement for which must reside in a single bank, then no issues... as the coder would ensure that there are no jumps outside those groupings.  Such groupings also don't need to conform to byte alignment within as they will always be local calls.

zhulien

Maybe a minimal model could be...

Block align 256 (or 64) to start an aligned block 
End block

The assembler would do 3 things only

1. Ensure a block if it cannot fit in the current bank goes to the next bank

2. Any calls to a far address not conditional calls, are changed to rst 6 followed by a packed address of the aligned block start

3. Any label references that are in another bank are packed


andycadley

Assuming that (3) only refers to CALLs, i.e. a label reference to data in another bank just flat out fails, then what you have spec'd out is essentially a linker.

All you need is an assembler that can generate the necessary relocation information for fixups (I have no idea whether RASM is capable of that or not).

The linker can take care of inserted packed addresses and swapping CALL for RST as necessary (you'd probably have to use the other RSTs for conditional CALLs too) as these are trivial modifications. The advantage of a linker is that it could theoretically also rearrange functions in memory to fit better (though that's not strictly necessary).

zhulien

Not only calls, also references to other far labels. If I make a table of assets that are all byte aligned in other banks, if the table is using labels I would expect them to be packed.

I agree a linker can do that, by rasm does so much already which is what makes it a good assembler.

andycadley

Quote from: zhulien on 13:30, 24 June 23Not only calls, also references to other far labels. If I make a table of assets that are all byte aligned in other banks, if the table is using labels I would expect them to be packed.
But that's where it falls down. The minute you try to do it with anything that is potentially data and not just direct calls it won't work. Consider the code below:

#entered with A holding a table entry number

ADD A, A
LD L, A
LD H, HIGH(lookuptable)
JP (HL)

lookuptable:

DEFW routine1
DEFW routine2
DEFW routine3

If routine2 is in another bank, what's the assembler (or linker) supposed to do? If it packs the address, the code will break, if it doesn't then you'll jump into code that isn't paged in an get unintended results. 

And that's before you even consider that one of the routines might need to be a far call and another a local call - leaving you in a situation where different things need to happen precisely because you've tried to hide what is going on under the hood. 

Once you need that level of sophistication it's much easier to do it all in your generator. You know the code you're generating so can easily estimate the worst case memory it'll use (unless your optimizer is massively increasing code size but that's probably unlikely). And with a rough estimate, you can figure out where to place routines in banks, you don't need to know exact address etc, you just generate macros that will work it out once it actually gets assembled. And since you know the intent behind the generated code you can choose a suitable approach that will work in all the situations your code requires.

zhulien

You must program in a different way than everyone else. Nothing falls over. It is a specific memory model with a specific behaviour that you code for in a specific way.

If you are using that model you will know how to use it and not code dumb code.

If you are looking up byte alighted packed addresses then you will likely use a routine somewhere that deals with packed addresses.

Since you obviously object to such features that you are not forced to use, that is bizarre..   instead of providing any constructive feedback on any improvements.  You have only consistently said what won't work when it doesn't actually matter to the person who would love such a feature and how i would use them will work 100%

zhulien

I needn't continue justifying why I would like such a feature. If rasm eventually gets this feature that will be awesome. But I will continue to use rasm without it.

andycadley

I'm trying to help you come up with a mechanism that might actually work for what you want to do. 

Transparently packing address during the assembly phase just doesn't work, there are too many cases where it isn't obvious what code you have to generate or where an assembler cannot possibly know whether a label is data or the entry point into a routine. If you knew they were packed addresses you could deal with it, as you say, but the whole point of your suggestion is that the assembler swaps them without you (or your code) ever knowing about it and fundamentally that is what breaks it.

As I said, it could work to some degree if you replace only CALL instructions with RSTs as part of a linking-style phase, although you still have to deal with data management manually and that's usually much more of a pain than code management. But you can't have tables of addresses etc because that requires treating the address as data and the program code has to understand what the data it would work on looks like.

zhulien

#349
I don't want data management handled automatically, only labels that are aligned in other banks to be packed. It makes it straight forward then to deal with data and/or logic anywhere in a 4mb or 16mb address space.

And I never said only entry point to a routine. Specifically I said labels that are referred to in another bank. It does not matter how they are referred... pack them.  Only code substitute is CDxxxx, nothing more. Other code can generate incorrectly for all I care if misused... don't misuse it.

The packed address happens to essentially a handle to a bit of memory in 2 parts, 1 part a handle to a bank, 1 part a handle to a byte aligned address.  Via both we will gain up to 16mb virtual memory on a standard 6128 at a reasonable speed... not very good speed but still possible on unexpanded 64k machine. 

If I have to pad the last code bank with a ds #8000-$ so that the rest of the 16mb becomes a heap for assets and data, I am ok with that because... we are gaining almost 16mb for the small bit of 16k sacrifice... unless your entire program is smaller than 16k then why use the gigantic model...

Powered by SMFPacks Menu Editor Mod