Topic 12: Single-Cycle Datapath & Control

310-2202 โครงสร้างระบบคอมพิวเตอร์ — สรุปเนื้อหา Topic 12

ภาพรวม Topic 12 — Single-Cycle Datapath & Control

ครั้งนี้เราสร้าง "ฮาร์ดแวร์จริง" ที่รันคำสั่ง RV32I — เปลี่ยนวงรอบ FETCH-DECODE-EXECUTE เป็นบล็อกที่ต่อสายกัน (PC, instruction memory, register file, ALU, immediate generator, data memory, mux, adder) สร้าง datapath ทีละกลุ่มคำสั่ง พร้อมหน่วยควบคุมที่ถอด opcode เป็นสัญญาณ และเข้าใจว่าทำไม single-cycle ถูกต้องแต่ช้า

เมื่อจบครั้งนี้ ผู้เรียนจะสามารถ: ระบุบล็อกของ single-cycle datapath และเส้นทางข้อมูลของแต่ละชนิดคำสั่ง (R/lw/sw/beq/jal) · อธิบายสัญญาณควบคุม (RegWrite/ALUSrc/MemRead/MemWrite/Branch/ALUOp/PCSrc) · อธิบาย critical path และเหตุผลที่ single-cycle ช้า (จูงใจสู่ pipelining)

ถ้าจำได้ประโยคเดียววงรอบ FETCH-DECODE-EXECUTE กลายเป็นสายไฟจริงได้ และสัญญาณควบคุมคือสิ่งที่บอกว่าคำสั่งไหนใช้เส้นทางใด
หัวข้อเนื้อหา
12.1เปลี่ยนวงรอบ FETCH-DECODE-EXECUTE เป็นบล็อกฮาร์ดแวร์ 8 ชนิด แล้วต่อเป็น datapath ภาพรวม
12.2สร้าง datapath ทีละกลุ่มคำสั่ง R-type → lw/sw → beq → jal/jalr เห็นว่า mux แต่ละตัวเกิดตรงไหนเพราะอะไร
12.3หน่วยควบคุม: opcode → สัญญาณ 8 ตัว แล้ว ALU control ถอด funct เป็น operation
12.4ทำไม single-cycle ถูกต้องแต่ช้า: critical path ของ lw กำหนด clock period และมองผ่าน Iron Law
🎮 เครื่องมือแนะนำRipes — เปิดมุมมอง single-cycle processor แล้วรันทีละคำสั่ง สังเกตสัญญาณควบคุม (RegWrite/ALUSrc/MemRead/…) สว่างตามชนิดคำสั่ง และเส้นทางข้อมูลไหลผ่าน mux/ALU/memory

12.1 จากวงรอบคำสั่งสู่บล็อกฮาร์ดแวร์

วงรอบ FETCH-DECODE-EXECUTE คือบล็อกฮาร์ดแวร์จริง

ก่อนหน้านี้เราเห็นวงรอบคำสั่ง (instruction cycle) เป็นขั้นตอนที่ CPU ทำซ้ำ: FETCH → DECODE → EXECUTE ใน Topic นี้เราจะเปลี่ยนแต่ละขั้นให้เป็นบล็อกฮาร์ดแวร์จริง แล้วต่อสายเข้าด้วยกันเป็นทางเดินข้อมูล (datapath) — ขั้นตอนเหล่านี้ไม่ต้องรอขอบนาฬิกาคั่นระหว่างขั้น ฮาร์ดแวร์วางบล็อกเรียงต่อกัน แล้วสัญญาณไหลผ่านทุกบล็อกตามลำดับให้จบภายในหนึ่งคาบเดียว

แนวคิดกุญแจ: ขั้นตอน (เวลา) → บล็อก (พื้นที่)FETCH-DECODE-EXECUTE เป็นการ "เล่าเรื่อง" ว่า CPU ทำอะไร ฮาร์ดแวร์จริงไม่รอให้ขั้นหนึ่งจบก่อนแล้วค่อยเริ่มขั้นถัดไป แต่ต่อบล็อกทั้งหมดเป็นสายเดียว สัญญาณไฟฟ้าไหลผ่านตามลำดับ combinational ภายในหนึ่งคาบนาฬิกา นี่คือที่มาของชื่อ single-cycle (หนึ่งคำสั่งจบในหนึ่ง clock)

FETCH (ดึงคำสั่ง): PC (program counter) เก็บที่อยู่คำสั่งปัจจุบัน → ป้อนให้ instruction memory อ่านคำสั่ง 32 บิตออกมา พร้อมกันมี adder บวก PC+4 เตรียมที่อยู่คำสั่งถัดไป (RV32I คำสั่งยาว 4 ไบต์เสมอ) · DECODE (ถอดรหัส): แยกฟิลด์ออกจากคำสั่ง 32 บิต — หมายเลข rs1, rs2, rd ส่งเข้า register file เพื่ออ่านค่า ส่วนบิต immediate ส่งเข้า immediate generator เพื่อขยายเป็นค่า 32 บิต ฟิลด์ opcode/funct ส่งให้หน่วยควบคุม · EXECUTE (ปฏิบัติการ): ALU คำนวณ (บวก/ลบ/AND/เปรียบเทียบ) แล้วผลลัพธ์อาจใช้เป็นที่อยู่เข้า data memory (สำหรับ load/store) หรือเขียนกลับเข้า register file

🎮 ลองเลยไล่ทีละเฟสของวงรอบคำสั่งแบบเดิม (FETCH → DECODE → EVALUATE ADDRESS → FETCH OPERANDS → EXECUTE → STORE RESULT) ก่อนดูว่ามันกลายเป็นบล็อกฮาร์ดแวร์ต่อกันได้อย่างไร

บล็อกพื้นฐาน 8 ชนิดของ single-cycle datapath

บล็อกหน้าที่ชนิด
PCเก็บที่อยู่คำสั่งปัจจุบัน อัปเดตทุกขอบนาฬิกาstate (มี clock)
Instruction memoryรับที่อยู่ → คืนคำสั่ง 32 บิต (อ่านอย่างเดียว)combinational (อ่าน)
Register fileอ่าน 2 พอร์ต (rs1, rs2) พร้อมกัน, เขียน 1 พอร์ต (rd)state + อ่าน combinational
ALUคำนวณเลขคณิต/ตรรกะ ตาม ALU controlcombinational
Immediate generatorดึง imm จากคำสั่ง แล้ว sign-extend เป็น 32 บิตcombinational
Data memoryload (อ่าน) / store (เขียน) ข้อมูลในหน่วยความจำstate + อ่าน combinational
Muxเลือกหนึ่งจากหลายเส้นทางข้อมูลตามสัญญาณควบคุมcombinational
Adderบวกที่อยู่ (PC+4, PC+imm สำหรับ branch)combinational

