comprendre la syntaxe de ce code assembleur

assembleur pour risc

a marqué ce sujet comme résolu.

Bonjour, voici un code assembleur pour riscV, il y a des choses que je n’arrive pas à comprendre je suis novice :

.equ LED_PIN, 27
.equ GPIO_OUT_COM, 1
.equ GPIO_IN_COM,  0

.equ IOPORT,          0xD0000000

.equ SIOBASE_CPUID  , 0x000 # Processor core identifier
.equ GPIO_IN        , 0x004 # Input value for GPIO pins
.equ GPIO_HI_IN     , 0x008 # Input value for QSPI pins
.equ GPIO_OUT       , 0x010 # GPIO output value
.equ GPIO_OUT_SET   , 0x014 # GPIO output value set
.equ GPIO_OUT_CLR   , 0x018 # GPIO output value clear
.equ GPIO_OUT_XOR   , 0x01c # GPIO output value XOR
.equ GPIO_OE        , 0x020 # GPIO output enable
.equ GPIO_OE_SET    , 0x024 # GPIO output enable set
.equ GPIO_OE_CLR    , 0x028 # GPIO output enable clear
.equ GPIO_OE_XOR    , 0x02c # GPIO output enable XOR
.equ GPIO_HI_OUT    , 0x030 # QSPI output value
.equ GPIO_HI_OUT_SET, 0x034 # QSPI output value set
.equ GPIO_HI_OUT_CLR, 0x038 # QSPI output value clear
.equ GPIO_HI_OUT_XOR, 0x03c # QSPI output value XOR
.equ GPIO_HI_OE     , 0x040 # QSPI output enable
.equ GPIO_HI_OE_SET , 0x044 # QSPI output enable set
.equ GPIO_HI_OE_CLR , 0x048 # QSPI output enable clear
.equ GPIO_HI_OE_XOR , 0x04c # QSPI output enable XOR


/*******************************************/
/* DONNEES INITIALISEES                    */
/*******************************************/ 
.data

/*******************************************/
/* DONNEES NON INITIALISEES                    */
/*******************************************/ 
.bss

/**********************************************/
/* SECTION CODE                              */
/**********************************************/
.text
.global main
main:      
    la a0,mess                    #  
    li a0,LED_PIN
    call gpio_init              # init led 25
    
    li a0,LED_PIN
    li a1,GPIO_OUT
    call seldirGpio             # set direction to out
 
1:    
    li a0,LED_PIN               # pin 25
    li a1,0                     # turn off led 
    call putGpio
    li a0, 250                  # waiting time
    call sleep_ms 
    li a0,LED_PIN               # pin 25
    li a1,1                     # turn on led 
    call putGpio
    li a0, 150                  # waiting time
    call sleep_ms 
    j 1b                        # and loop
    

/************************************/
/*       select direction pin gpio   */
/***********************************/
/* a0 pin   */
/* a1 value */
seldirGpio:
    li t1,1                             # set bit
    sll t1,t1,a0                        # shift bit in pin position
    bne a1,x0,1f                        # in or out direction ?
    li t2,IOPORT                        # load sio address
    sw t1,GPIO_HI_OUT_SET(t2)           # store pin 25 on register gpio set 
    j 2f                                # jump end
1:
    li t2,IOPORT
    sw t1,GPIO_HI_OUT_CLR(t2)           # store  pin 25 in gpio clear register
2:
    ret
/************************************/
/*       Put pin gpio               */
/***********************************/
/* a0 pin   */
/* a1 value */
putGpio:
    li t1,1                            # set bit
    sll t1,t1,a0                       # shift bit in pin position
    beq a1,x0,1f                       # turn on or turn off 
    li t2,IOPORT                       # load sio address
    sw t1,GPIO_OE(t2)                  # store pin 25 on register gpio
    j 2f                               # jump end
1:
    li t2,IOPORT                       # load sio address
    sw t1,GPIO_OUT_CLR(t2)             # store pin 25 on register gpio
2:
    ret
/************************************/
 mess:
     .ascii    "Hello...\0"

dans "putGpio", il y a un 1: et 2:

quand on fait appele au "call" que fait t’il il rentre dans le "1" et ensuite il passe au "2" qunad on arrive à la 1: à la fin de la ligne, comment on sait que le "1" est terminer ? il y a j1b et j2f la lettre b et f c’est une adresse ?

comment cela fonctionne ? c’est du code pour le risc V pour le pico2

