--- tags: [2025_Fall] --- # 計算機結構筆記 <!-- week 1 --> ## 電腦種類 * Desktop Computer: General Purpose,需要 Cost-effective solution * Server: **Dependability** & **Scalibility**,需要高 throughput。 * Embedded Computer: 在其他裝置(車子、手機、冰箱)內的電腦,需要 Minimize **power** & cost,**Dependability** 也很重要。用法較侷限,比如手機不會跑科學運算。 ## PostPC Era ### Personal Mobile Device 用電池、連網、便宜。 ARM 從新創成為 PMD 的王者。因為是第一個 low power, 32-bit RISC 架構。而且不像 Intel 全部自己來,是授權 IP。 ### Cloud Computing Software as a Service (SaaS)。 * 40s~60s: 第一台 supercomputer, 3 megaflops。 * 75~90s: Cray era * Early 90s: Fujitsu,日本加入 * Mid 90s: Cluster era,把一堆電腦連起來 * 00s: Petaflops($10^{15}$)。開始注重 power。 * 10s~: Exaflops($10^{18}$)。中國天河-1、Fujitsu、Cray。採用 CPU + GPU 的異質(Heterogeneous)架構。 AMD 大膽採用新技術,發展很好。 ## Intel Processor 歷史 * 4004(1971): 第一個微處理器,主要是商用。4-bit, 10 $\mu$m, 2,300 transistors * 8008(1972): Dumb Terminal。8-bit, 10 $\mu$m, 3,500 transistors * 8080(1974): 第一個 Personal Computer。8-bit, 6 $\mu$m, 6,000 transistors * 8086/8088(1978~9): IBM PC,第一個使用 **ISA**(x86),不用打卡。16-bit, 3 $\mu$m, 29k transistors * 80286(1982): 引入 **Virtual Memory**。16-bit, 1.5 $\mu$m, 134k transistors * 80386(1985): 現代的 x86 ISA(逐漸擴增)。32-bit, 1.5 ~1$\mu$m, 275k transistors, 16~33 MHz * 80486(1989): 第一次有 **Pipeline**, **8KB cache**, FPU(Floating point unit)。32-bit, 1 $\mu$m, 1.2M transistors, 25~100 MHz * Pentium(1993): **Superscalar** (2 op/cycle), Instruction, Data Cache 分離,各 8KB。32-bit, 0.8~0.35 $\mu$m, 3.2M transistors, 60~300 MHz * Pentium Pro/II/III(1995~9): **Out-of-order execution**, 3 op/cycle, Multimedia instructions, 16~32KB cache。32-bit * Pentium 4(2001): **Deep Pipeline**, 頻率非常高。1.4~3.4 GHz(大概到頂,很耗電), 180~90 nm(奈米年代) * Pentium D(2005): **Dual-core**,2.8~3.6 GHz, 90 nm ### Multicore Era * Core 2 Duo(2006): 2 cores, 65 nm * Core 2 Quad(2007): 4 cores, 45 nm * Core i-Series(2008~): i3/5/7/9。i9 可以到 18 cores, 14 nm ## GPU/TPU 很多核心,Tensor Core 作矩陣運算。 TPU: Google, 專門做 AI 運算。 <!-- week 2 --> ## Computer Architecture ### Application * 90s 開始有影像、3D 需求,不只是簡單的數值運算、Data 操作。 * 00s RMS(Recognition, Mining(資料), Synthesis(Ray Tracing)): 運算需求進一步增加。 * 10s ML: 需要 Domain-specific architecture(e.g. for Deep Neural Network(DNN))。 ### Technologies 年份, 科技, 相對 Performance/cost * 1951 真空管(Vacuum Tube), 1 * 1965 電晶體(Transistor), 35 * 1975 積體電路(IC), 900。變成矽 * 1995 Very large scale IC(VLSI), 2.4M * 2013 Ultralarge scale IC, 250B ### Engineering Methodology 需要先定義能量化、一致標準的 factor/metric,比如 Cost, Performance, Power。Design 與 Analysis 大約各佔一半。 ## Benchmark Performance 的 metrics 主要是 Response time(Latency), Throughput。 定義 Performance = 1/Execution Time。 Execution Time 分成:Elapsed time(整個 response time, 包含 I/O), CPU time(純 CPU 運算時間)。 CPU time = Cycles × Cycle time = Cycles / Clock rate Cycles 又能拆成 Instruction count × Cycles per instruction (CPI)。不同 Instruction 的 CPI 可能不同。 因此比較時不能只看 Instuction count 或 CPI,要兩個一起看。 使用多核的原因:Power 大約與 Capacitive load $\times$ Voltage$^2\times$ Frequency,而 Voltage 驅動 Frequency,以非常粗略估計大致呈正比。所以 Energy 大約 $t$(時間) $\times f^3$ 呈正比,因此同樣工作分給兩個,則 $t\times f^3$ 變成 $2\times t\times (0.5f)^3 = 0.25 t\times f^3$,省很多。 不過平行程式比較難寫,所以早期不重視。 ### SPEC Benchmark 衡量 **Performance** 的標準,首先選了一些代表性的運算任務(GP 則要各種任務,Embedded 則針對特定應用)。 對每個分別計算 SPECRatio = Execution time on reference computer / Execution time on evaluated computer。再使用幾何平均得到整體的數值。 衡量 **Power** 的標準,Performance (ssj_ops/sec) per Watt。 ### Pitfalls and Fallacies * Amdahl's Law: 如果改進的部分只佔一點點、不太重要,那就沒什麼用。 * Low power at idle: 低 load 時有些機器會很耗電,但通常很多時間都是低 load,比如 data center。所以要注重 idle 時的 power。 * MIPS: Million Instructions Per Second。 $$ \begin{aligned} &\text{MIPS} = \frac{\text{Instruction count}}{\text{Execution time} \times 10^6}\\ &= \frac{\text{Instruction count}}{\text{Instruction count}\times \text{CPI}/\text{Clock rate}\times 10^6}\\ &=\frac{\text{Clock rate}}{\text{CPI} \times 10^6} \end{aligned} $$ 不能只看 MIPS,因為不同機器的 Instruction set 不同,CPI 也不同。 ## Design Principles * Design for **Moore's Law**: 盡量跟上速度 * Use **abstraction** to simplify design: 利用分層架構更了解系統 * Make the **common case fast**: 讓程式更好平行化 * Performance via **parallelism**: Instruction, Thread, Processor level... * Performance via **pipelining** * Performance via **prediction**: 預測未來要做什麼 * **Hierarchy** of memories: L1, L2, L3 Cache * **Dependability** via redundancy: 備用。 <!-- week 3 --> ## Cost of Manufacturing IC $\text{Cost per die} = \frac{\text{Cost per wafer}}{\text{Die per wafer} \times \text{Yield}}$ Yield: wafer 上能運作的 die 比例。 $\text{Die per wafer} \approx \frac{\text{wafer area}=\pi r^2}{\text{Die area}}$ $\text{Yield} = \frac{1}{(1+\text{Defects per area} \times \text{Die area}/2)^2}$ Cost per die 與 Area、Defect rate 呈非線性關係。 ## ISA Design Principles 讓 programmer 能用到 hardware 的所有功能,且要是簡單的。 ### Interface Design Principles * 能支援底層的不同實作 * 能夠有多元的用法 * 提供方便的高階功能 * 同時提供高性能的低階功能 ### Instruction 運作方式 1. Instruction Fetch(要在 Memory) 2. Instruction Decode 3. Operand Fetch 4. Execute 5. Result Store 6. Next Instruction 比較 RISC(Reduced Instruction Set Computer) 與 CISC(Complex...) 如何做乘法。 * RISC: * Load A, 2:3 * Load B, 4:5 * Multi A, B * Store 2:3, A * CISC: * Multi 2:3, 4:5 這門課主要講 RISC-V,開源的 RISC 架構(ARM 等架構閉源、要錢)。 ## RISC-V Instructions Principles: Simplicity favors regularity,因此 CPI 大約只要 1。 * Operation 盡量恰好有三個 operands。 * Instruction format 盡量少 * 預設 operands 都在 registers 裡,用到 memory 的話要先 load 出來 (例外:immediate, memory operations) ### Arithmetic Operations 比如 `add a, b, c`: a=b+c。 ### Registers RISC-V 有 32 個 64-bit(double word) Registers x0~x31。 | Register | ABI Name | 敘述 | Saver | |----------|----------|------|-------| | x0 | zero | hard-wired 0 | - | | x1 | ra | return address | caller | | x2 | sp | stack pointer | callee | | x3 | gp | global pointer | - | | x4 | tp | thread pointer | - | | x5~x7 | t0~t2 | temporaries | caller| | x8 | s0/fp | saved register/frame pointer | callee | | x9 | s1 | saved register | callee | | x10~x11 | a0~a1 | function arguments/return values | caller | | x12~x17 | a2~a7 | function arguments | caller | | x18~x27 | s2~s11 | saved registers | callee | | x28~x31 | t3~t6 | temporaries | caller | ### Memory Operations load from, store to memory,存在 imm 的 offset 與存在 register 的 base 都以 bytes 為單位。 * `lw x9, 8(x22)`: x9=mem[8+reg[x22]] (Load Word) * `sw x9, 8(x22)`: mem[8+reg[x22]]=x9 (Store Word) 所以 A[12] = h + A[8] (base address of A in x22) 要先 `lw x9, 64(x22)` (8*8=64)。其中 offset 是 constant(immediate)。 若要搞清楚 register, memory,記得 register 是用名字或編號存取應該就行了。 * Byte order: RISC-V 是 Little Endian,把一個整數拆成多個 bytes 後,低位元組存在低位址。 * Alignment: 必須放在 word size 的整數倍位址上,也就是四的倍數。 而 RISC-V 不要求。 ### Constant or Immediate Operations 常常出現 A=A+5 之類的常數,如果用一般操作,得要把常數先 load 到 register 再計算,但因為太常見了還是簡化比較好。 `addi x22, x22, 5`,允許 Operand 是常數(Immediate)。 ### Logical Operations * `<<, slli`: Shift Left Logical Immediate * `>>, srli`: Shift Right Logical Immediate,但其實為了考慮 signed,比較常用 Arithmetic 的 `sra`, `srai` * `&, and, andi`: AND, AND Immediate * `|, or, ori`: OR, OR Immediate * `^, xor, xori`: XOR, XOR Immediate * `~`(用 `xori x9, x9, -1` 實現 not,-1 2-s complement 全 1) ### R-format Instructions 一個 instruction 以一個 32-bit word 表示,分成多段稱為 field 的欄位。 `funct7(7) | rs2(5) | rs1(5) | funct3(3) | rd(5) | opcode(7)` * opcode: operation 的大分類 * rs1, rs2: source registers * rd: destination register * funct3, funct7: operation 的小分類,以 bytes 數命名。 ### I-format Instructions 如果 load, store 的 offset 只能塞在 5-bit 裡,範圍太小不夠用。 反正 offset 以外只有兩個 operands,可以刪掉一個 operand。 `immediate(12) | rs1(5) | funct3(3) | rd(5) | opcode(7)` 可以發現 是把 R-format 的 rs2 拿掉,與 funct7 都被 immediate 替代。最 simple 的設計當然是只有一種 instruction,但不得已得要有多種,讓不同種長得盡量像會比較好做 pipeline。 ### S-format Instructions 另一種 Immediate format,能夠有三個 operands。 `imm[11:5](7) | rs2(5) | rs1(5) | funct3(3) | imm[4:0](5) | opcode(7)` <!-- week 4 --> ## Branching instructions 改變 program 執行的 control flow,也就是不照順序 (sequentially) 執行,而是跳到其他位置。在 high-level language 如 C 中,if-then-else、while loop、function call 等都屬於這類。 > `jalr rd, imm (rs1)`: 跳到 reg[rs1]+imm 的位置,並把 PC+4 存在 rd 中。 > `jal rd, Label`: 跳到 Label,並把 PC+4 存在 rd 中。 ### Program Counter (PC) **重要的 register**:存放目前要 fetch 的 instruction 的 address。 * Fetch instruction: 從 memory[PC] 讀取 * 一般情況:PC = PC + 4 (下一個 instruction,因為每個 instruction 4 bytes) * Branch 情況:PC = target address (跳到指定位置) ### Conditional Branch Instructions 只有在設定的條件符合時才會 branch,否則繼續 sequentially 執行。 * `beq rs1, rs2, L1`: if (reg[rs1]==reg[rs2]) PC=L1 (Branch if EQual) * `bne rs1, rs2, L1`: if (reg[rs1]!=reg[rs2]) PC=L1 (Branch if Not Equal) ### basic block 一段連續的 instructions,滿足以下條件: * **只有最後一個 instruction 是 branch instruction** * **只有第一個 instruction 可能是其他 branch 的 target** * 中間沒有任何 branch target,也沒有 branch instruction **重要性**: * Basic block 內的 instructions **一定會一起執行** * Compiler 可以在 basic block 內做 **optimization**,例如: * Instruction scheduling (重新排序) * Register allocation * 無法跨越 basic block 做 optimization,因為 branch 的結果要執行時才知道 (taken 或 not taken) ### More branching instructions **Signed 比較**: * `blt rs1, rs2, L1`: if (reg[rs1]<reg[rs2]) PC=L1 (Branch if Less Than) * `bge rs1, rs2, L1`: if (reg[rs1]>=reg[rs2]) PC=L1 (Branch if Greater or Equal) **Unsigned 比較**: * `bltu rs1, rs2, L1`: unsigned 版本的 blt * `bgeu rs1, rs2, L1`: unsigned 版本的 bge **Set 指令**(用於比較後設定值): * `slt rd, rs1, rs2`: if (reg[rs1]<reg[rs2]) rd=1 else rd=0 (Set if Less Than) * `slti rd, rs1, imm`: if (reg[rs1]<imm) rd=1 else rd=0 (Set if Less Than Immediate) * `sltu rd, rs1, rs2`: unsigned 版本的 slt ### Pseudo instruction Assembler 提供的簡化指令,會被展開成實際的 instructions: * `la x28, JumpTable`: load address of JumpTable into reg[x28] (Load Address) * 展開成 `auipc` + `addi` * `li rd, imm`: 把 rd 設為 imm * 展開成 `addi rd, x0, imm` * `mv x1, x2`: 把 x2 的值複製到 x1 * 展開成 `addi x2, x1, 0` * `jr x7`: PC=reg[x7] (Jump Register) * 展開成 `jalr x0, 0(x7)` * `j label`: 無條件跳到 label (Jump) * 展開成 `jal x0, label` * `ret`: 從 function 返回 (Return) * 展開成 `jr x1` 或 `jalr x0, 0(x1)` ### Implement if-else ```c if(i==j){ f=g+h; }else{ f=g-h; } ``` f,g,h in x19~x21 i,j in x22, x23 ```asm bne x22, x23, ELSE # if i != j, jump to ELSE add x19, x20, x21 # f = g + h (執行 then 部分) beq x0, x0, END # 無條件跳到 END (x0 always 0) ELSE: sub x19, x20, x21 # f = g - h (執行 else 部分) END: ``` ### Implement a while loop ```c while(save[i]==k){ i+=1; } ``` i in x22, k in x24, base address of save in x25 每個 element 是 8 bytes (double word) ```asm LOOP: slli x10, x22, 3 # x10 = i * 8 (計算 byte offset,shift left 3 = 乘以 8) add x10, x10, x25 # x10 = base + offset = &save[i] # 因為 ld 的 offset 必須是 immediate,所以要先算好完整位址 ld x9, 0(x10) # x9 = save[i] (load 該 element) bne x9, x24, END # if save[i] != k, 跳出迴圈 addi x22, x22, 1 # i += 1 beq x0, x0, LOOP # 無條件跳回 LOOP (x0 永遠是 0) END: ``` **重點**: * Array indexing: 位址 = base + (index × element_size) * Load instruction 的 offset 只能是 immediate (constant),所以要先計算完整位址 ### Implement a switch ```c switch(i){ case 0: f=g+h; break; case 1: f=g-h; break; } ``` f,g,h in x19~x21 i in x22 x5~x7: temporaries ```asm .data JumpTable: .word CASE0, CASE1 # Jump table 存放各 case 的位址 .text # 檢查 i 是否在有效範圍 [0, 1] slt x5, x22, x0 # if (i < 0) x5 = 1 else x5 = 0 bne x5, x0, END # if (i < 0) 跳出 slti x5, x22, 2 # if (i < 2) x5 = 1 else x5 = 0 beq x5, x0, END # if (i >= 2) 跳出 # 使用 Jump Table 跳到對應的 case la x6, JumpTable # x6 = JumpTable 的位址 slli x7, x22, 2 # x7 = i * 4 (假設每個位址 4 bytes) add x5, x6, x7 # x5 = &JumpTable[i] ld x7, 0(x5) # x7 = JumpTable[i] (讀取目標位址) jr x7 # 跳到該位址 CASE0: add x19, x20, x21 # f = g + h beq x0, x0, END # break (跳到 END) CASE1: sub x19, x20, x21 # f = g - h END: ``` **Jump Table 技巧**: * 適用於 case 值連續且範圍小的情況 * 先檢查 index 是否在有效範圍 * 用 array 存放各 case 的 target address * 透過 index 直接跳到對應位置,比一連串的 if-else 快 ### Implement a function 呼叫的稱為 caller,被呼叫的稱為 callee。 呼叫步驟: 1. Place parameters in registers x10~x17 2. Transfer control to the callee, placing the return address in x1 (`jal`) 3. Acquire stack space for local variables for the callee 4. Perform the callee function 5. Place the return value in register x10/x11 6. Return to the caller (in x1) (`jalr`) **第 2,6 步:** * `jal x1, function`: Save next instruction address in x1, jump to function (Jump And Link) * `jalr x0, 0(x1)`: Jump to address in x1, save next instruction address in x0 (x0 is never changed, so discarded) (Jump And Link Register) **第 3 步:** > 畫圖時高位址在上,低位址在下,而 stack 從上往下長,因此 stack pointer 往下移。 把目前的 SP 減少需要的空間大小(以 bytes 為單位)。 **Save and Restore registers:** registers 分成兩類: * temporary x5~x7, x28~x31: callee 不用還原,換句話說 caller 要確保不變的話要自己存在記憶體 * saved x8~x9, x18~x27: callee 要在一開始存到記憶體,return 時還原 **leaf function(不用再 jump) 範例:** ```c define int long long int leaf(int g, int h, int i){ int f; f = (g + h) + i; return f; } ``` g,h,i in x10,x11,x12 f in x20 x8: saved temporaries ```asm addi sp, sp, -16 # allocate stack space for f, x8 sd x8, 8(sp) # save x8 sd x20, 0(sp) # save f add x8, x10, x11 # x8 = g + h add x20, x8, x12 # f = x8 + i addi x10, x20, 0 # save return value f in x10 ld x20, 0(sp) # restore f ld x8, 8(sp) # restore x8 addi sp, sp, 16 # deallocate stack space jalr x0, 0(x1) # return ``` **Non-leaf function 範例:** ```c define int long long int fact(int n){ if(n<1) return 1; else return n*fact(n-1); } ``` x in x10, return value in x10 ```asm fact: addi sp, sp, -16 # allocate stack space for ra(x1), n(x10) sd x1, 8(sp) # save ra sd x10, 0(sp) # save n addi x5, x10, -1 # x5 = n - 1 bge x5, x0, RECURSE # if (n-1 >= 0) jump to RECURSE addi x10, x0, 1 # set x10 = 1 addi sp, sp, 16 # deallocate stack space jalr x0, 0(x1) # return, no need to restore ra and n, since not changed RECURSE: addi x10, x10, -1 # x10 = n - 1 jal x1, fact # jump to fact, save return address in x1 addi x6, x10, 0 # save return value (fact(n-1)) in x6 ld x10, 0(sp) # restore n ld x1, 8(sp) # restore ra addi sp, sp, 16 # deallocate stack space mul x10, x10, x6 # x10 = n * fact(n-1) jalr x0, 0(x1) # return ``` ### Memory layout 一些相關的 register: * PC 指到 text segment 的下一個 instruction * SP 指到 stack segment 的頂端(最低位址) * FP 指到 stack segment 的底端(最高位址) * GP(global pointer, x3) access static data 用的 ### Character Data * Byte-encoded: Stored in a byte * ASCII: 7 bits, 128 characters * Latin-1: 8 bits, 256 characters, more graphic characters * Unicode: 32-bit * Used in Java, C++ wide characters * UTF-8, UTF-16: variable-length encoding of Unicode ### Load & Store instructions 在 l 或 s 後面會有 b,h,w,分別代表 byte(8-bit), halfword(16-bit), word(32-bit),一般是 signed,會以讀取的值的 sign 填滿 rd 中的的高位元(到 64 bit)。 在 l[bhw] 後面有 u 代表 unsigned,一律以 0 填滿 rd 中的高位元。 #### String copy example ```c void strcpy(char *x, char *y){ unsigned int i=0; while((x[i]=y[i])!='\0'){ i++; } } ``` s,t in x11,x10 i in x19 ```asm strcpy: addi sp, sp, -8 # allocate 1 byte of stack space for x19 sd x19, 0(sp) # save x19 addi x19, x0, 0 # i = 0 LOOP: add x5, x19, x10 # x5 = &y[i] lbu x6, 0(x5) # x6 = y[i] add x7, x19, x11 # x7 = &x[i] sb x6, 0(x7) # x[i] = y[i] beq x6, x0, END # if (y[i] == '\0') jump to END addi x19, x19, 1 # i++ jal x0, LOOP # jump to LOOP, next iteration END: ld x19, 0(sp) # restore x19 addi sp, sp, 8 # deallocate stack space jalr x0, 0(x1) # return ``` ### 32-bit Immediate 原本 instruction 中 Immediate 只有 12 bits,因此用 lui(Load Upper Immediate) 先設定 [31:12]。 `lui rd, const`: const 為 20 bits (Load Upper Immediate) * rd[63:32] = 以 const[19] 填滿 * rd[31:12] = const[19:0] * rd[11:0] = 以 0 填滿 ### U-format Instructions `lui` 與 `auipc` `imm[31:12](20) | rd(5) | opcode(7)` ### UJ-format Instructions Unconditional Jump: jal (Upper-Jump format) `imm[20](1) | imm[10:1](10) | imm[11](1) | imm[19:12](8) | rd(5) | opcode(7)` imm 是要跳過去的位址,也是 PC-relative address。 > `jalr` 是 I-format。 ### SB-format Instructions branches: bne, beq (Store-Branch format) `imm[12](1) | imm[10:5](6) | rs2(5) | rs1(5) | funct3(3) | imm[4:1](4) | imm[11](1) | opcode(7)` 要比較的 registers 編號放在 rs1, rs2,要跳過去的位置放在 immediate(切成很多段)。 實際上跳過去的是 PC-relative address,也就是 PC + immediate × 2 byte。事實上可以乘以 4 來節省空間,因為 instruction 本來就對齊在 4 的倍數位址,但 RISC-V 保留 2-byte instruction 的可能性。 ### How to jump far away SB-format 的 offset 只能 12 bits × 2(共 $2\times2^{12}$ bytes 範圍,具體範圍要看 2 補數的範圍),雖然通常都夠用,但要跳更遠可以利用 UJ-format 的 jal 指令。 ```asm beq x10, x0, L1 # L2 code L1: ``` 改成 ```asm bne x10, x0, L2 jal x0, L1 # = j L1 L2: # L2 code L1: ``` #### long jump 如果 20 bits × 2 還不夠,可以用 lui + jalr,這樣是 32 bits × 2。 ```asm lui x8, address[31:12] jalr x0, address[11:0](x8) ``` ### Addressing Summary * Immediate: 只用 immediate,12 bits 或 32 bits * Register: 把位址存在 register 裡,64 bits * Base: register + 存在 immediate 裡的 offset * PC-relative: PC + immediate × 2 ### Synchronization 最基本的就是用 Lock,而 Lock 可以用 atomic swap 實作。 如果簡單用 lw, sw 來實作 lock,會有 race condition。 因此用: * `lr.w rd, (rs1)`: Load Reserved Word,讀取 memory[rs1] 到 reg[rd],並且在 reg[rs1] 的 address 做一個 reservation(透過硬體實作) * `sc.w rd, rs2, (rs1)`: Store Conditional Word,把 reg[rs2] 寫到 memory[rs1],如果 reg[rs1] 的 reservation 還在的話,就把 rd 設為 0,否則設為 non-zero(表示失敗) #### Implement atomic swap This will try to swap reg[x21] and memory[x20] ```asm again: lr.w x10,(x20) sc.w x11,x21,(x20) bne x11,x0,again addi x21,x10,0 ``` #### Implement lock/unlock Get the lock if it's initially 0 and successfully swap 1 into it. x12 is initially 1. ```asm lock again: lr.w x10,(x20) bne x10,x0,again sc.w x11,x12,(x20) bne x11,x0,again unlock sw x0,0(x20) ``` <!-- week 5 --> ## From C to Execution 程式從高階語言到執行的完整流程: 1. **C program**: 高階語言程式碼 2. **Compiler**: 將 C 編譯成 assembly program 3. **Assembler**: 將 assembly 轉成 object file (包含機器碼與 symbol table) 4. **Linker**: 將多個 object files 與 libraries 連結成 executable 5. **Loader**: 將 executable 載入 memory,產生 memory image 6. **CPU**: 執行程式 每個階段都有其特定功能,逐步將人類可讀的程式碼轉換成機器可執行的形式。 ### Assembler **功能**:將 assembly code 轉換成 object file。 **主要任務**: 1. **建立 Symbol Table**: 記錄所有 labels 與變數的位址資訊 2. **產生機器碼**: 將 assembly instructions 轉成對應的二進位碼 3. **處理 Pseudo Instructions**: 展開成實際的 instructions 4. **產生 Relocation Information**: 記錄需要 linker 處理的 references **Assembly Code 範例**: ```asm ld x10, X # load variable X jal x1, B # call function B ``` **產生的 Object File 內容**: * **Header**: 檔案名稱、各 segment 大小 * **Text Segment**: 機器碼 * `ld x10, 0(x3)` (x3 是 global pointer,指向 data segment) * `jal x1, 0` (offset 暫時為 0,等 linker 填入) * **Data Segment**: 全域變數的初始值 * 變數 `X` 的實際資料 * **Relocation Information**: 供 linker 使用 * `ld` 指令依賴變數 `X` 的位址 * `jal` 指令依賴函數 `B` 的位址 * **Symbol Table**: 記錄 symbols 的位置 * `X` 在 data segment 的 offset * `B` 在 text segment 的 offset (若定義在此檔案) ### Linker 又稱 Link Editor,因為會把 object file 內的值設為實際的位址。 步驟: 1. 把 code 與 data 放入 memory,決定各個 segment 的實際位址 2. 根據 relocation information 修改 code 與 data,把 symbol 換成實際的位址 3. 處理 external references,把多個 object file 連結起來,解決 symbol 的 reference 產生的 executable 包含: * text segment: 實際的機器碼 * data segment: 初始化的 global variables * symbol table: 剩下的 unresolved references ### Loader 載入 executable 到 memory 並執行。 步驟: 1. 讀取 executable header,確定 text, data segment 大小 2. 建立 virtual address space 3. 把 text, data segment 複製到 memory 4. 把 main 的參數複製到 stack 5. 初始化 registers (包括 SP, FP) 6. 跳到 start routine (初始化用的 code),會呼叫 main,main return 時會呼叫 exit system call ### Dynamic Linking 傳統的 Static Linking 會把所有用到的 library 都複製到 executable,造成檔案很大,而且如果 library 更新了,executable 也要重新 compile。 Dynamic Linking 只在 executable 內放 stub,實際執行時才把 library load 進來。 會使用 **Lazy Linkage**,也就是第一次呼叫時才會載入。呼叫的地方會先跳到 stub,stub 中再跳到 library 真正位置。之後就會改呼叫地方的位址,改為直接跳到 library,因此第一次會比較慢。 ## Datapath CPU 內部執行 instruction 的硬體路徑。包含: * ALU: Arithmetic Logic Unit,做運算 * Register File: 存放 registers * Memory: 存放 data, instruction * PC: Program Counter * Control Unit: 控制其他元件的運作 ### Instruction Execution Steps 1. **Instruction Fetch (IF)**: 從 memory 讀取 instruction * PC → Instruction Memory → IR (Instruction Register) * PC += 4 2. **Instruction Decode (ID)**: 解讀 instruction,讀取 registers * 從 Register File 讀取 rs1, rs2 * 產生 Control signals 3. **Execute (EX)**: 執行運算 * ALU 計算結果 * 計算 branch target address 4. **Memory Access (MEM)**: 存取 memory (若需要) * Load: 從 memory 讀取 * Store: 寫入 memory 5. **Write Back (WB)**: 寫回 register * 把結果寫入 rd 以下以一個支援 add, sub, ori, load(register+imm offset), store, beq 的處理器為例。 ### R-type Instruction Datapath 以 `add x5, x6, x7` 為例(第一步都是 Instruction Fetch 所以就跳過): 1. ID: 讀取 reg[x6], reg[x7] 2. EX: ALU 計算 reg[x6] + reg[x7] 3. WB: 把結果寫入 reg[x5] 需要有 ID 連到 ALU,再連到 WB(ID 的 busW),並且有輸入:RegWr(是否要 WB)、ALUctr(ALU 執行的 Op) ### I-type (Load) Instruction Datapath 以 `ld x5, 8(x6)` 為例: 1. ID: 讀取 reg[x6],取出 immediate(8) 2. EX: ALU 計算 reg[x6] + 8 3. MEM: 從 memory[reg[x6]+8] 讀取 4. WB: 把讀取的值寫入 reg[x5] 至此,WB 的來源有 ALU 與 Data Memory,所以需要一個 MUX 來控制 busWr 從哪個來源來,使用 MemtoReg 控制,若 1 就是從 Data Memory。 另外,ALU 的第二個輸入可能來自 imm,因此也要一個 MUX 控制,使用 ALUSrc 控制,1 則從 imm 來。 ### S-type (Store) Instruction Datapath 以 `sd x5, 8(x6)` 為例: 1. ID: 讀取 reg[x5], reg[x6],取出 immediate(8) 2. EX: ALU 計算 reg[x6] + 8 3. MEM: 把 reg[x5] 寫入 memory[reg[x6]+8] 因為要儲存,Data Memory 的輸入(WrEn) 需要從 ID 的輸出(busB)直接連過去,並且用 MemWr 控制是否要寫入 Memory。 ### SB-type (Branch) Instruction Datapath 以 `beq x5, x6, L1` 為例: 1. ID: 讀取 reg[x5], reg[x6],取出 immediate 2. EX: * ALU 計算 reg[x5] - reg[x6] (稱為 Zero),判斷是否為 0 * 計算 branch target: PC + immediate × 2 * 如果相等,PC = branch target,否則 PC += 4 因為 PC 來自 PC+4 或 ALU 算出的 Branch 位址,所以用 MUX PCSrc 控制,1 則從 ALU 來。PCSrc 可以設為 branch_inst & Zero。 ### Single-Cycle Implementation 所有 instruction 都在 1 個 cycle 完成,因此 cycle time 取決於最慢的 instruction、CPI=1,但會浪費時間。 <!-- week 6, 9 --> ## Pipeline 比如 laundry,需要洗衣服、烘衣服、摺衣服、收衣服,如果每袋衣服都完全分開等上一袋完全做完才做下一袋,會很浪費時間。Pipeline 可以讓每個階段同時進行不同袋的衣服,這樣就能提高 throughput。 ## Pipeline in RISC-V * IF: Instruction Fetch * ID: Instruction Decode and Register Fetch(因為 instruction 固定長度,而且 source register 都在 instruction 編碼的同個位置,可以很輕鬆的 decode 也能同時 Register Fetch) * EX: Execution, or Branch Completion * MEM: Memory Access * WB: Write-back time to drain the pipeline: 把最後幾個工作最後幾個階段做完的時間。 理想的狀況是每個 stage 都花一樣的時間,speedup 就是 stage 數量(time to drain 在 instruction 很多時可以忽略) ## Pipeline Hazards ### Structural hazards 同時用同個資源。解決方法:用多一點資源。 比如前面有 load instruction,之後的 instruction 又要 IF。 解法:可以暫停(stall)衝突的 instruction。把資料與指令存在不同的 memory。 ### Data hazards 有相依性且時間有衝突,上面修改後下面要讀取得要等到修改生效。 解法:可以透過 Forwarding(Bypassing) 解決,因為其實 Stage 3 從 ALU 出來就可以用,不需要 Stage 5 Write Back 完再從 Register 讀。 如果是 load-use hazard,load 完馬上使用,則 Stage 4 Memory Access 後才能用,所以 Forwarding 也沒辦法完全避免 stall。但在不改變結果的情況 compiler 可以調整 instruction 順序(instruction scheduling,也是要 basic block 的原因),避免 load-use 等。 ### Control hazards 在相依的結果算出來前就要做決定。例如 branching 後一般只能等 ALU 算完才 IF。 可以透過 Branch Prediction 解決,猜 branch 會不會 taken(跳走)。可以簡單假設全部都不 taken(static),或是根據歷史動態預測等(dynamic)。 Predict 在錯誤時需要能還原,或是根本就不改變狀態。 ## Pipeline Datapath 每個 stage 需要有各自的 register,比如 control 很多 unit 會用但同時會執行多個 instruction。命名為前後兩個 stage 名稱,例如 IF/ID。後面接具體內容,例如 IF/ID.IR。 以下以 load 舉例。 * IF: * IF/ID.IR=mem[PC] 是 Instruction Register,IF 讀出來後 ID 需要用到 * IF/ID.PC=PC 要存,因為如果要 branch 改變的話會用到,且下一個 instruction 也會需要增加 PC * PC=PC+4 * ID * ID/EX.A=Reg[IF/ID.IR[19-15]] * ID/EX.B=Reg[IF/ID.IR[11-7]] * ID/EX.Immediate=(sign-ext(IF/ID.IR[31-20])) * ID/EX.PC=IF/ID.PC * EX * EX/MEM.ALUout=ID/EX.A+ID/EX.immediate * MEM * MEM/WB.MDR=mem[EX/MEM.ALUout] * WB * Reg[Rd]=MEM/WB.MDR,但 Rd 會被洗掉,需要像 PC 一樣一路從 ID/EX EX/MEM 傳到 MEM/WB(實際上上面需要改)。 beq: * EX * EX/MEM.PC=ID/EX.PC+(ID/EX.immediate<<1): 先算好跳過去的位置 * EX/MEM.Zero=(ID/EX.A-ID/EX.B)?0:1 * MEM * If (EX/MEM.Zero) PC=EX/MEM.PC 雖然有些 instruction (R type, Arithmetic)不需要 MEM 等,但還是所有 instruction 都 5 個 stage 做完比較簡單,避免 structural hazards 也不用判斷類型。 ## Pipeline Control Control unit 出來的信號也需要在不同 stage 間的 register 儲存,每走一個 stage 消耗掉該 stage 需要的 control signals,其他的繼續傳下去。 實際的 Pipeline 中,forwarding, branch prediction 更複雜: (以下是 AI 生成,還沒改) ### Data Hazard Detection & Forwarding 在 EX stage 檢查是否有 hazard: * EX Hazard: `EX/MEM.RegWrite and (EX/MEM.RegisterRd $\ne$ 0) and (EX/MEM.RegisterRd == ID/EX.RegisterRs1 or rs2)` * MEM Hazard: `MEM/WB.RegWrite and (MEM/WB.RegisterRd $\ne$ 0) and (MEM/WB.RegisterRd == ID/EX.RegisterRs1 or rs2)` Forwarding Unit 產生 MUX control signals (ForwardA, ForwardB): * 00: 從 Register File 讀 (ID/EX) * 10: Forward 前一個 instruction 的 ALU 結果 (EX/MEM.ALUOutput) * 01: Forward 前兩個 instruction 的結果 (MEM/WB.ALUOutput 或 LMD) 優先順序:同時有 EX 與 MEM Hazard 時,優先 Forward 最新的 (EX/MEM)。 ### Load-Use Data Hazard `ld` 後立刻使用結果,Forwarding 不夠快 (data 在 MEM stage 才 ready,但 EX stage 就需要)。 偵測:在 ID stage,`ID/EX.MemRead and ((ID/EX.RegisterRd == IF/ID.RegisterRs1) or (ID/EX.RegisterRd == IF/ID.RegisterRs2))` 解決:Stall 1 cycle。Hazard Detection Unit 執行: 1. Freeze PC & IF/ID: `PCWrite=0`, `IF/IDWrite=0` 2. Insert Bubble: 將 ID/EX control signals 清零,EX stage 空轉 ### Control Hazard Branch 結果在 EX 或 MEM stage 才確定,但 IF stage 需要知道 PC。 解法: 1. **Stall**: 等結果確定再 fetch,MEM stage 確定則 penalty 3 cycles 2. **Reduce Delay**: 把 branch 判斷移到 ID stage,需要額外 adder 與 comparator,penalty 降為 1 cycle。可能產生新的 Data Hazard 3. **Branch Prediction**: 預測 branch outcome * Static: 假設全部 not taken,只有 taken 時才有 penalty (1 cycle) * Dynamic: Branch History Table (BHT) 記錄過去行為 * 1-bit Predictor: 記錄上次 T/NT,loop 在 first iteration 與 exit 會 mispredict * 2-bit Predictor: 4 個 state (Strongly/Weakly Taken/Not Taken),連錯兩次才改預測,loop 表現較好 * Branch Target Buffer (BTB): cache 預測的 target address Misprediction 處理:Flush IF/ID (或 ID/EX) 變成 NOP,restart fetch from correct address。 ### Exceptions & Interrupts * Exception: 內部 (overflow, illegal opcode) * Interrupt: 外部 (I/O) 處理流程: 1. 停止 offending instruction 與後續 instructions (Flush IF, ID, EX) 2. 讓之前的 instructions 完成 3. 把 PC 存到 SEPC (Supervisor Exception PC) 4. 把 cause 存到 SCAUSE 5. 跳到 handler address Precise Exceptions: 確保 offending instruction 之前的都完成,之後的都沒執行。 ### Instruction-Level Parallelism (ILP) 讓多個 instructions 平行執行,目標是 CPI < 1 或 IPC > 1。 方法: * Deeper Pipeline: 更短的 stage,更高的 clock rate * Multiple Issue: 每個 cycle fetch, decode, issue 多個 instructions **Static Multiple Issue (VLIW)**: Compiler 把獨立的 instructions 組成固定大小的 issue packets,負責 hazard detection 與 scheduling,必要時插入 NOP。Hardware 較簡單。例如每 cycle issue 1 ALU/branch + 1 Load/Store。 **Dynamic Multiple Issue (Superscalar)**: Hardware 檢查 instruction stream,在資源與相依性允許時 issue 多個 instructions。 * Out-of-Order Execution: Instructions 在 operands ready 時執行,不照程式順序。需要 Reservation Stations、多個 functional units、result broadcast * Register Renaming: 把 architectural registers 對應到更多 physical registers,消除 false dependencies (WAR, WAW) * In-Order Commit: 用 Reorder Buffer (ROB) 照原順序寫回 registers/memory,維持 precise exceptions **Loop Unrolling**: Compiler 複製 loop body,增加可排程的 instructions,減少 loop overhead。需要 Register Renaming 來打破 iterations 間的相依性。 限制:真相依性、branch prediction 準確度、cache miss、硬體複雜度、功耗。 <!-- week 11 --> ## Memory Hierarchy 主要在講 cache 的作用與設計。越 low level 的空間越大但越慢。 Coherency: 保持不同層級記憶體的資料一致性。 ### Locality 來自一般程式的特性: * Temporal Locality: 最近用過的資料可能很快會再用到。 因此把最近用過的資料存在較高層級的記憶體。 * Spatial Locality: 附近的資料可能很快會用到。 因此一次把附近的資料一起 load 進較高層級的記憶體。 ### Terms * Upper/Lower Level: 較快/較慢的記憶體層級 * Hit: Read/Write 時在 cache 找到 * Miss: Read/Write 時沒在 cache 找到 ### Direct-mapped cache 最簡單的 cache 結構。 Cache 以 block 為單位,每個 block 稱為 set。假設 cache 有 8 sets、一個 block 為 4 words=4*4 bytes=16 bytes。 把 memory 也分成 block,第 N 個 block 會存在 cache 的 (N mod 8) 位置。 通常 set 數量是 2 的次方,這樣一個 byte addressing 的 memory address 可以切成三個部分: **Word-addressing Address 分解**: * Cache Tag: 剩餘高位元 * Cache Index(Set Index): $\log_2(\text{set 數量})$ bits,作為索引決定存在 cache 中的哪個 set * Block Offset: $\log_2(\text{block size})$ bits,決定在 block 中的哪個位址。 前兩部分也稱為 Block Address。而 $2^{\text{後兩者的長度}}$ 是 Cache 的大小(bytes)。若是 byte-addressing,Block Offset 會多 2 bits(1 word=4 bytes=$2^2$ bytes)。 每個 cache set 需要儲存: * Valid bit: 該位置是否有有效資料 * Tag: 這個 set 會儲存的多個 block 中的哪一個 * Data: block 內的資料 在存取資料時:先用 Cache Index 找到對應的 set,再比對 Tag 是否相同且 Valid bit 為 1,若是則為 Hit,否則為 Miss。 ### Block Size Block size 大一點的話: * 優點: * 利用 Spatial Locality * 缺點: * Set 數量會減少,導致較多衝突 Miss * 每次 Miss 要從 Memory 搬更多資料。這可以透過 Early restart(搬完要的部分 CPU 就繼續執行) 或 Critical word first(先搬要的部分) 改善。 ### Handle Read Miss 去 Memory 找,複製到 Cache,設好 Tag 與 Valid bit,再回傳資料給 CPU。 ### Handle Write * Write through: 同時寫到 Cache 與 Memory,較簡單但 Memory 負擔較重。 * Write back: 只寫到 Cache,等同個 set 有其他 miss 時被 replace 時再寫回 Memory,減少 Memory 存取次數。但較複雜,需要 Dirty bit 記錄 block 是否被修改過。 #### Write Buffer 要寫回 Memory 時(write through 寫的時候、write back 被取代時),先寫到 write buffer(FIFO),再由 write buffer 由硬體慢慢寫回 Memory,減少 write 或 cache miss 時 CPU 等待時間。 write back 通常也會有。 但會產生 Coherency 問題,如果要讀的資料還在 write buffer 就要從 buffer 讀才對。 另外 write frequency 高於 DRAM write rate 時,會導致 buffer saturation。 ### Handle Write Miss Write 的 block 不在 cache 中。 * write allocate (fetch on write): 會把 block 從 memory load 到 cache * no write allocate (write around): 直接寫到 memory,不 load 到 cache 與 write through/back 會產生四種組合。 <!-- week 12 --> ### Memory & Cache 假設 * 要從 DRAM 讀取 1 個 block * 1 memory bus cycle(longer than CPU cycle) to send the address * 15 memory bus cycles for DRAM access * 1 memory bus cycle to send each word back to cache/CPU * 4-word block cache miss panelty(現在的 DRAM 更複雜許多,這只是簡化版): * **One-word wide memory organization**: 傳一次只能讀一個 word 1 (address) + 4 × 15 (access) + 4 × 1 (data) = 65 memory bus cycles * **Wide memory organization**: 傳一次可以讀多個 words(假設 2) 1 (address) + 2 × 15 (access) + 2 × 1 (data) = 18 memory bus cycles * **Interleaved memory organization**: 傳一次可以讀多個 words,但是每次只回傳一個 word,會先存在 banks 裡面再回傳給 cache/CPU 1 (address) + 1 × 15 (access) + 4 × 1 (data) = 20 memory bus cycles 一次從 DRAM 讀取一個 block,因此 block size 與 miss penalty 有關。現在會使用 Interleaved memory organization。 ### Cache Performance **Hit Time**: 存取 cache 並判斷 hit/miss 的時間。 **Miss Penalty**: Miss 時從下一層記憶體取得資料的時間(以 cycle 數計算)。 **Memory-stall cycles per program**: $$ = \frac{\text{Memory accesses}}{\text{Program}} \times \text{Miss Rate} \times \text{Miss Penalty} $$ **Average Memory Access Time (AMAT)**: $$ \text{AMAT} = \text{Hit Time} + \text{Miss Rate} \times \text{Miss Penalty} $$ ### Memory Wall **考慮 Memory Stall 的 CPU Time**: $I:\text{ \# of instructions}$. $$ \begin{aligned} \text{I-cache stall cycles} &= I \times \text{I-cache miss rate} \times \text{Miss penalty}\\ \text{D-cache stall cycles} &= I \times \text{Frequency of memory accesses per instruction} \times \text{D-cache miss rate} \times \text{Miss penalty}\\ \text{CPU Time} &= (I\times \text{CPI} + \text{I-cache stall cycles} + \text{D-cache stall cycles})\times \text{Cycle time}\\ \text{CPU Time with perfect caches} &= I\times \text{CPI} \times \text{Cycle time} \end{aligned} $$ 假設 clock rate 翻倍,但 Memory 不變,則 Miss Penalty 的 cycle 數也會翻倍(時間不變),所以 CPU Time 進步不到兩倍。這就是 Memory Wall。 ## Set-Associative Cache ### Associativity **Direct-Mapped Cache** 的問題:如果程式常常存取會映射到同個 set 的不同 blocks,會一直發生 **conflict miss**。 **Fully Associative Cache**: 相當於只有一個 set,包含所有 block。任何 block 可以放在任何位置,沒有 conflict miss,但需要比較所有 entries 的 tag,tag 也較長,硬體成本高且速度慢。 > 比如 Virtual Memory 用的 Page Table 就是 fully associative,Page 放哪都可以。 **N-way Set-Associative Cache**: 折衷方案。 * Cache 分成多個 sets,每個 set 有 N 個 ways (blocks) * 一個 memory block 會映射到特定的 set,但可以放在該 set 的任何一個 way * 需要並行比較該 set 內所有 N 個 tags Address 分解與一般 direct-mapped 一樣。 #### Hardware Design of Associative Cache 在每個 set 中,以每個 tag 與輸入的 tag 比較,並以 MUX 選出正確的 block data。因此 N 增加會增加比較器與 MUX 的數量。 ### Replacement Policy 當 set 已滿需要替換時,選擇哪個 block 替換。 **LRU (Least Recently Used)**: * 替換最久沒用過的 block * 實作:需要記錄每個 block 的使用順序 * 2-way: 1 bit 即可(哪個是最近用的) * 4-way: 需要較複雜的硬體(可能用 counter 或 matrix) * N 很大時硬體成本太高 **Random**: * 隨機選一個替換 * 實作簡單,效能通常不會差太多 * 適合 associativity 高的情況 **FIFO (First In First Out)**: * 替換最早進來的 block * 實作簡單但效能通常不如 LRU **實際使用**: * 小的 N (2-4 way): 常用 LRU 或 pseudo-LRU * 大的 N: 常用 Random ### Multi-level Cache 現代處理器通常有多層 cache: **L1 Cache**: * 最靠近 CPU,速度最快 * 容量較小(幾十 KB) * 通常分成 I-cache (instruction) 與 D-cache (data) * 目標:因為每個 Memory access (包含 Instruction) 都會先去 Cache 看看,要追求 Hit time 短。 因此大小不能太大,而且也不能有太高的 associativity。 **L2 Cache**: * 較大(幾百 KB 到幾 MB) * Unified cache(instruction 和 data 共用) * 目標:降低 miss rate **L3 Cache** (部分處理器): * 更大(幾 MB 到幾十 MB) * 可能在多個 cores 間共享 * 目標:進一步降低 miss rate **Multi-level AMAT**: $$ \begin{aligned} \text{AMAT} &= \text{Hit Time}_{L1} + \text{Miss Rate}_{L1} \times \text{Miss Penalty}_{L1}\\ \text{Miss Penalty}_{L1} &= \text{Hit Time}_{L2} + \text{Miss Rate}_{L2} \times \text{Miss Penalty}_{L2} \end{aligned} $$ * **Local Miss Rate**: 該層 cache 的 miss 佔該層存取的比例。 * **Global Miss Rate**: 該層 miss 佔總 memory 存取的比例 = $\text{本層 local miss rate}\times\prod_{\text{上層}} (1-\text{local miss rate})$。 上方都是指 Local,但看起來很高的話會誤導,也可以看 Global。 ### Caches vs. Performance 比如 radix sort 理論上比 quicksort 在 item 多的時候每 item 時間較少,但因為 memory access pattern 不佳,cache miss rate 較高,實際上反而較慢。 或是矩陣乘法,為了避免 capacity miss,可以每次只算一部分,compiler 通常也會幫忙優化。 ## Virtual Memory 利用 Memory 作為 Disk 的 Cache,主要講如何用硬體實作。 ### Motivation * 每個 Process 有獨立的 Address Space (Safe sharing) * Program 可以大於 Physical Memory * 簡化 Loading (Relocation) ### 系統架構 分成三層: 1. **TLB** (CPU): 作為 Page Table 的 cache 2. **Page Table** (Memory): 儲存 VA → PA 的映射 3. **Cache** (CPU): 儲存常用的資料 **階層關係**: * TLB Hit → Page Table 也一定有資料 (TLB 是 PT 的 subset) * Cache Hit → Page Table 也一定有資料 (Data 在 Cache 代表一定在 Memory) ### Address Translation * **Mapping**: MMU,Virtual Address (VA) $\rightarrow$ Physical Address (PA) * **Page Offset**: 不變 (VA 與 PA 的最低位元相同,由 Page Size 決定,e.g., 4KB page $\rightarrow$ 12 bits offset) * **Translation**: VPN (Virtual Page Number) $\rightarrow$ PPN (Physical Page Number) ### Page Table 存在 Memory 中,由 Page Table Register 指向。 **結構設計**: * **Linear (線性)**: Array of PTEs * 問題:Size 太大 (e.g. 32-bit addr, 4KB page, needs $2^{20}$ entries) * **Inverted (反向)**: Size 取決於 Physical Memory 大小 * 問題:需用 Hash 搜尋,較慢 * **Multi-level (多層)**: 像 Tree 一樣 (e.g., RISC-V 用 4-level) * 優點:省空間 (Sparse address space 不用 allocate 全部) * RISC-V 是 48-bit virtual address, 12-bit page offset,剩下 36 bits 分成 4-level,每層 9 bits index (512 entries)。 **Page Fault 處理**: * **觸發條件**: Valid bit = 0 * **Miss Penalty**: 極大 (Disk access) * **設計選擇** (因為 miss penalty 太大): * **Fully Associative**: 軟體處理 placement,任何 Page 可以放在任何 Physical Frame * **LRU** replacement: 值得花硬體/運算資源維護 Reference bit * **Write-back**: 減少寫入 Disk 次數,用 Dirty bit 記錄 * **處理方式**: 由 OS (Software) 處理 Fault * 因為硬碟很慢,沒必要用硬體處理 * 但若硬碟真的很快也未必要用軟體 ### TLB (Translation Lookaside Buffer) **Page Table 的 Cache** (存 VPN $\rightarrow$ PPN),避免每次存取資料都要 access 兩次 memory。 **設計特性**: * **Associativity**: 通常是 **Fully Associative** 或高 associativity (Small size, high hit rate) * **Fields**: * PPN (Physical Page Number) * Protection bits (R/W/X) * Dirty bit * **ASN** (Address Space Number): 類似 PID,避免 Context Switch 時要 Flush TLB **事件關係**: * TLB Hit $\rightarrow$ Page Table Hit (TLB 是 PT 的 subset) * Cache Hit $\rightarrow$ Page Table Hit (Data 在 Cache 代表一定在 Memory) * TLB Miss $\rightarrow$ Hardware 或 OS 查 Page Table reload ### Integrating TLB and Cache 三種設計方案: #### 1. Physically Addressed Cache * 流程:CPU $\rightarrow$ TLB $\rightarrow$ Cache * 缺點:慢,TLB 在 Critical path #### 2. Virtually Addressed Cache * 流程:CPU $\rightarrow$ Cache (直接用 VA 當 Key) * 優點:快 (不用翻譯) * 問題: * **Aliasing** (不同 VA 對應同 PA) * Context Switch 需 Flush (或加 PID tag) * Shared memory 無法運作 * 要分辨不同 process 的資料要存 PID,overhead 過大 #### 3. Virtually Indexed, Physically Tagged (VIPT) 最佳解 * **策略**: Overlap TLB access 與 Cache access 的時間 * **做法**: * 同時送 VPN 去 TLB 查 PPN (Tag) * 用 VA 的 Index 去 Cache 讀 Tag/Data * Cache 會先用 Cache Index 去找,第二步才需要 Cache Tag * **關鍵條件**: **Cache Index bits 必須完全落在 Page Offset 內** * 因為 Page offset 不會因為轉換而改變 (VA = PA) * 如果能保證 PA VA 的 Cache Index 區段相同,就能直接用 VA 先去找 Cache Set * **限制**: $\text{Cache Size} \le \text{Page Size} \times \text{Associativity}$ * 例如:4KB Page, Direct-mapped cache 最大只能 4KB * 若要更大的 cache,需要增加 associativity ## Advanced cache techniques ### Non-blocking caches 或 lockup-free cache。 允許在 cache miss 時,CPU 繼續執行不 stall,但還是有 miss 數量上限,因為需要存正在排隊的 cache miss。 ### Prefetch from memory to cache * static: compiler 塞一些 load * dynamic: 硬體先去找 memory access 但可能會造成 cache pollution (預取了不需要的資料) ### L3 cache 數 MB 的 cache。 ## Parallel Processor 平行程式設計的簡介 * Data Parallelism: processors 對同一塊資料的不同部分做同樣的操作 * Task Parallelism: processors 對同一塊資料做不同的操作 * Pipelining: 像 pipelined processor 一樣,但是在不同 processor 進行 當 N 個 processor 產生 M 倍 speedup 時,會說用了 $M/N$ 的 potential。 * Strong scaling: 無論 problem size 多大,用更多 processor 總是能得到好的 speedup * Weak scaling: problem size 太小時增加 processor 數量 speedup 未必好。大多數問題都是這個分類 * Shared Memory Model: 用在 UMA,共用一塊 memory,主要是要處理不同 processor 各自的 cache 的 coherency 問題。 * Message Passing Model: 可以用在 NUMA,比如 MPI,不同 processor 間使用 send, recv 溝通。 ### Snooping solution(snoopy bus) Maintain Coherency in Shared Memory Model * Send all requests for data to all processors * Processors snoop to see if they have a copy and respond accordingly * Requires broadcast, since caching information is at processors * Works well with bus (natural broadcast medium) * Dominates for small scale machines (most of the market) #### write protocol 分類 * Write **Invalidate** Protocol * 只有一個 processor 能寫,此時其他 processor 的這個 block 會變為 invalid * 可以多個 processors 一起讀 shared data * Read Miss 又分成 * Write through: Memory 一直是最新的,直接讀 Memory * Write back: Memory 可能不是最新,這樣的話需要從別的 processor 的 cache 裡面找 * Write **Update**(boardcast) Protocol * 寫的時候 Update 所有 processor 的 cache 以及 Memory * Read Miss 時只要從 Memory 讀就好 不同 processors 在同個 clock write 到同個 block 的話,bus 會自己決定哪個先。 #### 優劣勢比較 * Write Invalidate Protocol: 更好運用 Temporal Locality,只有一個 processor 一直寫的話就不用一直 boardcast。 * Write Update(boardcast) Protocol: read 與 write latency 比較差不多。 在 small scale 時通常 bus 是 bottleneck,所以會偏好 Write Invalidate。 #### Snoopy example write invalidation protocol, write-back cache 因此每個 memory block 可能是以下狀態: * Clean in all caches, up-to-date in memory * Dirty in exactly one cache * Not in any cache 在某 processor 的每個 cache block 可能是以下狀態: * Shared: 是最新的,可以讀 * Exclusive: 這個 processor 可以寫,並且是 dirty (還沒 write back 到 memory) * Invalid: 要讀要寫都要從別的地方來 ##### State machine 某 processor 的 cache block 來自 CPU(processor 本身) 的 request 導致的變化: * Invalid * Read miss -> Shared: place read miss on bus * Write miss -> Exclusive: place write miss on bus * Shared * Read hit -> Shared: None * Read miss -> Shared: place read miss on bus * Write miss -> Exclusive: write 一律是 miss,因為不是 exclusive。place write miss on bus * Exclusive * Read hit -> Exclusive: None * Read miss -> Shared: Write back, place read miss on bus * Write hit -> Exclusive: None * Write miss -> Exclusive: Write back, place write miss on bus 某 processor 的 cache block 來自 Bus(別的 processor) 的 request 導致的變化: * Invalid * 不用管,與這個 block 無關 * Shared * Write miss -> Invalid: 別的 processor 變成 Exclusive * Read miss -> Shared: 與本身無關,只是多了一個 processor 一起 share * Exclusive * Write miss -> Invalid: write back,別的 processor 取代本身成為 Exclusive * Read miss -> Shared: write back,別的 processor 要讀,變成一起 share 以上只是範例,不同設定應該都可以簡單推理出來。 #### Coherency Cache Miss 除了 Compulsory(Cold miss), Capacity, Conflict(交替使用對應到同個 set 的 block),還有 Coherency miss,因為不同 processor 交替寫到同個 block。 其中又能分成 * True: 不同 processors 確實是用到 block 內的同一塊資料 * False: 不同 processors 用到同 block 的不同塊資料,如果 block 小一點可能會改善 ## Interconnection Network Parallel architecture 的 processors 之間怎麼連接。 ### Topology 種類 * **Bus**: 最簡單,大家掛在同一條 bus 上。問題是一次只能一個 transaction,bandwidth 低。 * **Ring**: 每個 processor 跟相鄰的連。允許多對 processor 同時 communication (用不同 link segment)。Total network bandwidth = per link bandwidth × link number。Bisection bandwidth (切一半後兩 part 之間的 bandwidth) 是 bus 的兩倍。 * **Fully Connected**: 每兩個 node 之間都有 dedicated link。Bandwidth 最高但 cost 也最高。 * **2D Torus**: 環狀網格結構。 * **Cube**: 立方體連接。 * **Multi-stage Networks**: Processor 透過多個 switch 連接,不是一個 node 配一個 switch。允許較少 node 搭配較多 switch,從一個 processor 到另一個可能經過多個 switch。 * Crossbar: 用 crossbar 結構的 switch * Omega Network: 用 2-in-2-out 的 switch box 組成 ### 評估 Interconnection Performance * **Latency**: 傳一個 message 從一個 node 到另一個 node 要花的時間 * **Bandwidth**: Link bandwidth, Bisection bandwidth, Total network bandwidth * **Congestion delay**: Switch 中的 queue 累積訊息等待送出的時間,取決於 traffic 重不重 * **Cost & Power** * **Routability in silicon**: IC 裡能 afford 多少 wire ## Multicore 以前是一個 chip 一個 core,透過 off-chip bus 連接。現在是同一個 chip 內多個 core,透過 on-chip bus communication。 ### Cache 結構 * **Private cache**: 每個 core 有自己專用的 cache,不會 interference,但空間利用率較差 * **Shared cache**: 最後一層 (Last Level Cache, LLC) 是 shared,空間利用率好 * 通常會是 L1/L2 private,L3 shared ### Scalable Architecture (Tile-based) Core 數多 (10~100 個) 時用 tile-based 結構,每個 tile 包含: * **Core**: 運算核心 * **Router**: 負責 packet switching,像 network on chip * **Cache**: L1 private,L2 是 physically distributed 但 logically shared ### Big-Little (大小核) 同一 ISA 但不同 power/performance 的 core 組合 (如 ARM Cortex-A15 大核 + A7 小核)。 **用途**: Energy efficiency。不需要強大運算的 workload 用小核,可以省一半 energy 達到同樣 performance。 ## Multi-Threading Machine **Hardware thread**: 每個 thread 有自己的 registers、PC 等 states physically replicated,不需 context save/restore,thread switch 很快。 ### 類型 * **Fine-grained**: 每個 cycle 換 thread,消除 vertical waste (整排空白的 stall) * **Coarse-grained**: Run 到 long stall (如 cache miss) 才換 thread * **Simultaneous Multi-Threading (SMT)**: 同一 cycle 可以從多個 threads schedule instructions。Intel 叫 Hyper-Threading (HT)。增加 horizontal 彈性,更能填滿 superscalar 的 issue slots。 ## SIMD (Single Instruction Multiple Data) 一個 instruction 同時處理多個 data elements。例如一個 register 放兩個 2-byte data,一個乘法 instruction 就同時做兩組相乘。 **優點**: * 比 MIMD 更 energy efficient (只 fetch 一次 instruction) * Programmer 還是 think sequentially,比寫 parallel program 簡單 * 適合 multimedia (RGB 等 narrow data type)、matrix operations **演進**: MMX → SSE → AVX,register 越來越長,一次處理更多 data。要求 data 是 consecutive and aligned。 ## Vector Architecture 更 general 的 SIMD。Vector register 很長 (如 RISC-V 有 64 個 64-bit elements = 512 bytes),一個 instruction 操作整個 vector。 **操作流程**: 1. **Gather**: 把 data elements 讀進 vector register 2. **Pipeline operations**: 對 vector 做運算 3. **Scatter**: 結果寫回 memory **範例**: DAXPY (Y = a×X + Y) * 傳統 RISC-V: 要 loop 64 次,每次 load/multiply/add/store * Vector RISC-V: 幾個 vector instructions 就完成 **優點**: * 大幅減少 instruction fetch bandwidth * 避免 loop control overhead 和 control hazards * 利用 interleaved/burst memory * 使用 multiple-laned vector units (多個 functional units pipeline 處理不同 elements) ## GPU (Graphics Processing Unit) ### 歷史演進 * 早期: VGA display controller (fixed function) * 1999~2001: 開始 programmable (vertex, pixel shader) * 2005: Unified shader (vertex/pixel/compute 用同一種 processor) * 2017 Volta: 加入 Tensor Core (專門加速 machine learning) ### 3D Graphics Pipeline (基本概念) Vertex (座標) → Transform (轉成螢幕座標) → Rasterization (填 pixel) → Pixel Shader (顏色、lighting、texture) ### GPGPU (General Purpose Computing on GPU) 除了 graphics 也能跑 general applications (medical imaging, molecular dynamics 等)。程式語言: CUDA (NVIDIA), Stream SDK (AMD), OpenCL (標準)。 ### CPU vs GPU Architecture **CPU**: * Coarse-grained heavyweight thread * 大 cache + 複雜 control (branch prediction, OoO) * 少數大而強的 core **GPU**: * Fine-grained multi-threading machine * 小 cache,大部分 area 是 computing elements * 很多小 core * 用 latency hiding 解決 memory latency (thread 多到可以一直換) ### GPU 結構 * 很多 SM (streaming multiprocessor),每個是 SIMD lane * On-chip local memory (64KB) + off-chip global memory (DRAM) * Shared L2 cache ### SIMT (Single Instruction Multiple Threads) 類似 SIMD + threading 的結合。**Warp** (32 個 threads) 同步執行同一 instruction 但處理不同 data。Fine-grained threading 在 warp 之間切換。 **Thread 組織**: Thread → Thread Block (share local memory) → Grid ### Latency Hiding GPU 不需大 cache,因為 thread 夠多。一個 warp stall 時馬上換另一個 warp 執行,stall 時間都被隱藏。 ### Tensor Core (Volta) 專門做 matrix multiply-add (machine learning 最常用的運算)。 ### 系統整合演進 1. GPU 掛 PCI bus,有自己 address space (driver 管理) 2. GPU 整進 CPU die,共用 DRAM 但切分 GPU/system memory (data copy via memory bus) 3. Unified memory: CPU/GPU 共用 address space,不需 explicit data transfer (如 CUDA Unified Memory),但當然還是得傳輸。 ## Performance Evaluation ### Benchmark * **Linpack**: Matrix linear algebra (scientific computing) * **SPEC rate**: 多個獨立 programs 同時 run (job-level parallelism) * **SPLASH**: Stanford Parallel Applications for Shared Memory * **NAS**: NASA computational fluid dynamics kernels * **PARSEC**: Princeton multithreaded applications (Pthread, OpenMP) * **MLPerf**: Machine learning 標準 benchmark ### Roofline Model 快速分析系統是 compute bound 還是 memory bound。 **三個參數**: * Hardware peak performance (GFLOPS) * Hardware peak memory bandwidth (GB/s) * Application arithmetic intensity (operations per byte) **圖的畫法**: * X 軸: Arithmetic intensity * Y 軸: Achievable GFLOPS = min(Peak GFLOPS, Arithmetic intensity × Peak bandwidth) * 形成一條折線 (roof) **用途**: * Intensity 低 → memory bound,升級 CPU 沒用,要改善 memory 或增加 data reuse * Intensity 高 → compute bound,升級 CPU 有幫助 * 如果 working set 全在 cache 就不受 memory bandwidth 限制