60

l'utilisation de buffers pour moi c'était forcément avec le blitter, des buffers dynamiques remplis par le timer 1 qui gère le replay. et entièrement d'accord sur l'utilisation du blitter, difficile de le dédier au son.
déjà en stockant 4 octets de samples je réutilise, puisqu'a 50 Khz, je lis plusieurs le même octet, la fréquence des samples Amiga est souvent beaucoup plus faible
par exemple pour un module , LSP m'indique :

Instrument # 1: max replay rate = 16574Hz
Instrument # 3: max replay rate = 23334Hz
Warning: Instrument #3 only use 13459 sample bytes (len=24576)
Instrument # 4: max replay rate = 27928Hz
Instrument # 5: max replay rate = 20864Hz
Instrument # 6: max replay rate = 22168Hz
Instrument # 7: max replay rate = 22168Hz
Instrument # 8: max replay rate = 19704Hz
Instrument # 9: max replay rate = 28800Hz
Instrument #10: max replay rate = 28800Hz
Instrument #11: max replay rate = 14778Hz
Instrument #12: max replay rate = 22168Hz
Instrument #13: max replay rate = 11084Hz
Instrument #14: max replay rate = 9309Hz
Instrument #15: max replay rate = 28800Hz
avatar

61

Ah c'est certain que lire 32 bits d'un coup et seulement si c'est nécessaire, c'est beaucoup mieux que de lire 8 bits à chaque fois.

Ce que je veux dire, c'est que tu pousses le principe au-delà de 32 bits, l'intérêt est beaucoup moins évident. Mais je peux me tromper smile
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

62

je m'interroge aussi sur l'interet d'aller au dela de 45~50 KHz
je ne suis pas ingé son, loin de là, donc je ne sais pas si c'est vraiment interessant de pousser
là je cherche juste pour le fun et le challenge.
je suis persuadé que perso je ne vais pas entendre la différence smile
avatar

63

Si tu ne fais pas d'interpolation, augmenter la fréquence d'échantillonnage réduit la distorsion harmonique (surtout pour le YM). Après, c'est une méthode bourrin, il y a de meilleures solutions (mais plus compliquées à coder, c'est vrai).
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

64

et tu crois que c'est viable sur Jaguar de viser des algos + complexes comme l'interpolation ?
je m'interroge aussi sur la viabilité de conversion de Klang de l'Amiga au temps réels sur Jaguar
les instruments sont générés en RAM avant replay sur Amiga
la Jaguar serait elle capable de générer les instruments en live...

un exemple d'instrument Klang, moyennement complexe :

;----------------------------------------------------------------------------
; Instrument 2 - Virgill_PM_Chord1
;----------------------------------------------------------------------------

moveq #1,d0
bsr AK_ResetVars
moveq #0,d7
ifne AK_USE_PROGRESS
ifeq AK_FINE_PROGRESS
addq.b #1,(a3)
endif
endif
.Inst2Loop
; v1 = chordgen(0, 0, 2, 3, 12, 110)
move.l AK_SmpAddr+0(a5),a4
move.b (a4,d7.l),d6
ext.w d6
add.w #110,a4
moveq #0,d4
move.w AK_OpInstance+AK_CHORD1+0(a5),d4
move.b (a4,d4.l),d5
ext.w d5
add.w d5,d6
add.l #73472,AK_OpInstance+AK_CHORD1+0(a5)
move.w AK_OpInstance+AK_CHORD2+0(a5),d4
move.b (a4,d4.l),d5
ext.w d5
add.w d5,d6
add.l #77824,AK_OpInstance+AK_CHORD2+0(a5)
move.w AK_OpInstance+AK_CHORD3+0(a5),d4
move.b (a4,d4.l),d5
ext.w d5
add.w d5,d6
add.l #131072,AK_OpInstance+AK_CHORD3+0(a5)
asl.w #7,d6
bvc.s .NoClampChord_2_1
spl d6
ext.w d6
eor.w #$7fff,d6
.NoClampChord_2_1
move.w d6,d0

; v2 = osc_sine(1, 335, 42)
add.w #335,AK_OpInstance+12(a5)
move.w AK_OpInstance+12(a5),d1
sub.w #16384,d1
move.w d1,d5
bge.s .SineNoAbs_2_2
neg.w d5
.SineNoAbs_2_2
move.w #32767,d6
sub.w d5,d6
muls d6,d1
swap d1
asl.w #3,d1
muls #42,d1
asr.l #7,d1

; v1 = add(v1, v2)
add.w d1,d0
bvc.s .AddNoClamp_2_3
spl d0
ext.w d0
eor.w #$7fff,d0
.AddNoClamp_2_3

asr.w #8,d0
move.b d0,(a0)+
ifne AK_USE_PROGRESS
ifne AK_FINE_PROGRESS
addq.l #1,(a3)
endif
endif
addq.l #1,d7
cmp.l AK_SmpLen+4(a5),d7
blt .Inst2Loop
avatar

65

Je suis en train de faire un profiling de ton code DSP.
J'aurai fini dans la journée.

De ce que je vois, il y a pas mal d'optimisations possibles smile
avatar

66

salut
sur la partie 68000 c'est sur c'est orienté stabilité et sécurité
sur le Timer 1 surement aussi car pour 1 fois par VBL, je prefere que ce soit clair
et j'ai réoptimisé la partie I2S ce matin.
+ ajout de la lecture du pad en cours. pour l'instant juste le pad 1, avec le timer 2

je viens de l'uploader dans le github
avatar

67

j'ai ajouté et testé le deuxième pad
et j'ai entrelacé le code du timer 2

je ne touche plus à rien jusqu'a tes retours sur le profiling.

merci !
avatar

68

Voici le fichier avec le profiling.
Colonne 1 : Cycle 1 (en général lecture des registres)
Colonne 2 : Cycle 2 (en général tout ce qui est compute)
Colonne 3 : Cycle 3 (en général tout ce qui Writeback)

dans les colonnes :
R : read
W : write
M : Memory access
- : rien de spécial à indiquer

en résumé :
si il y a un "-" dans la colonne 1, c'est qu'il y a un équivalent du "nop" de fait à cause de plusieurs raisons possible (conflit de registres, accès mémoire, traitement plus long de l’instruction...)
si il y a un "-" dans la colonne 3, c'est qu'il y a un cycle mort

Le but est de minimiser les "-" dans la colonne 3 au maximum.
pour cela, il est possible de :
- réorganiser les instructions
- utiliser plus de registres (notamment retirer le max de movei par des moveta/movefa)
- remplacer les add successifs par des muls ou utiliser plusieurs registres pour faire le calcul afin de faire la moitié du calcul en parallèle et d'add les 2 à la fin

J'ai pris comme hypothese (pour me simplifier la vie) que tout tourne en mémoire interne
Je l'ai fait rapidement donc y a peut être des erreurs, mais je pense que dans l'ensemble ça doit être bon.


tromb Fichier joint : lsp_SCPCD.s

PS :
je vais donner plus d'explication plus tard, on m’appelle smile
avatar

69

SCPCD (./68) :
Voici le fichier avec le profiling.
Tu utilises un tool fait maison pour ce profiler?

70

C'est SCPCD, il fait ça de tête embarrassed

ericde45 (./64) :
et tu crois que c'est viable sur Jaguar de viser des algos + complexes comme l'interpolation ?
Oui. Je fais déjà de l'interpolation linéaire dans mon code audio, et ça améliore déjà pas mal la qualité sans impact significatif sur les perfs. Je pense que le DSP est assez rapide pour aller plus loin que ça.

ericde45 (./64) :
la Jaguar serait elle capable de générer les instruments en live...
Absolument, y'en a un peu dans FActS d'ailleurs smile
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

71

le code pour generer les instruments d'un module au hasard avec Klang, c'est 4875 lignes
jamais il n'y aura assez de RAM dans le DSP pour stocker les N instruments
dans Facts la musique est quand même très simple il me semble smile

je vais déjà optimiser un peu
puis me pencher sur une interpolation linéaire, même si ça me complique pas mal la vie puisque je vais devoir gérer 8 octets dans 2 registres pour pouvoir continuer à lire 4 octets d'un coup et pouvoir interpoler avec le suivant.
avatar

72

concernant l'optimisation, donc il faut que je m'efforce de virer les movei de ce que je comprends

ok pour les bank 1 de registres

est il aussi + pertinent de faire moveq + shlq ?

par exemple :
movei #$8000,R26
peut devenir
moveq #1,R26
shlq #15,R26
voir meme
moveq #0,R26
bset #15,R26

est ce mieux ?
( en interlacé bien sur )


et pour ce qui est de la lecture des variables, je peux faciler switcher à un systeme :

movei #premiere_variable,Rn (que je remplacerai par un movefa bien sur)
load (Rn),Ry

quand j'ai besoin de la 2eme variable

addq #4,Rn
load (Rn),Rx
( en interlacé bien sur )

une partie du code de ma première version du player YM7 était écrit comme cela mais c'est l'enfer à debugger smile
il faut ré-écrire le code de cette façon une fois que ça fonctionne.
avatar

73

dilinger (./69) :
Tu utilises un tool fait maison pour ce profiler?
Il est fait à la main.
Mais ça serait effectivement pratique d'avoir un tool qui le fasse smile


ericde45 (./72) :
concernant l'optimisation, donc il faut que je m'efforce de virer les movei de ce que je comprends
En général oui : pour tout ce qui est adresses/valeurs utilisées dont on a besoin immédiatement de la valeur.


ericde45 (./72) :
est il aussi + pertinent de faire moveq + shlq ?
Alors, ce n'est pas forcement plus judicieux.

Avec les exemples que tu donnes, on ne gagne rien voir on peut même perdre un cycle.
instruction
Cycle1
Cycle2
Cycle3
movei #$8000, R26
Rword1
-
-

Rword2
word1
-

-
-
word2word1 > Wr26

instruction
Cycle1
Cycle2
Cycle3
moveq #1,R26
#1
-
-
shlq #15,R26
Rr26
-
Wr26

-
Cr26
-

-
-
Wr26

instruction
Cycle1
Cycle2
Cycle3
moveq #0,R26
#0
-
-
bset #15,R26
Rr26
-
Wr26

-
Cr26
-

-
-
Wr26

Si tu as besoin d'utiliser r26 tout de suite, le 1er cas est plus rapide de 1 cycle par rapport aux 2 autres cas.
Si tu n'as pas besoin d'utiliser r26 tout de suite, le délai du writeback supplémentaire est compensé et revient donc au même, mais c'est moins lisible smile