merci d’avance de vos réponses

+0 -0

Salut,

Il faut comprendre que l’assembleur pilote une unité arithmétique et logique (UAL) qui exécute bêtement l’instruction indiqué par le program counter (PC). Chacune des instructions de ce programme (les lignes indentés) sont présentes tel quel dans la mémoire (après transformation des alias).
Un extrait en mémoire (main+seldirGpio) ressemble donc à :

Adresse Instruction
0x00 la a0,mess
0x01 li a0,LED_PIN
0x02 call gpio_init
0x03 li a0,LED_PIN
0x04 li a1,GPIO_OUT
0x05 call seldirGpio
0x06 li a0,LED_PIN
0x07 li a1,0
0x08 call putGpio
0x09 li a0, 250
0x0a call sleep_ms
0x0b li a0,LED_PIN
0x0c li a1,1
0x0d call putGpio
0x0e li a0, 150
0x0f call sleep_ms
0x10 j 1b
0x11 li t1,1
0x12 sll t1,t1,a0
0x13 bne a1,x0,1f
0x14 li t2,IOPORT
0x15 sw t1,GPIO_HI_OUT_SET(t2)
0x16 j 2f
0x17 li t2,IOPORT
0x18 sw t1,GPIO_HI_OUT_CLR(t2)
0x19 ret

L’UAL charge la ligne indiqué par le PC et l’exécute puis incrémente le PC et recommence. Donc il exécute bêtement les lignes une à une.
Sauf que certaines de ces opérations opèrent directement sur le PC. C’est le cas de j (jump), call, ou encore bne/beq qui vont carrément écrire une valeur dans le PC. Cette valeur elle est écrite dans les opérandes, il s’agit de gpio_init, seldirGpio ou encore 2f : ce sont des références, des aliases (les lignes non incrémenté nom:), à des adresses du programme.

En effet, en écrivant

seldirGpio:
    li t1,1                             # set bit

On a defini un label. On a dit au désassembleur que seldirGpio vaudra la valeur de l’adresse de cette instruction. Donc dans mon tableau exemple, seldirGpio = 0x11, et l’instruction call seldirGpio pourrait s’écrire call 0x11.
Je ne sais pas exactement comment ça marche pour 1f, 1b, 2f, mais je suppose que c’est à peu près pareil, c’est juste que puisque que 1 et 2 sont nommé plusieurs fois, on discrimine celui ciblé en disant b pour back, celui précédent, et f pour forward, celui suivant. Faudra aller lire la doc pour confirmer ça.

Pour en revenir aux questions :

quand on fait appele au "call" que fait t’il il rentre dans le "1" et ensuite il passe au "2" qunad on arrive à la 1: à la fin de la ligne, comment on sait que le "1" est terminer ?

call ne rentre pas dans 1 ou 2, il va au label correspondant et donc call seldirGpio, va à l’instruction li t1,1 plusieurs instructions avant 1 ou 2.
Parmi ces instructions, on trouve bne a1,x0,1f qui veut dire "si le contenu de a1 n’est pas égal à zéro, continu le programme au prochain label 1, sinon continue normalement" et on a ensuite j 2f juste avant le label 1 pour le sauter dans le cas où on ne l’aurait pas exécuté au moyens de bne.
Note là dedans que a1 n’est jamais touché dans les instructions de seldirGpio, puisque c’est en réalité un argument de fonction, c’est l’appelant qui lui donne une valeur.
Quand on voit dans le main

  li a1,GPIO_OUT
  call seldirGpio

C’est ça qui a défini si on exécute le code de 1 ou non (qui dans ce cas ci configure la GPIO en entrée ou en sortie).

Enfin, on ne sait pas particulièrement que 1 est terminé, le programme passe juste à la ligne suivante et il se trouve que c’est ret qui retourne à la suite du dernier call exécuté.

En fait je crois que l’erreur que tu fais est de croire que 1 est le then du if et 2 le else, alors que 1 est le then mais 2 est plutôt l’accolade fermante et le else est le bloc de code entre bne et j2f.

il y a j1b et j2f la lettre b et f c’est une adresse ?

Je l’ai déjà dit, je suis pas sûr, mais je pense que c’est une syntaxe des labels pour dire quel 1 ou 2 on cible car le label est présent plusieurs fois dans le programme.

+2 -0

merci de ta réponse :) pour le tableau j’ai compris que tu déployer chaque instruction en ligne en numérotant en hexa , pour le 1: et 2: c’est encore pas claire pour moi, j’aimerais donc essayer d’afficher un test de code plus simple afin de comprendre cela. voici un test simple pour debugger la réaction du programme:

.global main
main:
    # Prompt the user first
    la      a0, mess
    call    printf

 mess:
     .ascii    "Hello...\0"

pour le moment c’est d’arriver à afficher sur mon moniteur hello (mais pour le moment je n’ai pas sur mon terminal putty afficher hello .

pour fair plus simple à la place de hello afficher le chiffre "9", mais je ne sais pas comment afficher un nombre à la compilation il y a erreur ou si le chiffre n’est pas assez simple le mot binaire (1001):

.global main
main:
    # Prompt the user first
    la      a0, le_chiffre
    call    printf

le_chiffre:
     .int    9

une fois que j’aurai réussi à afficher le chiffre 9 je testerais ceci:

.global main
main:
    # Prompt the user first
    la      a0, le_chiffre
1:    
    call    printf
2:  
    ADD a0,  a0 //double de 9
    call    printf
3:
    la      a1, x0 // zero
    call    printf

c’est un peu tirer par les cheveux mais je pourrais savoir si ces label sont automatiquement appeler. mais en gros j’afficherais dans la console 9, puis 18, puis 0

pour le coup il faut regarder la doc de printf, elle attend comme premier argument une chaine de caractère de formatage, en C, ça donne :

printf("%d\n", 9);

ici %d indique qu’on va afficher une variable de type "nombre entier". puis ensuite on indique le paramètre. (et \n est là juste pour revenir à la ligne et s’assurer de l’affichage du texte)

Donc ça donne quelque chose comme (je suis loin d’être un expert RISC-V):

main:
    la      a0, mess
    la      a1, 9
    call    printf
    
    la      a0, 0
    call    exit // pour le programme s'arrête bien.

 mess:
     .string    "%d\n"
+1 -0

pour le tableau j’ai compris que tu déployer chaque instruction en ligne en numérotant en hexa

uniquement.sur.place

C’est surtout pour montrer que les instructions sont toutes les unes à la suite des autres et s’exécutent dans l’ordre, sans distinctions de fonction ou quoi que ce soit donc s’il n’y a pas d’instruction de renvoi pour boucler ou retourner, ben l’exécution continu bêtement, débordant sur la "fonction" suivante.
"fonction" entre guillemet, parce que c’est qu’une façon pour nous, pauvres humains, d’interpreter une certaine structure de code. Il n’y a pas de fonction en assembleur, les main, printf, seldirGpio etc… sont des labels. Et 1 et 2 ne sont pas différents, ce sont aussi des labels.

+0 -0

j’ai testé le .string passe :)

.global main      # Provide program starting address to linker

main:   jal   stdio_init_all
        mv    s0, x0
loop:   la    a0, helloworld # load address of helloworld
        addi  s0, s0, 1
        mv    a1, s0        # counter        
        jal   printf
        j     loop
.data
helloworld:      .string "Hell %d\n"

ensuite j’ai esssayé avec le cast decimal ça fonctionne aussi:

.global main      # Provide program starting address to linker

main:   jal   stdio_init_all
        mv    s0, x0
loop:   la    a0, aff_debug # load address of helloworld
        addi  s0, s0, 1
        mv    a1, s0        # counter        
        jal   printf
        j     loop
.data
aff_debug:      .string "%d9876\n"

sinon dans cette doc si je cherche ".string", il ne le trouve même pas ?

https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf

maintenant je j’ai injecter un call sleep et j’ai bien affiché toutes les cinq seconde

.global main      # Provide program starting address to linker

main:   jal   stdio_init_all
        mv    s0, x0

loop:   la    a0, aff_debug # load address of helloworld
        addi  s0, s0, 1
        mv    a1, s0        # counter        
        jal   printf
        li a0, 5250                  # waiting time
        call sleep_ms 
        j     loop
.data
aff_debug:      .string "%d9876\n"

j’ai retirer la boucle et mis à la place 1:, et la la console m’affiche rien du tout …

.global main      # Provide program starting address to linker

main:   jal   stdio_init_all
        mv    s0, x0

1:   la    a0, aff_debug # load address of helloworld
        addi  s0, s0, 1
        mv    a1, s0        # counter        
        jal   printf
        li a0, 5250                  # waiting time
        call sleep_ms 
        
.data
aff_debug:      .string "%d9876\n"
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte