Laboratori 2: Assemblador RISC-V i tipus bàsics de dades

Lliuraments

Cal lliurar les respostes als exercicis següents al campus virtual abans de l’inici de la sessió de laboratori:

Exercici Nom i format del fitxer
Exercici 29.1 s2_1_1.s
Exercici 29.2 s2_1_2.md
Exercici 29.3 s2_1_3.md
Exercici 29.4 s2_3_1.md
Exercici 29.5 s2_3_2.s
Exercici 29.6 s2_4_1.md i s2_4_1.s
Exercici 29.7 s2_4_2.s
Exercici 29.8 s2_5_1.s

Lectura prèvia

Abans de fer la sessió, cal haver llegit i entès els continguts següents:

Concepte On trobar-ho
Mida dels tipus de dades en C i RV32I EC 2.8, EC 2.9
Directives de declaració i inicialització de variables globals RISC-V 2.22
Model de memòria i adreces Secció 2.5
Ordre dels bytes (little-endian) Secció 2.5.1
Restriccions d’alineació RISC-V 2.21
Directiva .align RISC-V 2.31
Instruccions d’accés a memòria (lw, lh, lhu, lb, lbu, sw, sh, sb) RISC-V 2.29
Punters: declaració, &, *, aritmètica Secció 2.7.2, Secció 2.7.3, Secció 2.7.5, Secció 2.7.6
Vectors: declaració, indexació, relació amb punters Secció 2.8, Secció 2.8.3, Secció 2.8.4
Caràcters i cadenes de caràcters Secció 2.9
Pseudoinstruccions la i li RISC-V 2.17, RISC-V 2.16

29.1 Representació en memòria

Els tipus de dades bàsics del llenguatge C tenen una correspondència directa amb les directives de declaració de variables globals en RV32I. Les taules de mides i directives són a T2 (EC 2.8, EC 2.9, RISC-V 2.22).

Quan es declaren diverses variables globals consecutivament, l’assemblador insereix automàticament bytes de farciment (padding) entre elles per respectar les restriccions d’alineació (Secció 2.5.2), tret de les de .dword, que RARS només alinea a 4 bytes (RISC-V 2.22). A més, els valors s’emmagatzemen en ordre little-endian (Secció 2.5.1): el byte de menys pes s’emmagatzema a l’adreça més baixa.


Exercici 29.1 (s2_1_1.s) Traduïu a RV32I la declaració de variables globals en C del Codi 29.1:

Codi 29.1: Variables globals de tipus diferents.
C
char      aa = -5;
short     bb = -344;
long long cc = -3;
unsigned char dd = 0xA0;
int       ee = 5799;
short     ff = -1;

Afegiu-hi l’esquelet mínim del programa (RISC-V 2.19).


Solució 29.1.

s2_1_1.s
.data
aa:     .byte   -5
bb:     .half   -344
        .align 3          # cc és long long: alineació a 8
cc:     .dword  -3
dd:     .byte   0xA0
ee:     .word   5799
ff:     .half   -1

.text
        # Sortir del programa
        li      a7, 93
        li      a0, 0
        ecall

Exercici 29.2 (s2_1_2.md) Sabent que les dades globals s’emmagatzemen a partir de l’adreça 0x10010000, ompliu la taula de distribució de variables:

Variable Tipus Mida [B] Alineació [B] Adreça Farciment [B]
aa
bb
cc
dd
ee
ff

Ara escriviu el contingut de memòria byte a byte, en ordre little-endian. Indiqueu cada etiqueta a la posició que li correspon i marqueu amb -- els bytes de farciment. Deixeu en blanc els bytes lliures (no inicialitzats).

Etiqueta Adreça Contingut
0x10010000
0x10010001
0x10010002
0x10010003
0x10010004
0x10010005
0x10010006
0x10010007
0x10010008
0x10010009
0x1001000A
0x1001000B
0x1001000C
0x1001000D
0x1001000E
0x1001000F

 

Etiqueta Adreça Contingut
0x10010010
0x10010011
0x10010012
0x10010013
0x10010014
0x10010015
0x10010016
0x10010017
0x10010018
0x10010019
0x1001001A
0x1001001B
0x1001001C
0x1001001D
0x1001001E
0x1001001F

Solució 29.2. cc és un long long, que l’ABI alinea a 8 bytes (RISC-V 2.21). A RARS, la directiva .dword només l’alinea a 4 (RISC-V 2.22): per això la solució de l’Exercici 29.1 hi posa .align 3 al davant.

Variable Tipus Mida [B] Alineació [B] Adreça Farciment [B]
aa char 1 1 0x10010000
bb short 2 2 0x10010002 1
cc long long 8 8 0x10010008 4
dd unsigned char 1 1 0x10010010
ee int 4 4 0x10010014 3
ff short 2 2 0x10010018
Etiqueta Adreça Contingut
aa 0x10010000 0xFB
0x10010001 --
bb 0x10010002 0xA8
0x10010003 0xFE
0x10010004 --
0x10010005 --
0x10010006 --
0x10010007 --
cc 0x10010008 0xFD
0x10010009 0xFF
0x1001000A 0xFF
0x1001000B 0xFF
0x1001000C 0xFF
0x1001000D 0xFF
0x1001000E 0xFF
0x1001000F 0xFF

 

Etiqueta Adreça Contingut
dd 0x10010010 0xA0
0x10010011 --
0x10010012 --
0x10010013 --
ee 0x10010014 0xA7
0x10010015 0x16
0x10010016 0x00
0x10010017 0x00
ff 0x10010018 0xFF
0x10010019 0xFF
0x1001001A
0x1001001B
0x1001001C
0x1001001D
0x1001001E
0x1001001F

Essencial 29.1: RARS — Vista de memòria

RARS mostra el contingut de la memòria agrupat en paraules de 4 bytes. Cada fila mostra 8 paraules consecutives (columnes +0 fins a +1c) a partir d’una adreça alineada a 32 bytes (múltiple de 0x20). Dins de cada paraula, el byte de major adreça apareix a l’esquerra i el de menor adreça a la dreta. Això és contrari a l’ordre real d’emmagatzematge (little-endian) i sovint és font de confusió.

Per exemple, la paraula a l’adreça 0x10010000 conté els bytes (de menor a major adreça):

+0 +1 +2 +3
0xFB -- 0xA8 0xFE

i RARS el mostra com 0xFEA800FB.


Exercici 29.3 (s2_1_3.md) Després d’assemblar el programa de l’Exercici 29.1, RARS mostra la vista de memòria del Codi 29.2:

Codi 29.2: Vista de memòria de RARS.
Address      Value (+0)  Value (+4)  Value (+8)  Value (+c)  Value (+10) Value (+14) Value (+18) Value (+1c)
0x10010000   0xfea800fb  0x00000000  0xfffffffd  0xffffffff  0x000000a0  0x000016a7  0x0000ffff  0x00000000

Per a cada paraula, identifiqueu a quina variable correspon cada byte i expliqueu per què apareix en aquest ordre.


Solució 29.3. El byte de menor adreça ocupa la posició de la dreta; el de major adreça, la de l’esquerra:

Adreça Valor RARS +0 +1 +2 +3 Variable(s)
0x10010000 0xfea800fb 0xFB -- 0xA8 0xFE aa, farciment, bb
0x10010004 0x00000000 -- -- -- -- farciment (.align 3)
0x10010008 0xfffffffd 0xFD 0xFF 0xFF 0xFF cc (bytes 0–3)
0x1001000C 0xffffffff 0xFF 0xFF 0xFF 0xFF cc (bytes 4–7)
0x10010010 0x000000a0 0xA0 -- -- -- dd, farciment
0x10010014 0x000016a7 0xA7 0x16 0x00 0x00 ee
0x10010018 0x0000ffff 0xFF 0xFF ff
0x1001001C 0x00000000

29.2 Pseudoinstruccions la i li