บล็อกทั้งแปดต่อกันเป็นวง: PC ป้อนที่อยู่ให้ Instruction memory คำสั่งที่อ่านได้ส่งเลขรีจิสเตอร์ให้ Registers ค่าที่อ่านได้เข้า ALU และผลของ ALU กลายเป็น Address ของ Data memory แล้ววนกลับมาที่ PC ทางตัวบวก — ที่มา: Patterson & Hennessy, Computer Organization and Design (RISC-V Edition) รูปที่ 4.1

state vs combinationalบล็อกที่ "จำค่า" และอัปเดตตามขอบนาฬิกา (PC, register file, memory) เป็น state element บล็อกที่คำนวณผลทันทีจาก input โดยไม่จำอะไร (ALU, mux, adder, immediate generator) เป็น combinational ค่าใหม่ของ state element ทุกตัวถูก "ล็อกเข้า" พร้อมกันที่ขอบนาฬิกาเดียวกัน นี่คือกลไกที่ทำให้หนึ่งคำสั่งจบพอดีในหนึ่งคาบ
เหตุใด instruction memory และ register file (พอร์ตอ่าน) จึงทำงานแบบ combinational คือไม่ต้องรอขอบนาฬิกาก่อนคืนค่า
ตอบ: เพราะภายในคาบเดียวต้องได้คำสั่งและค่ารีจิสเตอร์ป้อนให้ ALU คำนวณทัน พอร์ตอ่านจึงคืนค่าตามที่อยู่ที่ป้อนทันที (ทั้งสองบล็อกยังมีหน่วยความจำเก็บค่าจริงอยู่ภายใน เป็น state element เพียงแต่ "ส่วนอ่าน" ทำงานแบบ combinational — ถ้าการอ่านต้องรอขอบนาฬิกาอีกชั้นจะทำงานครบไม่ทันในหนึ่ง clock)
ถ้าทั้ง instruction memory และ data memory ต้องทำงานในคาบเดียวกัน ทำไม single-cycle จึงนิยมแยกเป็นสองหน่วยความจำคนละก้อน
ตอบ: เพราะในคาบเดียว FETCH ต้องอ่านคำสั่งจาก instruction memory ขณะที่ load/store ต้องเข้าถึง data memory พร้อมกัน ถ้าใช้หน่วยความจำก้อนเดียวที่มีพอร์ตเดียว จะอ่านสองที่ในคาบเดียวไม่ได้ (โครงสร้างชนกัน) การแยกสองก้อนทำให้ทั้ง FETCH และ MEM เกิดขนานกันได้ในหนึ่งคาบ

ต่อบล็อกเป็น datapath ภาพรวม

flowchart LR PC["PC"] --> IM["Instructionmemory"] PC --> ADD4["adder (+4)"] IM --> RF["Register fileอ่าน 2 / เขียน 1"] IM --> IMM["Immediategenerator"] RF -->|rs1| ALU["ALU"] RF -->|rs2| MUXB["mux ขา B"] IMM --> MUXB MUXB --> ALU ALU --> DM["Datamemory"] ALU --> WBMUX["mux write-back"] DM --> WBMUX ADD4 --> WBMUX WBMUX -->|rd| RF IMM --> ADDBR["adder (PC+imm)"] PC --> ADDBR ADD4 --> PCMUX["mux ขา PC"] ADDBR --> PCMUX PCMUX --> PC

ลำดับการไหลในหนึ่งคาบ: PC ชี้ที่อยู่ → instruction memory คืนคำสั่ง 32 บิต → คำสั่งถูกแยกฟิลด์ (rs1,rs2 → register file; imm → immediate generator) → mux หน้า input ที่สองของ ALU เลือกว่าจะใช้ค่าจาก rs2 หรือค่า immediate → ALU คำนวณ → ผลลัพธ์เป็นค่าที่จะเขียนกลับ register file หรือที่อยู่เข้า data memory → mux หน้าพอร์ตเขียนของ register file เลือกจะเขียนผล ALU หรือข้อมูลจาก data memory (load) หรือค่า PC+4 (jal/jalr) → adder บวก PC+4 และอีก adder บวก PC+imm สำหรับ branch/jump → mux หน้า PC เลือกหนึ่งในนั้น แล้วล็อกเข้า PC ที่ขอบนาฬิกาถัดไป (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.2)

register file: ทำไม 2 พอร์ตอ่าน 1 พอร์ตเขียน

คำสั่งแบบ R ของ RV32I (เช่น add x1,x2,x3) ต้องอ่านรีจิสเตอร์ต้นทางสองตัวพร้อมกัน (rs1, rs2) ในคาบเดียว จึงต้องมี 2 พอร์ตอ่าน ที่ทำงานขนานกัน และคำสั่งหนึ่งเขียนผลกลับได้มากสุดหนึ่งปลายทาง (rd) จึงมี 1 พอร์ตเขียน การเขียนเกิดที่ขอบนาฬิกา ส่วนการอ่านคืนค่าทันที

mux และ adder: กาวที่เชื่อมบล็อก

mux สำคัญมีสามจุด (สัญญาณสั่งมาจากหน่วยควบคุม 12.3):

mux อยู่ที่เลือกระหว่างสัญญาณสั่ง
ขา B ของ ALUrs2 / immediateALUSrc
write-back (หน้าพอร์ตเขียน rd)ผล ALU / ค่า load / PC+4MemToReg
หน้า PCPC+4 / PC+imm / rs1+imm (jalr)PCSrc
ตัวอย่าง — ไล่ lw x5, 48(x2) ผ่าน datapath ครบทุกบล็อก

กำหนด x2 = 100, หน่วยความจำที่ที่อยู่ 148 เก็บค่า 777, รหัสเครื่อง 0x03012283, PC ปัจจุบัน = 200

  1. FETCH: PC=200 → instruction memory คืน 0x03012283 = lw x5,48(x2) · adder(+4) คำนวณ 200+4=204
  2. DECODE: rs1=x2, rd=x5, imm=48 (I-format, sign-extend) · register file อ่าน x2=100 · immediate generator ขยาย imm→48
  3. mux ขา B: ALUSrc เลือก imm (ไม่ใช่ rs2) → ป้อน 48 เข้า ALU input ที่สอง
  4. EXECUTE/ALU: บวก 100+48=148 ← ที่อยู่ของหน่วยความจำที่จะโหลด
  5. MEM: MemRead=1 → อ่าน data memory ที่ address 148 → ได้ค่า 777
  6. WRITE-BACK: write-back mux เลือก "ค่าจาก data memory" (ไม่ใช่ผล ALU) → 777, RegWrite=1 → เขียน 777 ลง x5
  7. เลือก PC ถัดไป: lw ไม่ใช่ branch/jump → PC mux เลือก PC+4=204
สรุป: x5 ← 777, PC ถัดไป = 204 — ALU ของ lw ให้ "ที่อยู่" (148) ไม่ใช่คำตอบ คำตอบ (777) ออกมาจาก data memory จึงต้องมี mux หน้าพอร์ตเขียนเลือกขา memory ก่อนลง x5 (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.24)
🎮 ลองเลยไล่ทีละบล็อกของ single-cycle datapath จริง — เลือกคำสั่ง lw (หรือคำสั่งอื่น) แล้วกด "ขั้นถัดไป" ดูว่าบล็อกไหนสว่าง สายไหนมีค่าวิ่ง
ในการรัน lw x5, 8(x6) บน single-cycle datapath บล็อกใดทำงานตามลำดับให้ได้ค่าโหลดมาเขียนลง x5
ตอบ: register file (อ่าน x6) → mux ขา B เลือก imm → ALU บวกหา address → data memory อ่าน → write-back mux เลือกค่าจาก data memory → เขียนลง x5 — lw เป็น I-format จึงเลือก imm เข้า ALU ไม่ใช่ rs2 และปลายทางต้องมาจาก data memory ไม่ใช่ผล ALU (ถ้าเลือกผล ALU จะได้แค่ที่อยู่ x6+8 ไม่ใช่ข้อมูลที่โหลด)
อ้างอิง
  • D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4

12.2 สร้าง Datapath ทีละกลุ่มคำสั่ง

วิธีสร้างแบบ COD: ค่อย ๆ เพิ่มทีละชนิดคำสั่ง

วิธีสร้าง datapath แบบ COD (Patterson & Hennessy) คือค่อย ๆ เพิ่มทีละชนิดคำสั่ง: เริ่มจากชิ้นส่วนที่ทุกคำสั่งใช้ร่วมกัน (instruction fetch) แล้วถามทีละกลุ่มว่า "คำสั่งนี้ต้องการ wire/mux/หน่วยใหม่อะไรเพิ่ม" ทำให้เห็นว่า mux และสัญญาณควบคุมแต่ละตัวมีไว้เพื่ออะไร แทนที่จะจ้องวงจรสำเร็จทั้งก้อน

กติกาการเพิ่มทีละชนิดเมื่อคำสั่งใหม่ต้องการ "แหล่งข้อมูล" ที่ต่างจากของเดิมเข้าจุดเดียวกัน เราไม่สร้างจุดนั้นซ้ำ แต่เพิ่ม mux มาเลือก แล้วให้ control unit เป็นคนสั่งว่าเลือกขาไหน ดังนั้นจำนวน mux ที่เพิ่มขึ้น คือ "ความหลากหลาย" ของชนิดคำสั่งที่ datapath รองรับ

ชิ้นส่วนร่วม (instruction fetch)

ทุกคำสั่งเริ่มเหมือนกัน: ใช้ PC ชี้ instruction memory เพื่ออ่านคำสั่ง 32 บิต แล้วเตรียม PC ของรอบถัดไปด้วย PC+4 (คำสั่งยาว 4 byte เสมอใน RV32I — ต่างจาก LC-3 ที่นับที่อยู่เป็น word จึงบวก +1)

ขั้นที่ 1: R-type (add, sub, and, or, slt)

รูปแบบ: rd = rs1 (op) rs2 ต้องการ register file (อ่าน 2 พอร์ต rs1,rs2 พร้อมกัน เขียน 1 พอร์ตที่ rd) ต่อเข้า ALU ซึ่งทำ op ตาม funct3/funct7 แล้วเขียนผลกลับ register file สิ่งที่เพิ่ม: register file + ALU + สาย rs1/rs2 → ALU → rd และสัญญาณ RegWrite (อนุญาตเขียน rd) กับ ALUControl (เลือกว่า ALU ทำ add/sub/and/…) — ยังไม่มี mux ที่ไหนเลยเพราะทุกขามีแหล่งข้อมูลทางเดียว

flowchart LR INST["คำสั่ง"] --> RF["Register Filers1, rs2 → rd"] RF -->|"data1"| ALU["ALU"] RF -->|"data2"| ALU ALU -->|"ผลลัพธ์"| RF CTRL["ALUControl"] --> ALU

ซ้ายคือ register file: ช่อง Read register 1, Read register 2 และ Write register กว้าง 5 บิต ให้ Read data 1 กับ Read data 2 ออกมาสองค่าพร้อมกัน ส่วน RegWrite คุมพอร์ตเขียนอย่างเดียว ขวาคือ ALU ที่รับ ALU operation 4 บิตแล้วให้ ALU result กับธง Zero — ยังไม่มี mux เลยเพราะทุกขามีแหล่งข้อมูลทางเดียว (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.7)

ตัวอย่าง — add x5, x6, x7 ผ่านบล็อกที่มีแค่ R-type

x6=7, x7=32, รหัสเครื่อง 0x007302B3

  1. FETCH: PC → instruction memory คืน 0x007302B3 · ตัวบวก+4 เตรียม PC+4 ขนานกัน
  2. แยกฟิลด์: rs1=6, rs2=7, rd=5, funct3=000, funct7=0000000
  3. register file อ่าน x6=7, x7=32
  4. ALU: ALUControl สั่ง add → 7+32=39
  5. เขียนกลับ: RegWrite=1 → x5←39 ที่ขอบนาฬิกา, PC←PC+4
จำนวน mux ที่ต้องตัดสินใจ = 0 — ทุกจุดมีแหล่งข้อมูลทางเดียว (ขา B ของ ALU มาจาก rs2 เท่านั้น พอร์ตเขียน rd มาจากผล ALU เท่านั้น) พอเพิ่ม lw ในขั้นถัดไป สองจุดนี้จะมีแหล่งที่สอง — และ mux จะเกิดตรงนั้นพอดี

ขั้นที่ 2: เพิ่ม lw / sw

load/store เป็นรูปแบบ I และ S: ที่อยู่ = rs1 + immediate เกิดความต้องการใหม่ 3 อย่าง: ImmGen (ดึง immediate จากคำสั่งแล้ว sign-extend เป็น 32 บิต — lw ดึงแบบ I-type, sw ดึงแบบ S-type) · ALU ต้องรับ immediate เป็นตัวตั้งที่ 2 แทน rs2 → เพิ่ม ALUSrc mux ที่ขาเข้าที่สองของ ALU (R-type เลือก rs2 ส่วน lw/sw เลือก immediate) · Data Memory — lw อ่าน (MemRead) เอาค่ากลับมาเขียน rd, sw เขียน (MemWrite) ค่าจาก rs2 ลงหน่วยความจำ (sw ไม่เขียน rd) และเพราะค่าที่จะเขียนกลับ rd มาได้จาก 2 ทาง (ผล ALU สำหรับ R-type, ค่าจาก memory สำหรับ lw) จึงต้องเพิ่ม MemtoReg mux หน้าพอร์ตเขียนของ register file

สิ่งที่เพิ่มทำไมสัญญาณ
ImmGenlw/sw ต้องมี imm 32 บิต—
ALUSrc muxขา B ของ ALU มีสองแหล่ง (rs2 / imm)ALUSrc
Data Memorylw อ่าน / sw เขียนMemRead, MemWrite
MemtoReg muxพอร์ตเขียน rd มีสองแหล่ง (ALU / memory)MemToReg

โครง R-type เดิมไม่ถูกรื้อ เพียงแทรก mux เข้าไป — นับกล่อง Mux ได้สองกล่อง: ALUSrc (ขา 0=Read data 2, ขา 1=ค่าจาก Imm Gen) และ MemtoReg (ขา 1=Read data จาก Data memory, ขา 0=ALU result) (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.10)

flowchart LR RF["Register File"] -->|"data1 = rs1"| ALU["ALU"] RF -->|"data2 = rs2"| MUX1{"ALUSrcmux"} IMM["ImmGen"] --> MUX1 MUX1 --> ALU ALU -->|"ที่อยู่"| DM["DataMemory"] RF -->|"rs2 = ค่าที่จะ store"| DM DM -->|"ค่าที่ load"| MUX2{"MemtoRegmux"} ALU -->|"ผล R-type"| MUX2 MUX2 -->|"เขียนกลับ"| RF
เพราะเหตุใดการเพิ่ม lw จึงทำให้ต้องมี MemtoReg mux ที่พอร์ตเขียนของ register file
ตอบ: เพราะค่าที่เขียนกลับ rd มาได้จากสองแหล่ง คือผล ALU (R-type) กับค่าจาก data memory (lw) จึงมี mux เลือก — mux เลือกเส้นทางข้อมูล ไม่ได้ทำหน้าที่หน่วงเวลา (ไม่ใช่เพราะ data memory ช้ากว่า ALU)
นับจำนวน mux ที่ต้องเพิ่มเข้า datapath เมื่อขยายจาก R-type ล้วน ไปรองรับ lw และ sw (ยังไม่นับ branch)
ตอบ: 2 ตัว — (1) ALUSrc mux เลือก rs2 หรือ immediate ป้อนขาที่สองของ ALU เพื่อคำนวณที่อยู่ rs1+imm และ (2) MemtoReg mux เลือกผล ALU หรือค่าจาก data memory ไปเขียน rd สำหรับ sw: ที่อยู่ = rs1+imm ใช้ ALUSrc mux ตัวเดิม (ไม่เพิ่ม), ค่าที่เขียน = rs2 ต่อสายตรงเข้าพอร์ต write-data ของ Data Memory, sw ไม่เขียน rd จึง RegWrite=0 ไม่แตะ MemtoReg mux

ขั้นที่ 3: เพิ่ม beq (branch)

beq rs1, rs2, label: ถ้า rs1==rs2 ให้กระโดดไปที่ PC+offset (PC-relative, offset เป็น signed 13 บิต ทวีคูณของ 2) ถ้าไม่เท่ากันไป PC+4 ตามปกติ ความต้องการใหม่: เปรียบเทียบ rs1 กับ rs2 — ตั้ง ALU ทำ subtract แล้วดูธง Zero (Zero=1 เมื่อ rs1−rs2=0) ใช้ ALU ตัวเดิม ไม่ต้องเพิ่มตัวเปรียบเทียบ (branch ป้อน rs2 เข้า ALU ดังนั้น ALUSrc=0) · Branch adder (ตัวบวกแยก) คำนวณ PC+immediate ขนานไปกับ PC+4 · PCSrc mux ที่ทางเข้า PC เลือกระหว่าง PC+4 กับ PC+offset สัญญาณเลือกคือ PCSrc = Branch AND Zero — กระโดดเฉพาะเมื่อเป็นคำสั่ง branch และเงื่อนไขเป็นจริง

flowchart LR PC["PC"] --> ADD4["+4"] PC --> BADD["branch adderPC + imm"] IMM["ImmGen"] --> BADD ADD4 --> PMUX{"PCSrcmux"} BADD --> PMUX ALU["ALU (sub)"] -->|"Zero"| AND["Branch AND Zero"] AND --> PMUX PMUX -->|"PC รอบถัดไป"| PC

ImmGen ที่มีอยู่ถูกใช้ซ้ำ เพียงเพิ่มรูปแบบ B-type — beq จึงไม่เพิ่ม ALU หรือพอร์ตรีจิสเตอร์ใหม่เลย ของใหม่จริงมีแค่ branch adder, PCSrc mux และเกต AND ของ Branch กับ Zero (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.9, 4.25)

ตัวอย่าง — beq x5, x6, L ที่ address 0x40, offset +16
  1. x5=7, x6=7: register file ส่ง rs1=7, rs2=7
  2. ALU ทำ subtract → 7−7=0 → Zero=1
  3. control ถอด opcode 1100011 (beq) → Branch=1
  4. PCSrc = Branch AND Zero = 1 AND 1 = 1 → เลือกขากระโดด
  5. branch adder: PC+imm = 0x40+16 = 0x50 → PC ถัดไป = 0x50