En remplaçant le movei par un move* (qui n'est pas movei) tu peux gagner un cycle si tu as besoin directement de R26.
Tous les move* (autres que movei) font 2 cycles au lieu de 3, du coup il faut les utiliser à des endroits stratégiques car ils peuvent casser le pipeline si on ne fait pas gaffe.
On le voit typiquement dans ton code avec les "move r26,r25" dans les "DSP_LSP_routine_interruption_I2S_pas_fin_de_sample_channel*"

Dans certains cas, il pourrait même être plus rapides d'utiliser un xor à la place de moveq afin de ne pas casser le pipeline : tout dépend en fait de ce qu'il y a avant et après smile

instruction
Cycle1
Cycle2
Cycle3
xor R26, R26
Rr26
-
-
(instruction utile)
RrX
Cr26
-
bset #15,R26
Rr26
CrX
Wr26
(instruction utilisant RX)
RrX
Cr26
WrX
(instruction utilisant R26)
Rr26
CrX
Wr26



et pour ce qui est de la lecture des variables, je peux faciler switcher à un systeme :

movei #premiere_variable,Rn (que je remplacerai par un movefa bien sur)
load (Rn),Ry

quand j'ai besoin de la 2eme variable

addq #4,Rn
load (Rn),Rx
( en interlacé bien sur )
Oui tout à fait.

Le but étant d'obtenir quelque chose comme ca :

instruction
Cycle1
Cycle2
Cycle3
movefa Rm,Rn
Rr'm
-
-
load (Rn), Ry
Rrn
-
Wrn
addq #4, Rn
Rrn
Mrn
-
(instruction utile)
-
Crn
Wry
load (Rn), Rx
Rrn
-
Wrn

une partie du code de ma première version du player YM7 était écrit comme cela mais c'est l'enfer à debugger smile
il faut ré-écrire le code de cette façon une fois que ça fonctionne.
Oui, vaut mieux faire un truc qui marche et ensuite optimiser par itération en vérifiant bien que chaque parties fonctionnent toujours (et non tout faire d'un coup smile)
C'est plus long à faire, mais on au moins on a une référence qui marche smile
avatar

74

Si on prend l'exemple suivant :
Il faut 19 cycles.
InstructionCycle1Cycle2Cycle3
DSP_LSP_routine_interruption_I2S_pas_fin_de_sample_channel1:
store R26,(R28) ; stocke internal sample pointeur, a virgule Rr26 & Rr28 - Wr26
shrq #nb_bits_virgule_offset,R26 ; nouveau pointeur adresse sample partie entiere Rr26 Mr28 -
shrq #nb_bits_virgule_offset,R17 ; ancien pointeur adresse sample partie entiere Rr17 Cr26 MWrite
- Cr17 Wr26
move R26,R25 ; R25 = nouveau pointeur sample Rr26 - Wr17
- - Wr25
and R22,R17 ; ancien pointeur sample modulo 4 Rr22 & Rr17 - -
and R22,R26 ; nouveau pointeur sample modulo 4 Rr22 & Rr26 Cr17 -
movei #LSP_DSP_PAULA_AUD1DAT,R28 ; 4 octets actuels Rword1 Cr26 Wr17
Rword2 word1 Wr26
not R22 ; => %11 Rr22 - word2word1 > Wr28
load (R28),R19 ; R19 = octets actuels en stock Rr28 Cr22 -
and R22,R25 ; R25 = position octet à lire Rr22 & Rr25 Mr28 Wr22
- Cr25 Wr19
- - Wr25
cmp R17,R26 Rr17 & Rr26 - -
- Cflags -
jr eq,DSP_LSP_routine_interruption_I2S_pas_nouveau_long_word1 Rflags - Wflags
nop - ? > WPC -

En reorganisant juste les instructions, on réduit à 13cycles.
+2cycles si jamais on ne jumperai pas avec un besoin du résultat du and dans l'instruction suivante, mais ce n'ai pas le cas dans ton code.
Dans le cas d'un jump, il est "gratuit" vu que ça sera inclut dans les cycles du remplissage à nouveau du pipeline.

InstructionCycle1Cycle2Cycle3
DSP_LSP_routine_interruption_I2S_pas_fin_de_sample_channel1:
store R26,(R28) ; stocke internal sample pointeur, a virgule Rr26 & Rr28 - Wr26
movei #LSP_DSP_PAULA_AUD1DAT,R28 ; 4 octets actuels Rword1 Mr28 -
Rword2 word1 MWrite
load (R28),R19 ; R19 = octets actuels en stock Rr28 - word2word1 > Wr28
shrq #nb_bits_virgule_offset,R17 ; ancien pointeur adresse sample partie entiere Rr17 Mr28 -
shrq #nb_bits_virgule_offset,R26 ; nouveau pointeur adresse sample partie entiere Rr26 Cr17 Wr19
and R22,R17 ; ancien pointeur sample modulo 4 Rr22 & Rr17 Cr26 Wr17
and R22,R26 ; nouveau pointeur sample modulo 4 Rr22 & Rr26 Cr17 Wr26
not R22 ; => %11 Rr22 Cr26 Wr17
cmp R17,R26 Rr17 & Rr26 Cr22 Wr26
move R26,R25 ; R25 = nouveau pointeur sample Rr26 Cflags -
jr eq,DSP_LSP_routine_interruption_I2S_pas_nouveau_long_word1 Rflags - Wr26 Wflags
and R22,R25 ; R25 = position octet à lire Rr22 & Rr25 ? > WPC -

En remplaçant le movei par un movefa ou move simple, on pourrait gagner encore un cycle.

(je n'ai pas testé, mais sauf erreur de ma part, ça devrait marcher smile)
avatar

75

bonsoir

merci beaucoup de cette analyse, je vais essayer de la comprendre et de la digérer dans les jours qui viennent
et d'optimiser mon code en accord.
avatar

76

une question supplémentaire avant de voir dans quel sens recoder mon timer I2S : si j'utilise des load indexés à R15 par exemple, je perds 2 ticks par load mais je n'ai pas besoin de gérer le addq, est ce un gain ? car c'est super plus facile à coder et à relire !
avatar

77

je dirais qu'en général tu peux laisser les load/store indexé.
Après il est possible dans certains cas où l'utilisation des addq feront gagner 1 cycle (ca va dépendre de ce qu'il y a avant & après le load/store), mais je dirais que c'est plus pour de l'optimisation extreme.

faut voir le nombre de load/store utilisés, mais il ne m'a pas semblé être là où il y avait le plus de pertes.
avatar

78

bonsoir

savez vous si il y a un bug spécifique ou un piège à éviter avec les load et store (R15+n) ?
j'ai passé mon code I2S en partie en R15+n et ça crash sur la console alors que ca passe sous les 2 emulateurs
le meme code copié/collé tourne avec une seule voie mais plante avec 2 fois le meme code successif
j'avoue que je suis complètement perdu
avatar

79

Oui, c'est un bug connu :
0VFs
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

80

ok, merci !
je vais voir si ça vient de cela. ( ou de mon code mal codé )
avatar

81

bon un complet échec l'utilisation de R15+indexation
j'ai essayé dans tous les sens, plantages par ci par la , sans recompilation ça marche et puis ça marche plus au reboot de la console
donc j'ai basculé sur des movefa + addq / subq

et à la fin j'essayerai de faire mon SCPCD en calculant les cycles
avatar

82

Si tu découvres la méthode de calcul, n'hésites pas a la partager.

83

je vais essayer de comprendre
puis d'appliquer pour créer les 3 colonnes à la SCPCD
puis me faire un programme C qui me les fera tout seul smile


avant d'en arriver là, je m'interroge sur l'amélioration de la qualité du son.
actuellement mon replay tourne à 59 KHz sur la Jaguar réelle
l'Amiga monte au maximum à 28800Hz
d'ailleurs dans de nombreux modules c'est ce que le convertisseur LSP indique comme frequence maximale donc c'est utilisé
si je redescend la fréquence je peux surement faire de l'amélioration de la qualité en créant des samples intermédiaires
Zerosquare a parlé d'interpolation.
quelle méthode vous semble jouable sur le DSP ? l'interpolation linéaire tout bêtement ? ou quelque chose de plus exigeant au niveau mathématique tout en étant réaliste à haute fréquence ?
il faut forcément un équilibre entre fréquence de replay et charge CPU DSP de chaque replay I2S
avatar

84

bonsoir

le lot de questions du jour, j'arrose smile

- j'ai essayé sans succès jwarn : il me dit qu'il n'y a pas assez de ligne, et je n'ai pas trouvé d'explications sur la syntaxe attendue pour que mon source soit au standard 'GASM' ? ( j'ai regardé le source mais trop c'est du C trop compact smile )

- pour le calcul manuel des cycles :
- est ce que cmp attend la fin de toute instruction qui modifie les flags ? meme si le cmp n'utilise pas les registres concernés par l'opération de modification des flags ?
- au final il ne suffit pas d'entrelacer les registres une instruction sur 2, c'est plutot une instruction sur 3 ?
- on peut avoir plusieurs writes en cycle 3 ?

je posterai mon analyse pour correction par le prof SCPCD si il a le temps.

et entre temps je me suis fait un convertisseur PNG => 256 couleurs + datas en C pour exploiter les graphs.
avatar

85

CMP ne lis pas les flags, du coup il n'y a pas d'attente généré si une opération précédente doit écrire un flags : CMPx utilisent les instructions SUBx sans écrire dans le registre de destination.

j'essaierai de faire un récap des règles de calcul ce weekend dans un topic dédié.
avatar

86

si ca peut deja t'eviter d'en ecrire certaines, voila les regles indiquées dans Jwarn :
( à condition qu'elles soient vraies )

* RULE 1:
* An instruction reads a register containing the result o the previous
* instruction, one tick of wait is incurred until the previous
* operation completes.

* RULE 2:
* An instruction uses the flags from the prvious instruction, one tick
* of wait is incurred until the previous operation completes.

* RULE 3:
* An ALU result, memory load value or divide result has to be written
* back and neither instruction about to be executed matches, one tick
* of wait is incurred to let the data be written.

* RULE 4:
* Two values are to be written back at once, one tick of wait is
* incurred.

* RULE 5:
* An instr. attempts to use the result of a divide inst. before it's
* ready. Wait states are inserted until the divide unit completes the
* divide, between one and sixteen wait states can be incurred.
*
* RULE 6:
* A dividie instr. is about to be executed and the previous one has not
* completed, between one and sixteen wait states can be incurred.

* RULE 7:
* An instr. reads a register which is awaiting data from an incomplete
* memory read, this will be no more than one tick from internal memory,
* but several ticks from external memory.

* RULE 8:
* A load or store instr. is about tob be executed and the memory
* interface has not completed the transfer for the previous ones (one
* internal load/store or two external loads/stores can be pending
* without holding up instruction flow).

* RULE 9:
* After a store instr. with indexed addressing mode

* RULE 10:
* After a jump or jr (three ticks if executing out of internal memory).
avatar

87

effectivement, elles semblent toutes là. wink

Où tu as trouvé jwarn ?
avatar

88

Chez Dilinger, son message sur la page d'avant :


dilinger (./41) :
Bonjour,
Je n'ai pas la réponse a ta question mais, au cas ou, il y a quelques années j'avais porté sur Windows l'outil JWARN d'Atari.
Il permet de checker les "best practices" discuter dans la doc développeur ('Writing Fast GPU Programs').
GitHub - djipi/Jwarn: Atari Jaguar wait states warning generatorGitHubAtari Jaguar wait states warning generator. Contribute to djipi/Jwarn development by creating an account on GitHub.
avatar

89

dans ton analyse je ne comprends pas ce morceau là, avec cmp:

and R22,R25 ; R25 = position octet à lire Rr22 & Rr25 Mr28 Wr22
- Cr25 Wr19
- - Wr25
cmp R17,R26 Rr17 & Rr26 - -

pourquoi le cmp attend la fin du and ?
avatar

90

ericde45 (./84) :
- j'ai essayé sans succès jwarn : il me dit qu'il n'y a pas assez de ligne, et je n'ai pas trouvé d'explications sur la syntaxe attendue pour que mon source soit au standard 'GASM' ? ( j'ai regardé le source mais trop c'est du C trop compact smile )
Oui, il faudrait que je rajoutes le support de VASM et Rmac. Vu que c'est les 2 assembleurs principalement utilisé.
Le port de l'outil sur Windows a été fait sans but précis mais ce fut rapide a faire.