National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 11

CPU 與指令執行

暫存器、指令集與組合語言
Interaction 30 minute(s)

Learning Objectives

  • 說出 CPU 內部的控制單元、算術邏輯單元與暫存器各自負責什麼
  • 看懂指令格式:運算碼與運算元,並能把組合語言翻成機器碼
  • 解釋旗標(零、負數、溢位)與條件跳躍如何做出「如果」和「迴圈」
  • 用一個小指令集寫出相加、倒數迴圈、找較大值的組合語言程式

為什麼軟體要分 x64 版和 ARM64 版

下載軟體時,常看到同一個程式提供好幾個版本:「x64」「ARM64」。手機 App 也要分別為不同的處理器準備。 同樣是「電腦」,為什麼程式不能通用?

因為每一種 CPU 都有自己認得的一套指令集(instruction set):哪些位元代表「加法」、哪些代表「跳躍」,各家設計不同。 編譯好的程式就是一長串指令的位元,換了指令集,同樣的位元意思就完全不同。

上一課看過 CPU 怎麼擷取、解碼、執行一條指令。這一課往裡面再走一層:

  1. CPU 裡面有哪些零件?暫存器是什麼?
  2. 一條指令的位元怎麼安排?組合語言和機器碼是什麼關係?
  3. 只有加、減和跳躍,怎麼寫出「如果」和「迴圈」?

最後你會用 UC8 教學處理器的 10 條指令,自己寫三個小程式。

CPU 裡面有什麼

零件 做什麼 UC8 裡的例子
控制單元(control unit) 擷取指令、解碼、送出控制訊號,指揮其他零件 PC、IR、解碼器
算術邏輯單元(ALU) 做加減等算術、比較與邏輯運算 ADD、SUB 的計算
暫存器(register) CPU 內部的小儲存格,存正在處理的值 ACC、PC、IR、MAR、MDR
旗標(flag) ALU 算完後順便記下的狀態,給條件跳躍用 Z、N、V

暫存器和記憶體的差別:暫存器在 CPU 裡面,數量很少(UC8 只有一個能讓你放計算結果的 ACC),但存取最快; 記憶體在 CPU 外面,空間大很多,但每次存取都要經過匯流排。所以程式常見的模式是: 從記憶體載入暫存器 → 在暫存器裡計算 → 存回記憶體。

時脈(clock):CPU 依照一個固定節拍的訊號一步一步動作。上一課模擬器的每個「微步驟」,可以想成一個節拍(示意,真實 CPU 的微步驟安排更複雜)。

旗標是條件判斷的基礎。UC8 的 ALU 每次算完都會更新:

旗標 什麼時候是 1 例子
Z(zero,零) 結果等於 0 5 − 5
N(negative,負數) 結果是負數(最高位元是 1) 5 − 7 = −2
V(overflow,溢位) 結果超出 8 位元能表示的 −128~127 127 + 1

指令集與機器碼

一條 UC8 指令有 8 個位元,分成兩個欄位:

  運算碼(3 位元)   運算元:位址(5 位元)
  ┌─────────┐       ┌───────────────┐
    0 1 1             0 0 1 1 0          →  ADD 6:ACC ← ACC + 記憶體[6]

3 個位元的運算碼可以表示 2³ = 8 種指令;5 個位元的位址可以指到 2⁵ = 32 格記憶體,這就是 UC8 只有 32 格記憶體的原因。 運算碼 000 這一組不需要位址,用後 5 位元區分 HLT、OUT、IN。

指令 機器碼 做什麼 會改的旗標
HLT 000 00000 停止執行
OUT 000 00001 把 ACC 送到輸出裝置
IN 000 00010 從輸入裝置讀一個數到 ACC Z N
LDA a 001 aaaaa 載入:ACC ← 記憶體[a] Z N
STA a 010 aaaaa 儲存:記憶體[a] ← ACC
ADD a 011 aaaaa 加法:ACC ← ACC + 記憶體[a] Z N V
SUB a 100 aaaaa 減法:ACC ← ACC − 記憶體[a] Z N V
JMP a 101 aaaaa 跳躍:PC ← a
JZ a 110 aaaaa Z 是 1 就跳到 a
JN a 111 aaaaa N 是 1 就跳到 a

組合語言(assembly language)是機器碼給人看的寫法:用 ADD 這種好記的名稱代替 011,用標籤 a 代替位址 6。 把組合語言翻成機器碼的程式叫做組譯器(assembler)。組合語言和機器碼大致是一行對一條; 而 Python、C 這類高階語言的一行,常常要變成好幾條甚至很多條機器指令。

UC8 的組合語言還有一個寫法 DATA n:在這一格放一個數字(−128~127),不是指令。

組譯器:把組合語言翻成機器碼

組譯器其實很簡單:查表找出運算碼,乘以 32(等於往左移 5 個位元,空出後 5 位),再加上位址。看 code 的值怎麼算出來,以及每一行印出的 8 個位元裡,前 3 位元和後 5 位元分別是什麼。

互動練習:機器碼與旗標

記憶體裡是「讀兩個數字相加」的程式,輸入裝置準備了 7 和 5。依序完成四個任務:先用指令集表(下方可以展開)判斷一個機器碼是哪條指令;再在編輯器把 ADD a 改成 SUB a,按「組譯並載入」後執行,看輸出與 N 旗標;接著在 CPU 方塊裡的「輸入/輸出裝置」換上兩個一樣的數,讓 SUB 的結果是 0,看 Z 旗標;最後按「還原成範例程式」,輸入換成 127, 1,看 127 + 1 的結果和 V 旗標。

