循序 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 讀最多兩個運算元(通常是 rArB 指定的,但 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,只差 ifunrrmovq/irmovq 用「加 0」讓值通過 ALU,且不改 CC

記憶體存取指令(rmmovq / mrmovq)— ALU 當位址加法器 (p.425)

Stage rmmovq rA, D(rB) mrmovq D(rB), rA
Fetch icode:ifunrA:rBvalC ← 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:ifunrA:rBvalP ← 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

控制轉移指令(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)。

追蹤範例速記(Figure 4.17 程式,p.424-431 各 Aside)

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):

繪圖慣例(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 僅執行 rmmovqpushqcall
Register file 兩個寫入埠每 cycle 皆可寫;埠位址為 0xF (RNONE) 表示該埠不寫
No reading back 原則 (p.437)

處理器永遠不需要讀回「本指令更新後的狀態」來完成本指令的處理。

  • 反例:若 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,%rbxje 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 ]
bool need_regids =
        icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
                   IIRMOVQ, IRMMOVQ, IMRMOVQ };

Decode 與 Write-back 階段 (p.442-444)

兩階段合併實作,因為都存取 register file(四埠:讀 A/B、寫 E/M;每埠有位址線 = register ID 與 64-bit 資料線;位址為 RNONE 即不存取)。四個控制區塊依 icoderArB(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 %rsp 的寫入埠優先權(Practice 4.22, p.444)

只有 popq 同時使用 E、M 兩個寫入埠。popq %rsp 時兩埠位址相同、資料不同,必須訂優先權:M port 優先,才符合「popq %rsp 把記憶體讀出的值放進 %rsp」的規格行為。

Execute 階段 (p.444-445)

ALU 對 aluAaluBalufun 執行 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

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 };

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 唯一的問題是太慢:

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 只有 rmmovqpushqcall(mem_write)
何時讀 data memory 只有 mrmovqpopqret(mem_read)
「No reading back」是什麼 本指令永不讀回自己更新的狀態;pushq 用 valE 同時當寫回資料與寫入位址即為例證
new_pc 的三個來源 valC(call、taken jXX)、valM(ret)、valP(其他)
Stat 的來源訊號 icode(IHALT)、imem_errorinstr_validdmem_error → SAOK/SADR/SINS/SHLT
cmovXX 如何實作 icode 同 IRRMOVQ;execute 產生 Cnd,Cnd=0 時 dstE 改為 RNONE
SEQ 為何慢 單 cycle 須容納最長路徑(如 ret 的 4 次串聯存取);單元利用率低 → 引入 pipeline