ถ้า x6=9 แทน: 7−9=−2≠0 → Zero=0 → PCSrc=1 AND 0=0 → mux เลือก PC+4=0x44 ทั้งที่ Branch ยังเป็น 1 — สาย Branch มาจากการถอด opcode ส่วนธง Zero มาจาก ALU ทั้งสองต้องเป็น 1 พร้อมกันจึงจะกระโดด
เหตุใดสัญญาณเลือกของ PCSrc mux ในกรณี beq จึงเป็น Branch AND Zero แทนที่จะเป็น Branch เพียงอย่างเดียว
ตอบ: เพราะ Branch บอกแค่ว่าเป็นคำสั่ง branch แต่ยังต้องดู Zero ว่า rs1 เท่ากับ rs2 จริงหรือไม่ จึงจะตัดสินกระโดด (beq เป็น conditional branch ไม่ใช่ unconditional)

ขั้นที่ 4: เพิ่ม jal และ jalr (jump)

สองคำสั่งนี้ "กระโดดเสมอ" (unconditional) และต้องเก็บ return address = PC+4 ลง rd

คำสั่งที่อยู่เป้าหมายใครคำนวณค่าที่เขียนลง rd
jal rd, label (J-type)PC + imm (PC-relative)branch adder ตัวเดิมPC + 4
jalr rd, rs1, imm (I-type)rs1 + imm (บิตล่างสุดบังคับเป็น 0)ALU (ALUSrc เลือก immediate)PC + 4

ผลต่อ datapath มีสองจุด: ทางเข้า PC มีสามแหล่ง (PC+4, PC+imm, ผล ALU) — PCSrc mux ขยายเป็นหลายขา และสาย Jump=1 บังคับเลือกขากระโดดโดยไม่ดู Zero · พอร์ตเขียน rd มีแหล่งที่สาม คือ PC+4 — write-back mux ขยายอีกหนึ่งขา

flowchart LR ADD4["PC+4"] --> WBMUX{"write-back mux"} ALU["ALU result"] --> WBMUX DM["Data Memory"] --> WBMUX WBMUX -->|"เขียน rd"| RF["Register File"] ADD4 --> PMUX{"PCSrcmux (หลายขา)"} BADD["PC+imm"] --> PMUX ALU -->|"rs1+imm (jalr)"| PMUX PMUX -->|"PC ถัดไป"| PCN["PC"]

ของใหม่ในผังนี้มีแค่สองขา — ขา PC+4 ที่เข้า write-back mux (สำหรับเก็บ return address) และขาผล ALU ที่เข้า PCSrc mux (สำหรับ jalr) ที่เหลือเป็นบล็อกเดิมทั้งหมด

ตัวอย่าง — jal x1,L ที่ 0x40 (offset +8) แล้ว jalr x0,0(x1)
  1. jal x1,L (PC=0x40, imm=+8): ตัวบวก+4 = 0x44 (return address) · branch adder PC+imm=0x48 (เป้าหมาย) · Jump=1 → PCSrc mux เลือก PC+imm → PC ถัดไป=0x48 · RegWrite=1, write-back mux เลือกขา PC+4 → x1←0x44
  2. jalr x0,0(x1) (x1=0x44,imm=0): ALUSrc=1: ALU บวก rs1+imm=0x44+0=0x44 · Jump=1 → PCSrc mux เลือกขา "ผล ALU" → PC ถัดไป=0x44 · rd=x0 → เขียน PC+4 ลง x0 ไม่มีผล — นี่คือ ret นั่นเอง
สรุป: x1=0x44, jal ไป 0x48, jalr กลับมา 0x44 — ค่า 0x44 ทำหน้าที่ต่อกันสองรอบ: รอบของ jal เขียนมันลง x1 เป็น return address แล้วรอบของ jalr อ่านค่าเดิมนี้จาก x1 มาใช้เป็นที่อยู่เป้าหมาย
ข้อใดอธิบายความต่างของการคำนวณที่อยู่เป้าหมายระหว่าง jal กับ jalr ได้ถูกต้อง
ตอบ: jal ใช้ PC+immediate (PC-relative) ใช้ branch adder เดิมได้ แต่ jalr ใช้ rs1+immediate (register-indirect) ต้องให้ ALU คำนวณ (ALUSrc เลือก immediate) เพราะมาคนละแหล่ง ทางเข้า PC จึงต้องมี mux เลือกระหว่าง PC+4, PC+imm และ ALU result

สรุปภาพรวม: แต่ละชนิดเพิ่มอะไร

ชนิดคำสั่งหน่วย/สายใหม่mux ที่เพิ่มcontrol ใหม่
R-typeregister file, ALU—RegWrite, ALUControl
lw / swImmGen, data memoryALUSrc, MemtoRegMemRead, MemWrite
beqbranch adder (PC+imm)PCSrc (หน้า PC)Branch (+ ลอจิก AND กับ Zero)
jal / jalr(ใช้ adder/ALU เดิม)ขยาย PCSrc + write-back mux (เพิ่มขา PC+4)Jump, jalr-select
หลังเพิ่มครบทั้ง jal และ jalr แล้ว write-back mux มีแหล่งข้อมูลป้อนเข้ากี่ทาง สำหรับชุดคำสั่งที่สอนใน Topic นี้ (R-type, lw, sw, beq, jal/jalr)
ตอบ: 3 แหล่ง — (1) ผลลัพธ์ ALU สำหรับ R-type/I-arith (2) ค่าที่อ่านจาก data memory สำหรับ lw (3) PC+4 = return address สำหรับ jal/jalr
🎮 ทบทวนภาพใหญ่ก่อนไปหน่วยควบคุมจับคู่อุปกรณ์จริงกับห้าองค์ประกอบคลาสสิกของคอมพิวเตอร์ (datapath / control / memory / input / output) ก่อนไปดูว่าหน่วยควบคุมสั่งงาน datapath ที่เพิ่งสร้างนี้อย่างไร
อ้างอิง
  • D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4

12.3 หน่วยควบคุม (Control Unit)

ถอด opcode เป็นสัญญาณควบคุม

datapath ใน 12.2 มี mux, หน่วยความจำ และ register file ที่ทำได้ทุกอย่าง — แต่จะทำอะไรขึ้นกับชนิดของคำสั่ง หน่วยควบคุม (control unit) คือวงจรเชิงรวม (combinational) ที่รับ opcode (7 บิต) แล้วผลิตสัญญาณควบคุม (control signal) ไปสั่งทุก mux และทุก enable ให้ตรงกับคำสั่งนั้น

