# RTL / Register Transfer Languages 我們可以用 RTL 來分析,如果要兜出某些指令集,到底需要那些操作。 # Single Cycle 我們現在要來嘗試自己刻 CPU 了:) 但是先刻 single cycle 的版本,一個指令一個 cycle。 下圖是最終的結果。 ![image](https://hackmd.io/_uploads/HkpAugGSZg.png) 順帶一提下面這是 MIPS 的 ![image001_imgupscaler.ai_銳化_2K](https://hackmd.io/_uploads/HJSdip-zZl.png) # Instructions Fetch 在圖的左半部。 - ==PC== 的輸入跟輸出都是一個地址,代表當前要讀取 Instruction Memory 中哪個位置的值,或者說哪條指令。 - 輸入可以看到是來自一個 ==MUX==,這個 MUX 決定下一條指令是要 branch 到某個地址,還是採用 PC + 4 的地址 - 輸出其實就是輸入,作為 Instruction Memory 的輸入來獲取指令。 - ==Instruction Memory== 輸出當前要處理的指令。 - 可以看到當前的指令大致分成 3 部分 - 一部分作為 ==Control== 元件的輸入,輸出許多 flag 來控制其他元件的行為 - 一部分作為 ==Register File== 的輸入,輸出當前要操作的 reg - 一部分作為 ==Sign Extend== 的輸入,輸出要操作的==立即數 Immediate== - 還有一小部分是作為 ==ALU control== 的輸入,輸出 ALU 的控制 flag # ALU ## 加減法 由於 RISC 的良好設計,machine code 中暫存器的 bit 是在固定位置, A 的輸出會一直是固定的位置,而 B 就需要透過 MUX 來決定是要用 B 的值,還是用 IMM 的值。 這兩條輸出會經過 ALU 做運算,所以我們需要一個「ALUctr」來控制一個 MUX,決定要哪個作為 read data two。 最終把結果拉回 Register file,把結果寫回去 Register file,所以我們除了需要寫回去的地址 rd 之外,也需要 RegWr 這個 flag。 ## 讀取 讀取指令是根據 rs1 和 Imm 來算出要存取的位置,所以 ALU 的 read data two 要透過 MUX 換成 Imm。 從記憶體拿到之後,因為跟加減法一樣是送回 rd,所以要透過一個 MUX 來判別當前的值是要用記憶體拿到的,還是 ALU 計算完;也就是我們需要一個「MemtoReg」的 flag 來幫我們判斷。 ## 寫入 算出地址的部分跟讀取一樣。 而 rs2 作為寫入的資料,要做為 Memory 的輸入;還需要「MemWr」來決定是否要寫入記憶體。 ## branch :::warning - 上面那張圖是只有實作 beq 的例子 - 可以注意到 MIPS 是 shift left 2 - 還可以注意到,RISC-V 是 IMM 跟 PC 相加,MIPS 則是跟 PC + 4 相加 ::: branch 家族需要兩個 reg 的比較結果,所以 ALU 有個特別的輸出 ==Zero==,代表當前比較的結果。 >如果相減等於 0 會輸出 1,否則是 0 但是 Zero 這個路線只有在 Branch 指令時才有意義,所以這條路會跟另一個叫做 branch 的 flag 來做 AND ,去判斷當前是否為 branch 指令,並且 zero 是否為 1。 這個運算的結果會作為「PCscr」的 flag ,用來決定一個 MUX 是要輸出 PC + 4 的值還是 branch 的 IMM + PC。 :::info 要注意 branch 的 Imm 是 PC-relative addressing,所以會跟 PC 做加法 ::: # Control 還記得 RISCV 中指令有所謂的 op code 跟 funct7 和 funct3 嗎,沒錯,就是由他們來決定這些 flag 到底是多少的。 - op code:用於決定各種 flag,以及決定出 ALU 的 op code (ALUop) - funct3 & funct7:會跟 ALUop 一起決定 ALU 的結果 所以第一步,每種列出各種指令的 op code,以及他們所需的各種 flag 跟 ALUop。 >這可以用 Quine McCluskey 方法達成化簡 第二步是透過 ALUop 跟 funct3 跟 funct7,以及他們所需的 ALU control >這一樣可以用 Quine McCluskey 方法達成化簡 由於 load 跟 store,他們只需要 ALU 的加法,所以我們可以把他們的 funct3 跟 funct7 直接作為 don't care,讓她們只用 ALUop 產生出 ALU control。 :::warning 記住,上面只是單純對簡單的指令子集所進行的示範,實際的指令集合更大、更複雜。 ::: # Performance Analysis 設計好了當然得分析一下。 首先你可以根據情境,制定每個操作需要多少 pico second,例如: - Memory units(Instruction Memory 跟 Data Memory):200 ps - ALU and adders:100 ps - Register file (read or write):50 ps - Instruction mix: - 25% loads - 10% stores - 45% ALU instructions - 15% branches - 5% jumps. ## 固定頻率 CPU 上面的電路最長的是 Load 指令,需要 200 +50 + 100 + 200 + 50 = 600 ps ![image](https://hackmd.io/_uploads/HJ9zPC-zZl.png) 對於固定頻率的 CPU,一個指令所需的時間,或者說 ==Clock Per Instruction== 會受最長時間的指令影響,因此就是 600 ps (當然你可以換成 cycle)。 ## 變動頻率 CPU 這時候假設有一個 CPU 可以動態調整頻率,遇到時間短的指令就頻率變高一點,時間長的指令就頻率變低一點,那麼這時候 ==CPI== 就會變成事各種指令所需時間的加權總和了: 所以我們可以假設某種混和指令,裡面有幾 % 是 A 指令,幾 % 是 B 指令...,以此算出他在這種混合指令下所需時間為多少: 600 × 25% + 550 × 10% + 400 × 45% + 350 × 15% + 200 × 5% = 447.5 ## 分析結果 根據之前的效率公式我們知道,變動 CPU 的效率是固定 CPU 的: $$ \frac{600}{447.5}\approx 1.34 倍 $$ 也就是說固定式 CPU 還有可提升空間。