---
# System prepended metadata

title: 第 4 講 Datapath
tags: [計算機結構]

---

# 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 還有可提升空間。