Tema 2: Instruccions i tipus de dades bàsics
20.1 Operands en registre
Enunciat: Problema 11.1.
Variables: f→t0, g→t1, h→t2, i→t3, j→t4.
a) f = g + h + i + j
Calen tres add encadenats; no existeix cap add de quatre operands:
RV32I
b) g = f + (h + 5) - i
Es calcula primer h + 5 (amb addi), s’afegeix f i finalment es resta i:
RV32I
El registre temporal (t5) no és imprescindible: com que el valor inicial de g (t1) no intervé en el càlcul, els resultats parcials es podrien acumular directament a t1 sense sobreescriure f (t0) ni h (t2).
Enunciat: Problema 11.2.
a)
Instrucció per instrucció:
add t0, t1, t2→t0 = t1 + t2→f = g + hadd t1, t3, t4→t1 = t3 + t4→g = i + jadd t1, t1, t1→t1 = t1 + t1 = 2·t1→g = 2 * (i + j)add t0, t0, t1→t0 = t0 + t1→f = (g + h) + 2 * (i + j)
Resposta: f = (g + h) + 2 * (i + j)
Nota: el codi també modifica g (t1), que queda amb el valor 2 * (i + j). La resposta anterior dona f en funció dels valors inicials de g, h, i, j.
b)
Instrucció per instrucció:
add t1, t1, t2→g = g + hadd t3, t3, t4→i = i + jadd t0, t1, t3→f = (g + h) + (i + j)addi t0, t0, 2→f = (g + h) + (i + j) + 2add t0, t0, t0→f = 2 * ((g + h) + (i + j) + 2)
Resposta: f = 2 * (g + h + i + j + 2)
Nota: el codi també modifica g (t1, que queda amb g + h) i i (t3, que queda amb i + j). La resposta dona f en funció dels valors inicials de g, h, i, j.
20.2 Constants i immediats
li en lui + addi i rang d’immediats
Enunciat: Problema 11.5.
a) Seqüència per carregar 0xDEADBEEF a t0.
El valor 0xDEADBEEF no cap en 12 bits, de manera que calen dues instruccions. Es divideix en:
- 20 bits superiors (\(hi_{20}\)):
0xDEADB. - 12 bits inferiors (\(lo_{12}\)):
0xEEF= \(1110\,1110\,1111_2\).
El bit 11 de \(lo_{12}\) val 1, de manera que addi aplicarà extensió de signe i restarà 0x111 (273 en positiu). Cal incrementar \(hi_{20}\) en 1 per compensar:
- \(hi_{20}\) ajustat:
0xDEADB + 1 = 0xDEADC. - \(lo_{12}\) com a enter de 12 bits amb signe:
0xEEF - 0x1000 = -273.
Comprovació: 0xDEADC000 - 0x111 = 0xDEADBEEF ✓
b) Per què cal ajustar \(hi_{20}\) quan el bit 11 de \(lo_{12}\) val 1.
addi fa extensió de signe de l’immediat de 12 bits. Si el bit 11 és 1, el valor s’interpreta com a enter negatiu: el valor efectiu és \(lo_{12} - \texttt{0x1000}\) (en lloc de \(lo_{12}\)). Sense l’ajust, el resultat seria 0x1000 (4096) menys del valor desitjat.
Per 0xDEADBEEF: sense ajust, lui t0, 0xDEADB → t0 = 0xDEADB000; addi t0, t0, 0xEEF → l’extensió de signe interpreta 0xEEF com \(\texttt{0xEEF} - \texttt{0x1000} = -273\) → 0xDEADB000 - 273 = 0xDEADAEEF ≠ 0xDEADBEEF. Amb l’ajust hi20 = 0xDEADC: 0xDEADC000 - 273 = 0xDEADBEEF ✓
c) Rangs de valors.
- Amb una sola instrucció
addi: l’immediat és de 12 bits amb extensió de signe → rang \([-2048,\ 2047]\). - Amb
lui+addi:luicarrega 20 bits als bits [31:12];addiafegeix fins a 12 bits amb signe. El rang cobert és \([-2^{31},\ 2^{31}-1]\), és a dir, tots els enters representables en 32 bits.
20.3 Operands en memòria
lw i sw
Enunciat: Problema 11.7.
Estat inicial: t0 = 0x10010000, t2 = 0x10010008.
Contingut de memòria inicial (little-endian):
| Adreça | +0 |
+1 |
+2 |
+3 |
|---|---|---|---|---|
0x10010000 |
00 |
01 |
02 |
03 |
0x10010004 |
04 |
05 |
06 |
07 |
0x10010008 |
08 |
09 |
0A |
0B |
0x1001000C |
0C |
0D |
0E |
0F |
0x10010010 |
00 |
00 |
00 |
00 |
0x10010014 |
EC |
FD |
0E |
0F |
Seguiment:
lw t1, 0(t0) — adreça efectiva: 0x10010000 + 0 = 0x10010000
Llegeix 4 bytes a partir de 0x10010000 (little-endian): bytes 00 01 02 03 → t1 = 0x03020100.
lw t3, 0(t2) — adreça efectiva: 0x10010008 + 0 = 0x10010008
Llegeix 4 bytes a partir de 0x10010008: bytes 08 09 0A 0B → t3 = 0x0B0A0908.
sw t3, -8(t2) — adreça efectiva: 0x10010008 - 8 = 0x10010000
Escriu t3 = 0x0B0A0908 a partir de 0x10010000 (little-endian): bytes 08 09 0A 0B.
Memòria modificada: M[0x10010000..0x10010003] = 08 09 0A 0B.
lw t4, 4(t0) — adreça efectiva: 0x10010000 + 4 = 0x10010004
Llegeix 4 bytes a partir de 0x10010004: bytes 04 05 06 07 → t4 = 0x07060504.
sw t1, -4(t2) — adreça efectiva: 0x10010008 - 4 = 0x10010004
Escriu t1 = 0x03020100 a partir de 0x10010004: bytes 00 01 02 03.
Memòria modificada: M[0x10010004..0x10010007] = 00 01 02 03.
sw t4, 0(t2) — adreça efectiva: 0x10010008 + 0 = 0x10010008
Escriu t4 = 0x07060504 a partir de 0x10010008: bytes 04 05 06 07.
Memòria modificada: M[0x10010008..0x1001000B] = 04 05 06 07.
Resum de resultats:
| Operació | Destí | Valor resultant |
|---|---|---|
lw t1, 0(t0) |
t1 |
0x03020100 |
lw t3, 0(t2) |
t3 |
0x0B0A0908 |
sw t3, -8(t2) |
M[0x10010000] |
08 09 0A 0B |
lw t4, 4(t0) |
t4 |
0x07060504 |
sw t1, -4(t2) |
M[0x10010004] |
00 01 02 03 |
sw t4, 0(t2) |
M[0x10010008] |
04 05 06 07 |
Enunciat: Problema 11.10.
a) Significat de little-endian.
En una arquitectura little-endian, el byte de menys pes (least significant byte, LSB) d’una dada de més d’un byte s’emmagatzema a l’adreça més baixa, i el byte de més pes (most significant byte, MSB) a l’adreça més alta (vegeu Secció 2.5.1). Per a un enter de 32 bits, doncs, els 4 bytes s’emmagatzemen en l’ordre invers al que s’escriu el valor en hexadecimal: el parell de dígits de la dreta (LSB) ocupa la primera adreça, i així successivament.
b) int x = 0x12345678 a partir de 0x10010000.
Els bytes del valor, de l’MSB a l’LSB, són 12 34 56 78. En little-endian s’emmagatzemen començant per l’LSB:
| Adreça | Contingut | Byte |
|---|---|---|
0x10010000 |
78 |
LSB |
0x10010001 |
56 |
|
0x10010002 |
34 |
|
0x10010003 |
12 |
MSB |
c) Valor de t1.
lb t1, 0(t0) llegeix un sol byte de l’adreça 0x10010000, que en little-endian conté el byte de menys pes de x: 0x78. El bit 7 de 0x78 és 0, de manera que l’extensió de signe afegeix zeros:
t1 = 0x00000078
d) Resultat en big-endian.
En big-endian, l’adreça més baixa (0x10010000) contindria el byte de més pes: 0x12. La mateixa instrucció retornaria t1 = 0x00000012 (el bit 7 de 0x12 també és 0). El mateix codi pot produir, doncs, resultats diferents segons l’ordre dels bytes de la màquina quan s’accedeix a fragments d’una dada de més d’un byte.
Enunciat: Problema 11.11.
Estat inicial: t0 = 0x10010000.
| Adreça | +0 |
+1 |
+2 |
+3 |
|---|---|---|---|---|
0x10010000 |
11 |
22 |
CC |
DD |
a) Resultat de cada instrucció (partint sempre del contingut inicial):
lb t1, 1(t0) — adreça efectiva 0x10010001, byte = 0x22.
El bit 7 de 0x22 és 0 → extensió de signe afegeix zeros: t1 = 0x00000022.
lb t1, 2(t0) — adreça efectiva 0x10010002, byte = 0xCC.
El bit 7 de 0xCC és 1 → extensió de signe afegeix uns: t1 = 0xFFFFFFCC.
lh t1, 0(t0) — adreça efectiva 0x10010000, dos bytes (little-endian): 11 22 → 0x2211.
El bit 15 de 0x2211 és 0 → extensió de signe: t1 = 0x00002211.
lh t1, 2(t0) — adreça efectiva 0x10010002, dos bytes: CC DD → 0xDDCC.
El bit 15 de 0xDDCC és 1 → extensió de signe: t1 = 0xFFFFDDCC.
lbu t1, 2(t0) — adreça efectiva 0x10010002, byte = 0xCC.
Extensió per zero (sense signe): t1 = 0x000000CC.
lhu t1, 2(t0) — adreça efectiva 0x10010002, dos bytes: CC DD → 0xDDCC.
Extensió per zero: t1 = 0x0000DDCC.
Resum:
| Instrucció | Resultat al registre |
|---|---|
lb t1, 1(t0) |
t1 = 0x00000022 |
lb t1, 2(t0) |
t1 = 0xFFFFFFCC |
lh t1, 0(t0) |
t1 = 0x00002211 |
lh t1, 2(t0) |
t1 = 0xFFFFDDCC |
lbu t1, 2(t0) |
t1 = 0x000000CC |
lhu t1, 2(t0) |
t1 = 0x0000DDCC |
b) Execució seqüencial:
RV32I
sb escriu només el byte menys significatiu del registre font (0xCC), sense extensió de cap mena.
Estat final de la memòria:
| Adreça | +0 |
+1 |
+2 |
+3 |
|---|---|---|---|---|
0x10010000 |
11 |
CC ← |
CC |
DD |
20.4 Representació de naturals i enters
Vegeu les solucions de Capítol 19.
20.5 Representació de caràcters
Enunciat: Problema 11.13.
Declaracions, a partir de 0x10010000:
RV32I
Càlcul adreça per adreça, amb les restriccions d’alineació de l’ABI (l’assemblador afegeix farciment (padding) per respectar-les). El .dword u s’alinea a 8 bytes; aquí l’alineació de RARS 1.6, a 4 bytes, donaria la mateixa adreça:
| Variable | Tipus | Mida | Adreça | Farciment previ |
|---|---|---|---|---|
p |
.half |
2 B | 0x10010000 |
— |
q |
.byte |
1 B | 0x10010002 |
— |
r |
.half |
2 B | 0x10010004 |
1 B |
s |
.word |
4 B | 0x10010008 |
2 B |
t |
.byte |
1 B | 0x1001000C |
— |
u |
.dword |
8 B | 0x10010010 |
3 B |
Valors byte a byte (little-endian):
p = 54 = 0x0036→36 00q = 'C' = 0x43→43- farciment:
??(indeterminat) r = 0xAAAA→AA AA- farciment:
?? ?? s = 0x00004F3C→3C 4F 00 00t = -18 = 0xEE(Ca2 de 8 bits) →EE- farciment:
?? ?? ?? u = 0x00000000FFFFEEEE(.dwordde 64 bits) →EE EE FF FF 00 00 00 00
| Adreça | Contingut (byte a byte, hex) |
|---|---|
0x10010000 |
36 00 43 ?? AA AA ?? ?? 3C 4F 00 00 EE ?? ?? ?? |
0x10010010 |
EE EE FF FF 00 00 00 00 |
Els bytes marcats ?? corresponen a farciment i el seu valor és indeterminat.
20.6 Format de les instruccions
Enunciat: Problema 11.16.
Per resoldre cada instrucció cal identificar el format, localitzar els camps i concatenar els bits. Els números de registre ABI es converteixen als corresponents ISA:
| ABI | ISA | ABI | ISA |
|---|---|---|---|
t4 |
x29 = 11101 |
t3 |
x28 = 11100 |
t5 |
x30 = 11110 |
t2 |
x7 = 00111 |
t6 |
x31 = 11111 |
t0 |
x5 = 00101 |
t1 |
x6 = 00110 |
add t4, t3, t5 — Tipus R, opcode = 0x33 = 0110011, funct3 = 000, funct7 = 0000000
| funct7 | rs2 | rs1 | funct3 | rd | opcode |
|---|---|---|---|---|---|
0000000 |
11110 |
11100 |
000 |
11101 |
0110011 |
Binari: 0000000 11110 11100 000 11101 0110011
Hexadecimal: 0x01EE0EB3
addi t6, t5, 25 — Tipus I, opcode = 0x13 = 0010011, funct3 = 000
25 = 0x019 = 000000011001 (12 bits)
| imm[11:0] | rs1 | funct3 | rd | opcode |
|---|---|---|---|---|
000000011001 |
11110 |
000 |
11111 |
0010011 |
Binari: 000000011001 11110 000 11111 0010011
Hexadecimal: 0x019F0F93
lw t3, 0(t2) — Tipus I, opcode = 0x03 = 0000011, funct3 = 010
offset = 0 = 000000000000 (12 bits)
| imm[11:0] | rs1 | funct3 | rd | opcode |
|---|---|---|---|---|
000000000000 |
00111 |
010 |
11100 |
0000011 |
Binari: 000000000000 00111 010 11100 0000011
Hexadecimal: 0x0003AE03
sw t0, 0(t1) — Tipus S, opcode = 0x23 = 0100011, funct3 = 010
offset = 0; imm[11:5] = 0000000, imm[4:0] = 00000
| imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode |
|---|---|---|---|---|---|
0000000 |
00101 |
00110 |
010 |
00000 |
0100011 |
Binari: 0000000 00101 00110 010 00000 0100011
Hexadecimal: 0x00532023
Resum:
| Assemblador RISC-V | Binari | Hexadecimal |
|---|---|---|
add t4, t3, t5 |
0000000 11110 11100 000 11101 0110011 |
0x01EE0EB3 |
addi t6, t5, 25 |
000000011001 11110 000 11111 0010011 |
0x019F0F93 |
lw t3, 0(t2) |
000000000000 00111 010 11100 0000011 |
0x0003AE03 |
sw t0, 0(t1) |
0000000 00101 00110 010 00000 0100011 |
0x00532023 |
20.7 Punters
Enunciat: Problema 11.21.
Declaracions:
vec és global → adreça base accessible via la. punter és global → cal fer la + lw/sw.
a) punter = &vec[2];
&vec[2] = @vec + 2*4 = @vec + 8:
Nota: la sintaxi la label+offset és admesa a EC (EC 2.3), però a RARS cal substituir-la per la t1, vec seguit d’addi t1, t1, 8 (EC 2.6).
b) punter = punter + 1;
El punter apunta a int (mida 4 bytes), de manera que punter + 1 incrementa en 4:
RV32I
c) vec[1] = vec[0] + vec[1];
RV32I
d) vec[2] = *punter + 1;
Cal llegir el valor de punter (una adreça), desreferenciar-lo per obtenir el valor apuntat, sumar-hi 1 i escriure el resultat a vec[2]:
RV32I
e) *(punter + 2) = *punter + 1;
punter + 2 desplaça 8 bytes (2 enters × 4 bytes), de manera que l’adreça de destí és punter + 8:
20.8 Vectors
Enunciat: Problema 11.26.
Declaracions: int v[10] global; i a t0, suma a t1.
a) Inicialitza tots els elements de v a zero:
RV32I
b) Calcula la suma de tots els elements:
RV32I
c) Copia els elements de v al revés a w:
RV32I
la t2, v # invariant: @v[0]
la t3, w # invariant: @w[0]
li t4, 10 # invariant: N
li t5, 9 # invariant: N - 1
li t0, 0 # i = 0
bucle_c:
bge t0, t4, fibucle_c
slli t6, t0, 2 # t6 = i * 4
add t6, t6, t2 # t6 = @v[i]
lw t6, 0(t6) # t6 = v[i] (l'adreça ja no cal, es reutilitza t6 per al valor)
sub t1, t5, t0 # t1 = 9 - i
slli t1, t1, 2 # t1 = (9 - i) * 4
add t1, t1, t3 # t1 = @w[9 - i]
sw t6, 0(t1) # w[9 - i] = v[i]
addi t0, t0, 1 # i++
j bucle_c
fibucle_c:A la declaració cal afegir w a continuació de v:
Enunciat: Problema 11.27.
Declaracions: int vec[100] global; i a t0, val a t1; el resultat (l’índex de la primera ocurrència o −1) s’ha de deixar a t2.
El bucle recorre el vector i compara cada element amb val. La novetat respecte als bucles de Solució 20.10 és la sortida anticipada: en trobar la primera coincidència, se surt del bucle immediatament amb l’índex actual, sense recórrer la resta del vector.
RV32I
la t3, vec # invariant: @vec[0]
li t4, 100 # invariant: N
li t0, 0 # i = 0
bucle:
bge t0, t4, notrobat # final del vector sense coincidència
slli t5, t0, 2 # t5 = i * 4
add t5, t5, t3 # t5 = @vec[i]
lw t5, 0(t5) # t5 = vec[i]
beq t5, t1, trobat # coincidència: surt amb l'i actual
addi t0, t0, 1 # i++
j bucle
trobat:
mv t2, t0 # t2 = índex de la primera ocurrència
j fi
notrobat:
li t2, -1 # t2 = -1 (val no és a vec)
fi:El j fi després de mv t2, t0 és imprescindible: sense aquest salt, el flux d’execució continuaria seqüencialment cap a notrobat i sobreescriuria l’índex trobat amb −1.
short
Enunciat: Problema 11.28.
Declaracions: short v[8] global; p a t0, i a t1.
a) p = v + 3;
p apunta a short, un tipus de mida 2 bytes. Per la regla d’escalat de l’aritmètica de punters (Secció 2.7.6), v + 3 desplaça l’adreça base \(3 \times 2 = 6\) bytes: el punter s’incrementa en 6 bytes (no en 3).
Nota: la sintaxi la label+offset és admesa a EC, però a RARS cal substituir-la per la t0, v seguit d’addi t0, t0, 6 (EC 2.6).
b) Bucle que dobla cada element usant p:
RV32I
Com que els elements són short, l’accés a memòria es fa amb lh/sh i l’increment del punter (p++) és de 2 bytes per element, a diferència dels 4 bytes dels vectors d’int vistos fins ara.
20.9 Cadenes de caràcters
longitud
Enunciat: Problema 11.30.
La funció ha de recórrer la cadena byte a byte fins a trobar el '\0' i retornar el nombre de caràcters llegits sense comptar-lo.
RV32I
# longitud: retorna la longitud d'una cadena acabada en '\0'
# a0: adreça base de la cadena
# Retorn: a0 = longitud (sense comptar el '\0')
longitud:
mv t0, a0 # t0 = punter al caràcter actual
li t1, 0 # comptador = 0
bucle:
lb t2, 0(t0) # t2 = caràcter actual
beq t2, zero, fi # si '\0', acabar
addi t0, t0, 1 # avança el punter
addi t1, t1, 1 # comptador++
j bucle
fi:
mv a0, t1 # retorn: a0 = longitud
retLa funció usa únicament registres temporals (t0–t2), de manera que no cal preservar cap registre.
Enunciat: Problema 11.31.
Es recorre src byte a byte amb dos punters que avancen alhora, un per a cada cadena. Cada byte es copia abans de comprovar si és el '\0'; d’aquesta manera, el caràcter de final de cadena també queda copiat a dst sense cap instrucció addicional després del bucle:
RV32I
la t0, src # punter a src
la t1, dst # punter a dst
bucle:
lb t2, 0(t0) # t2 = caràcter actual de src
sb t2, 0(t1) # el copia a dst (inclòs el '\0')
beq t2, zero, fibucle # si era '\0', la còpia és completa
addi t0, t0, 1 # avança el punter de src
addi t1, t1, 1 # avança el punter de dst
j bucle
fibucle:Si la comprovació del '\0' es fes abans de l’sb (com al bucle de Solució 20.13), el bucle acabaria sense copiar el terminador i caldria un sb addicional després de l’etiqueta de sortida.