# Overview
* 高階語言(.c) -> (Compiler) -> 組合語言(.s) -> (Assembeler) -> 機器語言(.o)
* 每一種處理器(Processer)都有自己的指令,多個指令形成程式
* 組合語言的種類
* ARM: 用於各種嵌入式系統 (本課程目標)
* SIMD(x86): 用於大部分電腦系統中
* RISC-V
* 使用組合語言的原因:
* 速度更快
* 程式碼大小更小
* 有存取特殊硬體的需求
# Computer System
## 電腦基本架構
電腦系統主要可以分成三大區,並透過"bus"互相溝通
![](https://hackmd.io/_uploads/rJ5N2r6Ah.png)
## Processor
Processor可以被視為系統的控制中樞
執行流程 (以下三點不斷循環):
1. Fetch: 從記憶體(Memory)中取出指令
1. 透過address bus傳遞指令所在的記憶體位址訊息
2. 透過Control bus傳遞"讀指令"這個動作
3. 透過data bus放置讀出來的資料
3. Decode: 解讀指令
4. Execute: 執行指令
* 透過ALU(Arithmetic and Logic Unit)執行指令
### Processor register
* register是在Processor中,讀取速度非常快的記憶單元,但空間是有限的
* 一個register的大小通常是4 bytes
* 必須先將資料要先放到register中才能做運算
觀念釐清:
1. 誰提供指令給Process執行? 使用者撰寫的程式
2. 誰把要執行的指令放到memory中供Processor Fetch? 作業系統會將使用者撰寫的程式從disk中轉移至memory
3. Processor 如何知道指令所在的記憶體位址? 作業系統再將程式從disk中轉移至memory的過程中,會順便告訴Processor記憶體位址相關資訊
## Instruction
基本格式(一條指令就是一行)
![](https://hackmd.io/_uploads/SJ4YGt0Ch.png =50%x)
* Function: 指定要做哪種運算
* OP1、OP2: 運算所需的參數
### Number of address
在設計組合語言的指令時,跟據參數的不同,可以將組合語言的指令分成五大類
註: address可以被視作oprand(參數)
* 4-address machine:
* 包含一個result、兩個sourse、下個指令的位置
* ex: `ADD d,s1,s2,next` (等價於`d := s1+s2`)
* 3-address machine:
* 包含一個result、兩個sourse
* 使用Progrem Counter(一種特別的register)來記錄下個指令的位置
* ex: `ADD d,s1,s2` (等價於`d := s1+s2`)
* 2-address machine:
* 包含一個"同時當result跟sourse"的參數、一個sourse
* ex: `ADD d,s1` (等價於`d := d+s1`)
* 1-address machine:
* 包含一個sourse
* 使用accumulator(一種特別的register)來同時當result跟sourse
* ex: `ADD s1` (等價於`accumulator := accumulator+s1`)
* 0-address machine:
* 不需要任何參數
* 利用額外的Stack 來儲存參數,instruction只需要指定要對參數做甚麼事即可
* ex: `ADD`
![](https://hackmd.io/_uploads/S1h7dKC03.png =50%x)
## Memory
![](https://hackmd.io/_uploads/ry_DTAykT.png =50%x)
* Memory 的每一格大小都是一個byte,且都有一個Address與之對應,故這種Memory 又被稱為**byte addressable**
### Read Operation
![](https://hackmd.io/_uploads/HkAf001Jp.png)
1. 先將CPU想要讀取的記憶體位置放到address bus中
2. 透過Control bus 通知Memory 要做"讀取"操作
3. Memory將資料讀出來,並放到data bus 中
4. 結束讀取操作
### Write Operation
![](https://hackmd.io/_uploads/HkAf001Jp.png)
1. 先將CPU想要寫的記憶體位置放到address bus中
2. 把要寫進記憶體中的資料放到data bus 中
3. 透過Control bus 通知Memory 要做"寫入"操作
4. Memory成功寫入資料,並結束寫入操作
若是要寫入的資料不只一個byte,則會有兩種擺法(必考)
![](https://hackmd.io/_uploads/ByM4HylJT.png =80%x)
* 將欲寫入資料**最右邊的bit**稱為Most significant bit(MSB)
* 將欲寫入資料**最左邊的bit**稱為Least significant bit(LSB)
* 將資料存進Memory時,都是從Address低的擺放到高的
* **若先擺LSB,則這種擺法稱為Little-endian byte ordering** (大部分processor的預設模式)
* **若先擺MSB,則這種擺法稱為Big-endian byte ordering**
### Data aligment
系統在讀取記憶體時,基於性能考量,會一次讀取2、4、8... byte的資料,故一筆資料在記憶體中是否處於對齊的位置會直接影響到讀取速度
舉例來說,當資料跨區儲存時,Processor需要讀取兩次Memory才能得到想要的資料
![](https://hackmd.io/_uploads/HJsjc1eya.png)
資料對齊的定義:
* 1 byte 資料: 永遠都是對齊的
* 2 byte 資料: 需儲存在"2的倍數"的記憶體中才算對齊
* 4 byte 資料: 需儲存在"4的倍數"的記憶體中才算對齊
* 其他以此類推
針對資料是否要對齊,可以將Processor分成兩類
* soft alignment: Processor並不強迫對齊資料,但對齊能提高性能
* hard alignment: Processor強迫資料一定要對齊
## Instruction Set Architecture(ISA)
從組合語言撰寫者的角度來看操作CPU的api
## Instruction types
可以分成三類:
* Data processing instructions: 資料運算與處理相關的指令 (ex: add,sub)
* Data movement instructions: 資料搬移相關的指令 (ex: )
* Program control flow instructions:
# System software
## Assember
程式執行流程:
![](https://hackmd.io/_uploads/rJLbf9B16.png =70%x)
* Assember:
* 將組合語言的指令翻譯成機器語言
* 將翻譯好機器語言存成適當的格式(ex: a.out)
* 給予symbolic labels正確的記憶體
* 將型態轉換成機器讀得懂的表示法
* Object code: 機器語言,由0跟1組成 (ex: a.out);分成三個區塊,分別存放程式、已初始化全域變數、未初始化全域變數
![](https://hackmd.io/_uploads/S1fODOwe6.png =50%x)
* Linker: 程式可能會寫在不同的檔案中,並分別被轉成Object code,Linker的工作便是負責合併不同的Object Code,使之變成一個執行檔
* Executable code: 隨時可執行的程式
* Loader: 隸屬作業系統,負責載入Executable code至memory,並告訴Processor這段Executable code可執行
關於symbolic labels:
![](https://hackmd.io/_uploads/ByUCHqSJp.png =70%x)
* LABEL的語法就如同C語言的goto,當程式執行到0x108那行時,會跳回0x110那邊執行指令
* Assember其中之一的工作便是賦予label跳回去位置的記憶體位址
### Assember Directives
負責提供Assember資訊的指令,不會被翻譯成machine code
ex:
![](https://hackmd.io/_uploads/BJvfvcBkT.png =50%x)
### Pseudo Instruction
不是實際的指令,但Assember能看懂並轉成等價的實際指令
ex: 把r2的值搬到r1
```
MOV r1, r2 (Pseudo Instruction)
add r1, r2, #0 (等價實際指令,r1=r2+0)
```
### Two Pass Assember
在Assember逐行翻譯指令的時候,可能會遇到Label要跳去的地方還沒讀到的問題
![](https://hackmd.io/_uploads/rJUQpcB1T.png =50%x)
解決方法是使用Two Pass Assember:
* 第一輪先掃過一次全部的指令,已得知Label的位置
* 第二輪再逐行翻譯指令
# 跨平台工具編譯
![](https://hackmd.io/_uploads/B1hzc2BJ6.png =70%x)
* Building machine: 負責編譯程式
* Host machine: 負責執行程式
* Target machine: 負責享受Host machine的執行結果
# ARM 指令介紹
學習目標: 32 bit ARM instruction set (ARM v4T)
* Load-store 架構: 從記憶體中讀取資料至register,處理完後,儲存回記憶體
## condition code flag觀念
![](https://hackmd.io/_uploads/SJQ7iwvxa.png)
* 電腦在運算的過程中,還存了32個bit的condition code flag,用來記錄一些必要的資訊
應用: 64 bit 加法
![](https://hackmd.io/_uploads/Bkydivvgp.png)
* ADDS指令中的S代表考慮進位,若ADD的運算過程中有進位,condition code flag C會變成1
* 並使用ADC來使用上一次進位的C
## Comparition Operation
這些指令只會對condition code bit造成影響
![](https://hackmd.io/_uploads/Hy0oTBDlT.png)
## Immediate Operand
包含了Immediate value(常數)
![](https://hackmd.io/_uploads/BJt4CBPla.png =80%x)
* 常數可放十進位或十六進位
## Shift Register Operand
* 包含了左移與右移運算等 (只會處理最後8個bit)
* ARM 並沒有提供獨立的指令操作,只能可以**放在Data Processing instruction的第二參數後來使用**
![](https://hackmd.io/_uploads/Syw7gUDga.png =80%x)
* LSL: 左移運算,不足的補0
* ASL: 跟LSL完全相同
![](https://hackmd.io/_uploads/H1uUlLPl6.png =80%x)
![](https://hackmd.io/_uploads/Sk4wlUveT.png =80%x)
* LSR: 右移運算,不足的補0
* ASR: 右移運算,**不足的補「Most significant bit」** (目的是處理負數)
![](https://hackmd.io/_uploads/SkDrfIPxT.png =80%x)
* ROR: 右移運算,把被丟掉的bit補到最左邊,先丟的先補
![](https://hackmd.io/_uploads/B1Ypz8wep.png)
* RRX: 右移運算,**只右移一個bit**,將當前的C flag存到最左邊,接著把被丟掉的bit存至flag
左右移使用範例:
![](https://hackmd.io/_uploads/BJgVJIvea.png =80%x)
* r3 = r2 + (r1左移3位)
* 仍被視為一條指令,所需CPI不變
## 其他語法
![](https://hackmd.io/_uploads/ryNfDuvxp.png =70%x)
* .section .text: 表示到 .end前的區塊都屬於object file 中的text區域
* .global main: 讓linker認識main這個名字
* .type main,%function: 告訴linker main這個名字的型態 (其他可能值為"object"及"common")
* main: 屬於Label
* 單行註解: 加@
* 多行註解: /* */
## Data Processing instruction
專門處理register中的資料
![](https://hackmd.io/_uploads/HkSSsHwlT.png)
* ADD: 加法運算
* ADC: 加法運算,加上carry bit
* SUB: 減法運算
* SBC: 減法運算,加上carry bit
* RSB: 反向減法運算
* RSC: 反向減法運算,加上carry bit
![](https://hackmd.io/_uploads/SkMjiHPgp.png)
* AND: bitwise AND 運算
* ORR: bitwise OR 運算
* EOR: bitwise XOR 運算
* BIC: 清除特定bit指令 (將r1中 「r2中為1中的那些位置」更新成0)
![](https://hackmd.io/_uploads/SJCD3Svep.png =50%x)
![](https://hackmd.io/_uploads/SkMcnSve6.png)
* MOV: 將r2的值搬到r0
* MVN: 將 not r2的值搬到r0
![](https://hackmd.io/_uploads/ryLuhwvl6.png)
* MUL: 乘法運算 (速度慢,要約12個Clock Cycle)
* result register(ex: r4) 不可以跟sourse register(r2,r3)是同一個register
* 第二個參數不支援Immediate value(常數)
* 無法處理進位問題
## Data transfer instruction
這些指令負責協助資料在register跟memory間搬移
### single register load and store instruction
![](https://hackmd.io/_uploads/SkOB4zhl6.png =90%x)
* LDR: 將4 bytes的資料從r1所指向的記憶體中load至r0中
* STR: 將4 bytes資料store至r1所指向的記憶體中
* LDRB: 將1 bytes的資料從r1所指向的記憶體中load至r0中
* STRB: 將1 bytes資料store至r1所指向的記憶體中
* LDRH: 將2 bytes的資料從r1所指向的記憶體中load至r0中
* STRH: 將2 bytes資料store至r1所指向的記憶體中
* LDRSB: 將1 bytes的資料(有分正負)從r1所指向的記憶體中load至r0中
* LDRSH: 將2 bytes資料(有分正負)從r1所指向的記憶體中load至r0中
註: 之所以需要LDRSB跟LDRSH,是因為一個register的大小是4個bytes,當load的資料不足四個bytes時,剩餘的空間便需要補0(正數)或1(負數),取決於memory中most significant bit
其他特殊用法
pre-index addressing mode
![](https://hackmd.io/_uploads/SyCEVm2xp.png =70%x)
* 執行LDR操作時,memory指標指向「r1向後位移四格」的位置(r1本身不變)
auto indexing
![](https://hackmd.io/_uploads/HJECNQhlp.png =70%x)
* 執行LDR操作時的,memory指標指向「r1向後位移四格」的位置;**執行完畢後,r1向後位移四格** (!便表示會更新r1指標的位置)
post-index addressing mode
![](https://hackmd.io/_uploads/B1S4Bm3g6.png =70%x)
* 執行LDR操作時,memory指標指向「r1」的位置;**執行完畢後,r1向後位移四格**
### Multiple register load and store instruction
一次便能處理一個區間內的資料,非常適用於進入跟離開function時搬移資料
![](https://hackmd.io/_uploads/SkNos7hea.png =80%x)
* 總共有2*4種指令組合
ex1:
![](https://hackmd.io/_uploads/SJnnKJTlT.png =80%x)
* IA 是increase after,!代表會動到r0本身
ex2:
![](https://hackmd.io/_uploads/Sy2XnJ6gp.png =80%x)
* Decrease系列,先讀到的數會被搬至後面的register (先讀到60 搬給r3)
### single register swap instruction
* 該操作將一個register中的值跟一個memory中的值交換
* 操作過程中是atomic的
* 分成
* SWP: 一次交換4個byte
* SWPB: 一次交換1個byte
範例:
![](https://hackmd.io/_uploads/B1fdu_pep.png =60%x)
* 將r2所指向memory中的值轉移到r0,並將r1中的值轉移到r2所指向的memory中
### 其他Pseudo instruction
![](https://hackmd.io/_uploads/ByC9pOTlp.png =50%x)
* ADR指令可以將指定label代表的記憶體位址賦值給register
* 將table1的記憶體位址賦值給r5
## Control flow instruction
目的是滿足特定條件後,"跳到"指定的label
![](https://hackmd.io/_uploads/rJCopR9W6.png)
* B: 無條件的跳到指定的label
* BL:
![](https://hackmd.io/_uploads/ryG5XysZ6.png)
* 跳到指定的label,並將return的位置(BL 指令的下一條指令位置)儲存於LR(r14 register)中
* 使用STMFD跟LDMFD來將return 位置儲存進Stack中跟從Stack中讀出,以因應多個function call 的情況
* 透過MOV指令,將return的位置賦值給pc(指向下一條要執行的指令位置)
## Conditional excution (predicate)
這類型的修飾字可以作用於大部分的指令上
原理是利用CMP 指令來改動condition code flag,並利用condition code flag來判斷要不要執行該指令
ex:
![](https://hackmd.io/_uploads/SylFLxoba.png)
* ADD+NE 代表「當上一條CMP指令的結果是"Not equal時,才會執行這條指令"」
## Supervisor Calls(SVC)
觸發 system interrupt, 交由作業系統執行某些事後再回來
![](https://hackmd.io/_uploads/HJFkvMsZT.png)
* 交由作業系統印出一個字元在螢幕上
## Jump tables
* 類似C語言中的switch語法,透過傳進來的值決定程式要執行哪個subroutine
![](https://hackmd.io/_uploads/HyPDtMiZa.png)
* ADR那行: 將jump table的開頭load到r1
* CMP 那行: error handling (jump table 透過r0決定要執行哪個subroutine)
* LDRLS那行: 取得下一條要執行的指令
* DCD: 預定義資料語法
# linkerscript and crt0
## Object file format (.o)
一個Object file 可以依功能分成三個區塊
![](https://hackmd.io/_uploads/Sk-AwgkGT.png =50%x)
* TEXT: 存放指令
* DATA: 存放以初始化的全域變數
* BSS: 存放還未初始化的全域變數
ex:
![](https://hackmd.io/_uploads/SkzRWQyMa.png =80%x)
* 可以把.matrix、.word等當作是指標(指向某個記憶體位址)
* 整體概念如下
![](https://hackmd.io/_uploads/BJ9hcQyGT.png =50%x)
回憶:
![](https://hackmd.io/_uploads/SytA3mkMa.png =50%x)
* ADR: 將.matrix這個label的記憶體位址賦值給r0
* LDR: 取得 .matrix 指向的記憶體空間中的值
## Linker
Linker的目的是連結不同的Object file,使其變成執行檔
LMA(鏈結載入記憶體位址): Object file中的section被載入時,應該擺放的記憶體位址
VMA(虛擬記憶體位址): 程式在實際執行時,各個Object file中的section應該擺放的記憶體位址
## crt0
* crt0 是程式被執行時最先被執行的部分(比main()還更早),負責初始化等等的工作
* 原理: linker在link程式時,會順便link crt0.o
# ARM instruction set
* 支援 [signed, unsigned] [32bits, 16bits, 8 bits] 共六種資料類型
* 一個instruction 大小為32 bits (a word)
* 支援Little-endian(預設)、Big-endian
* ARM 透過CPSR中的第0~4個bit來決定現在位於那個operating mode下
![image](https://hackmd.io/_uploads/BkcHfH0Q6.png =70%x)
* ARM 透過SPSR跳回原本的mode中
## Register Organization
![image](https://hackmd.io/_uploads/B1TJdSAma.png)
* ARM 在User mode以及System mode的情況下,共有以下register可被使用
* r0~r12: 一般register
* r13(sp): stack pointer,追蹤runtime中top位置
* r14(lr): link register,在bl指令時,負責存要跳回去時的記憶體位址
* r15(pc): progrem counter,指向下一道指令的位置
* cpsr:
![image](https://hackmd.io/_uploads/r1LrpSCQ6.png)
* 0~4個bit負責存現在位於那個operating mode
* 29~31bit負責存當前的condition code flag
* r0~r7又稱為Low register,其他的被稱為High register
## Exception (例外情況)
發生Exception後的標準流程:
1. 切換至對應的mode
2. 將回去的記憶體為址存至r14中
3. 將切換前的CPSR存至SPSR
4. 禁止發生其他Exception
5. 透過查表,使progrem counter指到中斷向量表(Exception Vector Address)中的固定位置(指定下一次要執行的指令)
* 中斷向量表中的指令通常是branch instruction,會再跳到真正執行服務的地方
回復Exception的標準流程:
1. 回復修改的register
2. 使用SPSR回復CPSR
3. 使PC指向下一條該執行的指令位置
```
MOVS pc, r14 @加了S代表在執行第三步時,同時執行第二步
```
## condition excution 細節
* 每一條ARM instruction的28~31個bit保留來處理condition;接著便可以根據CSPR中存的值判斷要不要執行
```
ADDNE r0, r1, r2 @ 28~31個bit用來存"NE"這個資訊
```
* 沒指定condition excution的情況下,預設條件為"always"
* 共有16種可能的情況
![image](https://hackmd.io/_uploads/S19rASAQT.png)
## 指令的encode方式
### Branch instruction
![image](https://hackmd.io/_uploads/SyMEOL07a.png)
* offset: 當前指令到下一條指令的"距離" (直接加到 PC 中,單位為 byte)
* L: 該指令是否包含link(BL 中的"L")
* Cond: 保留用來處理condition
格式:
![image](https://hackmd.io/_uploads/B1znDEzNa.png =50%x)
範例:
```
B LABEL
LABEL:
```
## SWI(Software interrupt) instruction (SVC)
![image](https://hackmd.io/_uploads/H1uZhI0Xa.png)
* 0~23 bits: 表示特定的功能,例如輸出字元到螢幕上等等
* cond: 保留用來處理condition
## Data Processing instruction
![image](https://hackmd.io/_uploads/ByfkkezVa.png)
* operand: 參數二
* 若 # = 1: immediate value
* 若 # = 0: 分成
* Rm: 做為第二參數的register
* 4 bit: 決定shift大小的來源
* Sh: shift的種類
* 7~11 bits: shift的大小,來源可以是register或immediate value
* Rd: 目標 register,因為ARM的register總共有16顆,故需要4個bits
* Rn: 參數一register,因為ARM的register總共有16顆,故需要4個bits
* S: 決定是否要set condition (ADD**S**)
* opcode: 決定指令種類
* \# : 決定參數二的定義
* cond: 保留用來處理condition
格式:
![image](https://hackmd.io/_uploads/r13ldNf4a.png)
範例:
```
ADD r2, r2, #3
```
```
@ 將r7 右移r2個bit後,r4=r5-r7
SUB r4, r5, r7, LSR r2
```
## 乘法及乘加指令
![image](https://hackmd.io/_uploads/HkaoiWGET.png)
* Rd,Rn,Rs,Rm: 乘法所需參數
格式:
![image](https://hackmd.io/_uploads/SyhetEzEp.png)
* Rd= Rm*Rs中較低的32 bits
* Rd= Rm*Rs+Rn中較低的32 bits
![image](https://hackmd.io/_uploads/BkhMFNGEp.png)
* 進行特殊的64 bits乘法時
較高的32 bits會存至Rd中 (RdHi)
較低的32 bits會存至Rn中 (RdLo)
* RdHi,RdLo = Rm*Rs (先寫lo再寫hi)
範例:
```
@ r1=r2*r3 較低的32個bits
MUL r1, r2, r3
```
```
@ r4, r1 = r2*r3
UMULL r1, r4, r2, r3
@ r5,r1 = (r2*r3)+r5
UMLAL r1, r5, r2, r3
```
## Data Transfer instruction
### single word or unsigned byte
例如LDR、STRB
![image](https://hackmd.io/_uploads/B1pVeMG4p.png)
* offset: 根據 # 決定是immediate還是Register
* Rd: destination register
* Rn: base register
* L: 決定該指令是Load/store
* W: 決定是否會動到base
* B: 決定針對的資料型態是single word還是unsigned byte
* U: 決定要往memory大還是小的地方走
* P: 決定是先做再動還是先動在做
* cond: 保留用來處理condition
格式:
![image](https://hackmd.io/_uploads/HJq1MGfVa.png)
* condition要寫在 {B} 的前面,例如LDREQB
範例:
```
@ R2代表的address加上R4後,將R1中的值儲存至該位址
STR R1, [R2, R4]
```
```
@ 將R1中的值儲存至R2代表的address後,R2代表的address加上R4
STR R1, [R2], R4
```
```
@ 將R1中的值儲存至PLACE這個label的address
STR R1, PLACE
```
### Half-word or Signed Byte
例如STRH、LDRSB
![image](https://hackmd.io/_uploads/rkT9fMGEa.png)
* 與上面不同的是,offset儲存的方式被切成兩半,並由 # 決定存的是甚麼
* S、H:
* 00: 進行SWP操作
* 進行Unsigned halfwords 操作
* 進行Signed byte操作
* 進行Signed halfword操作
格式:
![image](https://hackmd.io/_uploads/r19VjNMN6.png)
範例:
```
@ 將R3儲存至「R4向後移14格」的記憶體位址
STRH R3, [R4, #14]
```
```
@ 將R2代表的address中的值給R8,結著R2向後位移12格
LDRSB R8, [R2], #12
```
### Multiple register Transfer instruction
例如STMIA、LDMFD
![image](https://hackmd.io/_uploads/SyN44Mf4T.png)
* Register list: 用16個bit表示那些Register有參與操作
* 同樣先注意是先寫condition,再寫addressing mode (LDM**EQ**IA)
格式:
![image](https://hackmd.io/_uploads/HJOG3NzVp.png)
範例:
![image](https://hackmd.io/_uploads/ByjL24zNT.png)
### Swap instruction
回憶:
![image](https://hackmd.io/_uploads/rJhhrzGEp.png)
![image](https://hackmd.io/_uploads/rkqaSMf4T.png)
格式:
![image](https://hackmd.io/_uploads/HJ2O3Vf4a.png =50%x)
範例:
```
@ 將R2代表的address load至R0,並將R1中的值存入R2
@ 過程是atomic的
SWP R0, R1, [R2]
```
### Status register to general register transfer instruction
Status register 指的是SPSR或CPSR
general register 指的是一般的register
![image](https://hackmd.io/_uploads/Sy45LzGVT.png)
* R: 決定是SPSR還是CPSR
格式:
```
MRS{cond} Rd, CPSR @ 將CPSR的值給Rd
@ CPSR可替換成SPSR
```
範例:
```
@ 將CPSR的值移動至r0
MRS r0, CPSR
```
### Status register to general register transfer instruction
上面指令的相反,但只能轉移特定區域的bit,不能全轉 (透過field控制)
![image](https://hackmd.io/_uploads/B1AzwGzNp.png)
用法:
```
MSR CPSR_<field> Rm @將Rm中的某部分搬到CPSR中
@ CPSR可替換成SPSR
```
* field可以為:
* c: 將Rm的0~7 bits 搬到CPSR中
* x: 8~15 bits
* s: 16~23 bits
* f: 24~31 bits
範例:
```
@ 將r0中c field儲存進CPSR中
MSR CPSR_c, r0
```
### Coprecessor instruction
* Coprecessor 可以視作輔助運算的處理器,專門用來執行一些特殊的運算
* ARM 架構最多支援16個Coprecessor,每個Coprecessor最多可以有16個private register
* 分成三種:
* Coprocessor data operations
* Coprocessor data transfers
* Coprocessor register transfers
#### Coprecessor data operations
![image](https://hackmd.io/_uploads/SJ1x6XGEa.png)
* CP#: 用來識別要使用哪個Coprecessor的運算
* CP Opc: 用來識別要使用某個Coprecessor的第幾號運算
* CRd, CRn, CRm: 可以想成就是Rd, Rn, Rm,代表參數跟回傳的結果
格式:
![image](https://hackmd.io/_uploads/HJRGCEG4T.png)
範例:
```
@ 對c2, c3執行p1 Coprecessor的第10號運算後,將結果存至c1
CDP p1, 10, c1, c2, c3
```
#### Coprecessor data transfer
![image](https://hackmd.io/_uploads/Sk_n07GV6.png)
* N: 用來決定要transfreeed多少word (因為只有一個bit,故只能決定兩種長度,根據不同的Coprecessor有不同長度定義)
格式:
![image](https://hackmd.io/_uploads/SJMqREM46.png)
範例:
```
@ 從label table的address中,載入資料至p1 Coprecessor的c2 register
LDC p1, c2, table
```
```
將p2 Coprecessor 中的c3 register 儲存至「R5望後移24格」的位置
STC p2, c3, [R5, #24]
```
#### Coprecessor register transfer
讓ARM的register跟Coprecessor的register互相交換資料
![image](https://hackmd.io/_uploads/HyfLmEzEa.png)
格式:
![image](https://hackmd.io/_uploads/B1aQSNM4a.png)
範例:
```
@ 對c5跟c6進行p2 Coprecessor的第5號運算後,將結果存至R3
@ (將資料從Coprecessor的register轉移至ARM的register)
MRC p2, 5, R3, c5, c6
```
```
@ 對R4進行p6 Coprecessor的第0號運算後,將結果存至c6
MCR p6, 0, R4, c5, c6
```
# SWI(SVC) 深入解析
* SWI指令存在的目的是將某些特定功能"外包"給其他人執行 (例如System call、I/O等等)
* SWI會產生一個software interrupt
SWI 也是一種exception,故處理流程很類似
觸發SWI後的標準流程:
* User Progrem 至 Vector table
1. 切換至supervisor mode
2. 將回去的記憶體位址存至r14中
3. 將切換前的CPSR存至SPSR
4. 禁止發生其他Exception
5. 將PC設成0x8
* Vector table 至 SWI handler
1. 在 0x08 位置的指令通常是jump instruction,接著會跳到SWI Handler執行
* SWI handler 執行的邏輯:
1. 儲存當前的register值
2. 透過r14載入SWI指令 (這個例子是載入0x6)
3. 使用BIC清除不必要的部分,只保留0~23 bits (也就是服務的部分)
4. 呼叫相關服務 (這個例子是呼叫 0x6 代表的服務)
![image](https://hackmd.io/_uploads/BJpulUG46.png)
## Semihosting
![image](https://hackmd.io/_uploads/HJz5HLGET.png)
* 問題: 當在開發嵌入式系統(target system)時,可能在debug期間會有某些SWI指令要求(例如Printf()),但這些功能是target system 不支援的
* 解法: 透過Semihosting的技術,將這個需求交給host machine(Debugger)代為執行
實作方法1:
1. Debugger在SWI指令這行設定一個break point,target system執行到那行時,將控制權轉交給Debugger
2. Debugger執行SWI指令要求
3. Debugger將控制權轉交給target system,繼續執行接下來的指令
實作方法2:
1. target system執行到SWI那行時,透過debug channel 通知debugger
2. debugger跟target system上的SWI handler 溝通,執行SWI指令要求
3. target system繼續執行接下來的指令
GNU Newlib的實作方法
![image](https://hackmd.io/_uploads/r1LuP8MEp.png)
* 針對那些target system無法執行的SWI要求,在設計程式時
1. 將要執行的SWI要求種類放入r0 register中
2. 將要執行的SWI要求所需參數陣列的第一個位置放入r1 register中
3. 呼叫特殊的SWI服務
4. 若這個SWI服務有回傳值,則會放入r0中
# 高階語言與ARM之間的轉換
## 資料型態
* **字元(ASC2)**: ARM 本身支援Ubsigned byte load/store instruction
* **字串**: 一次讀取一個字元即可
* **short integers, integer, long**: ARM 本身支援對應大小的 load/store instruction
* **array**: 一次讀取一格即可
```
ldr r0, [r1, #4]! (透過自動更新base來實現)
```
* Floating-Point Data type
* 根據IEEE-754協議
![image](https://hackmd.io/_uploads/BJY9QZINp.png =50%x)
* exponent(指數): yyyy的部分
* fraction(小數): xxxx的部分
![image](https://hackmd.io/_uploads/HJdUX-UVa.png)
* 小數部分為0,指數部分為最大值時,根據S的值表示+-INF
* 小數部分不為0,指數部分為最大值時,表示NaN
* 小數部分不為0, 指數部分為0時,表示Denormalized number(小到無法表示的數)
補充: 資料存放的位置
* 區域變數: Stack中
* 全域變數: static area中
* 常數: Procedure's literal pool (常數池)中
### Fixed Point(Q Format) Arithmetic
* 若Processor硬體不支援浮點數運算,還可以透過Fixed Point的方式進行浮點數運算
* 概念: 計算7.5+2.1時,可以計算75+21,再補上小數點
* 實作:透過一組integer (n, e)表示小數
* ex: (0x00000001, 14)表示0x00000001*2$^{-14}$
* 備註: (n, e), (n, Q)兩種寫法都可
* 注意: 在Fixed Point架構下,兩數要計算時,要確保兩數的"e"一樣
* 優點: 快速
* 缺點: 準確度較低
ex:
將十進位小數a=9.216957436091198轉成Q5.11格式的二進位小數
1. Q5.11代表轉成二進位後,小數點後有11位
2. 先計算a*2$^{11}$ (因為a=(a * 2$^{11}$) * 2$^{-11}$)
* a * 2$^{11}$ = 18876.3288
3. 省略小數部分,整數部分轉成二進位
18876 = (100100110111100)$_{2}$
4. 補上小數點
* a = (1001.00110111100)$_{2}$
ex:
將Q2.2格式的**有號**Fixed Point a=(10.01)$_{2}$轉成十進位小數
1. 先轉成正的,最後再補上負號
* (10.01)$_{2}$ = 負的(01.11)$_{2}$
* (01.11)$_{2}$ = 2$^{0}$ + 2$^{-1}$ + 2$^{-2}$ =(1.75)$_{10}$
* 補上負號
* 答案為(-1.75)$_{10}$
## conditional statement
* 針對簡單的if-else判斷,可以使用conditional execution達成
![image](https://hackmd.io/_uploads/SyU0nBwNT.png)
* 針對if-else中比較多statement的邏輯,可以透過Branch instruction轉換
![image](https://hackmd.io/_uploads/ryg_8hBDNT.png)
針對switch case語法
![image](https://hackmd.io/_uploads/Sk4W0Hw4a.png)
* 載入SUBTAB這個label代表的記憶體 (base)
* 預先將要跳幾格的資訊存在r0,並檢查r0 是否合法
* 直接讓pc指向下一道指令執行的位置
## 迴圈
for:
![image](https://hackmd.io/_uploads/Sy2rgUwN6.png)
* 用CMP 控制條件
* 用B LOOP跳回去繼續循環
* 用BGE EXIT 跳出迴圈
while:
![image](https://hackmd.io/_uploads/HJdFZLP46.png =80%x)
* 假設終止條件是CMP結果相等
* 檢查條件(evaltate exp)至於迴圈最前面執行
do-while:
![image](https://hackmd.io/_uploads/ByRNW8wNp.png)
* 假設終止條件是CMP結果相等
* 類似while迴圈,但檢查條件(evaltate exp)置於迴圈最後面執行
## function
### 名詞解釋
Subroutine: 被更高階routine所呼叫的routine
Function: 有傳入參數跟回傳值的subroutine
Procedure: 有傳入參數,但沒回傳值的subroutine
Argument、parameter:
![image](https://hackmd.io/_uploads/BJz1BUDVa.png =50%x)
### Procedure call standard for ARM Architexture(AAPCS)
規範了關於函式互相呼叫時的細節,使不同人所寫的程式能互相呼叫
![image](https://hackmd.io/_uploads/BkyZcIPVa.png)
* Synonym指的是在AAPCS的架構下,各register的功能
* r0\~r3(a1~a4):
* 負責Argument傳入:
* 前四個Argument傳入會透過這四個register來傳,若Argument多於四個,剩餘的則會以逆序放入Stack中(為了確保順序能被還原)
* 若參數包含了浮點數,則會先使用floating-point register(f0\~f3),再使用a1~a4,再使用Stack
* 負責回傳值:
* 若回傳值大小為1 word,則會使用a1
* 若回傳值大小為2 word,則會使用a1~a2,以此類推
* 若回傳值大小>4 word,則會放入Memory後回傳其位置
* 這四個參數是caller-saves register,caller需復原這幾個register作為Argument前的值
![image](https://hackmd.io/_uploads/BJZMi8vVT.png =50%x)
* r4\~r11(v1~v8):
* 可被任意使用的register
* 這八個參數是callee-saves register,callee需復原這幾個register作為回傳值前的值
![image](https://hackmd.io/_uploads/B1gmh8wET.png =50%x)
* 在不同的環境下,r9可能須扮演其他角色,例如SB等等
### 在function間切換的細節
![image](https://hackmd.io/_uploads/Hyl7oqPEa.png)
* 在AAPCS的規範中,每個function都會占用Stack中的一段空間,並規定了每個變數存放的位置順序
* 為了符合AAPCS的規範,在每個function的一開始都要加上這段
![image](https://hackmd.io/_uploads/H1-hDqDET.png=80%x)
* 使用ip(Internal Pointer) 暫存sp(stack pointer的值)
* ip的功能便是暫存
* 將母函式(caller)的相關資訊存起來
* 將fp(frame pointer)的位置往下移四個bits,以指向"該函式在Stack中所占用的記憶體位址"的第一個位置
* fp的功用便是指向"該函式在Stack中所占用的記憶體位址"的第一個位置,以透過位移取得caller的pc、lr等資訊
* 為了符合AAPCS的規範,在每個function結束前都要加上這段
![image](https://hackmd.io/_uploads/r1Fh55wNa.png)
* 復原caller的相關資訊
結論: 經過上述方法儲存後,整體架構如下:
![image](https://hackmd.io/_uploads/HkJf2cDEa.png)
補充:
* Tail Continued Funcitons: 多層function在回傳時,不需要一層層慢慢回傳,而能直接跳回最外層的function以增加效率
* inline function: 在編譯時,直接將程式碼"嵌入"至呼叫的地方
* 優點: 不需要再執行函式呼叫的步驟,速度較快
* 缺點: 若函式被呼叫了很多次,程式碼大小會增加
## Memory
ARM C Compiler會將變數對齊 memory(Align),以提升效率
也可以指定某變數對齊的方式
```
//指定變數x落在記憶體位址為4的倍數的地方
int x __attribute__ ((align(4)));
```
這邊針對struct結構考慮
* 一般情況:
![image](https://hackmd.io/_uploads/B1Qi5iw46.png =50%x)
* 使用__packed關鍵字,使各變數緊密對齊
![image](https://hackmd.io/_uploads/H1LJjsw4T.png =50%x)
# ARM 組合語言與C code
## 將ARM組合語言嵌入至C Code中
基本格式如下:
![image](https://hackmd.io/_uploads/r1wuaqdV6.png =80%x)
* asm-qualifiers的值可以為
* volatile: 表示這段組合語言不可以被編譯優化動到
* inline: 表示這段組合語言以inline的格式嵌入
* goto: 允許使用Branch指令跳到C code中的某個Label (需要多一個參數指定Label)
![image](https://hackmd.io/_uploads/B1vEfsOVp.png)
* %l2 表示的是 HERE這個Label (l表示Label),也可以寫成 %l[Here]
* 邏輯為: 若a==b,跳至Label Here
* 每個operand 的格式如下:
![image](https://hackmd.io/_uploads/HJUFJsOE6.png)
* asmSymbolicName: 為對應的變數取別名以提升可讀性,而非%0, %1...
* constraint:
* "=r": 表示作為輸出
* "r": 表示作為輸入
* cvariablename: 表示對應的變數名稱
ex(本範例沒有asm-qualifiers):
![image](https://hackmd.io/_uploads/r19Jj9O4a.png)
* 可以透過以下語法將ARM組合語言嵌入至C Code中
* asm(...)
* \_\_asm\_\_(...)
* 每一條ARM組合語言指令都使用 "" 括住,後面都加上 \n\t
* 因為不知道C語言中的某變數實際上使用的是哪個register,故在組合語言的程式中先使用%0, %1等代替,之後再填入
* %0, %1, %2, %3 分別使用變數p, m, n, k填入 (由上至下、由左至右)
* :"=r"(p\): p所代表的register是"作為輸出的register"
* :"r"(m): m所代表的register是"作為輸入的register"
* :"r2" 表示插入的組合語言指令會用到r2作為暫存變數,告訴編譯器不要使用到
ex: asmSymbolicName使用範例
![image](https://hackmd.io/_uploads/HJ9Hyj_Vp.png)
* [a]便代表變數a所使用的register
## 在ARM 語言中使用C function
ex: 使用Printf
![image](https://hackmd.io/_uploads/SyxbOmK4p.png)
* 同樣遵守AAPCS的規範,使r0, r1作為傳入的參數
補充: 在編譯時,compiler可能會將C code轉成不符合AAPCS規範但仍等價的程式碼;若希望編譯出的組合語言能嚴格遵守AAPCS規範,則須加上"-mapcs-frame"參數
![image](https://hackmd.io/_uploads/S1cKOQY4p.png =70%x)
## 在C 語言中使用ARM function
ex:
call.c 內容 (作為caller呼叫用ARM定義的函式)
![image](https://hackmd.io/_uploads/rJpniQYV6.png)
* extern關鍵字表示square這個function來自外部,須交由linker 連結
square.s 內容 (包含了被呼叫的函式定義)
![image](https://hackmd.io/_uploads/HksBnmYVT.png)
* .global square: 告訴linker這個有square這個名字
* .type square, %function": 告訴linker square這個名字是function (就像main函式一樣)
* 注意為了符合AAPCS的規範,函式的開頭跟結尾須為固定用法
編譯方法(將兩個檔案放在一起編譯):
![image](https://hackmd.io/_uploads/H1p6yNt46.png =50%x)
## 其他觀念
* Position Independent Code:
* 程式的執行結果跟在memory中的擺放位置無關
![image](https://hackmd.io/_uploads/S1KW-HqVp.png =50%x)
* 透過紀錄PC與target的"相對位置"來進行branch的跳躍
![image](https://hackmd.io/_uploads/BkKgWHqVT.png =50%x)
* Position Independent Data:
* 程式的執行結果與Data section的擺放位置無關
![image](https://hackmd.io/_uploads/B17UZB5Vp.png =50%x)
* 使用base (r5) + offset (Here)的方式來定位資料,而非直接使用絕對位置
![image](https://hackmd.io/_uploads/SkjjZr54p.png =50%x)
# Thumb instruction set
* 為了避免浪費空間,Thumb instruction set在相同功能下,節省了一半的空間
* 即每一條Thumb instruction指占用16 bits的空間
* Thumb instruction set可以被視為壓縮後的ARM指令,屬於ARM instruction set的子集,每條Thumb instruction 都必定可以對應至一條 ARM instruction
* 透過CPSR中的第五個bit來決定當前指令是不是Thumb instruction
![image](https://hackmd.io/_uploads/r1z8Sw9E6.png =50%x)
* 透過BX或BLX指令切換ARM跟Thumb模式 (差異在於BLX會將回去的位置存在r14)
* ![image](https://hackmd.io/_uploads/Bk-7_P54p.png =50%x)
* Rm中放的是要跳至的記憶體位址
* 若Rm[0]=1,會跳到Thumb
* 若Rm[0]=0會跳到ARM
* Rm也可以改成Label,此時永遠會跳到Thumb
* 為了進一步節省空間
* 一般的Thumb instruction只能使用register r0~r7,故每個register只需要占用3 bits
* 大部分的Thumb instruction都是unconditionally
* 許多Thumb instruction都是2-address format (某個register同時當參數跟結果)
![image](https://hackmd.io/_uploads/B1D3haoEp.png =50%x)
* 一般ARM instruction:
1. 從記憶體中被讀出
2. 進入 instruction pipeline
3. 透過 mux 被選中
4. 交由 ARM instruction decoder 解讀
* Thumb instruction
1. 從記憶體中被讀出
2. 進入 instruction pipeline
3. 透過 mux 選擇上半還是下半解讀 (因為 load 進來是一個 word)
4. 透過 Thumb decompressor 轉成對應的 32-bits instruction
5. 透過 mux 被選中
6. 交由 ARM instruction decoder 解讀
## Branch instruction
總共有這五種
![image](https://hackmd.io/_uploads/ry-_Fu9N6.png =50%x)
### B 指令
![image](https://hackmd.io/_uploads/B1KegO5VT.png =80%x)
* 少數仍保留condition的指令
* 因為一條Thumb instruction大小為2 bytes,故Branch instruction只需存二的倍數的位置即可
* 將label所在的記憶體位址**右移一格**後存入指令中
* 讀取指令中的offset後,**左移一格**以對應至真實的記憶體位址
註: 針對unconditional Branch instruction,可以更進一步增加offset的可跳至的距離
![image](https://hackmd.io/_uploads/BksVW_cNa.png =50%x)
### BL指令
* 因為該類指令可能會需要跳到比較遠的地方,故使用特殊的方法使兩條指令的offset能組合成更大的offset以滿足需求
![image](https://hackmd.io/_uploads/rkHeYO94T.png =50%x)
![image](https://hackmd.io/_uploads/rkVlDucVT.png =80%x)
* H: 透過這個bit決定這條指令的offset是屬於low還是high
![image](https://hackmd.io/_uploads/HkgVt_qNa.png =50%x)
## BX、BLX
如Thumb instruction set介紹,用於切換Thumb及ARM
## SWI指令
![image](https://hackmd.io/_uploads/B1Mn3sqNa.png =80%x)
* 透過8個bit表示想呼叫的服務
## Data Processing instruction
### shift
因為Thumb instruction 可以使用的bit不夠,故shift指令獨立出來
![image](https://hackmd.io/_uploads/Bysaps5Ea.png =80%x)
* op: 負責決定指令的類型
* offset: 決定左右移幾個bit
* 注意: 這類型的指令一定會set condition code flag (ex: MOV**S**)
### ADD、Substract (three address instruction format)
![image](https://hackmd.io/_uploads/H1ERAiqET.png =80%x)
* Op: 決定指令類型是加還是減
* bit 10: 決定第二個輸入參數是immediate value還是register
* 注意: 這類型的指令一定會set condition code flag (ex: MOV**S**)
### Move、Compare... (two address instruction format)
* 比起上面介紹的指令,少了一個輸入參數
![image](https://hackmd.io/_uploads/BkNoy3cET.png =80%x)
* 注意這邊的Rd可以同時作為參數跟結果
* 這邊的CMP是拿一個register跟一個immediate value做比較
* 注意: 除了CMP外,這類型的指令一定會set condition code flag (ex: MOV**S**)
### ALU operation
![image](https://hackmd.io/_uploads/Byxqgn9Np.png =80%x)
* 可用的op包含了AND、ADC等等
* 這邊的CMP是拿兩個register做比較
* 注意: 除了CMP外,這類型的指令一定會set condition code flag (ex: MOV**S**)
### ADD PC/SP
專門針對PC及SP位移的指令
![image](https://hackmd.io/_uploads/By1J739E6.png =80%x)
* SP用來決定要位移的是PC 還是 SP
* 注意: 因為SP、PC在32 bit ARM 指令集架構中總是指向4的倍數的位置,故Branch instruction只需存四的倍數的位置即可
* 將label所在的記憶體位址**右移兩格**後存入指令中
* 讀取指令中的offset後,**左移兩格**以對應至真實的記憶體位址
* 注意: 這兩種指令不會影響到condition code flag
### Add offset to stack pointer
專門用來操作SP的指令
![image](https://hackmd.io/_uploads/ryuarncVT.png =80%x)
* bit 7: 控制offset 是正的還是負的
* 使用範例: ADD SP, #immediate
* 相當於 ADD R13, R13, #immediate
* 注意: 因為SP在32 bit ARM 指令集架構中總是指向4的倍數的位置,故Branch instruction只需存四的倍數的位置即可
* 將label所在的記憶體位址**右移兩格**後存入指令中
* 讀取指令中的offset後,**左移兩格**以對應至真實的記憶體位址
### Hi register Operation
專門用來操作Hi register的operation
![image](https://hackmd.io/_uploads/r1MAq5jN6.png)
* 透過H1, H2 來分別決定 destination 跟 sourse 是否為Hi register
ex: ![image](https://hackmd.io/_uploads/BkrEics46.png)
### Data transder instruction
* PC-Relative Load: 從 PC 指向的位置加上 offset 的地方 load 資料
* ![image](https://hackmd.io/_uploads/BJpX1ojVp.png)
* 因為固定只有PC 作為 base,故不需要特別花 bit 存
* ex: LDR R3, [PC, #844]
* offset只需存四的倍數的位置即可
* 將label所在的記憶體位址**右移兩格**後存入指令中
* 讀取指令中的offset後,**左移兩格**以對應至真實的記憶體位址
* Load/Store with Register offset:
* ![image](https://hackmd.io/_uploads/ByBUn5i4a.png =50%x)
* B: 決定要 load/store 一個 byte 還是 word
* L: 決定要 load 還是 store
* ex: STR R3, [R2, R6]
* Load/Store sign-extended byte/halfword
* ![image](https://hackmd.io/_uploads/ryhg65iVa.png =50%x)
* S: 決定處理的資料類型是否是sign-extended byte
* H: 決定處理的資料類型是否是Half word
* LDSB R2, [R3, R0]
* Load/Store with immediate offset
* ![image](https://hackmd.io/_uploads/SkQFJjj46.png =50%x)
* B: 決定要 load/store 一個 byte 還是 word
* L: 決定要 load 還是 store
* ex: LDR R2, [R5, 116]
* 注意: offset 沒有特別對齊
* Load/Store Halfword
* ![image](https://hackmd.io/_uploads/BJU-JsiNT.png =50%x)
* L: 決定要 load 還是 store
* ex: STRH R6, [R1, #56]
* offset只需存二的倍數的位置即可 (halfword)
* 將label所在的記憶體位址**右移一格**後存入指令中
* 讀取指令中的offset後,**左移一格**以對應至真實的記憶體位址
* SP-Relative Load/Store
* ![image](https://hackmd.io/_uploads/B1N1ejj4p.png =50%x)
* L: 決定要 load 還是 store
* ex: STR R4, [SP, #492]
* * offset只需存四的倍數的位置即可
* 將label所在的記憶體位址**右移兩格**後存入指令中
* 讀取指令中的offset後,**左移兩格**以對應至真實的記憶體位址
* Push/Pop register
* Push負責在進入函式時,將 register 的值存起來,對應至 STMDB 指令
* Pop負責在離開函式時,還原 register 的值,對應至 LDMIA 指令
* ![image](https://hackmd.io/_uploads/S1hmZiiEp.png =50%x)
* L: 決定要 load 還是 store
* R: 是否要順便處理LR/PC register
* ex: PUSH {R0-R4, LR}
* Multiple load/store
* 永遠會 update base
* 只包含了LDMIA、STMIA 兩種指令
* ![image](https://hackmd.io/_uploads/HkQcbjiNa.png =50%x)
* L: 決定要 load 還是 store
* ex: STMIA R4!, {R1, R2, R3}
## Thumb2 instruction
在略為犧牲效能的情況下,最大幅度的降低 code size
* 包含了所有的 16-bit Thumb instruction
* 多了一些新的 16-bit instruction 跟32-bit instruction
* 可以進行所有一般 ARM 指令可以進行的操作
# ARM Floating point instruction
* 透過coprocessor,便可以使 ARM 支援浮點數運算
![image](https://hackmd.io/_uploads/B1GGq0MHT.png =60%x)
* 使用CP10、CP11 作為協助運算的coprocessor
* 包含一個 floating-point status and control register (FPSCR)
* 類似ARM 原生架構中的 CPSR
* 儲存系統當前狀態
* ![image](https://hackmd.io/_uploads/SJrLeRbHp.png =60%x)
* N、Z、C、V: condition code flag
* RMODE: Rounding mode,用於決定將浮點數轉整數時的規則(共有往最近的取、往 +inf取、往 -inf 取、往0取四種)
![image](https://hackmd.io/_uploads/S1_uKCZST.png =60%x)
* 包含一個 Coprocessor access control register(CPACR)
* 控制那些功能不可以被一般的 application 存取
* 共有32個 32-bits register 可使用 (s0~s31)
* s0~s15: caller saved register
* s16~s31: callee saved register
* 相鄰的 register 合併可以用來存 64-bits 的值
## Moving data
補充:
* R$_{i}$: 一般 register
* S$_{i}$: 32-bits floating point register
* D$_{i}$: 64-bits floating point register
### VMOV 指令
![image](https://hackmd.io/_uploads/B1h2UAbH6.png =60%x)
* 可以操作一般 register 跟 floating-point register
* 可以做到
* 浮點數 register 搬到另一個浮點數 register
* 浮點數 register 搬到一般 register
* 一般 register 搬到浮點數 register
* 浮點數 register 搬到一般 register (一次搬兩個)
* 一般 register 搬到浮點數 register (一次搬兩個)
* 只會照搬不誤,並不會做整數跟浮點數之間的轉換
補充: VMOV 指令只能拿有限的常數浮點數作為 immediate value
![image](https://hackmd.io/_uploads/SJJ-5hfSp.png)
* 若一個浮點數不斷乘以二最多七次,有任一次乘出來的值是16~31間的整數,則該浮點數是合法的 immediate value
* 若想要 load 任意的immediate value,須將該值暫時存在 data section 中,接著
* 將data section 中的 label 使用LDR載入至一般 register 中
* 從該 register 中使用VLDR 載入至 floating point register 中
* ![image](https://hackmd.io/_uploads/SyvUYCfrp.png =20%x)
### VCVT 指令
![image](https://hackmd.io/_uploads/SyA2D0WBa.png =60%x)
* 只能操作floating-point register (s0~s31)
* 透過在 "VCVT" 後面加上關鍵字決定要執行哪種轉換
* .F32
* .S32
* .U32
* 可以做到
* 將無號整數轉成浮點數
* 將有號整數轉成浮點數
* 將浮點數轉成無號整數 (truncated,直接截斷)
* 將浮點數轉成無號整數 (round,使用 RMODE 定義的方法省略)
* 將浮點數轉成有號整數 (truncated)
* 將浮點數轉成有號整數 (round)
### VLDR 指令
![image](https://hackmd.io/_uploads/ryoE_sMST.png =60%x)
* label必須在 text section 才可以直接使用
* 若 label 在 data section 則必須間接透過 register 來傳遞
* 都是將某個記憶體位址(只可能由一般 register 存) load 至一個 floating point register 中
* 可以做到
* 從 **register** 中 load 一個 32-bits 浮點數
* 從 **register+偏移量** 中 load 一個 32-bits 浮點數
* 從 label 代表的記憶體位址 中 load 一個 32-bits 浮點數
* 上述三種指令可以改成 load 64-bits
類似的有 LOAD multiple 指令,使用方法同一般 register
![image](https://hackmd.io/_uploads/SJW-iozra.png =60%x)
### VSTR 指令
![image](https://hackmd.io/_uploads/Hk4qsifSa.pn =60%x)
* 可以做到
* 將一個 32-bits 浮點數從 floating point register 中 存至一般**register** 代表的記憶體中
* 將一個 32-bits 浮點數從 floating point register 中 存至一般**register+偏移量** 代表的記憶體中
* 上述三種指令可以改成 store 64-bits
* 注意不可直接將值存進一個 label 代表的記憶體中
類似的有 STORE multiple 指令,使用方法同一般 register
![image](https://hackmd.io/_uploads/r1mrhofHa.png =60%x)
## 函式
* input parameter
* **S0~S15 拿來作為函式傳入"浮點數"的參數**
* **若傳入的參數是指向浮點數的指標,仍然使用R0~R3**
* 若 floating point register 還不夠用,則利用 **VPUSH 跟 VPOP 指令**(**只能操作floating point register)存進 stack 中**
* return value
* **浮點數的回傳結果存在S0**
* **指向浮點數的指標或是其他資料型態的回傳結果存在R0**
ex:
![image](https://hackmd.io/_uploads/S13pznMSa.png)
* 將 R4(callee saved register) 跟 LR 存起來
* 利用 R4 暫存 S0 的值
* 呼叫function bar
* 復原 s0 的值至 s1
* 計算回傳值,並放到s0中
* 復原暫存的 R4 跟 LR
## arithmetic instruction
* 共有三種操作模式,硬體必須支援才能使用
* .F16
* .F32
* .F64
指令種類
![image](https://hackmd.io/_uploads/r1xz6TzBT.png =60%x)
* 輸入跟輸出一定要是floating point register
* 多了除法跟開庚號兩種指令
## Control flow instruction
![image](https://hackmd.io/_uploads/H1XCCTGSp.png =60%x)
* 比較兩個浮點數 register 是否相等,並將結果存在 FPSCR 的condition code flag 中
* 比較一個浮點數 register 是否等於0,並將結果存在 FPSCR 的condition code flag 中
* 將FPSCR 的condition code flag 移動至 CPSR 中 (如此一來才能使用一般的 condition instruction 或是 conditional execution)
![image](https://hackmd.io/_uploads/Hknke0GSp.png =50%x)
ex:
![image](https://hackmd.io/_uploads/SyA3X0MH6.png =60%x)
* 使用VMRS 指令之後,便可以用VMOV**LT** 等 conditional execution
# 其他觀念與指令
## LDR sudo instruction
(重要)
指令
`LDR r0, =0x12345678` (直接從0x12345678 load 資料至 r0 中)
相當於
```
LDR r0, [pc, #offset]
.word 0x12345678
```
## MOVW、MOVT
目的是將 immediate value 分段 load 進 32 bits register 中
```
MOVW r3, #0xFACE @r3 = 0x0000FACE (載入 immediate value 至下半,上半清0)
MOVT r3, #0xBEEF @r3 = 0xBEEFFACE (載入 immediate value 至上半)
```
## CBZ、CBNZ
簡化跟0比較的指令
指令
`CBZ Rn, label`
相當於
```
CMP Rn, #0
BEQ label
```
指令
`CBNZ Rn, label`
相當於
```
CMP Rn, #0
BNE label
```
## IT instruction
目的是使 Thumb instruction 可以支援 conditional execution
格式:
![image](https://hackmd.io/_uploads/ry7bj6EHT.png =30%x)
* 寫下該道指令後,最多可以使後面四行指令支援 conditional execution
* 使用方法
* T: then,代表與 cond 相同的修飾字
* E: Else,代表與 cond 相反的修飾字
注意: 若 IT instruction 後面包含了 Branch instruction,則 Branch instruction 只能寫在該指令區塊的最後一行
這樣是不合法的
![image](https://hackmd.io/_uploads/HJ4A364r6.png =50%x)
這樣是正確的
![image](https://hackmd.io/_uploads/HkdZaaEra.png =50%x)
ex:
```
ITTE NE
ANDNE R0, R0, R1
ANDNE R2, R2, #1
MOVEQ R2, R3
```
* 原本thumb instruction 不支援 conditional execution
* 透過 `ITTE NE` 這行指令,便可使後面的指令支援 conditional execution
* I 後面有三個字母,代表該指令可以使後面三行支援 conditional execution
* TTE 分別對應 NE, NE, EQ
* 雖然 `ITTE NE` 這行指令已經可以決定後面指令的 condition;但語法上,後面的三行指令仍要加上 conditional execution 的修飾字
## Unified Assembly Language (UAL)
* UAL 是一種整合不同種類 ARM 語言的語法,目的是讓使用者能以統一的語法撰寫組合語言,最後再決定要轉成 ARM 還是 Thumb instruction
* 此時,稱之前學過、未被整合的程式語言為 "Pre-UAL"
* 透過 assembly directive 決定該組合語言是哪種類型
* .syntax unified: 代表該組合語言是 UAL
* .syntax divided: 代表該組合語言是 Pre-UAL (預設)
* 若組合語言類型是 UAL,則可以透過 assembly directive 決定要轉成 ARM 還是 Thumb instruction
* .code .arm: 轉成 ARM 組合語言
* .code .thumb: 轉成 Thumb 組合語言
### UAL 與 Pre-UAL 的不同之處
1. LSL 指令獨立出來,不再依附在其他指令後
2. load/store multiple 指令只支援 "IA"
3. 可以用一行指令將一個 64-bits instruction 直接 load 至兩個 register 中
# SIMD 指令
* SIMD 指的是 Single instruction Multiple data,是一種由 processor(硬體) 提供的指令
* SIMD 指令 **同時對多筆資料進行同一種運算**,故只需一時間單位便能完成運算
![image](https://hackmd.io/_uploads/SJ6EOQyIa.png =50%x)
## 使用 SIMD 指令
想要享受 SIMD 指令 帶來的加速效果,**可以透過以下方式**
### Assembly language
開發者直接寫對應的組合語言來使用 SIMD 指令
* 優點: 有彈性
* 缺點: 開發困難
### Intrinsic function
使用編譯器內部提供的低階 function
* 優點: 不需要處理 register 相關的問題,但卻能操作底層指令
* 缺點: 開發還是有點困難
低階 function api:
_mm_add_epi32: 將兩個 \__m128i 型態的變數相加
_mm_sub_epi32: 將兩個 \__m128i 型態的變數相減
_mm_srli_si128: 將指定 \__m128i 型態的變數右移 k 個 bytes 後,回傳結果 (不動到變數本身)
_mm_cvtsi128_si32: 將最後一個位置回傳
_mm_hadd_ps:
![image](https://hackmd.io/_uploads/rkOew5lIT.png =50%x)
ex: **將陣列中的所有元素相加** (此範例重要,描述將一般邏輯轉成 SIMD 的流程)
![image](https://hackmd.io/_uploads/H1oQsEJL6.png =50%x)
![image](https://hackmd.io/_uploads/SkGUjEJLa.png)
* 宣告 \__m128i 型態的變數 accum (可以看成 register 陣列)
* 宣告\__m128i 型態的指標,指向傳入陣列的開頭
![image](https://hackmd.io/_uploads/r1NTiVJIp.png =30%x)
* \因為 __m128i 是 int 大小 32bits 的四倍,故這段連續的空間會被切成每 128bits 一個單位
* 清空 accum
* _mm_add_epi32 可以一次處理四個 int (128 bits),故迴圈只需要原本的 1/4 次即可
* 註: 若陣列長度沒辦法整除四,可以將沒辦法整除的部分改成用一般的指令算;或視運算情況補0補到四的倍數,
* [1,2,3,4] + [5,6,7,8] = [6,8,10,12]
* 接著要將 [6,8,10,12] 四個數字相加
* [6,8,10,12] + [-,-,6,8] = [-,-,10+6,12+8]
* [-,-,10+6,12+8] + [-,-,-,10+6] = [-,-,-,12+8+10+6]
* 使用 _mm_cvtsi128_si32 將 最後一個位置的數字回傳
### Language Extension
可以用接近高階語言的方式操作 SIMD 指令,例如透過 C++ class data type
優點: 可讀性高,容易開發因為交由 compiler 產生組合語言,故 performance 能受到保證
缺點: 彈性較差、可能沒辦法使用 SIMD 支援的所有功能
ex: 將陣列中的所有元素相加
![image](https://hackmd.io/_uploads/H1EKRNkIT.png =50%x)
### Auto-Vectorization
透過 compile 原生的優化(-O2 等)來使用 SIMD 指令
優點: 很方便
缺點: 只能優化邏輯單純的部分
## SIMD 指令與資料對齊
* 資料對齊能提升性能,而在 SSE/SSE2/SSE3 這三個SIMD指令集架構中,除了少數指令外,皆**強迫要求對齊資料**
### 資料對齊的方法
1. 之前提到的 \_\_attribute\_\_ (適用於linux 中的 靜態變數)
![image](https://hackmd.io/_uploads/HJky0tgLa.png =70%x)
2. _mm_malloc() (適用於動態配置的變數),可以直接指定 malloc 的記憶體要aligned 哪裡
* 概念等價於
* 假設要求 16-bytes aligned
* 會先使用 malloc 配置"所需記憶體+15" 大小的空間
* 接著將指標移至16-bytes aligned的位置,並作為回傳值
## SIMD 與 Data padding
若要分別計算 AW, BX, CY, DZ
方法一的資料排列 (需要 4 個 SIMD 乘法指令,12 個 SIMD 加法指令)
![image](https://hackmd.io/_uploads/SJ6Ni-M_6.png)
方法二的資料排列 (需要 4 個 SIMD 乘法指令,3 個 SIMD 加法指令,較佳)
![image](https://hackmd.io/_uploads/ByGrkix8a.png)
## ARM SIMD
* 在 ARM v6 中,都是使用 32 bits register 來進行 SIMD 指令的 (可以同時做兩個 16 bits 或 4個 8 bits instruction)
* 在 ARM v7 中,新增了 NEON 系列指令,多了 64-bits register (D register, ARM 本身提供)、128-bits register (Q register, 由兩個 D 合併而成)
格式範例:
![image](https://hackmd.io/_uploads/BJ4VM58Ia.png =50%x)
* 一個 Q register 為 128 bits
* 又指令為 "I16" 格式 (integer, 16 bits)
* 故這條指令能一次處理 8 筆資料
## X86 SIMD (GCC)
* 想要在 X86 架構下使用 SIMD 系列指令,可以使用以下方法
1. 使用 Intrinsic function (編譯器內部提供的低階 function)
2. 定義新的資料型態,之後在使用時,compiler 會根據硬體是否支援來編譯成對應的組合語言指令
![image](https://hackmd.io/_uploads/Sk38P3U8T.png)
* 宣告 16 bytes, 每個元素是 int 的陣列 (共四個元素),作為新的形態
* 宣告 v4si 型態的三個變數
* 可以用類似 operator overloading 的方式直接運算