As-tu seulement un compilateur décent sur ton Z80? A-t-il une option pour montrer le code généré? En soi, les instructions me semblent correctes, mais ça dépend de l'environnement ou du contexte. Ça fait trop longtemps que je n'ai pas codé en assembleur Z80. Avec une RAM de 64Kb, ça ne doit pas êttre évident.
Le Tout est souvent plus grand que la somme de ses parties.
Parameters for the function are pushed to the stack before the function is called. They are pushed from right to left, so the leftmost parameter is the first one found when going up in the stack:
char SumTwoChars(char x, char y) __naked
{ __asm ld iy,#2
add iy,sp ;Bypass the return address of the function
ld l,(iy) ;x
ld a,1(iy) ;y
add l
ld l,a ;return value
ret
__endasm;
}
- Edité par michelbillaud 16 février 2021 à 18:58:46
Merci pour vos réponses éclairées, sauf que la lumière ne s'est pas allumée dans mes vieux neurones.
J'ai pas compris la manipulation pour pouvoir affecté dans mes registres mes variables x et y.
Si je pense a Ld DE, x autrement dit DE prend la valeur contenue dans x
et pour LD HL, y....
La syntaxe serait : LD DE, iy et LD HL, 1(iy)
Mon but est d'utiliser les vecteurs de la rom du cpc464 d'amstrad pour m'affranchir justement de coder en langage machine
Ensuite pour vous répondre PierrotLeFou je compile à la main avec. Bat sous Windows. J'ai pas réussi à régler code::bloc. Je suis un tuto sur cpcmania sur le sujet.
Parmi ces trois propositions pour résoudre un problème, laquelle est la moins efficace ?
1. lire la doc
2. essayer soi-même
3. demander sur un forum à des gens qui n'y connaissent rien.
La moins efficace s'est certainement répondre à une question par une énigme. Si vous savez pas je me passe de votre verbiage. Ou alors expliquez moi la sémantique de votre code postée. Merci
Parmi ces trois propositions pour résoudre un problème, laquelle est la moins efficace ?
1. lire la doc
2. essayer soi-même
3. demander sur un forum à des gens qui n'y connaissent rien.
🤔demander à des gens qui n'y connaissent rien d'essayer après avoir lu la doc ? 🤪😅
Sinon sur le site susmentionné, il y a un bout de code :
; FILE: putchar.s
;; Modified to suit execution on the Amstrad CPC
;; by H. Hansen 2003
;; Original lines has been marked out!
.area _CODE
_putchar::
_putchar_rr_s::
ld hl,#2
add hl,sp
ld a,(hl)
call 0xBB5A
ret
_putchar_rr_dbs::
ld a,e
call 0xBB5A
ret
Et comme ça à vue de nez je dirais bien que :
ld hl,#2 : charge la valeur 2 dans le registre hl
add hl,sp : ajoute l'adresse de base de la pile (où doit se trouver le paramètre)
ld a,(hl) : charge le registre a avec la valeur pointée par hl, donc la valeur qui a été empilée sur la pile, donc le paramètre
Oui mais non, ça c'est du langage d'assemblage, et le monsieur il veut intégrer des instructions dans du C compilé par sdcc. Pas forcément une bonne idée, mais bon.
Il y a des chances que les syntaxes des instructions différent dans les deux cas. Si le monsieur voulait bien se donner la peine de regarder les liens qu'on lui donne (et qu'on s'emmerde a chercher pour lui...) au lieu de rochonner, il trouverait certainement des exemples.
Spoiler : on y trouve des "ld hl,..."
Ps : un call suivi d'un ret, ça se remplace avantageusement par un jmp, dans mes lointains souvenirs (Le JRST hack du pdp 11 !)
- Edité par michelbillaud 16 février 2021 à 21:52:51
Ps : un call suivi d'un ret, ça se remplace avantageusement par un jmp, dans mes lointains souvenirs (Le JRST hack du pdp 11 !)
Je ne te suis pas trop dans ton raisonnement: le call va exécuter un sous programme, puis le ret va sortir du sous-programme courant. En quoi un jmp pourrait-il remplacer cette séquence?
Je pense que la syntaxe de la plupart des assembleurs Z80 doit êttre pratiquement identique. Je suis peut-être lemt à comprendre, mais que vient faire un script .bat et Code::Block sur Windows? À moins d'avoir un cross-compiler pour le Z80, on est dans le champs. J'ai déjà vu des compilateurs Fortran sur un Z80, mais pas de C. Moi, j'utilisais un cross-assembler et il y avait un loader dans le résident du Z80. Il faut absolument passer par la pile quand on appelle une fonction. Il n'y a pas de symbole généré pour les variables X et Y parce que ce sont des paramètres.
Le Tout est souvent plus grand que la somme de ses parties.
Je code malheuresement en Z80 (oui je n'aime pas cet assembleur)
Effectivement LD DE,x et LD HL,y n'existe pas , c'est simple dans ton cas x et y sont des variables , donc il faudrait écrire : LD DE,(x) et LD HL,(y)
C'est simple : sans parenthèse => valeur immédiate avec parenthèse => valeur indirect (adresse)
Mais les variable sstocké en argument de fonction ne sont pas forcément stocké en RAM avec une mémoire fixe , mais sur la pile ,il faut sûrement regardé les call conventions de ton compilo (soit il push les arguments , soit certain registres sont dédié).
PS: avant de faire de l'asm du z80 faudrait effectivement lire une doc dessus
@HelbaSama: Utilises-tu un compilateur C sur un Z80? Si on pouvait voir le code généré, on aurait la réponse. Mais je pens que c'est comme michelbillaud l'a expliqué.
Le Tout est souvent plus grand que la somme de ses parties.
Ps : un call suivi d'un ret, ça se remplace avantageusement par un jmp, dans mes lointains souvenirs (Le JRST hack du pdp 11 !)
Je ne te suis pas trop dans ton raisonnement: le call va exécuter un sous programme, puis le ret va sortir du sous-programme courant. En quoi un jmp pourrait-il remplacer cette séquence?
Bon, imaginons que la fonction foo ci-dessous soit appelée quelque part dans le code
foo:
...
call bar ; 1
ret ; 2
au moment de faire le call, dans la pile on a l'adresse à laquelle foo devra retourner.
le "call bar" empile l'adresse de l'instruction ret (2)
la fonction bar contient normalement une instruction ret qui depilera et fera retourner à cette adresse
puis le ret de foo dépilera l'adresse de retour et fera revenir à l'appelant de foo.
Maintenant si on fait
foo:
...
jmp bar
le jmp n'empile rien
on va exécuter le code de bar
le ret de bar dépile l'adresse de retour et s'y branche.
Economies
code plus court (call + ret / jmp), prend moins de place
plus rapide
ne prend pas de place dans la pile.
- Edité par michelbillaud 17 février 2021 à 12:50:34
@HelbaSama: Utilises-tu un compilateur C sur un Z80? Si on pouvait voir le code généré, on aurait la réponse. Mais je pens que c'est comme michelbillaud l'a expliqué.
Non je code en full asm pour le Z80 , donc aucune idée de ce que fait le compilo (et puis ça dépend des compilo aussi , pas tous respecte les mêmes conventions). Le Z80 et le 6502 sont les rares processeurs où les compilateurs C sont assez mauvais et que programmer à la main est bien plus rapide , mais c'est un peu normal , à l'époque on pensait pas forcément faciliter les langages de haut niveau
Pour l'optimisation du call/ret / jmp , cet opti marche que sur des cas particulier (faut qu'il y'a pas de code après le jmp) , en général , si on y va en asm ce cas là il arrive rarement En général quand on fait une boucle , on fait comme les compilo C , on inline et on déplie la boucle
Techniquement sur le Z80 faut éviter l'utilisation de ix/iy (qui va souvent à plus de 20 cycles ) ,c'est les plus gourmand en cycle et privilégier hl (7-10 cycles)
Pour l'optimisation du call/ret / jmp , cet opti marche que sur des cas particulier (faut qu'il y'a pas de code après le jmp) , en général , si on y va en asm ce cas là il arrive rarement
En effet, ça marche pour un appel _terminal_ seulement, quand un call est suivi d'un ret (c'est bien indiqué !)
Mais de là à dire que c'est rare : c'est le cas pour les 3 fonctions présentées dans le message d'origine. Et probablement pour quasiment toutes les fonctions qu'il veut écrire qui sont des "wrappers" compatibles avec C pour les fonctions de la ROM.
Dans le cas où la fonction C ne contient qu'un call
il y a probablement mieux à faire : déclarer wait_char comme un symbole dont la valeur est 0xBB06. En assembleur probablement, ne me demandez pas comment. Comme ça l'appel de wait_char ira directement au bon endroit.
- Edité par michelbillaud 17 février 2021 à 14:13:34
merci les enfants pour vos conseils, papy n'a rien compris a vos gesticulations intellectuelles. Bon sinon , si quelqu'un a une idée simple à mon problème cité au dessus, suis preneur......
Effectivement faire du C sur des bécanes avec 64 K de mémoire - 16 Ko de rom , il y a mieux.
Ceci dit SDCC est un des mieux optimisé pour la circonstance. Mon code posté au dessus je note , n'est pas académique, j'ai juste fait une transcription avec ce que je connais en langage machine sur le bestiau.
Après avoir lu le R.T.F.M. 130 pages sur SDCC , un paquet de tuto sur le sujet pour compiler à la main et avoir une disquette exécutable pour mon cpc464,
je coince sur mon problème. qui est de pouvoir me creer une fonction avec des parametres en entrée et les utiliser dans mon code machine
merci les enfants pour vos conseils, papy n'a rien compris a vos gesticulations intellectuelles. Bon sinon , si quelqu'un a une idée simple à mon problème cité au dessus, suis preneur......
Ouaw ça donne pas envie de t'aidersi tu ne lis pas les réponses , mais je répète pour ma part : 1) ce n 'est pas comme ça qu'on lit en RAM 2) il faut respecter les calls conventions
Après avoir lu le R.T.F.M. 130 pages sur SDCC , un paquet de tuto sur le sujet pour compiler à la main et avoir une disquette exécutable pour mon cpc464,
je coince sur mon problème. qui est de pouvoir me creer une fonction avec des parametres en entrée et les utiliser dans mon code machine
Merci de votre patience
- Edité par Dark-linux il y a environ 13 heures
Tu coinceras sans doute moins en partant des exemples qui ont été indiqués. Il suffit pas de prétendre avoir lu la doc, il faut l'appliquer
Fais nous voir tout ce que tu as essayé, avec les messages d'erreur. On pourra peut être voir ce qui ne va pas dans ce que tu as ecrit. Et ce que tu as oublié d'essayer.
De notre côté, on n'a pas forcément d'amstrad pour jouer avec, donc la motivation pour installer tout le bordel et apprendre à sans servir juste pour dépanner quelqu'un en lui donnant des solutions verifiées, c'est assez limité.
@pierrot les compilateurs C qui tournent sur z80, y en a plein google c compiler cp/m
---
Bon
1. Installé sdcc sous linux (debian)
2. copié le source dans un fichier a.c
3. trouvé comment on compile pour z80. Rédaction d'un Makefile
all : a.ihx
%.ihx : %.c
sdcc -mz80 $^
4. Coup d'oeil a la doc. Annotation "__naked" indique de générer du code sans prelude ni postlude (le ret). Je préfère déclarer au compilateur que c'est moi qui prends les manettes de A à Z dans la fonction. Je mettrai un "ret" SI J'EN AI ENVIE, c'est pas ses oignons.
void mode2(void) __naked
{
__asm
ld a, #2
call #0xBC0E
ret
__endasm;
}
5. Lecture des exemples : ils indiquent précisément comment récupérer les paramètres sur la pile. Ils sont stockés successivement dans la pile, 2 octets pour un int, à partir de l'offset 0. Utilisation de iy pour les récupérer
sous réserve que je ne sois pas planté dans l'ordre des registres (big endian etc).
6. Remplacement de l'appel (call) par un saut.
Vu que je ne connais pas le Z80, et que mes souvenirs de 8085 remontent aux années 80, un coup d'oeil pour trouver que c'est l'instruction JP (pas jump)
Apparemment, il faut enlever le # devant l'adresse numérique
"Apparemment, il faut enlever le # devant l'adresse numérique" effectivement les valeurs immediate sans sans parenthèse (donc pas de #) le # est pour le 6502 ou le M68000 , mais je pensais que c'était son compilo qui demandais ce '#' , au moins tu confirme que ce n'est pas le cas
Ce que je confirme, c'est que ca passe à la compilation si on l'enlève, et pas si on le laisse, contrairement à call. Après, faut essayer pour valider.
J'ai pas envie de chercher une confirmation dans les docs pour un truc qui ne me servira probablement jamais.
désolé si j'ai froissé c'était pas le but ! Je part du fond de l'abîme de la méconnaissance et je fais avec les neurones qui me restent et suis pas programmeur de métier....bon je rentre du boulot et voulait vous poster ou j'en étais avec mes turlupetudes intellectuelles....
d'abord mon fichier compil.bat qui me sert a compiler et linker sur mon z80 avec les specificités de l'amstrad.... lignes 3 et 4
ligne5 ma compilation pour faire démarrer mon code à 138 hexa sinon ca crache à l'éxecution...
et le linkage ligne suivante
la derniere ligne pour creer une disquette virtuelle pour essayer sur mon émulateur Caprice.
j'ai corrigé un peu mon code car j'ai compris que mes x et y en fait sont stockés sur la pile et qu'il faut les récuperer sur la pile dans le bon ordre
et pour faire cela j'ai modifié comme suit sans forcement bien comprendre dans les détails, rajouté __naked à ma déclaration de fonction pour éviter un "retour intempestif ?"
bon maintenent je vais regarder tous vos commentaires essayer de comprendre mes erreurs, refaire des tests , et je reviendrai vers vous pour faire le point......
Mais il me faut du temps , suis long à la détente et pour moi c'est un passe temps
Merci pour toutes vos remarques constructives @+
nota bene : celui qui a passé 1h à chercher sur son temps libre (michelbillaud) , laissez moi votre adresse en PV, dans le sud ouest on fabrique du foie gras ...
Merci pour vos réponses , bon pour l'instant j'ai pas de question subsidiaire sur le sujet, je vous mets un exemple de code que j'ai réalise (sauf la fonction de traçage de ligne et de cercle que j'ai honteusement copié sur le net). Histoire de remercier ceux qui m'ont aider.....
Si vous avez des remarques je prends, histoire de m'améliorer . Merci
A l'exécution sur mon CPC j'ai un traçage de rectangle et de cercle comme je le souhaitais.
#include <stdio.h>
#define WAIT_CHAR \
__asm \
call #0xBB06 \
__endasm;
#define CLS \
__asm \
call #0xBBDB\
__endasm;
#define SET_MODE1 \
__asm ld a,#1 \
call #0xBC0E \
__endasm;
#define SET_MODE2 \
__asm ld a,#2 \
call #0xBC0E \
__endasm;
typedef unsigned int mot;
typedef unsigned char octet;
void PutPixelMode1(mot nX, mot nY, octet nColor)
{
mot nPixel = nX % 4;
unsigned char *pAddress = (octet *)((mot)(0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 4)));
switch (nPixel)
{
case 0 :
{
*pAddress &= 119;
if(nColor & 1) *pAddress |= 128;
if(nColor & 2) *pAddress |= 8;
break;
}
case 1:
{
*pAddress &= 187;
if(nColor & 1) *pAddress |= 64;
if(nColor & 2) *pAddress |= 4;
break;
}
case 2:
{
*pAddress &= 221;
if(nColor & 1) *pAddress |= 32;
if(nColor & 2) *pAddress |= 2;
break;
}
case 3:
{
*pAddress &= 238;
if(nColor & 1) *pAddress |= 16;
if(nColor & 2) *pAddress |= 1;
break;
}
}
}
void lineBresenham(mot x1,mot y1,mot x2,mot y2, octet nColor)
{
int cx, cy,
ix, iy,
dx, dy,
ddx= x2-x1, ddy= y2-y1;
if (!ddx) { //vertical line special case
if (ddy > 0) {
cy= y1;
do PutPixelMode1(x1, cy++, nColor);
while (cy <= y2);
return;
} else {
cy= y2;
do PutPixelMode1(x1, cy++, nColor);
while (cy <= y1);
return;
}
}
if (!ddy) { //horizontal line special case
if (ddx > 0) {
cx= x1;
do PutPixelMode1(cx, y1, nColor);
while (++cx <= x2);
return;
} else {
cx= x2;
do PutPixelMode1(cx, y1, nColor);
while (++cx <= x1);
return;
}
}
if (ddy < 0) { iy= -1; ddy= -ddy; }//pointing up
else iy= 1;
if (ddx < 0) { ix= -1; ddx= -ddx; }//pointing left
else ix= 1;
dx= dy= ddx*ddy;
cy= y1, cx= x1;
if (ddx < ddy) { // < 45 degrees, a tall line
do {
dx-=ddy;
do {
PutPixelMode1(cx, cy, nColor);
cy+=iy, dy-=ddx;
} while (dy >=dx);
cx+=ix;
} while (dx > 0);
} else { // >= 45 degrees, a wide line
do {
dy-=ddx;
do {
PutPixelMode1(cx, cy, nColor);
cx+=ix, dx-=ddy;
} while (dx >=dy);
cy+=iy;
} while (dy > 0);
}
}
void Cercle(mot rayon, mot x_centre, mot y_centre, octet nColor)
{
int x =0;
unsigned char y = rayon;
int d = rayon - 1;
while ( y >= x )
{
PutPixelMode1( x+x_centre, y+y_centre, nColor );
PutPixelMode1( y+x_centre, x+y_centre, nColor );
PutPixelMode1( -x+x_centre, y+y_centre, nColor );
PutPixelMode1( -y+x_centre, x+y_centre, nColor );
PutPixelMode1( x+x_centre, -y+y_centre, nColor );
PutPixelMode1( y+x_centre, -x+y_centre, nColor );
PutPixelMode1( -x+x_centre, -y+y_centre, nColor );
PutPixelMode1( -y+x_centre, -x+y_centre, nColor );
if ( d >= 2*(x-1) )
{
d = d - (2*x);
x = x + 1;
}
else if ( d <= 2*(x-y) )
{
d = d + (2*y) - 1;
y = y-1;
}
else
{
d = d + 2*(y-x-1);
y = y-1;
x = x+1;
}
}
}
void main(void)
{
int i;
SET_MODE1;
for(i=0; i<25; i++)
{
lineBresenham(108+i,48+i, 211+i,48+i,i%4);
lineBresenham(211+i,48+i, 211+i,151+i,i%4);
lineBresenham(211+i,151+i, 108+i,151+i,i%4);
lineBresenham(108+i,151+i, 108+i,48+i,i%4);
// trace ligne oblique
lineBresenham(108+i,48+i, 211+i,151+i,i%4);
lineBresenham(211+i,151+i, 108+i,48+i,i%4);
}
WAIT_CHAR;
CLS;
for (i=0; i< 50; i++)
{
Cercle(30+i, 160, 100, i%4);
}
WAIT_CHAR;
}
Moi j'aime beaucoup les anciens codages sur les anciennes machines, et je suis en train d'éplucher ta fonction PutPixelMode1 avec attention.
Je suis allé me documenter, et j'ai vu qu'effectivement l'adresse de l'écran commence a 0xC000
Je suis d'ailleurs surpris de voir, dans ta formule ligne 30 que les lignes de pixels ne se suivent pas en mémoire !
Chaque ligne fait 80 octets et se suit, ok.
ligne 0 commence a 0 ( a partir de 0xC000), ligne 1 a 2048, ligne 2 a 4096 .... ligne 8 commence a 80, ligne 9 a 80 +2048, tout est entrelacé.
Ensuite j'ai vu aussi en ligne qu'effectivement les 2 bits utilisés par la couleur ne se suivent pas, d'ou les &= et les | dans ton code.
Après, je trouve ta fonction putpixel très calculatoire, pour un seul pixel. Tes algos de Bresenham ne seront pas très rapides sur un Amstrad, car pour chaque pixel, tu recalcules tout, et pire, tu switch (un branchement est souvent plus lent qu'un autre calcul direct). Mais ça doit marcher, et l'optimisation viendra après.
J'aime beaucoup les vieux modes graphiques. J'ai bossé pas mal sur le mode 13h sous DOS à l'époque : pareil on écrivrait directement en mémoire (adresse virtuelle 0xA000000). J'adore l'encodage graphique de la NES, la façon maline de faire des couleurs sous le ZXSpectrum, tout ça.
Et j'ai fait une lib légère (que Windows par contre) qui permet aussi d'afficher des pixels via un tableau, pour ceux qui aiment le "from scatch" (même si dans les fait, c'est une surcouche a la Winapi)
En tout cas, merci pour ton code qui m'a bien instruit sur la façon de coder le graphisme sur l'Amstrad !
EDIT, je mets cet excellent lien qui montre un remplissage, dans quel sens c'est rempli :
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Le Tout est souvent plus grand que la somme de ses parties.
Le Tout est souvent plus grand que la somme de ses parties.
Le Tout est souvent plus grand que la somme de ses parties.
Le Tout est souvent plus grand que la somme de ses parties.
Recueil de code C et C++ http://fvirtman.free.fr/recueil/index.html