แนวคิดกุญแจ: control เป็นฟังก์ชันของ opcode ล้วน ๆใน single-cycle คำสั่งทั้ง 32 บิตถูกอ่านมาครบตั้งแต่ต้นคาบ หน่วยควบคุมหลักดูแค่ opcode 7 บิตก็รู้ชนิดคำสั่ง จึงตั้ง RegWrite/ALUSrc/MemRead/MemWrite ได้ทันที ส่วนการเลือกว่า ALU จะบวก/ลบ/AND ต้องดู funct3/funct7 เพิ่ม งานนี้แยกไปให้ ALU control
สัญญาณค่า 0 หมายถึงค่า 1 หมายถึงคำสั่งที่ตั้ง 1
RegWriteไม่เขียน register fileเขียนค่ากลับที่ rd ปลายคาบR-type, addi, lw, jal/jalr
ALUSrcขา B ของ ALU = rs2ขา B ของ ALU = immediateaddi, lw, sw, jalr
MemReadไม่อ่าน data memoryอ่าน data memory ตามที่อยู่จาก ALUlw
MemWriteไม่เขียน data memoryเขียนค่า rs2 ลง data memorysw
MemToRegเขียนกลับผล ALUเขียนกลับค่าที่อ่านจาก memorylw (เป็น X เมื่อ RegWrite=0)
Branchไม่ใช่ branch"อาจ" กระโดด — ต้องรวมกับธง Zero ก่อนbeq/bne
Jumpไม่ใช่ jumpกระโดดแน่นอนjal, jalr
ALUOp (2 บิต)00=สั่งบวก · 01=สั่งลบ · 10=ให้ ALU control ไปดู functlw/sw=00, branch=01, R/I-type=10

PCSrc ไม่ใช่ output ของหน่วยควบคุมโดยตรง แต่คำนวณจาก PCSrc = (Branch AND Zero) OR Jump — ดีไซน์ที่มี jal/jalr ขยาย write-back mux เป็นสามขา บางตำราจึงเรียก MemToReg ว่า WBSel เป็นสัญญาณตัวเดียวกัน

Branch ต่างจาก PCSrc อย่างไร
ตอบ: Branch เป็น output ของหน่วยควบคุม (มาจาก opcode=beq/bne/...) ส่วน PCSrc เป็น output ของวงจร AND/OR เล็ก ๆ ที่รวม Branch เข้ากับ Zero ของ ALU — beq จะกระโดดก็ต่อเมื่อ Branch=1 และ rs1−rs2=0 พร้อมกัน
คำสั่ง lw ตั้งสัญญาณกี่ตัวเป็น 1 ในบรรดา RegWrite, ALUSrc, MemRead, MemWrite, MemToReg, Branch, Jump
ตอบ: 4 ตัว — RegWrite=1 (เขียนค่ากลับ rd), ALUSrc=1 (บวก offset กับฐาน), MemRead=1 (อ่าน data memory), MemToReg=1 (เขียนกลับค่าที่อ่านจาก memory) ส่วน MemWrite, Branch, Jump เป็น 0 — lw คือคำสั่งที่เปิดสัญญาณมากที่สุดในกลุ่มนี้ เพราะวิ่งผ่านทุกบล็อกของ datapath
flowchart LR INSTR["คำสั่ง 32 บิต"] --> OPC["opcode (7)"] INSTR --> FUNC["funct3 (3) + funct7 บิต30"] OPC --> MC["หน่วยควบคุมหลัก (main control)"] MC --> S1["RegWrite / ALUSrc"] MC --> S2["MemRead / MemWrite / MemToReg"] MC --> S3["Branch / Jump"] MC --> AO["ALUOp (2 บิต)"] AO --> ALUC["ALU control (decoder ชั้นสอง)"] FUNC --> ALUC ALUC --> OP["ALU operation 4 บิต"] MC -.- NOTE["PCSrc = (Branch AND Zero) OR Jump"]

วงรี Control รับแค่ opcode 7 บิต แล้วแตกออกเป็นเจ็ดสาย ปลายสายทุกเส้นไปจบที่ mux หรือพอร์ต enable ส่วนเกต AND ที่รวม Branch กับ Zero เป็นตัวเลือกของ mux หน้า PC คือ PCSrc (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.20, 4.21)

ALU control: decoder สองชั้น

หน่วยควบคุมหลักไม่บอก ALU ตรง ๆ ว่าทำอะไร มันส่งแค่ ALUOp 2 บิตแบบหยาบ วงจรเล็กชื่อ ALU control เป็น decoder ชั้นสอง: รับ ALUOp + funct3 + บิต 30 ของ funct7 แล้วผลิตสัญญาณ ALU operation 4 บิต บิต 30 คือตัวแยก add/sub และ srl/sra

flowchart LR AO["ALUOp (2)"] --> DEC["ALU control"] F3["funct3 (3)"] --> DEC F7["funct7 บิต 30"] --> DEC DEC --> OUT["ALU op: add/sub/and/or/slt/shift"] AO -.- A1["00 → add เสมอ"] AO -.- A2["01 → sub เสมอ"] AO -.- A3["10 → ดู funct"]
เหตุผลที่แยกสองชั้นถ้าให้หน่วยควบคุมหลักถอด funct3/funct7 ทั้งหมดเอง ตารางจะใหญ่และซ้ำซ้อน การมี ALUOp เป็น "คำสั่งระดับสูง" ทำให้ตารางหลักเล็ก แล้วผลักรายละเอียดการเลือก operation ของกลุ่มคำนวณไปไว้ที่ ALU control ที่เดียว
ALUOpชนิดคำสั่งALU ทำอะไรดู funct ไหม
00lw, swบวก (ฐาน + offset)ไม่ดู
01branch (beq/bne/...)ลบ (เพื่อหา Zero)ไม่ดู
10R-type / I-type คำนวณตาม funct3 (+funct7 บิต30)ดู
ข้อควรระวัง — บทบาทของ funct7 บิต 30เมื่อ ALUOp=10 และ funct3=000 (add/sub) หรือ 101 (srl/sra) ALU control ดูบิต 30 เพื่อแยก add(0)/sub(1) และ srl(0)/sra(1) แต่สำหรับ I-type คำนวณ (addi) บิต 30 เป็นส่วนหนึ่งของ immediate ไม่ใช่ funct7 — ALU control จึงรู้จาก opcode ว่าคำสั่งนี้เป็น I-type แล้วไม่ใช้บิต 30 มาแยก add/sub จำง่าย ๆ ว่าการแยก add กับ sub ดูบิต 30 ได้เฉพาะ R-type
🎮 ลองเลยเลือกรูปแบบคำสั่งแล้วดูว่า funct3/funct7 อยู่ตำแหน่งบิตไหน — ใช้ทวนก่อนดูตารางสัญญาณควบคุมด้านล่าง