Els programes d’aquesta sessió operen amb variables globals emmagatzemades en memòria. Com que l’únic mode d’adreçament per accedir-hi en RV32I és el mode base + desplaçament (Secció 2.6.3), abans de cada accés cal carregar l’adreça de la variable en un registre. Per fer-ho s’usen dues pseudoinstruccions (Secció 2.3.5.2), que l’assemblador expandeix en una o dues instruccions reals en el moment d’assemblar:

  • la rd, etiqueta (load address, RISC-V 2.17) carrega a rd l’adreça associada a etiqueta. Per exemple, la t0, vec deixa a t0 l’adreça de vec.
  • li rd, valor (load immediate, RISC-V 2.16) carrega a rd un valor immediat de fins a 32 bits. Per exemple, li t1, 10.
Essencial 29.2: RARS — la amb desplaçament

A la teoria i als exàmens d’EC s’admet la sintaxi la rd, etiqueta+desplaçament, però RARS no la suporta (EC 2.6): al laboratori cal substituir-la sempre per la parella la + addi.

29.3 Punters

Un punter és una variable que conté l’adreça de memòria d’una altra variable. En RV32I, un punter ocupa sempre 4 bytes (una paraula), ja que les adreces són de 32 bits (RISC-V 2.30). La pseudoinstrucció la carrega l’adreça d’una variable en un registre (RISC-V 2.17), i les instruccions lw, lh, lhu, lb, lbu, sw, sh i sb permeten accedir al valor apuntat (RISC-V 2.29).


Exercici 29.4 (s2_3_1.md) Donada la declaració del Codi 29.3, on pdada és un punter inicialitzat amb l’adreça de dada:

Codi 29.3: Variable dada i punter pdada.
RV32I
.data
dada:   .half   3
pdada:  .word   dada

Sabent que les variables s’emmagatzemen a partir de 0x10010000, ompliu els blancs de les vinyetes i calculeu el valor de cada expressió C:

  • dada és un _____ (__ bytes, alineació __), s’emmagatzema a 0x1001000__.
  • pdada és un _____ (__ bytes, alineació ), s’emmagatzema a 0x1001000__ ( bytes de farciment).
Expressió C Valor (hex)
&dada
&pdada
dada
pdada
*pdada

Comproveu que, en assemblar la declaració a RARS, la paraula a 0x10010000 mostra 0x00000003 i la paraula a 0x10010004, 0x10010000.


Solució 29.4.

  • dada és un short (2 bytes, alineació 2), s’emmagatzema a 0x10010000.
  • pdada és un punter (4 bytes, alineació 4), s’emmagatzema a 0x10010004 (2 bytes de farciment).
Expressió C Valor (hex) Explicació
&dada 0x10010000 Adreça de dada
&pdada 0x10010004 Adreça de pdada
dada 0x0003 Contingut de dada (un short, 2 bytes)
pdada 0x10010000 Contingut de pdada (adreça de dada)
*pdada 0x0003 Contingut de l’adreça apuntada per pdada (un short)

En llegir dada (o *pdada) en un registre amb lh, el valor s’estén en signe a 32 bits: 0x00000003.

ExempleComprovació pràctica

No cal executar el programa: n’hi ha prou d’assemblar-lo i obrir la vista del segment de dades, on es veuen el short dada seguit de 2 bytes de farciment i, a continuació, el punter pdada.


Exercici 29.5 (s2_3_2.s) Traduïu a RV32I el programa C del Codi 29.4. La variable temporal temp s’emmagatzema al registre s0:

Codi 29.4: Programa amb un punter al vector A.
C
int A[3] = {3, 5, 7};
int *punter = 0;

void main() {
    int temp;
    punter = &A[2];
    temp = *punter + 2;
    temp = *(punter - 2) + temp;
    A[1] = temp;
}

Comproveu que, en acabar l’execució, s0 val 0x0000000C (12), que A[1] (0x10010004) conté 0x0000000C i que punter (0x1001000C) conté 0x10010008, l’adreça d’A[2].


