In RISC-V hanno tutte la dimensione di
Le istruzioni possono referenziare al massimo 3 registri (a
Riguardo al modello di memoria gli indirizzi sono a
Le istruzioni in RISC-V sono di
Formato | Istruzioni | Parametro Immediato | Immediato esteso ( |
---|---|---|---|
R (register) |
aritmetico-logiche | n/a | n/a |
I (immediate) |
di caricamento da memoria (load ), aritmetico-logiche con immediati, salto incondizionato, link a registro |
estensione segno | |
S (store) |
di memorizzazione in memoria (store ) |
estensione segno | |
B (branch) |
branch condizionale | estensione segno | |
U (load upper integer) |
istruzione lui |
N/A | |
J (jump & link) (UJ in PH) |
salto incondizionato e link | estensione segno |
Nelle istruzioni di tipo immediato il parametro:
load
e store
è offset rispetto a un registro base per individuare l'indirizzo di memoria riferito al datobranch
e jump
è rispetto al PC
, o un altro registro, per individuare l'indirizzo di memoria dell'istruzione destinazione del salto
Il campo funct3
e funct7
agisce come estensione del codice operativo per specificare esattamente cosa fa la funzione, in aggiunta a quanto già indicato dal codice operativo
In RISC-V, un'istruzione aritmetico-logica possiede tre operandi: i due registri contenenti i valori da elaborare (registri sorgente) e il registro contenente il risultato (registro destinazione). L'ordine degli operandi è fisso: prima il registro contenente il risultato dell'operazione e poi due operandi:
OPCODE DEST, SORG1 SORG2
Sintassi | Traduzione | Significato |
---|---|---|
add rd, rs1, rs2 |
rd <- rs1 + rs2 |
add addiziona il contenuto di due registri sorgente rs1 e rs2 e mette nel registro destinazione rd il risultato dell'addizione dei contenuti di rs1 e rs2 . |
sub rd, rs1, rs2 |
rd <- rs1 - rs2 |
sub sottrae il contenuto dei due registri sorgente rs1 (minuendo) e rs2 (sottraendo) e mette nel registro destinazione rd il risultato della sottrazione dei contenuti di rs1 . |
addi rd, rs1, NUM |
rd <- rs1 + NUM |
addi addiziona una costante NUM a un registro rs1 e mette nel registro di destinazione rd . La costante deve essere un numero rappresentabile su |
RISC-V non ha un istruzione di sottrazione di costante perché basta usare addi
con costante di segno cambiata.
Data la formula:
s1
è associato a s2
a s3
a s4
a s5
a g
e il risultato finale viene messo in s3
(riutilizzato)t0
e t1
contengono valori temporaneiadd t0, s1, s2
sub t1, s3, s4
sub t0, t0, t1
add s3, t0, s5
Questo tipo di istruzioni si occupano di trasferire dati dalla memoria fisica ai registri (load
) e viceversa (`store):
OPCODE REG, MEMADDR
Sintassi | Traduzione | Significato |
---|---|---|
ld rd, NUM(LOC) |
rd <- memoria[LOC + NUM] |
L'istruzione ld di load trasferisce una copia della parola doppia contenuta in una specifica locazione di memoria (LOC + NUM ) (a lw , lh e lb che caricano rispettivamente |
sd rs2, NUM(LOC) |
memoria[LOC + NUM] <- rs2 |
L'istruzione sd di store trasferisce una parola doppia da un registro della CPU (a LOC + NUM ) e sovrascrivendo il contenuto precedente di quella locazione. |
li t0 VAL |
t0 <- VAL |
L'istruzione li carica una costante VAL nel registro t0 . |
la a0, LABEL |
a0 <- LABEL |
L'istruzione la carica un indirizzo LABEL in a0 dove LABEL è un etichetta simbolica per indicare il valore dell'indirizzo di memoria di un dato. Vengono caricati i |
.eqv NAME, VAL |
#define NAME VAL |
La direttiva .eqv associa un valore decimale al simbolo NAME . Non è un istruzione macchina, è solo una dichiarazione di costante. |
Durante l'istruzione ld
la CPU invia l'indirizzo della locazione desiderata alla memoria e richiede un'operazione di lettura del contenuto della memoria. Poi la memoria effettua la lettura della parola memorizzata all'indirizzo specificato e la invia alla CPU.
Durante l'istruzione sd
la CPU invia l'indirizzo della locazione desiderata alla memoria, insieme alla parola che vi deve essere scritta, e richiede un'operazione di scrittura. Poi la memoria effettua la scrittura della parola all'indirizzo specificato.
Una parola doppia contiene più byte, per esempio una parola da
RISC-V usa little endian.
Con parole da
Istruzione | Significato |
---|---|
and rd, rs1, rs2 |
and esegue l'and bit a bit tra il contenuto dei due registri rs1 e rs2 e ne memorizza il risultato in rd . |
or rd, rs1, rs2 |
or esegue l'or bit a bit tra il contenuto dei due registri rs1 e rs2 e ne memorizza il risultato in rd . |
xor rd, rs1, rs2 |
xor esegue lo xor bit a bit tra il contenuto dei due registri rs1 e rs2 e ne memorizza il risultato in rd . |
not rd, rs1 |
not esegue il not bit a bit sul contenuto di rs1 e ne memorizza il risultato in rd . È realizzata come xori rf, rs1, -1 . |
slli rd, rs1, cost12 |
slli (shift left logical) sposta a sinistra il contenuto di rs1 del numero specificato e introduce bit |
srli rd, rs1, cost12 |
srli (shift right logical) sposta a destra il contenuto di rs1 del numero specificato e introduce bit |
srai rd, rs1, cost12 |
srai (shift right arith) sposta a destra il contenuto di rs1 del numero di bit specificato, ed estende il segno a sinistra. |
Esistono anche andi
, ori
e xori
che eseguono rispettivamente and, or e xor bit a bit tra un operando e una costante (la costante viene prolungata da
Nelle operazioni di shift si ha che cost12
è una costante intera positiva compresa tra sll
, srl
e sra
in cui il terzo argomento è un registro rs2
che specifica il numero di posizioni di scorrimento.
Si ha che la locazione dell'elemento
Consideriamo a = A[i]
e assumiamo che s1
, l'indirizzo del primo elemento dell'array sia contenuto nel registro s3
e a
sia in s2
:
slli t1, s1, 3 # t1 contiene i * 8
add t2, t1, s3 # t2 contiene indirizzo di A[i]
ld s2, 0(t2) # carico in s2 il valore di A[i]
Se poi volessi caricare A[i+1]
basterebbe modificare lo spiazzamento:
ld s2, 8(t2)
Le istruzioni di salto si suddividono in salti condizionati e non condizionati. Nei salti condizionati si ha la forma:
OPCODE SORG1, SORG2, IND_SALTO
Avviene cioè on confronto tra SORG1
e SORG2
e se la condizione viene soddisfatta, allora salta. L'indirizzo di destinazione di salto IND_SALTO
è espresso relativamente rispetto al PC
.
Nelle istruzioni di salto incondizionato il salto viene sempre eseguito.
Istruzione | Traduzione | Significato |
---|---|---|
beq rs1, rs2, ind_salto |
/ | Se confrontando rs1 e rs2 essi sono uguali allora si effettua il salto. |
bne rs1, rs2, ind_salto |
/ | Se confrontando rs1 e rs2 essi sono diversi allora si effettua il salto. |
blt rs1, rs2, ind_salto |
/ | Se confrontando rs1 e rs2 si ha che |
bge rs1, rs2, ind_salto |
/ | Se confrontando rs1 e rs2 si ha che |
j spi20 |
pc <- pc + spi20 |
Si effettua un salto relativo a pc di spi20 . |
jr rs1 |
pc <- rs1 |
Si effettua un salto impostando pc al valore di rs1 . |
jal spi20 |
ra <- pc + 4 pc <- pc + spi20 |
Si effettua un salto relativo a pc di spi20 , salvando l'indirizzo di rientro nel registro ra . |
jalr rs1 |
ra <- pc + 4 pc <- rs1 |
Si effettua un salto impostando pc al valore di rs1 , salvando l'indirizzo di rientro nel registro ra . |
ret |
pc <- ra |
Salto impostando pc al valore di ra . |
L'offset di un salto può variare tra
Nelle istruzioni in un linguaggio macchina si usano gli indirizzi, tuttavia per un programmatore non è conveniente, o possible calcolare l'indirizzo numerico di dati e istruzioni. Si usano quindi etichette mnemoniche dette label che si associano a specifici punti del programma, l'assemblatore poi traduce le etichette nei corrispondenti indirizzi.
Per le istruzioni:
LABEL_NAME: addi x4, x5, 10
sub x4, x4, x6
...
Mentre per i dati:
.data
A: .zero 8
B: .zero 8
C: .zero 8
Consideriamo il codice c:
if (i == j)
f = g + h
else
f = g - h
E supponiamo che le variabili f
, g
, h
, i
e j
siano associate rispettivamente ai registri s0
, s1
, s2
, s3
e s4
. Allora una traduzione in RISC-V:
IF: bne s3, s4, ELSE
THEN: add s0, s1, s2
j ENDIF
ELSE: sub s0, s1, s2
ENDIF: ...
Notiamo che le etichette IF
e THEN
non servono per saltare, ma aiutano a distinguere le varie parti.
Istruzione | Significato |
---|---|
slt s1, s2, s3 |
L'istruzione slt assegna il valore s1 se |
slti s1, s2, NUM |
L'istruzione slti assegna il valore s2 se NUM è una costante. Altrimenti assegna il valore |
slt
/slti
esegue i confronti in aritmetica cno segno, cioè interprentando i valori come CPL2. Se i valori numeri sono degli indirizzi bisogna usare sltu
/sltiu
.
Consideriamo il codice c:
if (i < j)
k = i + j
else
k = i - j
E supponiamo che le variabili i
, j
e k
siano rispettivamente associate a s0
, s1
e s2
.
IF: slt t0, s0, s1
beq t0, zero, ELSE
THEN: add s2, s0, s1
j ENDIF
ELSE: sub s2, s0, s1
ENDIF: ...
Le istruzioni di tipo I permettono di rappresentare costanti esprimibili su
lui
per caricare il primo immediato nei addi
consente di aggiungere tramite il secondo immediati, i Istruzione | Traduzione | Significato |
---|---|---|
lui rd, cost20 |
rd <- cost20 |
Carica parzialmente la costante cost20 nei rd , poi azzera i rd , e infine estende in segno la metà inferiore di rd fino al |
auipc rd, spi20 |
rd <- pc + spi20 |
Carica parzialmente un indirizzo relativo al pc . Prima somma lo spiazzamento spi20 all'indirizzo dell'istruzione auipc , e poi lo tratta come la costante nell'istruzione lui e lo carica nel registro rd . |
Normalmente la costante cost20
è specificata come valore numerico o come costante simbolica dichiarata tramite la direttiva .eqv
.
In realtà l'istruzione lui
carica nei li
e la
.
Ci è fornita un istruzione di chiamata (invocazione) di sistema operativo, che fornisce un insieme di funzioni di servizio. Per richiedere una funzione di servizio bisogna impostare il codice di chiamata nel registro a7
, inserire gli argomenti nei registri richiesti, e utilizzare l'istruzione macchina ecall
senza argomenti.
Traduciamo il seguente codice c in assembly:
#include <stdio.h>
void main() {
printf("Hello, World!");
return;
}
.data
OUT_STRING: .asciz "\nHello, World!\n"
.text
MAIN: li a7, 4
la a0, OUT_STRING
ecall
EXIT: li a7, 93
li a0, -1
ecall
Direttiva | Significato |
---|---|
.align num |
Allinea il dato dichiarato secondo num : |
.ascii str |
Riserva spazio per la stringa str nel segmento dati, senza terminatore NULL. |
.asciiz str |
Riserva spazio per la stringa str nel segmento dati, con terminatore NULL. |
.byte b1,... |
Riserva spazio per i byte elencati e li inizializza con i valori a |
.data ind |
Dichiara il segmento dati con indirizzo iniziale ind (default: 0x0000000010000000 ). |
.dword d1,... |
Riserva spazio per parole doppie ( |
.eqv sym, val |
Definisce un simbolo sym con valore val (come #define in C, senza allocare memoria). Locale al modulo. |
.globl sym |
Rende il simbolo sym visibile agli altri moduli oggetto. |
.half h1,... |
Riserva spazio per mezze parole ( |
.space num |
Riserva spazio per num byte non inizializzati. Usata per array, struct o quando mancano valori iniziali. |
.text ind |
Dichiara il segmento testo (codice) con indirizzo iniziale ind (default: 0x0000000000400000 ). |
.word w1,... |
Riserva spazio per parole ( |
.zero num |
Come .space , ma inizializza a zero (segmento BSS). Utilizzata da GCC. |
RISC-V ha quattro modalità di indirizzamento:
pc
.