ตารางค่าสัญญาณควบคุมต่อชนิดคำสั่ง

คำสั่งRegWriteALUSrcMemReadMemWriteMemToRegBranchJumpALUOp
R-type100000010
I-type คำนวณ (addi/andi/slti)110000010
lw111010000
sw0101X0000
beq / branch0000X1001
jal1X00PC+401X
jalr1100PC+40100

(X = don't care; ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.22)

สามจุดที่มักสับสน: sw: MemToReg=X เพราะ RegWrite=0 ไม่มีการเขียนกลับ · beq: ALUOp=01 สั่งลบเพื่อหา Zero แล้ว PCSrc=Branch AND Zero ตัดสินจริง · jal: ALUSrc/ALUOp=X เพราะเป้าหมายมาจาก adder (PC+imm) ไม่ใช่ ALU ส่วน jalr ต้องใช้ ALU บวก rs1+imm จึงตั้ง ALUSrc=1, ALUOp=00

don't care มีประโยชน์จริงช่อง X (เช่น MemToReg ของ sw/beq) คือสัญญาณที่ "ผลลัพธ์ไม่ขึ้นกับมัน" เพราะ RegWrite=0 อยู่แล้ว ไม่มีการเขียนกลับ การปล่อยเป็น don't care ช่วยให้วงจรถอดรหัสง่ายขึ้น (logic minimization) โดยไม่กระทบความถูกต้อง
🎮 ลองเลยเลือกคำสั่งแล้วดูสัญญาณควบคุมทั้ง 8 ตัวเปลี่ยนค่าจริง — เทียบกับตารางด้านบนว่าตรงกันไหม
สำหรับคำสั่ง beq หน่วยควบคุมควรตั้ง ALUOp เป็นค่าใด และเพราะอะไร
ตอบ: 01 เพราะต้องให้ ALU ลบ rs1−rs2 เพื่อให้ได้สัญญาณ Zero มาตัดสินการกระโดด (ถ้าผลเป็น 0 แปลว่า rs1=rs2 จึงกระโดด) ALUOp=10 ใช้กับกลุ่มคำนวณ R/I ต่างหาก และ ALUOp=00 ใช้กับ lw/sw
จากตารางค่าสัญญาณ ในบรรดาคำสั่งทั้ง 7 แถว มีกี่คำสั่งที่ตั้ง MemWrite=1
ตอบ: 1 คำสั่ง — มีเพียง sw ที่เขียน data memory ที่เหลือ MemWrite=0 ทั้งหมด
ตัวอย่าง — ไล่สัญญาณควบคุมของ sub x5,x6,x7 (0x407302B3, funct7 บิต30=1)
  1. ถอด opcode=0110011 → R-type → เข้าแถว R-type ของตารางค่าสัญญาณ
  2. อ่านสัญญาณหลัก: RegWrite=1, ALUSrc=0, MemRead=0, MemWrite=0, MemToReg=0, Branch=0, Jump=0, ALUOp=10
  3. ALU control: ALUOp=10 → ดู funct3=000 (กลุ่ม add/sub) → ดูบิต 30 ต่อ: funct7 บิต 30=1 → เลือก sub (ถ้าเป็น 0 จะเป็น add)
  4. ALU operation สุดท้าย = SUB (รหัส 4 บิตตามตำรา: AND 0000, OR 0001, add 0010, subtract 0110) → คำนวณ x6−x7 → เขียนกลับ x5
ถ้าเปลี่ยนเป็น add x5,x6,x7 ทุกอย่างเหมือนเดิม ยกเว้น funct7 บิต 30=0 → ALU op=ADD (x6+x7) — นี่คือสาเหตุที่ add/sub ใช้แถวตารางหลักแถวเดียวกัน ต่างกันแค่บิต 30 (รหัสเครื่อง: sub=0x407302B3, add=0x007302B3)
อ้างอิง
  • D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4

12.4 ทำไม Single-Cycle ถูกต้องแต่ช้า

ถูกต้องด้วยความเรียบ แต่ช้าด้วย critical path

ดาต้าพาธแบบ single-cycle ทำทุกคำสั่งให้เสร็จภายในหนึ่งสัญญาณนาฬิกา (one clock cycle): ปลายไซเคิลค่าทั้งหมดถูกเขียนกลับ (register file, PC, memory) แล้วไซเคิลใหม่จึงเริ่ม แบบนี้ถูกต้องเสมอ เพราะไม่มีคำสั่งสองตัวซ้อนทับกันในเวลาเดียว จึงไม่มี hazard ใด ๆ ให้กังวล ราคาที่จ่ายคือความเร็ว: สัญญาณนาฬิกาต้องยาวพอให้คำสั่งที่ช้าที่สุดทำงานครบทุกขั้นภายในไซเคิลเดียว นั่นคือ clock period ต้องครอบ critical path = เส้นทางหน่วงเวลา (propagation delay) ที่ยาวที่สุดในวงจร

critical path คืออะไรเส้นทางสัญญาณจาก register/PC ตัวหนึ่ง ผ่านวงจร combinational ไปจนถึง register/หน่วยความจำที่จะถูกเขียน ที่ใช้เวลารวมมากที่สุด clock period ต้องไม่สั้นกว่าเวลานี้ ไม่งั้นค่ายังไม่นิ่งตอน clock edge ผลลัพธ์จะผิด

คำสั่งที่ยาวที่สุดคือ lw

flowchart LR PC["PC"] --> IMEM["Instruction Memory (อ่านคำสั่ง)"] IMEM --> RF["Register File (อ่าน rs1)"] RF --> ALU["ALU (rs1 + imm = ที่อยู่)"] ALU --> DMEM["Data Memory (อ่านข้อมูล)"] DMEM --> WB["Write-back (เขียนลง rd)"]

เส้นทางของ lw คือ PC → IMEM → register read → ALU → DMEM → write-back ต่อกันเป็นอนุกรม คำสั่งอื่นสั้นกว่า เช่นกลุ่ม R (add) ไม่แตะ data memory จึงข้ามขั้น DMEM ไปได้ ส่วน sw ไม่มี write-back เข้า register file แต่ในเครื่อง single-cycle clock period ถูกตั้งตาม lw ที่ยาวสุด

ในเครื่อง single-cycle คำสั่งใดมักเป็นตัวกำหนด critical path
ตอบ: lw เพราะต้องวิ่ง regread → ALU → DMEM → write-back ต่อกันเป็นอนุกรม รวม delay ยาวที่สุด — add เป็น R-type ที่ "ข้าม DMEM" ได้ เส้นทางจึงสั้นกว่า และ beq ไม่มี write-back เข้า register file (RegWrite=0) และไม่แตะ DMEM เส้นทางจึงสั้นกว่า lw เช่นกัน

ทุกคำสั่งใช้เวลาเท่ากัน แม้คำสั่งที่สั้น

เพราะ clock period คงที่ตาม critical path ของ lw คำสั่งที่ "ทำงานน้อย" จึงไม่ได้เร็วขึ้น มันแค่นั่งรอจน clock edge ถัดไปมาถึง เวลาที่เกินจากงานจริงคือเวลาว่างเปล่า (slack ที่เสียไป)

ตัวอย่าง — คำนวณ clock period จากเวลาหน่วงแต่ละบล็อก

กำหนด: IMEM 200 ps, register read 100 ps, ALU 200 ps, DMEM 200 ps, register write 100 ps

คำสั่งIMEMregreadALUDMEMregwriteรวม (ps)
R-type (add)200100200—100600
lw200100200200100800
sw200100200200—700
beq200100200——500
clock period = 800 ps ตามแถวที่ยาวสุด (lw) — add เสร็จที่ 600 ps แล้วนั่งรอ slack 200 ps เปล่า ๆ, sw ใช้จริง 700 ps เสีย slack 100 ps, beq ใช้จริง 500 ps เสีย slack มากสุดถึง 300 ps ทั้งที่โปรแกรมจริงใช้ beq บ่อยมาก (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.28)
🎮 ลองเลยลากตัวเลื่อนปรับ delay ของแต่ละบล็อก แล้วดูว่า clock period (เส้นแดง) และ slack ของแต่ละคำสั่งเปลี่ยนตามจริง

CPI = 1 แต่ clock ยาว — มอง Iron Law

เครื่อง single-cycle มี CPI (Cycles Per Instruction) = 1 ซึ่งดูดีในแง่หนึ่ง แต่ราคาคือ clock period ยาวมาก นำมาประกอบกับสมการพื้นฐานของสมรรถนะ (Iron Law of Performance):

Iron LawCPU time = IC × CPI × clock period (IC = จำนวนคำสั่ง, CPI = ไซเคิลต่อคำสั่ง, clock period = ความยาวหนึ่งไซเคิล) — single-cycle ทำให้ CPI ต่ำสุด (=1) แต่ดัน clock period สูงสุด เพราะถูกล็อกไว้ที่ critical path ของ lw ผลคูณรวมจึงยังไม่ดี — เราจ่ายราคา clock ยาวให้ทุกคำสั่งรวมถึงคำสั่งสั้นที่ไม่ต้องการ

นี่คือแรงจูงใจสู่ pipelining (Topic 13): แทนที่จะรอให้ lw วิ่งครบเส้นทางในไซเคิลเดียว เราซอยเส้นทางเป็นช่วง (stage) สั้น ๆ แล้วตั้ง clock period ตามstage ที่ยาวที่สุด (ไม่ใช่ผลรวมทั้งเส้น) clock period สั้นลงมาก แลกกับ CPI ที่สูงกว่า 1 เล็กน้อยจาก hazard — แต่ผลคูณ Iron Law โดยรวมดีขึ้นชัดเจน (ที่มา: Patterson & Hennessy, COD RISC-V Edition รูปที่ 4.29)

🎮 ลองเลยลากตัวเลื่อน IC / CPI / ความถี่สัญญาณนาฬิกาเอง แล้วดู CPU time ที่คำนวณได้ตาม Iron Law เปลี่ยนตามจริง
ตัวอย่าง — speedup ของ pipeline เทียบ single-cycle

โปรแกรมมี IC = 1,000,000 คำสั่ง single-cycle: CPI=1, clock period=800 ps เทียบกับ pipeline: clock period=200 ps แต่ CPI จริง=1.25 เพราะ hazard

  1. single-cycle: 1,000,000 × 1 × 800 ps = 800,000,000 ps = 800 µs
  2. pipeline: 1,000,000 × 1.25 × 200 ps = 250,000,000 ps = 250 µs
  3. speedup = 800 µs / 250 µs = 3.2 เท่า
เช็กด้วยอัตราส่วน: clock ดีขึ้น 800/200=4 เท่า, CPI แย่ลง 1.25 เท่า → 4/1.25 = 3.2 — pipeline เสีย CPI (1→1.25) แต่ได้ clock สั้นลง 4 เท่า กำไรจาก clock ชนะการเสีย CPI นี่คือเหตุผลที่ CPU จริงเลือก pipeline
เหตุใดเครื่อง single-cycle ที่มี CPI = 1 จึงยังถือว่าช้า
ตอบ: เพราะ clock period ถูกตั้งตาม critical path ของ lw ที่ยาวสุด คำสั่งสั้นจึงต้องใช้เวลายาวเท่ากันด้วย — ตาม Iron Law (CPU time = IC×CPI×clock) clock ที่ยาวจึงฉุดสมรรถนะแม้ CPI จะต่ำสุดแล้วก็ตาม
ถ้าซอยเส้นทางของ lw เป็น 5 ช่วง (IF, ID, EX, MEM, WB) clock period ใหม่จะถูกตั้งตามอะไร และจะสั้นลงประมาณเท่าใดเทียบกับ 800 ps
ตอบ: ตั้งตาม stage ที่ยาวที่สุด (ในตัวอย่างคือ IMEM/ALU/DMEM stage ราว 200 ps) ไม่ใช่ผลรวมทั้งเส้น (800 ps) จึงสั้นลงเหลือราว 1 ใน 4 (800→200 ps) ทำให้ throughput สูงขึ้นมากแม้ latency ของคำสั่งเดี่ยวยังใกล้เคียงเดิม นี่คือหัวใจของ pipelining
🎮 เครื่องมือแนะนำRipes — เลือกโปรเซสเซอร์ single-cycle แล้วรันทีละคำสั่ง เปรียบเทียบ lw กับ add แล้วดูว่าบล็อกใดถูกใช้/ข้าม เพื่อเข้าใจว่าทำไม lw จึงกำหนด critical path
อ้างอิง
  • D. Patterson และ J. Hennessy, Computer Organization and Design (RISC-V Edition) — บทที่ 4