Solució 29.5.

s2_3_2.s
.data
A:      .word   3, 5, 7
punter: .word   0

.text
        # punter = &A[2]
        la      t0, A
        addi    t0, t0, 8   # &A[2] = &A + 2*4
        la      t1, punter
        sw      t0, 0(t1)   # punter = &A[2]

        # temp = *punter + 2
        lw      s0, 0(t0)   # s0 = *punter = A[2] = 7
        addi    s0, s0, 2   # s0 = 9

        # temp = *(punter - 2) + temp
        addi    t0, t0, -8  # punter - 2 = &A[0]
        lw      t2, 0(t0)   # t2 = A[0] = 3
        add     s0, t2, s0  # s0 = 3 + 9 = 12

        # A[1] = temp
        la      t0, A
        sw      s0, 4(t0)   # A[1] = 12

        # Sortir del programa
        li      a7, 93
        li      a0, 0
        ecall
ExempleComprovació pràctica

El resultat es reparteix entre dues vistes de RARS: s0 a la finestra de registres i els valors d’A[1] i punter al segment de dades. Avançant pas a pas es pot seguir com punter s’escriu abans que A[1].


29.4 Vectors

Un vector és una seqüència d’elements del mateix tipus emmagatzemats consecutivament en memòria. L’adreça de l’element vec[i] es calcula a partir de l’adreça base del vector i la mida de cada element, segons l’Equació 2.1 (Secció 2.8, Secció 2.8.3):

\[@\texttt{vec[i]} = @\texttt{vec[0]} + i \cdot T\]

on \(T\) és la mida en bytes de cada element. En RV32I, l’accés indexat requereix calcular aquesta adreça explícitament, ja que no hi ha mode d’adreçament indexat (Secció 2.6.3).


Exercici 29.6 (s2_4_1.md i s2_4_1.s) Donat el vector global en C del Codi 29.5:

