News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_kilon

Starting coding for Amstrad

Started by kilon, 08:45, 09 March 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

kilon

Quote from: AMSDOS on 23:47, 13 March 15



That's perfectly fine, I was only draw a comparison between Language and end result.  :D 


Well if you do produce something, there will be people testing it out on Real Hardware. We've had issues in the past where something has been produced that works fine on all the Emulators but crashes on the Real Hardware.  :D

And this why I think Emulators rock :D

But then I never worry when my code  crashes to oblivion, I am terrified when it does not. I know there is a nasty big bug like predator is in stealth mode waiting patiently to get me. But then this where you good people come in to the rescue to be used as cannon fodder . 

kilon

So here we go with first coding question.

I have seen a couple of example of how z80 can exchange data with C via direct access of memory , in case of C with the use of a pointer and function memset(). Is this the only way I can pass data from C to assembly ? I am using SDCC.



pelrun

It can be a bit confusing finding this info in the SDCC documentation, as it's written from an 8051 standpoint and not entirely obvious how other processors work. As C code gets transformed into assembly as part of the compile process, the fundamental answer to your question is 'do it the way the compiled code does it'.


This comes down to a few different techniques.


All in-scope variables have labels that are accessible as asm labels. You'll see this in the inline asm in Section 3.13; pointers to the C variables 'head' and 'tail' are available to the asm as '_head' and '_tail' - the C name prefixed with an underscore. All asm labels (including function labels) follow this convention.


Next is the calling convention: the compiler has a standard way of storing C function parameters in memory before calling a function. Section 3.14 in the SDCC docs describe it's calling convention; the first parameter always goes into registers (it lists the 8051 registers here; the z80 equivalents are in section 4.3, basically DEHL depending on how long the param is) and the remaining parameters either get permanently allocated memory with a specific label for each parameter: _functionname_PARM_x (for a non reentrant function) or are pushed onto a stack frame pointed to by IX (when marked re-entrant). The whole non-re-entrant mess is due to 8051 limitations; the stack-auto option will compile all functions as re-entrant by default.


Nich

Quote from: kilon on 08:48, 15 March 15
So here we go with first coding question.

I have seen a couple of example of how z80 can exchange data with C via direct access of memory , in case of C with the use of a pointer and function memset(). Is this the only way I can pass data from C to assembly ? I am using SDCC.

The parameters are passed on to the stack, and my preferred method is to read them by pointing HL to the appropriate place in the stack (reading from HL is a lot quicker than reading from IX). Here is an example from some code I wrote to load a file using the firmware:

__asm
ld hl,#5
add hl,sp
ld d,(hl)
dec hl
ld e,(hl) ; DE = address to load file to
dec hl
ld a,(hl)
dec hl
ld l,(hl)
ld h,a ; HL = address of filename string

; ...


Or you can of course read the registers in the opposite direction if you need to, like this piece of code I wrote to read ink colours from a table:

__asm
ld hl,#2
add hl,sp
ld a,(hl)
inc hl
ld h,(hl)
ld l,a ; HL = start address of table containing ink data

; ...

kilon

#54
Thank you both, I am reading the recommended chapters from the SDCC manual

Nich may I ask you why you do

ld hl,#5

?

peirum what makes re-entrant and non re-entrant function in the case of Z80 ?

If I understand both of you recommend to read the parameters from the stack. But how I know where each parameters starts and ends ?

pelrun

Quote from: kilon on 21:35, 15 March 15
Nich may I ask you why you do

ld hl,#5


That's the number of bytes taken up by the parameters on the stack; after adding the stack pointer (which points to the bottom end of the parameter block) HL will point to the top end of the parameter block.

Nich, you're using SP instead of IX there; does SDCC actually point SP at the right place or are you manually copying IX into it earlier?
Quote
pelrun* what makes re-entrant and non re-entrant function in the case of Z80 ?


SDCC treats all functions as non-reentrant by default (which is not standard C behaviour). Either add '__reentrant' to every function definition (annoying), or add --stack-auto to your sdcc command line to change the default to re-entrant instead.


Quote
If I understand both of you recommend to read the parameters from the stack. But how I know where each parameters starts and ends ?


You wrote the parameter list in the function definition; it's just that crammed together in order - you need to know how many bytes each parameter is (char=1, int=2, long=4 etc).


ronaldo

#56
Quote from: kilon on 08:48, 15 March 15
I have seen a couple of example of how z80 can exchange data with C via direct access of memory , in case of C with the use of a pointer and function memset(). Is this the only way I can pass data from C to assembly ? I am using SDCC.
As others have pointed out, you have to look for calling conventions to better understand how parameters are passed to C functions (there are different ways). By default, SDCC uses the stack, with a policy called "caller saves". This means 2 things: Stack is used to pass parameters to functions and, also, the caller is responsible for saving the values of the registers, as callee will most likely alter them.

To understand how parameters are passed, it is better to see a couple of code examples:

;src/main.c:59: case _4x8:     cpct_drawSprite4x8_aligned(sprite, video_mem); break;
00104$:
    push    de
    push    bc
    call    _cpct_drawSprite4x8_aligned
    pop    af
    pop    af

