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:
Afegiu-hi l’esquelet mínim del programa (RISC-V 2.19).
Solució 29.1.
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 |
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:
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 ardl’adreça associada aetiqueta. Per exemple,la t0, vecdeixa at0l’adreça devec.li rd, valor(load immediate, RISC-V 2.16) carrega ardun valor immediat de fins a 32 bits. Per exemple,li t1, 10.
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:
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 a0x1001000__.pdadaés un _____ (__ bytes, alineació ), s’emmagatzema a0x1001000__( 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 unshort(2 bytes, alineació 2), s’emmagatzema a0x10010000.pdadaés un punter (4 bytes, alineació 4), s’emmagatzema a0x10010004(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.
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:
A.
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
ecallEl 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:
- Escriviu la fórmula per calcular l’adreça de
vec[i]en funció de l’adreça base deveci de l’índexi. - Escriviu un fragment de codi RV32I, amb la declaració corresponent, que copiï el valor de
vec[i]al registres1, suposant queies troba al registres2.
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:
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:
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
ecall29.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.
vec a caràcters ASCII.
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
ecallLlegint 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".