Codi 29.5: Vector global vec de deu enters.
C
int vec[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
  1. Escriviu la fórmula per calcular l’adreça de vec[i] en funció de l’adreça base de vec i de l’índex i.
  2. Escriviu un fragment de codi RV32I, amb la declaració corresponent, que copiï el valor de vec[i] al registre s1, suposant que i es troba al registre s2.

Comproveu que, afegint li s2, 7 abans del fragment i executant-lo a RARS, s1 val 0x00000002 (= vec[7]).


Solució 29.6. 1. Fórmula d’accés:

\[@\texttt{vec[i]} = @\texttt{vec[0]} + i \cdot 4\]

(o, equivalentment, \(@\texttt{vec} + i \cdot 4\), ja que vec és &vec[0], Secció 2.8.4)

2. Declaració en RV32I i fragment de codi:

s2_4_1.s
.data
vec:    .word   9, 8, 7, 6, 5, 4, 3, 2, 1, 0

.text
        la      t0, vec     # t0 = @vec
        slli    t1, s2, 2   # t1 = i * 4
        add     t0, t0, t1  # t0 = @vec[i]
        lw      s1, 0(t0)   # s1 = vec[i]

Exercici 29.7 (s2_4_2.s) Traduïu a RV32I el programa C del Codi 29.6, que calcula els 10 primers termes de la sèrie de Fibonacci i els escriu al vector fib. La variable i s’emmagatzema al registre s0:

Codi 29.6: Programa que calcula els deu primers termes de la sèrie de Fibonacci.
C
int fib[10];

void main() {
    int i = 2;
    fib[0] = 0;
    fib[1] = 1;
    while (i < 10) {
        fib[i] = fib[i-1] + fib[i-2];
        i++;
    }
}

Comproveu que, en acabar l’execució, el segment de dades conté els 10 termes: 0x0, 0x1, 0x1, 0x2, 0x3, 0x5, 0x8, 0xD, 0x15, 0x22.


Solució 29.7.

s2_4_2.s
.data
fib:    .space  40          # 10 enters * 4 bytes

.text
        # fib[0] = 0
        la      t0, fib
        sw      zero, 0(t0) # fib[0] = 0

        # fib[1] = 1
        li      t1, 1
        sw      t1, 4(t0)   # fib[1] = 1

        # i = 2
        li      s0, 2

while:
        # if i >= 10, sortir
        li      t1, 10
        bge     s0, t1, fi

        # t1 = fib[i-1]
        la      t0, fib
        slli    t2, s0, 2   # t2 = i * 4
        add     t3, t0, t2  # t3 = @fib[i]
        lw      t1, -4(t3)  # t1 = fib[i-1]

        # t2 = fib[i-2]
        lw      t2, -8(t3)  # t2 = fib[i-2]

        # fib[i] = fib[i-1] + fib[i-2]
        add     t1, t1, t2
        sw      t1, 0(t3)   # fib[i] = t1

        # i++
        addi    s0, s0, 1
        j       while

fi:
        # Sortir del programa
        li      a7, 93
        li      a0, 0
        ecall

29.5 Cadenes de caràcters

Una cadena de caràcters (string) és un vector de caràcters (char) que finalitza amb el caràcter sentinella '\0' (valor 0x00). La codificació utilitzada és ASCII de 7 bits (Secció 2.9.1). Per recórrer una cadena de caràcters no cal conèixer-ne la longitud prèviament: n’hi ha prou de recórrer-la fins a trobar el sentinella.


Exercici 29.8 (s2_5_1.s) El vector vec conté els dígits decimals del nombre 19865, ordenats de menys a més pes. El programa del Codi 29.7 converteix cada dígit a la seva representació ASCII i l’escriu a la cadena cadena; els caràcters queden ordenats de més a menys pes per poder imprimir el nombre correctament.

Codi 29.7: Conversió dels dígits de vec a caràcters ASCII.
C
char         cadena[6];
unsigned int vec[5] = {5, 6, 8, 9, 1};

void main() {
    int i = 0;
    while (i < 5) {
        cadena[i] = vec[4-i] + '0';
        i++;
    }
    cadena[5] = 0;
}

Traduïu el programa a RV32I. La variable i s’emmagatzema al registre s0.

Nota: la conversió de dígit a caràcter ASCII consisteix a sumar el valor del dígit al codi ASCII de '0' (0x30). Per interpretar el contingut de cadena a la vista de memòria de RARS, vegeu Essencial 29.1.

Comproveu que, en acabar l’execució, la paraula a 0x10010000 mostra 0x36383931 i la paraula a 0x10010004, 0x00000035.


Solució 29.8.

s2_5_1.s
.data
cadena: .space  6
vec:    .word   5, 6, 8, 9, 1

.text
        # i = 0
        li      s0, 0

while:
        # if i >= 5, sortir
        li      t0, 5
        bge     s0, t0, fi

        # t1 = vec[4-i]
        li      t0, 4
        sub     t0, t0, s0  # t0 = 4 - i
        la      t1, vec
        slli    t2, t0, 2   # t2 = (4-i) * 4
        add     t1, t1, t2  # t1 = @vec[4-i]
        lw      t1, 0(t1)   # t1 = vec[4-i]

        # cadena[i] = vec[4-i] + '0'
        li      t2, 0x30    # '0' en ASCII
        add     t1, t1, t2  # t1 = vec[4-i] + '0'
        la      t2, cadena
        add     t2, t2, s0  # t2 = @cadena[i]
        sb      t1, 0(t2)   # cadena[i] = t1

        # i++
        addi    s0, s0, 1
        j       while

fi:
        # cadena[5] = 0
        la      t0, cadena
        sb      zero, 5(t0)

        # Sortir del programa
        li      a7, 93
        li      a0, 0
        ecall
ExempleComprovació pràctica

Llegint els bytes d’aquestes dues paraules de menys a més adreça —és a dir, invertint l’ordre amb què RARS els mostra dins de cada paraula (Essencial 29.1)— s’obté "19865\0".