Here we are calling a function which draws an sprite, and needs 2 parameters: a pointer to the sprite and another pointer to the video memory location where to paint the sprite. Both parameters are 16-bits, so they are stored in registers de=video_memory, bc=sprite. The compiler pushes these two values to the stack in reverse order (from right to left, video_mem first, sprite second). Doing it this way, is easier for the function to recover them, as they will be in direct order with respect to the top of the stack, as you see in the next piece of code:
   
   ;; GET Parameters from the stack (Push+Pop is faster than referencing with IX)
   pop  af                 ;; [10] AF = Return Address
   pop  hl                 ;; [10] HL = Source address (Sprite)
   pop  de                 ;; [10] DE = Destination address (Video Memory)
   push de                 ;; [11] Leave the stack as it was
   push hl                 ;; [11]
   push af                 ;; [11]

It is important to note that a third 16-bit value is also pushed to the stack by the CALL instruction: it pushes the return address, which points to the next code instruction that should be executed when the function is finished (when a RET is performed). It is also important to note that we have to be careful when messing up the stack. When a RET is executed, stack should be in the same state that it was when function was called (at least, SP should point to the same address and it should contain the return address). Otherwise, horrible evils of side effects will come and eat us, poor human programmers :D .

This is a different example:

;src/main.c:81: printAllScreen(sprites[i], spr_widths[i], functions[i]);
    push    af
    inc    sp
    push    bc
    inc    sp
    push    de
    call    _printAllScreen
    pop    af
    pop    af

The main difference here is that functions[ i ] and spr_widths[ i ] are 8-bit, whereas sprites[ i ] is 16-bit. So, the compiler uses a combination of push af + inc sp which effectively inserts A register into the stack (it writes AF but incrementing SP makes the stack point to A, and leave F just out of the top of the stack). The same happens with push bc + inc sp: B is inserted at the top of the stack. At the end, there are 6 bytes at the top of the stack: Return address (2bytes), sprites[ i ] (2bytes), spr_widths[ i ] (1byte), functions[ i ](1byte). Then, with only 3 pops, like in the previous example, you may get the 6 values out of it.

I hope this example is useful for you to understand how it all works :) .

Nich

Quote from: pelrun on 04:58, 16 March 15
Nich, you're using SP instead of IX there; does SDCC actually point SP at the right place or are you manually copying IX into it earlier?
Yes, SP automatically points to the right location, so there's no need to do anything with IX.

ronaldo

Quote from: Nich on 20:12, 16 March 15
Yes, SP automatically points to the right location, so there's no need to do anything with IX.
SP = Stack Pointer, this is the register the CPU uses to point to the top of the stack, not IX. IX means Index Register and is designed to point at the start of tables or vectors to be able to easily access all of their members.

SDCC most of the time (and depending on the configuration you use for compiling) synchronizes IX with SP, but I would advice against assuming that it works this way allways. It is better to use more standard ways of accessing the stack (and, moreover, they are faster than using IX)

kilon

wow you guys are amazing, you spent all this time explaining things to me and with detailed examples. Can't thank you enough . Sometimes I code because I enjoy coding sometimes because I enjoy the community, you people seem to have both here :)

I think now with your help I understand how the interaction between C and Assembly works. Thank you . These are my first steps I am sure more will follow.

pelrun

Quote from: ronaldo on 22:18, 16 March 15
SDCC most of the time (and depending on the configuration you use for compiling) synchronizes IX with SP, but I would advice against assuming that it works this way allways. It is better to use more standard ways of accessing the stack (and, moreover, they are faster than using IX)


I'm working from the manual, which isn't great for architectures other than 8051 (it only mentions IX.) Haven't had a chance to poke it and see what the actual behaviour was.

AMSDOS

I only remember banging my head when I was trying to Integrate my Small-C code into Assembly.


RSXes would be easier surely? I was able to Integrate Easi-Sprite Driver with CPC BASIC 3 that got me an extremely fast result when the Assembly was compiled, though CPC BASIC 3 can also produce ccz80 code as a workaround?
* Using the old Amstrad Languages :D * And create my own ;)
* Incorporating the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

Morri

Quote from: AMSDOS on 09:13, 17 March 15
I was able to Integrate Easi-Sprite Driver with CPC BASIC 3 that got me an extremely fast result
I would be interested in seeing this. I have been playing around with easi-sprite driver thinking of making a very simple game, but the ability to compile it would be very cool! Do you mind sharing?
Keeping it Kiwi since 1977

AMSDOS

Quote from: Morri on 17:55, 17 March 15
I would be interested in seeing this. I have been playing around with easi-sprite driver thinking of making a very simple game, but the ability to compile it would be very cool! Do you mind sharing?


It should be this post along with all my other Ramblings.  :D  Here's a direct link to that attached file if you prefer.
* Using the old Amstrad Languages :D * And create my own ;)
* Incorporating the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

Powered by SMFPacks Menu Editor Mod