用跳躍做出「如果」和「迴圈」

UC8 沒有 if、也沒有 while,只有跳躍:JMP 一定跳,JZ、JN 看旗標決定跳不跳。 高階語言的判斷和迴圈,翻成機器指令後都是「比較 → 條件跳躍」。

如果:要判斷 a 是否比 b 大,就算 b − a,看結果是不是負數:

if b - a < 0:      # a 比較大
    print(a)
else:
    print(b)
        LDA b
        SUB a       ; ACC ← b − a,同時設定 N 旗標
        JN aBig     ; 負數就跳去 aBig
        LDA b       ; 沒跳:b 比較大
        OUT
        HLT
aBig:   LDA a
        OUT
        HLT

迴圈:在程式後面放一個「跳回前面」的指令,再用條件跳躍決定什麼時候離開:

n = 3
while True:
    print(n)
    n = n - 1
    if n == 0:
        break
        LDA n
loop:   OUT
        SUB one     ; ACC ← ACC − 1,結果是 0 時 Z=1
        JZ done     ; 減到 0 就離開
        JMP loop    ; 否則跳回 loop
done:   HLT

這就是上一課 PC 的妙用:改 PC 就能改變下一條要執行的指令。

互動練習:用 UC8 寫程式

下方有三個實作題(三數相加、倒數迴圈、找較大的數)。每一題先按「載入起始程式」,在編輯器把 TODO 補完,按「組譯並載入」後可以單步或執行到停止,用上方的 CPU 檢查自己的想法;寫好後按「檢查我的程式」,程式會用好幾組輸入測試。卡住時可以看提示;檢查沒通過一次後,可以載入參考作法對照。

真實的 CPU 比 UC8 多了什麼

UC8 刻意做得很小,好讓你看清楚每一步。真實的處理器原理相同,但多了很多讓速度變快的設計:

UC8 真實的 CPU
只有一個 ACC 有十幾到幾十個通用暫存器,計算時少跑幾趟記憶體
8 位元,數字只能到 127 常見 64 位元,一次處理的數字大得多,可用的記憶體位址也多很多
一條指令做完才擷取下一條 管線化(pipelining):上一條還在執行,下一條已經在解碼、再下一條已經在擷取
一個核心 多核心:一顆晶片裡有好幾個 CPU 核心,可以同時執行不同的工作
直接讀寫記憶體 中間有好幾層快取(cache),常用的資料不必每次都到主記憶體拿

常見的指令集有 x86(多數桌上型與筆記型電腦)、ARM(多數手機與平板,也有筆電採用)、RISC-V(開放的指令集,任何人都可以拿來設計處理器)。 指令集的設計有兩種常見取向:

  • 複雜指令集(CISC):指令種類多、一條指令能做比較多事,指令長度不固定,例如 x86
  • 精簡指令集(RISC):指令種類少而規則,每條指令做簡單的事、長度固定,讓硬體容易做成管線化,例如 ARM、RISC-V

同一個 Python 或 C 程式,可以由不同的編譯器分別產生 x86 版和 ARM 版的機器碼,這就是開頭「x64 版、ARM64 版」的由來。

想一想:暫存器與機器碼

1.

UC8 的 STA 9 組譯成機器碼是哪一個?(STA 的運算碼是 010)

2.

哪一個暫存器存放「下一條要擷取的指令位址」?

3.

UC8 執行 SUB 之後結果是 0,哪個旗標會變成 1?

第 1 次作答

想一想:組合語言程式

1.

下面這段 UC8 程式會依序輸出哪些數?

        LDA n
loop:   OUT
        SUB two
        JN done
        JZ done
        JMP loop
done:   HLT
n:      DATA 7
two:    DATA 2
2.

組合語言和機器碼是什麼關係?

3.

為 x86 處理器編譯好的程式,為什麼不能直接拿到 ARM 處理器上執行?

第 1 次作答

重點整理

  • CPU 由控制單元(擷取、解碼、指揮)、ALU(計算)和暫存器(CPU 內部最快的小儲存格)組成;ALU 算完會更新旗標(Z 零、N 負數、V 溢位)。
  • 一條指令分成運算碼和運算元。UC8 用 3 位元運算碼、5 位元位址,所以最多 8 組運算碼、32 格記憶體。
  • 組合語言是機器碼給人看的寫法,組譯器把它翻成機器碼:查表找運算碼、左移、加上位址。
  • 機器層級沒有 if 和 while:判斷和迴圈都是「計算或比較 → 看旗標 → 條件跳躍」,跳躍就是改變 PC。
  • 每種 CPU 有自己的指令集(x86、ARM、RISC-V…),編譯好的程式只能在相同指令集的 CPU 上直接執行。
  • 真實 CPU 有更多暫存器、更寬的位元數、管線化、多核心與快取,基本流程仍是擷取、解碼、執行。

下一課:CPU 很快,記憶體相對慢。電腦怎麼用暫存器、快取、主記憶體、硬碟組成「記憶體階層」,讓 CPU 不必一直等資料? 想用真正的程式語言練習判斷和迴圈,可以接著上 Python 軌。

這一課完成了!
Next lesson:記憶體階層與快取
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。