Course

Registri

i registri sono “parti di memoria” molto vicine alla CPU , infatti la CPU accede a questi registri molto velocemente , come possiamo notare ci sono 8 zeri , ogni zero rappresenta un valore hex . Ora siccome un valore hex è rappresentato da 4 bit avremo bit in un solo registro , [ infatti questo è un processore a 32 bit ] , quindi in un registro possiamo memorizzare 32 bit. Questa grandezza si chiama word , infatti per esempio se abbiamo una macchina a 64-bit allora una word sarà di 64 bit.

Tutti questi registri sono a nostra disposizione a eccezione di alcuni che hanno un ruolo specifico , per esempio il registro r7 ha il ruolo di System Calls , in pratica serve a “parlare” con il sistema quando il nostro programma è in esecuzione , magari gli serve una risorsa o fermare l’esecuzione. Al sistema parliamo settando il registro r7 con alcuni valori , ogni valore rappresenta una roba diversa , per esempio se settiamo a 1 allora vorrà dire che vogliamo fermare l’esecuzione del programma.

Altri registri con ruolo specifico :

  • sp : stack pointer , che ci dice il prossimo “pezzo” di memoria disponibile nello stack [ in RAM ]
  • lr : link register , che ci dice dove deve andare dopo che una funzione ha fatto il suo dovere
  • pc : program counter , memorizza l’indirizzo della prossima istruzione [ mem. in memoria ]
  • cpsr : memorizza informazioni riguardo le operazioni aritmetiche che si fanno , tipo se faccio 5 - 7 = -2 , allora cpsr deve memorizzare che mi è uscito un numero negativo

First program

CPULator ci mette già della roba nell’editor :

.global _start
_start:
	

sono dei label e quando facciamo _start: stiamo dicendo : ok qui metti il codice , invece quando facciamo .global _start stiamo dicendo : ok esegui la roba che si trova in _start [starting point] .

Ora proviamo a “spostare” dati in r0 : lo facciamo con l’operazione MOV <destinazione>,<sorgente>

.global _start
_start:
	MOV r0,#30
 
	MOV r7,#1
	SWI 0
  • prende 30 [ decimale ] e lo butta dentro r0 , se vogliamo usare hex possiamo fare cosi #0x1e invece che #30
  • butta dentro r7 il valore 1
  • chiama swi che sarebbe il software interrupt , diciamo che chiama il OS che poi controlla r7 [ infatti è 1 e quindi termina l’esecuzione ]

nota che su CPUlator non viene usato SWI 0 , ma questo dovrebbe essere usato in processore ARM.

Addressing Modes

Il modo di indirizzare la roba che abbiamo usato prima con MOV r0,#30 si chiama Immediately addressing , siccome #30 è un valore costante diretto. Possiamo anche indirizzare tra registri tipo MOV r1,r0 e si chiama Direct Addressing.

Un’altro modo di indirizzare ha a che fare con lo stack e si chiama Indirect Addressing , quindi vediamo prima come mettere/prendere della roba dallo stack :

abbiamo bisogno di una sezione .data che conterrà le dichiarazioni della roba che vogliamo mettere nello stack :

.data 
list: //nome dei dati
	.word 4,5,-9,1,0,2,-3 // lista di numeri

con .word stiamo dicendo che ogni valore va assunto come un word ovvero 32 bit , infatti con .word va ad allocare spazio in memoria.

Ora vogliamo prendere questi dati dallo stack e lo facciamo con LDR :

.global _start
_start:
	LDR  R0 , =list

notiamo quando eseguiamo che il r0 viene memorizzato l’indirizzo di 4 ovvero il primo elemento di list e non il valore 4.

\newpage

Per prendere il valore lo facciamo sempre con LDR ma sta volta usiamo una sintassi diversa per prendere il valore

.global _start
_start:
	LDR  R0 , =list
	LDR  r1 , [r0] -- prendo il valore

dove infatti con [r0] specifichiamo che siamo interessati al valore associato all’indirizzo che contiene r0.

Ora se voglio prendere il valore successivo basta che aggiungo un certo offset in questo caso 4 , perché se il primo valore è indirizzato a 10 per esempio il secondo sarà indirizzato da 14 perché ogni valore è 32 bit che sono 4 byte :

.global _start
_start:
	LDR  R0 , =list
	LDR  r1 , [r0]
	LDR  r2 , [R0 , #4] //ADD offset per prendere il valore successivo 

Un’altro modo di indirizzare è il Pre-Increment , che sarebbe in pratica che in un lang di alto livello sarebbe :

R0 = r0+1
list[r0]

mentre prima avevamo fatto solo cosi

list[r0+1]

per fare il pre-increment basta aggiungere un ! alla fine di LDR <dest>,[<sorgente>,<offset>] quindi abbiamo :

.global _start
_start:
	LDR  R0 , =list
	LDR  r1 , [r0]
	LDR  r2 , [R0 , #4]! //ADD offset pre-increment

Un’altro metodo è Post-Increment che l’unica differenza dal Pre-Increment è che “incrementa” dopo e lo facciamo in questo modo :

.global _start
_start:
	LDR  R0 , =list
	LDR  r1 , [r0]
	LDR  r2 , [r0] , #4 //ADD offset post-increment  

in questo modo in r2 viene memorizzato il valore di r0 ovvero anche lo stesso di r1 e poi r0 viene incrementato dell’offset = 4 .

Operazioni

Le operazioni aritmetiche elementari che possiamo fare sono :

  • ADD <dest>,<n1>,<n2>
  • SUB <dest>,<n1>,<n2>
  • MUL <dest>,<n1>,<n2>

dove con n1 e n2 si riferisce ai registri che contengono effettivamente quel numero , un esempio è il seguente :

.global _start
_start:
	MOV r0,#5
	MOV r1,#7
	ADD r2,r0,r1 // r2 = R0 + r1 

la cosa interessante è quando il risultato è un numero negativo , facciamo per esempio :

.global _start
_start:
	MOV r0,#5
	MOV r1,#7
	SUB r2,r0,r1 // r2 = R0 - r1 = -2 

siccome , però in hex sarà in complemento a due a 32-bit e quindi sarà fffffffe , questo è un problema perché confonde un po , a questo ci viene in aiuto il registro cpsr :

che viene deve essere settato dopo qualsiasi operazioni elementare per sapere se il c’è stato un carry , zero , o è negativo il risultato. Dobbiamo usare subs [ invece che SUB ] setterà il cspr . Possiamo avere anche un carry quando facciamo una somma troppo grossa per essere messa in un solo registro e quindi usiamo adds per settare il cpsr :

.global _start
_start:
	MOV r0,#0xfffffff
	MOV r1,#3
	adds r2,r0,r1 
 

e se vogliamo sommare anche il carry usiamo adc :

.global _start
_start:
	MOV r0,#0xfffffff
	MOV r1,#3
	adc r2,r0,r1 // r2 = R0 + r1 + carry  
 

Logical Operators

Abbiamo :

  • and <dest>,<n1>,<n2> : AND
  • orr <dest>,<n1>,<n2> : OR
  • eor <dest>,<n1>,<n2> : XOR
  • mvr <dest>,<source> : butta dentro <dest> la negazione di <source>

Shift and Rotating

Ci sono altre operazioni che possiamo fare :

  • lsl <reg>,<n> : fa lo shift a sinistra del registro reg n volte moltiplica per n*2
  • lsr <reg>,<n> : fa lo shift a destra del registro reg n volte divide per n*2
  • ror <reg>,<n> : fa il rotation a destra del registro reg n volte significa in pratica che tutto quello che “sparisce” a destra facendo lo shift a destra , “ritorna” da sinistra [ davanti ].

Conditions and Branches

Nel nostro programma vogliamo la possibilità di fare delle comparazioni in base a delle condizioni e “saltare” in un determinato punto del programma se una condizioni si verifichi o no. Per farlo usiamo i comparatori e i branches :

  • i comparatori ci permettono di comparare due valori
  • i branches ci permettono di muoversi all’interno del nostro programma basandoci sul risultato di una comparazione
.global _start
_start: 
	MOV R0,#1 
	MOV R1,#2
	CMP R0,R1 // comparazione fa : R0-R1
	BGT greater // bigger-than  
	MOV R2,#2 
	
greater: // qui ci arriva lo stesso 
	MOV R2,#1	 

BGT <label> dice che se R0 R1 allora fai la roba che sta dentro <label> Siccome il flusso di esecuzione è sequenziale , prima o poi arriviamo a greater e quindi esegue la roba che sta la dentro anche se la comparazione non è vera . Per evitare questo si usa BAL <label> o B <label> che dice cosa deve eseguire se la comparazione non risulta vera e quindi fa lo skip del branch greater :

.global _start
_start: 
	MOV R0,#1 
	MOV R1,#2
	CMP R0,R1 
	BGT greater   
	B end // va subito a end li saltando greater 
	
greater: 
	MOV R2,#1	 
 
end: 
	MOV R2,#2

Ci sono diversi branches :

  • BGE :
  • BEQ :
  • BNE :
  • BLT :
  • BMI : se negativo
  • BPL : se positivo

Inoltre c’è un’altro metodo di fare le condizioni molto più pulito [ senza l’uso di branches che sporcano un po ] , è quello di fare prima il CMP tra due registri e poi aggiungere la condizione LT , GE , EQ etc proprio accanto alla operazione da fare in caso affermativo :

.global _start
.start:
	MOV R0,#2
	MOV R1,#4
	CMP R0,R1
	
	MOVLT R2,#9 // butta in R2 solo se R0 < R1 

Looping

Possiamo fare un loop sfruttando i branches , vediamolo con un esempio :

.global _start
 
.equ endlist, #0xaaaaaaaa // definiamo una costante "endlist" pari al valore 0xaaaaaaaa ovvero la fine della lista
 
_start: 
	LDR R0,=list
	LDR R1,[R0]
	LDR R3,=endlist // carichiamo il valore endlist in R3
 
loop: // definizione del loop 
	LDR R1,[R0,#4]! // carica in R1 il valore successivo e fa il pre-increment 
	CMP R1,R3 // comparo R1 e R3 [endlist]
	BEQ exit // se sono uguali esci 
	ADD R2,R2,R1 // se non sono uguali viene qua e somma a R2 , R1 
	BAL loop // ritorna sopra [ripeti]
	
exit: // nulla
 
.data
list:
	.word: 1,2,3,4,5,6,7,8,9

Per creare una funzione utilizziamo il registro speciale LR che abbiamo a disposizione , questo registro verrà settato - prima di “saltare” in un branch - con l’indirizzo della prossima istruzione che dobbiamo fare dopo quel “salto” al branch e per farlo non saltiamo al branch con BAL \ B ma con BL :

.global _start
_start:
	MOV R0,#1
	MOV R1,#2
	
	BL add_branch // vai su add_branch , ma ricordati l'indirizzo della prossima istruzione
	
	MOV R3,#9 // prossima istruzione che deve fare quando finisce in add_branch 
	
	B end // finisci senno poi va a leggere sotto add_branch
 
add_branch:
	ADD R2,R1,R0
	
	BX LR // ritorna dove eri secondo il registro LR [LINKED REGISTER]
 
end:

Preserving and Retrieving Data from Stack Memory

Siccome i registri non sono infiniti , possiamo “pushare” il valore dei registri nello stack memory e poi riprenderli quando ci servono ancora e dopo che ci abbiamo lavorato [ magari con una funzione tipo ] :

.global _start
_start:
	MOV R0,#1
	MOV R1,#2
	
	PUSH {R0,R1} // pusha nello stack R0 e R1
	BL add_branch 
	POP {R0,R1} // riprendi dallo stack R0 e R1 [ come li avevo messi ] 
	B end 
 
add_branch:
	// modifico R0 e R1
	MOV R0,#5 
	MOV R1,#7
	ADD R2,R1,R0
	BX LR
 
end: