循序 SEQ 實作 (Sequential SEQ Implementation)
Overview Table
| 主題 | 核心內容 | 書頁 |
|---|---|---|
| SEQ 定義 | 循序處理器 (sequential processor):每個 clock cycle 完成一條完整指令的所有處理 | p.420 |
| 六階段組織 | Fetch → Decode → Execute → Memory → Write back → PC update,所有指令共用同一框架 | p.420-431 |
| SEQ 硬體結構 | PC 是 SEQ 中唯一的時鐘暫存器;資料流由下而上、順時針回繞 | p.432-436 |
| SEQ 時序 | 單一 clock 控制 4 個狀態單元;No reading back 原則保證正確性 | p.436-439 |
| 各階段 HCL 實作 | fetch / decode+write-back / execute / memory / PC update 的控制邏輯區塊 | p.440-447 |
| SEQ 的缺陷 | 週期必須容納最慢指令(如 ret)的完整傳播延遲 → 時脈太慢,硬體利用率低 → 動機:pipeline |
p.448 |
4.3.1 將處理組織成六個階段 (p.420-431)
SEQ 在每個 clock cycle 完成一整條指令的所有處理。為了讓差異極大的指令共用最少硬體(硬體複製成本遠高於軟體複製),把每條指令的處理統一切成六個階段,所有指令走同一條均勻流程 (uniform flow),以最少硬體單元(單一 ALU、單一 register file)涵蓋全部指令 (p.421)。
+--------+ +--------+ +---------+ +--------+ +------------+ +-----------+
PC --> | Fetch |-->| Decode |-->| Execute |-->| Memory |-->| Write back |-->| PC update |--+
+--------+ +--------+ +---------+ +--------+ +------------+ +-----------+ |
^ |
+------------------------------ 新 PC (下一條指令) -------------------------------+
| 階段 | 動作 | 產生的訊號 |
|---|---|---|
| Fetch | 以 PC 為位址讀指令位元組;拆出 icode:ifun,可能再讀 rA:rB 與 8-byte 常數 valC;算出下一條循序位址 |
icode, ifun, rA, rB, valC, valP |
| Decode | 從 register file 讀最多兩個運算元(通常是 rA、rB 指定的,但 push/pop/call/ret 讀 %rsp) |
valA, valB |
| Execute | ALU 執行運算(依 ifun)、算有效位址、或 ±8 調整 stack pointer;OPq 設定 CC;jXX/cmovXX 依 CC 與 ifun 算出 1-bit Cnd |
valE, Cnd, (Set CC) |
| Memory | 讀或寫 data memory | valM |
| Write back | 最多寫兩個結果回 register file(E port 寫 valE,M port 寫 valM) |
— |
| PC update | PC 設為下一條指令位址(valP / valC / valM 三選一) |
新 PC |
此簡化實作中,處理器遇到任何 exception(執行 halt、非法指令、讀寫非法位址)就直接停機;完整設計應進入 exception-handling 模式執行特殊代碼 (p.421)。另見 08-Exceptional-Control-Flow/01-Exceptions。
各指令的階段計算表(Figures 4.18–4.21 精華)
表中 M1[x] 表示存取位址 x 的 1 byte,M8[x] 表示存取 8 bytes;← 表示賦值,概念上由上而下依序讀(實際硬體不必嚴格循序,見 4.3.3)。
運算類指令(OPq / rrmovq / irmovq)— 結果寫回暫存器 (p.423)
| Stage | OPq rA, rB |
rrmovq rA, rB |
irmovq V, rB |
|---|---|---|---|
| Fetch | icode:ifun ← M1[PC];rA:rB ← M1[PC+1];valP ← PC+2 |
同左 | 同左 + valC ← M8[PC+2];valP ← PC+10 |
| Decode | valA ← R[rA];valB ← R[rB] |
valA ← R[rA] |
— |
| Execute | valE ← valB OP valA;Set CC |
valE ← 0 + valA |
valE ← 0 + valC |
| Memory | — | — | — |
| Write back | R[rB] ← valE |
R[rB] ← valE |
R[rB] ← valE |
| PC update | PC ← valP |
PC ← valP |
PC ← valP |
valE ← valB OP valA(valB 在前):subq %rax,%rdx 計算的是 R[%rdx] − R[%rax],與 Y86-64/x86-64 慣例一致 (p.422)。四個整數運算 (addq/subq/andq/xorq) 共用同一 icode,只差 ifun。rrmovq/irmovq 用「加 0」讓值通過 ALU,且不改 CC。
記憶體存取指令(rmmovq / mrmovq)— ALU 當位址加法器 (p.425)
| Stage | rmmovq rA, D(rB) |
mrmovq D(rB), rA |
|---|---|---|
| Fetch | icode:ifun、rA:rB、valC ← M8[PC+2]、valP ← PC+10 |
同左 |
| Decode | valA ← R[rA];valB ← R[rB] |
valB ← R[rB] |
| Execute | valE ← valB + valC(有效位址 = 位移 + 基底) |
valE ← valB + valC |
| Memory | M8[valE] ← valA |
valM ← M8[valE] |
| Write back | — | R[rA] ← valM |
| PC update | PC ← valP |
PC ← valP |
堆疊指令(pushq / popq)— 最難實作:同時動記憶體與 %rsp (p.425-428)
| Stage | pushq rA |
popq rA |
|---|---|---|
| Fetch | icode:ifun、rA:rB、valP ← PC+2 |
同左 |
| Decode | valA ← R[rA];valB ← R[%rsp] |
valA ← R[%rsp];valB ← R[%rsp](讀兩份) |
| Execute | valE ← valB + (−8) |
valE ← valB + 8 |
| Memory | M8[valE] ← valA(用遞減後位址) |
valM ← M8[valA](用未遞增位址) |
| Write back | R[%rsp] ← valE |
R[%rsp] ← valE;R[rA] ← valM |
| PC update | PC ← valP |
PC ← valP |
- pushq:寫入位址用
valE(先減 8 再寫)符合 Y86-64/x86-64 慣例,但%rsp實際更新要到 clock 上升時才發生。 - popq 讀兩份
%rsp看似冗餘,實為讓流程與其他指令一致、提升設計均勻性 (p.425);記憶體位址用未遞增的valA,符合「先讀記憶體、再遞增指標」的慣例。 pushq %rsp推入的是%rsp的原值(valA在 decode 讀取,早於更新);popq %rsp使%rsp得到從記憶體讀出的值(Practice 4.15/4.16)。
控制轉移指令(jXX / call / ret)— push/pop 的是 PC 值 (p.428-431)
| Stage | jXX Dest |
call Dest |
ret |
|---|---|---|---|
| Fetch | icode:ifun;valC ← M8[PC+1];valP ← PC+9(無 regid byte) |
同左 | icode:ifun;valP ← PC+1 |
| Decode | — | valB ← R[%rsp] |
valA ← R[%rsp];valB ← R[%rsp] |
| Execute | Cnd ← Cond(CC, ifun) |
valE ← valB + (−8) |
valE ← valB + 8 |
| Memory | — | M8[valE] ← valP(推入返回位址) |
valM ← M8[valA](彈出返回位址) |
| Write back | — | R[%rsp] ← valE |
R[%rsp] ← valE |
| PC update | PC ← Cnd ? valC : valP |
PC ← valC |
PC ← valM |
x ? a : b 語意同 C 的條件運算式。call/ret 與 pushq/popq 極相似,只是推入/彈出的是 PC 值(valP / 返回位址)(p.430)。
subq %rdx,%rbx(%rdx=9, %rbx=21)→ valE = 21−9 = 12,ZF=SF=OF=0,PC+2。
rmmovq %rsp,100(%rbx)(%rsp=128, %rbx=12)→ valE = 12+100 = 112,M8[112] ← 128,PC+10。
pushq %rdx(%rsp=128)→ %rsp=120、M8[120] ← 9,PC+2。
je(CC=000,條件不成立)→ Cnd = Cond(0,0,0,3) = 0,PC ← valP(僅 PC+9)。
ret(%rsp=120, M8[120]=0x040)→ %rsp=128,PC ← 0x040。
4.3.2 SEQ 硬體結構 (p.432-436)
抽象視圖(Figure 4.22):PC 暫存器在左下角,資訊沿線路由下而上再繞回右側(順時針);右側回饋路徑帶回要寫入 register file 的值(valE, valM)與新 PC。所有處理在單一 clock cycle 內完成。由下而上畫圖的理由留待 pipeline 設計時說明 (p.432)。
PC update newPC <--------------------------+
|
Write back valE, valM ----------------------+ (回饋路徑)
^ valM
Memory [ Data memory ] <- Addr, Data
^ valE
Execute [CC]--[ ALU ]--> Cnd <- aluA, aluB
^ valA, valB
Decode [ Register file ] 讀埠 A,B / 寫埠 E,M <- srcA,srcB / dstE,dstM
^ icode, ifun, rA, rB, valC, valP
Fetch [ Instruction memory ] [ PC increment ]
^
[ PC ] (SEQ 唯一的時鐘暫存器)
各階段硬體單元 (p.432-434):
- Fetch:instruction memory(唯讀)以 PC 為位址讀出指令 + PC incrementer 產生
valP。 - Decode:register file 具兩個讀取埠 A、B,同時讀出
valA、valB。 - Execute:單一 ALU 身兼多職——整數運算、有效位址加法、
%rsp±8、「加 0」傳遞;CC 暫存器存 3 個 condition code bits,由 ALU 計算新值;Cnd由 CC 與ifun算出,供 cmovXX 與 jXX 用。 - Memory:data memory 讀/寫一個 word;instruction memory 與 data memory 存取同一記憶體空間,只是用途不同。
- Write back:register file 的兩個寫入埠——E port 寫 ALU 結果 valE,M port 寫記憶體讀出值 valM。
- PC update:新 PC 從
valP/valC/valM中選出。
繪圖慣例(Figure 4.23, p.434):白色矩形 = 時鐘暫存器(SEQ 只有 PC)、淺藍方塊 = 硬體單元(視為黑盒)、灰色圓角框 = 控制邏輯區塊(需寫 HCL 的部分)、白圈 = 線路名稱、中粗線 = 64-bit word(64 條並聯導線)、細線 = 4/8-bit、虛線 = 1-bit 控制訊號。
除了階段值之外還有四個 register ID 訊號(Figure 4.24, p.434-436):srcA(valA 來源)、srcB(valB 來源)、dstE(valE 寫入目的)、dstM(valM 寫入目的)。
這裡假設 instruction memory 與 data memory 是獨立單元、讀取如組合邏輯;真實系統通常合併為一個雙埠記憶體,且完整記憶體系統(多層 RAM、非揮發儲存、管理機制)遠更複雜 (p.420),見 06-Memory-Hierarchy/02-Locality-and-Memory-Hierarchy。
4.3.3 SEQ 時序 (p.436-439)
SEQ = 組合邏輯 + 兩種記憶元件:時鐘暫存器(PC、CC)與 random access memories(register file、instruction memory、data memory)。組合邏輯不需時序控制——輸入變了輸出就跟著變;RAM 的讀取也假設如組合邏輯般運作(小型記憶體合理,大型電路可用特殊時鐘電路模擬);instruction memory 只用來讀,可直接視為組合邏輯 (p.436-437)。
只有 4 個單元需要明確時序控制,全由單一 clock 訊號觸發載入/寫入:
| 單元 | 何時寫入 |
|---|---|
| PC | 每個 cycle 都載入新指令位址 |
| CC 暫存器 | 僅執行 OPq 整數運算指令時 |
| Data memory | 僅執行 rmmovq、pushq、call 時 |
| Register file | 兩個寫入埠每 cycle 皆可寫;埠位址為 0xF (RNONE) 表示該埠不寫 |
處理器永遠不需要讀回「本指令更新後的狀態」來完成本指令的處理。
- 反例:若 pushq 先把 %rsp 減 8 寫回、再讀更新後的 %rsp 當寫入位址,就違反此原則。正確做法:ALU 產生
valE,同一訊號既當暫存器寫回資料、又當記憶體寫入位址,兩個寫入在 clock 上升時同時發生。 - CC 同理:OPq 設定 CC、cmov/jXX 讀 CC,但沒有指令既設又讀 CC——CC 在下個 cycle 開始才更新,讀它的一定是後續指令。
- 此原則成立(拜 Y86-64 指令集性質與計算的安排),表格「由上而下循序賦值」的語意就能用「一次 clock 轉換 + 組合邏輯傳播」等效實現。
兩個 cycle 的執行追蹤(Figure 4.25, p.438-439;程式:irmovq×2 → addq %rdx,%rbx → je dest,初始 CC=100):
Clock __|~~~~~~|______|~~~~~~|______
cycle 3 cycle 4
(1) (2) (3) (4)
(1) cycle 3 開始:狀態元件 = 前一條 irmovq 更新後的值;PC=0x014 載入
組合邏輯尚未反應
(2) cycle 3 結束:組合邏輯已依 addq 算出新值
(新 CC=000、%rbx 新值 0x300、新 PC=0x016) — 但狀態元件仍是舊值!
(3) clock 上升 → cycle 4 開始:PC / CC / register file 同時載入新值
組合邏輯尚未反應
(4) cycle 4 結束:je 的組合邏輯完成(ZF=0 → 不跳),新 PC=0x01f 待載入
每次 clock 由低變高,處理器就開始執行一條新指令;狀態元件整個 cycle 保持前一條指令的結果,所有狀態更新只在下一個上升沿同時發生。
4.3.4 SEQ 各階段實作(HCL 控制邏輯) (p.440-447)
HCL 常數(Figure 4.26,慣例用大寫命名):
| 類別 | 常數 (hex) |
|---|---|
| icode | IHALT=0, INOP=1, IRRMOVQ=2, IIRMOVQ=3, IRMMOVQ=4, IMRMOVQ=5, IOPQ=6, IJXX=7, ICALL=8, IRET=9, IPUSHQ=A, IPOPQ=B |
| 其他 | FNONE=0(預設 ifun)、RESP=4(%rsp)、RNONE=F(不存取暫存器)、ALUADD=0 |
| 狀態碼 | SAOK=1(正常)、SADR=2(位址例外)、SINS=3(非法指令)、SHLT=4(halt) |
另外 nop 幾乎不做事、只 PC+1;halt 把處理器狀態設為 HLT 而停機 (p.441)。
Fetch 階段 (p.441-442)
icode ifun rA rB valC valP
^ ^ ^ ^ ^
[icode] [ifun] [ Align ] [PC increment]
^ ^ ^ ^ ^
[ Split ] Bytes 1-9 need_regids need_valC
Byte 0 (1-bit, 由 icode 算出)
^_____________^
[ Instruction memory ] -- imem_error;另產生 instr_valid
^
[ PC ]
- 一次從 PC 讀 10 bytes(byte 0 = 指令位元組);Split 把 byte 0 拆成
icode:ifun;若imem_error(指令位址越界)則以nop的編碼取代。 - 由
icode算三個 1-bit 訊號:instr_valid(合法指令?偵測非法指令)、need_regids(有 register specifier byte?)、need_valC(有常數字?)。instr_valid與imem_error供 memory 階段產生狀態碼。 - Align 單元:
need_regids=1時 byte 1 拆成rA:rB,否則兩者皆為RNONE;valC取 bytes 1–8(無 regid byte,如 jXX/call)或 bytes 2–9。 - HCL 範例:
bool need_regids =
icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
IIRMOVQ, IRMMOVQ, IMRMOVQ };
- PC incrementer 公式:PC 值 p、need_regids 值 r、need_valC 值 i,則
valP = p + 1 + r + 8i(涵蓋 1/2/9/10 bytes 四種指令長度)。
Decode 與 Write-back 階段 (p.442-444)
兩階段合併實作,因為都存取 register file(四埠:讀 A/B、寫 E/M;每埠有位址線 = register ID 與 64-bit 資料線;位址為 RNONE 即不存取)。四個控制區塊依 icode、rA、rB(dstE 還需 Cnd)產生四個 register ID:
word srcA = [
icode in { IRRMOVQ, IRMMOVQ, IOPQ, IPUSHQ } : rA;
icode in { IPOPQ, IRET } : RESP;
1 : RNONE; # 不需讀暫存器
];
# 警告:此版尚未正確處理 conditional move
word dstE = [
icode in { IRRMOVQ } : rB;
icode in { IIRMOVQ, IOPQ } : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RESP;
1 : RNONE; # 不寫任何暫存器
];
(srcB、dstM 同理由各指令表歸納:srcB 對 OPq/rmmovq/mrmovq 為 rB、對 push/pop/call/ret 為 RESP;dstM 只在 mrmovq/popq 時為 rA。)
只有 popq 同時使用 E、M 兩個寫入埠。popq %rsp 時兩埠位址相同、資料不同,必須訂優先權:M port 優先,才符合「popq %rsp 把記憶體讀出的值放進 %rsp」的規格行為。
Execute 階段 (p.444-445)
ALU 對 aluA、aluB 依 alufun 執行 add / subtract / and / exclusive-or,輸出即 valE;三個控制區塊產生資料與控制訊號(Figure 4.29):
word aluA = [
icode in { IRRMOVQ, IOPQ } : valA;
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ } : valC;
icode in { ICALL, IPUSHQ } : -8;
icode in { IRET, IPOPQ } : 8;
# 其他指令不需要 ALU
];
word alufun = [
icode == IOPQ : ifun; # 只有 OPq 用 ifun 指定的運算
1 : ALUADD; # 其餘一律當加法器
];
bool set_cc = icode in { IOPQ }; # 只有 OPq 更新 CC
- 運算元排列 aluB 在前、aluA 在後,確保
subq算的是valB − valA(aluB 對 OPq/rmmovq/mrmovq 為valB,對 push/pop/call/ret 為valB(%rsp),對 rrmovq/irmovq 為 0)。 - ALU 每次運算都會產生 zero / sign / overflow 三訊號,但 set_cc 只在 OPq 時允許 CC 暫存器載入。
- cond 單元由 CC 與
ifun產生Cnd,同時供 cmovXX 的 dstE 條件化與 jXX 的 next-PC 邏輯;其他指令的 Cnd 值可能為 0 或 1 但會被控制邏輯忽略。cmovXX(icode 同IRRMOVQ)的實作即修改 dstE:條件不成立時把目的地改為 RNONE(Practice 4.24)。
Memory 階段 (p.445-447)
兩個控制區塊選出記憶體位址與寫入資料,另外兩個產生 read / write 控制訊號;讀取結果為 valM(Figure 4.30):
word mem_addr = [
icode in { IRMMOVQ, IPUSHQ, ICALL, IMRMOVQ } : valE;
icode in { IPOPQ, IRET } : valA;
# 其他指令不需位址
];
bool mem_read = icode in { IMRMOVQ, IPOPQ, IRET };
- 位址永遠是 valE 或 valA;寫入資料永遠是 valA 或 valP(rmmovq/pushq 寫 valA,call 寫 valP;Practice 4.25);
mem_write只在IRMMOVQ, IPUSHQ, ICALL時為真(Practice 4.26)。 - Memory 階段還負責產生狀態碼 Stat:綜合
icode(是否 IHALT)、fetch 階段的imem_error、instr_valid與 data memory 的dmem_error,輸出SAOK / SADR / SINS / SHLT之一(Practice 4.27, p.447)。
PC Update 階段 (p.447)
新 PC 依指令型別與分支旗標從 valC / valM / valP 選出(Figure 4.31):
word new_pc = [
icode == ICALL : valC; # call:目標位址
icode == IJXX && Cnd : valC; # 跳轉成立:目標位址
icode == IRET : valM; # ret:從 stack 彈出的返回位址
1 : valP; # 預設:下一條循序指令
];
Surveying SEQ:為什麼還不夠? (p.447-448)
SEQ 以「均勻流程 + 少量硬體單元 + 單一 clock 控制時序」實現了完整 Y86-64 處理器;控制邏輯負責在單元間路由訊號、依指令型別與分支條件產生控制訊號。但 SEQ 唯一的問題是太慢:
- Clock 必須慢到讓訊號在一個 cycle 內傳過所有階段。以
ret為例:讀 instruction memory → 讀 register file(%rsp)→ ALU 加 8 → 讀 data memory(返回位址)→ 才能決定新 PC——全部須在 cycle 結束前完成,cycle time 由最長路徑決定。 - 每個硬體單元只在整個 cycle 的一小段時間內活動,利用率極差。
- 解法:pipelining(提升 throughput,latency 可能微增)——見 04-Processor-Architecture/04-Pipelining-Principles。
Exam/Test Patterns
| 情境 / 關鍵字 | 答案 |
|---|---|
| 指令長度 → valP | valP = PC + 1 + r + 8i(r = need_regids, i = need_valC);OPq/push/pop/rrmovq 為 PC+2,jXX/call 為 PC+9,irmovq/rmmovq/mrmovq 為 PC+10,nop/halt/ret 為 PC+1 |
subq %rax,%rdx 的 ALU 計算 |
valE ← valB − valA = R[%rdx] − R[%rax](aluB 在前) |
| 哪些指令設定 CC | 只有 OPq(set_cc = icode in {IOPQ});irmovq/rrmovq/cmovXX 皆不設 |
pushq %rsp 推入什麼 |
%rsp 的舊值(valA 於 decode 讀取,早於 %rsp 更新) |
popq %rsp 後 %rsp = ? |
記憶體讀出的值 valM(M port 優先於 E port) |
| popq 的記憶體位址 | valA(未遞增的 %rsp);寫回 %rsp 的才是 valE = valB+8 |
| pushq 的記憶體位址 | valE = valB−8(遞減後)——「先減後寫」慣例 |
| 給定 trace 填表(4.13/4.14/4.18 型) | 套 Figure 4.18–4.21 的 generic 欄,代入具體暫存器值與位址 |
| 需要 register specifier byte 的指令 | rrmovq/cmovXX、irmovq、rmmovq、mrmovq、OPq、pushq、popq(jXX/call/ret/nop/halt 不需要) |
| 需要 valC 的指令 | irmovq、rmmovq、mrmovq(bytes 2–9);jXX、call(bytes 1–8) |
| SEQ 中唯一的時鐘暫存器 | PC;需時序控制的四單元 = PC、CC、data memory、register file |
| 何時寫 data memory | 只有 rmmovq、pushq、call(mem_write) |
| 何時讀 data memory | 只有 mrmovq、popq、ret(mem_read) |
| 「No reading back」是什麼 | 本指令永不讀回自己更新的狀態;pushq 用 valE 同時當寫回資料與寫入位址即為例證 |
| new_pc 的三個來源 | valC(call、taken jXX)、valM(ret)、valP(其他) |
| Stat 的來源訊號 | icode(IHALT)、imem_error、instr_valid、dmem_error → SAOK/SADR/SINS/SHLT |
| cmovXX 如何實作 | icode 同 IRRMOVQ;execute 產生 Cnd,Cnd=0 時 dstE 改為 RNONE |
| SEQ 為何慢 | 單 cycle 須容納最長路徑(如 ret 的 4 次串聯存取);單元利用率低 → 引入 pipeline |
Related Notes
- 04-Processor-Architecture/01-Y86-64-ISA — SEQ 所實作的指令集、編碼與 icode/ifun 定義
- 04-Processor-Architecture/02-Logic-Design-and-HCL — 本節 HCL 控制邏輯、組合邏輯與時鐘暫存器的基礎
- 04-Processor-Architecture/04-Pipelining-Principles — 解決 SEQ 時脈過慢問題的下一步
- 04-Processor-Architecture/05-Pipelined-PIPE-Implementation-and-Hazards — 由 SEQ 演化出的管線化實作
- 03-Machine-Level-Programs/04-Procedures-and-the-Stack — call/ret 與堆疊慣例在 x86-64 的對應
- 08-Exceptional-Control-Flow/01-Exceptions — 完整處理器對例外的處理方式