La Place des Développeurs Coder en C avec SDCC
ericb59
Membre non connecté
Conseiller Municipal
ericb59
Membre non connecté
Conseiller Municipal
@aoineko :
Je vois dans ta lib, que tu ajoute __FASCALL sur tes fonctions C, pourtant elle ne contiennent pas de code ASM pour autant.
- Quel est donc ce maléfice ?
D'autre part, __FASCALL tout seul ne semble plus fans la de SDCC. Quelle version de SDCC utilises tu ?
car moi je connais __z88dk_fastcall , mais pas __fastcall tout seul.
Autre chose... As tu testé --peep-hole ? Edité par ericb59 Le 22/12/2020 à 15h29
Je vois dans ta lib, que tu ajoute __FASCALL sur tes fonctions C, pourtant elle ne contiennent pas de code ASM pour autant.
- Quel est donc ce maléfice ?
D'autre part, __FASCALL tout seul ne semble plus fans la de SDCC. Quelle version de SDCC utilises tu ?
car moi je connais __z88dk_fastcall , mais pas __fastcall tout seul.
Autre chose... As tu testé --peep-hole ? Edité par ericb59 Le 22/12/2020 à 15h29
aoineko
Membre non connecté
Conseiller Municipal
__FASCALL est un define de __z88dk_fastcall
J'ai un historique de programmeur cross-platform donc j'ai pris l'habitude d'encapsuler les directives trop spécifique à un compilo.
TOUTES tes fonctions qui n'ont qu'un seul argument devrait avoir la directive __z88dk_fastcall.
Ca n'a aucun lien avec son contenu, ça change juste la façon dont sont passés les arguments.
J'ai même qq fonctions où je merge plusieurs arguments pour pouvoir utiliser cette directive (ce qui reste bien plus rapide que de passer par la pile).
D'après les discussions que j'ai lu sur le forum SDCC, il est probable qu'on ait un système plus souple dans les futures versions.
Et non, je n'ai pas encore utilisé le peep-hole... tout simplement parce que j'en ai pas encore vu l'utilité.
En tout cas, j'ai pas vu de code assembleur généré ou je me suis dis que le peep-hole serait la solution (c'est souvent plus un problème de C).
J'ai un historique de programmeur cross-platform donc j'ai pris l'habitude d'encapsuler les directives trop spécifique à un compilo.
TOUTES tes fonctions qui n'ont qu'un seul argument devrait avoir la directive __z88dk_fastcall.
Ca n'a aucun lien avec son contenu, ça change juste la façon dont sont passés les arguments.
J'ai même qq fonctions où je merge plusieurs arguments pour pouvoir utiliser cette directive (ce qui reste bien plus rapide que de passer par la pile).
D'après les discussions que j'ai lu sur le forum SDCC, il est probable qu'on ait un système plus souple dans les futures versions.
Et non, je n'ai pas encore utilisé le peep-hole... tout simplement parce que j'en ai pas encore vu l'utilité.
En tout cas, j'ai pas vu de code assembleur généré ou je me suis dis que le peep-hole serait la solution (c'est souvent plus un problème de C).
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
Citation :
Ca n'a aucun lien avec son contenu, ça change juste la façon dont sont passés les arguments.
Ha bon ?
En fait je croyais que ce n'étais utile que dans le cas où la routine était écrite en ASM.
Je n'avais pas compris que c'était valable pour tout type de routine.
aoineko
Membre non connecté
Conseiller Municipal
Le compilo C de SDCC gère très bien le passage par registre
(tu y gagnes à l'appel de la fonction et dans le corps de la fonction)
(tu y gagnes à l'appel de la fonction et dans le corps de la fonction)
Code C :
u8 Acc(u8 a) { u8 ret = g_Counter + a; return ret; }
Code ASM :
_Acc:: ld hl, #2 ; Passage par la pile == caca add hl, sp ld a,(#_g_Counter + 0) add a, (hl) ld l, a ret
Code C :
u8 Acc_FC(u8 a) __FASTCALL { u8 ret = g_Counter + a; return ret; }
Code ASM :
_Acc_FC:: ld a,(#_g_Counter + 0) add a, l ; SDCC cherche bien le paramètre dans le registre L ld l, a ret
On est toujours ignorant avant de savoir.
aoineko
Membre non connecté
Conseiller Municipal
aoineko :
J'ai un historique de programmeur cross-platform donc j'ai pris l'habitude d'encapsuler les directives trop spécifique à un compilo
Un autre truc que je fais toujours, c'est SURTOUT ne pas utiliser le type int. Le standard AINSI C permet que le int puisse avoir une taille variable en fonction des implémentations. Sur SDCC, c'est du 16-bits, sur un GCC moderne, ça sera du 32-bits. Si tu as besoin de changer de compilo un jour, rien ne t'assures que ça sera des 16-bits.
Il y a donc trois problèmes : 1) on ne sait pas exactement quelle taille il aura, 2) on peut facilement oublier quel est sa taille (ce qui est un gros problème quand on programme pour le Z80), 3) le statut de signé/non-signé n'est pas assuré (ce qui est aussi un gros problème pour le Z80).
Tu pourrais utilisais le short à la place, qui lui est assuré d'être en 16-bits, mais je te conseille très fortement d'utiliser des types explicitant la taille et le statut signé/non-signé des variables.
Perso j'ai pris l'habitude d'utiliser :
- u8 : 8-bits unsigned integer
- u16 : 16-bits unsigned integer
- i8 : 8-bits signed integer
- i16 : 16-bits signed integer
- ...
Mais si tu veux garder des types standards, tu peux utiliser ceux fournis par <stdint.h> : uint8_t, uint16_t, etc.
Ca à l'air de rien, mais ça permet à l'utilisateur d'avoir bien conscience de ce qu'il fait... ce qui a un énorme impact pour le Z80.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
Oui, SHORT et INT sont identiques avec SDCC.
Je ne pense pas passer un code SDCC vers GCC, ni inversement quoi que ... ?
Moi je m'étais contenté d'utiliser les types standard de SDCC, dont j'avais l'habitude quand j'ai appris à coder en C avec GCC.
Je ne me suis pas cassé la tête plus loin.
Mais je comprend bien le problème...
De même je conseille aux autre sde toujours indiquer le "unsigned" alors que je ne le fait pas moi même !
Je ne pense pas passer un code SDCC vers GCC, ni inversement quoi que ... ?
Moi je m'étais contenté d'utiliser les types standard de SDCC, dont j'avais l'habitude quand j'ai appris à coder en C avec GCC.
Je ne me suis pas cassé la tête plus loin.
Mais je comprend bien le problème...
De même je conseille aux autre sde toujours indiquer le "unsigned" alors que je ne le fait pas moi même !
aoineko
Membre non connecté
Conseiller Municipal
Vu les piètres performances de SDCC... je pense qu'on hésiterait pas beaucoup à changer pour un nouveau toolchain qui serait vraiment bien optimisé pour le Z80.
Et on a aucune assurance de ce que cet hypothétique nouveau compilo choisirait pour les int.
Et on a aucune assurance de ce que cet hypothétique nouveau compilo choisirait pour les int.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
@aoineko
En utilisant ton CRT0, je me suis rendu compte que hex2bin m'indiquait
"Data Record skipped at 0xCxxx"
je pense que cela es dû à la variable g_HeapStartAddress qu'il y a dans ton CRT0.
Est-ce normal ce data skipper ? tu l'as aussi de ton coté ?
En utilisant ton CRT0, je me suis rendu compte que hex2bin m'indiquait
"Data Record skipped at 0xCxxx"
je pense que cela es dû à la variable g_HeapStartAddress qu'il y a dans ton CRT0.
Est-ce normal ce data skipper ? tu l'as aussi de ton coté ?
aoineko
Membre non connecté
Conseiller Municipal
Tu parles de quel crt0 ? Y en a 6
EDIT: Et non, l'erreur n'est pas normale. En tout cas, j'ai aucune erreur avec mes ctr0 sur tous les supports.
Ca peut aussi venir des paramètres qu'on utilise pour build le programme binaire.
Ce soir j'archiverai sur GitHub tout ce que j'ai fait ces derniers jours pour clean la gestion multi-support (mais les crt0 ont pas bougé il me semble).
EDIT: Et non, l'erreur n'est pas normale. En tout cas, j'ai aucune erreur avec mes ctr0 sur tous les supports.
Ca peut aussi venir des paramètres qu'on utilise pour build le programme binaire.
Ce soir j'archiverai sur GitHub tout ce que j'ai fait ces derniers jours pour clean la gestion multi-support (mais les crt0 ont pas bougé il me semble).
On est toujours ignorant avant de savoir.
aoineko
Membre non connecté
Conseiller Municipal
J'ai fait une grosse passe sur ma lib pour m'assurer la compatibilité MSX1 (pour les fonctionnalités compatibles évidemment ).
J'ai notamment check toutes les questions des temps d'accès en lecture/écriture du VDP.
Je sais pas ou en est Fusion-C à ce sujet, mais si tu veux je peux faire un petit topo.
J'ai notamment check toutes les questions des temps d'accès en lecture/écriture du VDP.
Je sais pas ou en est Fusion-C à ce sujet, mais si tu veux je peux faire un petit topo.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
@aoineko. Tout il est bon pour le MSX1 dans FUSION-C.
J'ai même refait une routine CopyRam2VRAM spécifique pour MSX2 car on peut aller plus vite sur la VRAM du MSX2 que sur MSX1.
Mais vas y fais ton topo... J'suis certain que j'y apprendrai encore des choses qui vont me servir.
J'ai même refait une routine CopyRam2VRAM spécifique pour MSX2 car on peut aller plus vite sur la VRAM du MSX2 que sur MSX1.
Mais vas y fais ton topo... J'suis certain que j'y apprendrai encore des choses qui vont me servir.
aoineko
Membre non connecté
Conseiller Municipal
C’est cadeau
Déjà, le plus important à savoir c’est que quand l’affichage est désactivé, il n’y a aucune limitation de vitesse d’écriture/lecture en VRAM quelque-soit le type de MSX (comprendre : le Z80 n’ira jamais plus vite que le VDP). Donc, pour charger un niveau de jeu par ex., le mieux c’est de couper l’affichage et de charger tous ses graphismes en VRAM à pleine vitesse.
Les problèmes de temps d’accès n’interviennent donc que pour les lectures/écritures en VRAM pendant l’affichage. Dans ce cas, il faut croiser d’un côté :
- la version du VDP (= la version du MSX dans la plupart des cas) et le mode d’affichage qu’on souhaite utiliser
- les différentes façons d’écrire en VRAM et leur vitesse (la lecture à exactement les mêmes contraintes donc je vais pas détailler)
On peut essayer de faire de la dentelle en gérant tous les cas de façon optimale, mais une bonne approche c’est de prendre le pire cas (worst-case scenario).
Pour le MSX1 (V9918), l'intervalle minimale entre deux écritures en nombre de cycle Z80 est de :
- 29 cc pour les modes graphiques (Screen 1 & 2)
- 13 cc pour les modes textuels (Screen 0 & 3)
Sur MSX2/2+ (V9938/58) :
- 15 cc pour les modes graphiques (Screen 1-8)
- 20 cc pour les modes textuels (Screen 0 Width 40/80)
Coté Z80, il a plusieurs façons d’envoyer des données au VDP plus ou moins rapide :
- 18 cc pour une succession de outi (c’est ce que le Z80 du MSX peut faire de plus rapide)
- 23 cc pour un otir (ou une série de otir si on a besoin d’envoyer plus de 256 octets)
- 29 cc pour une boucle de outi (ce qui fait pile le worse-case du MSX1 )
Déjà, on se rend compte qu’avec un otir (23 cc), on est déjà plus lent que le pire cas du MSX2.
Le choix que j’ai fait, c’est donc d’utiliser le otir pour mes fonctions d’écriture en VRAM par défaut. Ça peut être optimisé (cf. plus bas), mais ça suffit à mes besoins, surtout que c’est le genre de fonction qu’on a pas trop de raison d’utiliser pendant le déroulement d’un jeu sur MSX2. Dans ce cas, on utilise plutôt les commandes VDP qui ont le gros avantage d’avoir un nombre d’octets fixe à écrire et donc sont idéals pour utiliser une succession de outi. Je crois qu’il existe une astuce pour pouvoir utiliser les commandes VDP avec les modes textuels ; c’est le seul cas qui pourrait poser problème (18 cc < 20 cc).
La fonction à base de otir est aussi valable pour les modes textes du MSX1 (23 cc >13 cc). Le seul problème restant est donc les modes graphiques du MSX1. Pour eux, j’ai donc fait un set de fonction particulier à base de boucle de outi qui font pile la durée du pire case (29 cc).
Voilà.
Tout ce qui est dis pour l’écriture est aussi valable pour la lecture (avec ini et inir).
Normalement tu devrais avoir aucun nop dans ton programme.
Et en bonus, une solution pour aller encore plus vite qu’avec le otir pour un nombre variable d’octets à copier. Utiliser des paquets de outi !
Par ex., tu peux faire des paquets de 16 outi :
- tu fais d’abord un outir avec "compteur & 0x000F",
- ensuite tu boucles "compteur >> 4" fois sur ton paquet de 16 outi.
C’est valable pour n’importe quelle valeur puissance de 2.
Par ex., pour un programme qui copie des images complètes en VRAM, tu peux même faire des blocs 256 outi !
Ca prend de place en code, mais le gain va être significatif.
Par contre, cette astuce n’a aucun intérêt pour de petites boucles.
PS : Pour la fonction FillVRAM, y aussi la boucle "loop: out (VDP_DATA), a ; djnz loop" qui prends de 21 à 26 cc et qui peut donc être problématique sur les modes graphiques d'un MSX1 (V9918) quand l'affichage est activé.
Déjà, le plus important à savoir c’est que quand l’affichage est désactivé, il n’y a aucune limitation de vitesse d’écriture/lecture en VRAM quelque-soit le type de MSX (comprendre : le Z80 n’ira jamais plus vite que le VDP). Donc, pour charger un niveau de jeu par ex., le mieux c’est de couper l’affichage et de charger tous ses graphismes en VRAM à pleine vitesse.
Les problèmes de temps d’accès n’interviennent donc que pour les lectures/écritures en VRAM pendant l’affichage. Dans ce cas, il faut croiser d’un côté :
- la version du VDP (= la version du MSX dans la plupart des cas) et le mode d’affichage qu’on souhaite utiliser
- les différentes façons d’écrire en VRAM et leur vitesse (la lecture à exactement les mêmes contraintes donc je vais pas détailler)
On peut essayer de faire de la dentelle en gérant tous les cas de façon optimale, mais une bonne approche c’est de prendre le pire cas (worst-case scenario).
Pour le MSX1 (V9918), l'intervalle minimale entre deux écritures en nombre de cycle Z80 est de :
- 29 cc pour les modes graphiques (Screen 1 & 2)
- 13 cc pour les modes textuels (Screen 0 & 3)
Sur MSX2/2+ (V9938/58) :
- 15 cc pour les modes graphiques (Screen 1-8)
- 20 cc pour les modes textuels (Screen 0 Width 40/80)
Coté Z80, il a plusieurs façons d’envoyer des données au VDP plus ou moins rapide :
- 18 cc pour une succession de outi (c’est ce que le Z80 du MSX peut faire de plus rapide)
- 23 cc pour un otir (ou une série de otir si on a besoin d’envoyer plus de 256 octets)
- 29 cc pour une boucle de outi (ce qui fait pile le worse-case du MSX1 )
Code ASM :
loop: outi jp nz, loop
Déjà, on se rend compte qu’avec un otir (23 cc), on est déjà plus lent que le pire cas du MSX2.
Le choix que j’ai fait, c’est donc d’utiliser le otir pour mes fonctions d’écriture en VRAM par défaut. Ça peut être optimisé (cf. plus bas), mais ça suffit à mes besoins, surtout que c’est le genre de fonction qu’on a pas trop de raison d’utiliser pendant le déroulement d’un jeu sur MSX2. Dans ce cas, on utilise plutôt les commandes VDP qui ont le gros avantage d’avoir un nombre d’octets fixe à écrire et donc sont idéals pour utiliser une succession de outi. Je crois qu’il existe une astuce pour pouvoir utiliser les commandes VDP avec les modes textuels ; c’est le seul cas qui pourrait poser problème (18 cc < 20 cc).
La fonction à base de otir est aussi valable pour les modes textes du MSX1 (23 cc >13 cc). Le seul problème restant est donc les modes graphiques du MSX1. Pour eux, j’ai donc fait un set de fonction particulier à base de boucle de outi qui font pile la durée du pire case (29 cc).
Voilà.
Tout ce qui est dis pour l’écriture est aussi valable pour la lecture (avec ini et inir).
Normalement tu devrais avoir aucun nop dans ton programme.
Et en bonus, une solution pour aller encore plus vite qu’avec le otir pour un nombre variable d’octets à copier. Utiliser des paquets de outi !
Par ex., tu peux faire des paquets de 16 outi :
- tu fais d’abord un outir avec "compteur & 0x000F",
- ensuite tu boucles "compteur >> 4" fois sur ton paquet de 16 outi.
C’est valable pour n’importe quelle valeur puissance de 2.
Par ex., pour un programme qui copie des images complètes en VRAM, tu peux même faire des blocs 256 outi !
Ca prend de place en code, mais le gain va être significatif.
Par contre, cette astuce n’a aucun intérêt pour de petites boucles.
PS : Pour la fonction FillVRAM, y aussi la boucle "loop: out (VDP_DATA), a ; djnz loop" qui prends de 21 à 26 cc et qui peut donc être problématique sur les modes graphiques d'un MSX1 (V9918) quand l'affichage est activé.
On est toujours ignorant avant de savoir.
ericb59
Membre non connecté
Conseiller Municipal
OK. Donc tu as 2 jeux d'instructions MSX1 et MSX2.
Question, comment tu coupes l'affichage sur MSX 1 ?
Citation :
Déjà, le plus important à savoir c’est que quand l’affichage est désactivé, il n’y a aucune limitation de vitesse d’écriture/lecture
Question, comment tu coupes l'affichage sur MSX 1 ?
Répondre
Vous n'êtes pas autorisé à écrire dans cette catégorie