Chu kỳ fetch-decode-execute — một lệnh đi qua CPU thế nào
Vòng lặp cốt lõi mọi CPU lặp hàng tỷ lần mỗi giây: nạp lệnh, giải mã, thực thi. Vai trò program counter, instruction register và nhịp clock điều phối ra sao.
TL;DR: Mọi CPU đều chạy một vòng lặp vô tận gồm ba pha: Fetch (nạp lệnh từ bộ nhớ vào chip), Decode (control unit giải mã lệnh đó là làm gì), và Execute (ALU tính toán hoặc đọc/ghi bộ nhớ). Program counter (PC) theo dõi lệnh tiếp theo, instruction register (IR) giữ lệnh hiện tại, và nhịp clock đẩy từng pha tiến lên. Hiểu ba pha này, bạn hiểu được tại sao "nhiều dòng code" không đồng nghĩa "chạy chậm hơn" và tại sao truy cập bộ nhớ đắt hơn phép tính thuần túy.
Bạn nghe nói CPU chạy 3 GHz — tức 3 tỷ nhịp clock mỗi giây. Nhưng điều đó thực sự có nghĩa là gì? CPU đang làm gì trong 3 tỷ nhịp đó? Câu trả lời là nó đang lặp đi lặp lại một vòng lặp cực kỳ đơn giản: nạp lệnh → giải mã → thực thi → nạp lệnh tiếp → giải mã → thực thi → ... mãi cho đến khi máy tắt.
Bài này giải thích vòng lặp đó — từng pha là gì, ai điều phối, và tại sao hiểu nó giúp bạn lập trình tốt hơn.
1. Analogy — công nhân dây chuyền đơn
Hãy hình dung một công nhân làm việc ở dây chuyền sản xuất. Quy trình của anh ta lặp đi lặp lại suốt ngày:
- Lấy đơn hàng từ kệ phiếu — anh ta cầm tờ phiếu tiếp theo trong xếp hàng.
- Đọc hiểu yêu cầu trên phiếu — tờ phiếu ghi "lắp ốc vít số 4 vào vị trí B3", anh ta hiểu phải dùng dụng cụ nào, lấy linh kiện nào.
- Thực hiện công việc — vặn ốc vào đúng chỗ, dùng đúng dụng cụ.
- Lấy đơn hàng tiếp theo. Lặp lại.
CPU làm đúng điều này, chỉ khác là với lệnh máy thay vì phiếu giấy, và tốc độ hàng tỷ lần mỗi giây.
| Dây chuyền sản xuất | CPU |
|---|---|
| Kệ phiếu đơn hàng | Bộ nhớ RAM (chứa các lệnh chương trình) |
| Số thứ tự phiếu hiện tại | Program counter (PC) |
| Phiếu đang cầm trên tay | Instruction register (IR) |
| Đọc hiểu yêu cầu trên phiếu | Pha Decode (control unit giải mã) |
| Thực hiện công việc tay | Pha Execute (ALU hoặc đọc/ghi bộ nhớ) |
| Giám sát viên đếm nhịp | Clock |
Fetch = lấy phiếu. Decode = đọc phiếu. Execute = làm theo phiếu. PC là con trỏ "đang đọc phiếu số mấy".
2. Ba pha: Fetch → Decode → Execute
2.1 Pha Fetch — nạp lệnh
CPU cần biết lệnh tiếp theo là gì. Để làm vậy, nó dùng program counter (PC) — một thanh ghi đặc biệt lưu địa chỉ bộ nhớ của lệnh sắp thực thi.
Trong pha Fetch:
- CPU đọc nội dung ở địa chỉ bộ nhớ mà PC đang trỏ tới.
- Bytes đọc được chuyển vào instruction register (IR) — thanh ghi tạm giữ lệnh đang xử lý.
- PC tăng lên để trỏ sang lệnh tiếp theo (trên kiến trúc 32-bit như ARM tăng 4 byte; trong ví dụ 8-bit đơn giản ở trên, tăng 1-2 byte tuỳ độ dài lệnh).
Truoc Fetch: PC = 0x1000
RAM[0x1000] = 10110001 (mot lenh ma hoa)
Sau Fetch: IR = 10110001 (lenh da vao chip)
PC = 0x1004 (tang len, chuan bi lenh ke)
2.2 Pha Decode — giải mã
Control unit — bộ não điều phối của CPU — đọc nội dung IR và giải mã xem lệnh yêu cầu làm gì.
Mỗi lệnh máy có cấu trúc nhị phân định sẵn: một phần là opcode (mã thao tác — ví dụ "cộng", "load từ RAM", "nhảy tới địa chỉ X"), phần còn lại là toán hạng (operand — ví dụ "thanh ghi nào", "địa chỉ nào", "hằng số bao nhiêu").
Ví dụ trên kiến trúc đơn giản 8-bit:
Lenh nhi phan: 1011 0001
^^^^ <- opcode: "LOAD thanh ghi A tu bo nho"
^^^^ <- operand: dia chi 0x01
Control unit doc: "phai load gia tri tai dia chi 0x01 vao thanh ghi A"
2.3 Pha Execute — thực thi
Dựa trên kết quả decode, control unit ra lệnh cho phần cứng thích hợp:
- ALU (Arithmetic Logic Unit) thực hiện phép tính (cộng, trừ, AND, OR, dịch bit...).
- Bus bộ nhớ mang dữ liệu từ RAM vào thanh ghi (LOAD) hoặc ngược lại (STORE).
- PC bị ghi đè nếu lệnh là nhảy/rẽ nhánh (JUMP, BRANCH) — đây là cơ chế điều khiển luồng.
Sau khi Execute xong, CPU quay lại Fetch với PC mới, vòng lặp tiếp tục.
3. Sơ đồ vòng lặp
flowchart LR F["FETCH\n(doc lenh tu RAM vao IR,\ntang PC)"] D["DECODE\n(control unit giai ma\nopcode + toan hang)"] E["EXECUTE\n(ALU tinh toan /\ndoc-ghi bo nho /\nghi de PC neu jump)"] F --> D --> E --> F
Nhịp clock đẩy từng pha tiến lên: một lệnh đơn giản có thể mất 1–4 chu kỳ clock; CPU hiện đại pipeline hóa để chồng lấn nhiều lệnh cùng lúc (xem Tầng đào sâu dưới).
4. Trace một lệnh thực tế
Giả sử chương trình pseudo-assembly sau:
; Chuong trinh don gian
; Khoi dau: R0 = 5, R1 = 3
LOAD R2, [0x200] ; lenh tai dia chi 0x100: doc RAM[0x200] vao R2
ADD R3, R0, R1 ; lenh tai dia chi 0x104: R3 = R0 + R1
Trace lệnh LOAD R2, [0x200] (PC = 0x100):
| Pha | Điều gì xảy ra |
|---|---|
| Fetch | PC = 0x100 → đọc RAM[0x100] → nội dung vào IR; PC tăng lên 0x104 |
| Decode | Control unit đọc IR: opcode = LOAD, toán hạng = thanh ghi R2, địa chỉ nguồn = 0x200 |
| Execute | Bus bộ nhớ đọc RAM[0x200]; giá trị đọc được ghi vào R2; ALU không tham gia |
Trace lệnh ADD R3, R0, R1 (PC = 0x104):
| Pha | Điều gì xảy ra |
|---|---|
| Fetch | PC = 0x104 → đọc RAM[0x104] → nội dung vào IR; PC tăng lên 0x108 |
| Decode | Control unit đọc IR: opcode = ADD, nguồn 1 = R0, nguồn 2 = R1, đích = R3 |
| Execute | ALU cộng giá trị R0 và R1; kết quả ghi vào R3; không đụng RAM |
5. Lệnh nhảy và điều khiển luồng
Khi CPU gặp lệnh JUMP (hoặc BRANCH — nhảy có điều kiện):
JUMP 0x300 ; nhay toi dia chi 0x300
Trong pha Execute, thay vì ALU tính toán, control unit ghi đè PC bằng địa chỉ đích (0x300). Lần Fetch tiếp theo sẽ nạp lệnh tại 0x300, không phải lệnh kế tiếp trong dãy.
Đây là cơ chế nền tảng của mọi if, while, for, gọi hàm — tất cả đều dịch ra lệnh nhảy ở tầng máy. Khi CPU không biết trước điều kiện nhảy (true hay false), nó phải đoán — gọi là branch prediction. Đoán sai phải huỷ và làm lại — chi phí đáng kể ở CPU pipeline. Bài sau trong module sẽ đề cập cụ thể hơn.
Mô tả ba pha ở trên là mô hình đơn chu kỳ (single-cycle) — đơn giản để học, nhưng CPU thật phức tạp hơn nhiều:
Multi-cycle: Phần lớn lệnh thực ra tốn nhiều chu kỳ clock. LOAD từ RAM có thể tốn 10–100+ chu kỳ (do RAM chậm hơn CPU nhiều bậc). Lệnh ADD đơn thuần có thể chỉ tốn 1 chu kỳ.
Pipeline: CPU hiện đại không chờ lệnh 1 xong mới bắt đầu lệnh 2. Trong khi lệnh 1 đang Execute, lệnh 2 đã đang Decode, lệnh 3 đã đang Fetch — chồng lấn như dây chuyền. Module 3 sẽ đào sâu cơ chế này.
CISC vs RISC: Kiến trúc CISC (x86) cho phép một lệnh làm nhiều việc phức tạp (ví dụ MOVS sao chép vùng nhớ). Kiến trúc RISC (ARM, RISC-V) thiết kế lệnh đơn giản, đồng đều — dễ pipeline hơn, từng lệnh chạy nhanh hơn nhưng cần nhiều lệnh hơn để làm cùng việc. x86 bên trong thực ra dịch lệnh phức tạp thành micro-ops RISC-like trước khi chạy.
Clock cycle vs instruction: Tốc độ CPU 3 GHz nghĩa là 3 tỷ chu kỳ clock mỗi giây, không phải 3 tỷ lệnh. Một lệnh thường tốn nhiều chu kỳ; đôi khi CPU pipeline cho phép hoàn thành trên 1 lệnh/chu kỳ trung bình nhờ cơ chế superscalar.
6. Áp dụng vào code của bạn
Hiểu chu kỳ fetch-decode-execute thay đổi cách bạn lý luận về hiệu năng:
Đếm dòng code không đo được tốc độ. Một dòng a = b + c dịch ra 1–3 lệnh máy và tốn vài chu kỳ clock. Một dòng result = database.query(...) dịch ra hàng trăm lệnh máy, hàng nghìn chu kỳ chờ I/O. Tốc độ nằm ở số chu kỳ clock thực tế, không phải số dòng.
Truy cập bộ nhớ đắt hơn phép tính thuần túy. Trong pha Execute, lệnh ADD đụng ALU — hoàn thành trong 1 chu kỳ. Lệnh LOAD đụng RAM — có thể tốn hàng trăm chu kỳ chờ dữ liệu truyền qua bus. Đây là lý do cache tồn tại (bài tiếp trong module sẽ đề cập).
Vòng lặp nóng: giảm công việc trong Execute. Nếu một vòng lặp chạy hàng triệu lần, mỗi lần bớt được một lần đọc RAM là bớt được hàng triệu chu kỳ clock. Tư duy: dữ liệu nào có thể giữ trong thanh ghi (hoặc cache L1) thay vì đọc từ RAM mỗi vòng?
# Vi du tu duy: doc bien ngoai vong lap
# Khong tot: moi vong lap doc lai gia tri tu bo nho (co the chua cache)
total = 0
for i in range(1_000_000):
total += data[i] * multiplier # multiplier co the cache duoc
# Tot hon: gan vao bien cuc bo (compiler/CPU co the giu trong thanh ghi)
m = multiplier
total = 0
for i in range(1_000_000):
total += data[i] * m
Sự khác biệt ở đây không phải số dòng — mà là số lần pha Execute phải ra ngoài RAM để lấy multiplier.
7. Liên hệ các bài khác
- Bài 01 — Mô hình von Neumann: chu kỳ fetch-decode-execute là vòng lặp lõi của máy stored-program — PC, IR và bus bộ nhớ đều xuất hiện trong sơ đồ von Neumann đó.
- Bài 03 — Register, ALU và Control Unit: các thành phần này tham gia cụ thể vào pha decode (control unit giải mã opcode) và execute (ALU tính toán, bus ghi/đọc thanh ghi).
- Bài 04 — Assembly nhập môn: lệnh assembly chính là nội dung CPU fetch vào IR rồi decode — hiểu opcode và toán hạng ở bài này giúp đọc assembly tự nhiên hơn.
8. Tóm tắt
- Fetch-decode-execute là vòng lặp cốt lõi mọi CPU lặp hàng tỷ lần mỗi giây — không có ngoại lệ.
- Program counter (PC) trỏ tới lệnh tiếp theo; tự động tăng sau mỗi Fetch, và bị ghi đè bởi lệnh nhảy.
- Instruction register (IR) giữ tạm lệnh vừa nạp từ RAM trong khi Decode và Execute xử lý.
- Control unit giải mã opcode + toán hạng, điều phối ALU, bus bộ nhớ, hoặc ghi đè PC tùy loại lệnh.
- Lệnh nhảy (JUMP/BRANCH) ghi đè PC — đây là cơ chế nền tảng của
if,while, gọi hàm. - Một lệnh thực tế tốn nhiều chu kỳ clock, không phải một; truy cập RAM tốn nhiều hơn phép tính ALU rất nhiều.
- Số dòng code không đo được tốc độ — số chu kỳ clock thực tế mới là thước đo; hiểu pha Execute giúp bạn tư duy đúng về bottleneck.
9. Tự kiểm tra
Q1Program counter (PC) thay đổi như thế nào sau pha Fetch, và điều gì xảy ra với PC khi CPU gặp lệnh nhảy (JUMP)?▸
if, vòng lặp, gọi hàm) ở tầng máy.Q2Vì sao chạy một lệnh LOAD từ RAM có thể tốn nhiều chu kỳ clock hơn một lệnh ADD?▸
Q3Nếu CPU chạy 3 GHz, điều đó có nghĩa là nó thực thi 3 tỷ lệnh mỗi giây không? Giải thích.▸
Q4Tại sao đếm số dòng code không giúp bạn đánh giá tốc độ chương trình?▸
a = b + c có thể chỉ cần 1–2 lệnh máy và 1–2 chu kỳ. Một dòng result = file.read() có thể cần hàng nghìn lệnh và hàng triệu chu kỳ chờ I/O. Tốc độ thực sự nằm ở tổng số chu kỳ clock — đặc biệt tốn kém ở pha Execute khi lệnh LOAD/STORE đụng RAM chậm.Q5Vai trò của instruction register (IR) trong chu kỳ fetch-decode-execute là gì? Nó khác program counter (PC) thế nào?▸
Q6Một lập trình viên viết vòng lặp tính tổng 1 triệu phần tử, và đặt một phép đọc hằng số bên ngoài vòng lặp thay vì đọc lại mỗi lần. Điều này ảnh hưởng đến pha nào trong chu kỳ fetch-decode-execute và tại sao lại nhanh hơn?▸
Bài tiếp theo: Register, ALU và control unit
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên