Learning Objectives
- 說出馮紐曼架構的組成:運算、控制、記憶體、輸入、輸出,以及連接它們的匯流排
- 解釋「儲存程式」的概念:程式和資料都以位元存在同一個記憶體裡
- 描述指令週期的擷取、解碼、執行三個階段,以及 PC、IR、MAR、MDR、ACC 各自的角色
- 比較馮紐曼架構與哈佛架構,說明「馮紐曼瓶頸」指的是什麼
同一台手機,為什麼能跑各種 App
你的手機今天裝了一個新遊戲,明天裝了記帳 App,硬體一顆螺絲都沒動,卻能做完全不同的事。 洗衣機、微波爐就不行:它們的功能在出廠時就固定了。
差別在於:手機(以及所有通用電腦)把程式當成資料存在記憶體裡。換一個程式,只是把記憶體裡的位元換掉。 這個想法叫做儲存程式(stored program),是今天幾乎所有電腦的共同設計,通常稱為馮紐曼架構(von Neumann architecture)。
這一課要回答三個問題:
- 一台電腦由哪幾個部分組成?它們之間怎麼傳遞資料?
- 程式和資料存在哪裡?CPU 怎麼分辨哪一格是指令、哪一格是資料?
- CPU 一條一條執行指令時,裡面到底發生了什麼事?
我們會用一顆自己設計的教學用處理器 UC8(8 位元、32 格記憶體、10 條指令)一步一步看。
儲存程式的由來
ENIAC(1946 年公開)是早期著名的電子計算機,算得很快,但它沒有「把程式存起來」的地方。 換一個問題,工作人員要重新拔插電纜、撥動開關,把程式「接」出來,往往要花上好幾天。
ENIAC 的團隊在設計下一台機器 EDVAC 時,討論出另一種做法:既然記憶體可以存數字,那就把指令也編成數字,和資料一起存進記憶體。 1945 年 6 月 30 日,數學家馮紐曼(John von Neumann)寫的〈EDVAC 報告初稿〉(First Draft of a Report on the EDVAC)開始流傳, 第一次有系統地描述了這種設計,「馮紐曼架構」這個名稱就是從這份報告來的。
這個名稱有爭議:報告只署名馮紐曼,但儲存程式的想法來自整個團隊的討論,ENIAC 的設計者 Eckert 與 Mauchly 也有重要貢獻。
1948 年 6 月 21 日,英國曼徹斯特大學的實驗機器 Manchester Baby 執行了存在自己記憶體裡的程式, 通常被認為是第一台執行儲存程式的電子計算機。從此以後,換程式只要把新的位元載入記憶體,不必再重新接線。
馮紐曼架構的組成
EDVAC 報告把電腦分成幾個主要部分,對應到今天的說法:
| 部分 | 做什麼 | UC8 裡的對應 |
|---|---|---|
| 運算單元(ALU,算術邏輯單元) | 加、減、比較等計算 | ALU 與累加器 ACC |
| 控制單元(control unit) | 決定下一步做什麼:擷取指令、解讀指令、指揮其他部分 | PC、IR、解碼 |
| 記憶體(memory) | 存放程式和資料,每一格有一個位址 | 32 格,每格 8 位元 |
| 輸入(input) | 把外界的資料送進來 | 輸入裝置(IN 指令) |
| 輸出(output) | 把結果送出去 | 輸出裝置(OUT 指令) |
運算單元和控制單元合起來就是 CPU(中央處理器)。CPU 和記憶體之間靠匯流排(bus)連接,可以想成三組電線:
- 位址匯流排:CPU 告訴記憶體「我要第幾格」
- 資料匯流排:真正搬運內容(指令或資料)
- 控制訊號:說明這次是「讀取」還是「寫入」
┌───────────── CPU ─────────────┐
輸入 ──▶│ 控制單元(PC、IR) ALU(ACC)│──▶ 輸出
└──────────────┬────────────────┘
位址匯流排 / 資料匯流排 / 控制訊號
┌──────────────┴────────────────┐
│ 記憶體:程式和資料放在一起 │
└───────────────────────────────┘
指令週期:擷取、解碼、執行
CPU 一生只做一件事,不斷重複這個指令週期(instruction cycle):
- 擷取(fetch):照 PC(程式計數器)指的位址,從記憶體把一格內容搬進 IR(指令暫存器),然後 PC 加 1,指向下一格
- 解碼(decode):控制單元看 IR 的位元,判斷這是哪一條指令、要用哪個位址
- 執行(execute):真的去做,例如從記憶體讀一個數、讓 ALU 做加法、把結果寫回記憶體
CPU 裡的幾個暫存器(register)是 CPU 內部的小儲存格,速度比記憶體快很多:
| 暫存器 | 全名 | 放什麼 |
|---|---|---|
| PC | 程式計數器(program counter) | 下一條要擷取的指令位址 |
| IR | 指令暫存器(instruction register) | 正在執行的指令 |
| MAR | 記憶體位址暫存器 | 要存取的記憶體位址,經位址匯流排送出 |
| MDR | 記憶體資料暫存器 | 從記憶體讀進來、或要寫出去的內容 |
| ACC | 累加器(accumulator) | 計算的結果 |
UC8 的每條指令是 8 個位元:前 3 位元是運算碼(要做什麼),後 5 位元是位址(對哪一格做)。例如:
001 00101 → 001 是 LDA(載入),00101 是位址 5 → LDA 5:ACC ← 記憶體[5]
下面的模擬器把每個階段再切成幾個微步驟,你可以看到值在匯流排上怎麼流動。
用 Python 寫出指令週期
同樣的指令週期,用幾行 Python 就能模擬:memory 是一串數字,while 迴圈每一圈就是一個指令週期。注意三件事:ir = memory[pc] 之後 pc 馬上加 1;>> 和 & 把 8 個位元拆成運算碼和位址;程式和資料都在同一個 memory 串列裡。
程式就是資料
馮紐曼架構最重要的後果是:記憶體裡的位元沒有標記「我是指令」或「我是資料」。
同一格 001 00101,PC 指到它時,CPU 把它當成指令 LDA 5;用 LDA 把它讀進 ACC 時,它就是數字 37。
CPU 不會檢查,它只照 PC 去擷取、照指令去讀寫。這帶來兩種結果:
- 好處:程式可以被載入、複製、下載、修改,就像處理一般資料。作業系統把 App 從儲存裝置載入記憶體、編譯器產生新的程式,都靠這一點。 早期一些電腦沒有專門走訪陣列的硬體,程式設計師會讓程式改寫自己指令裡的位址,一格一格處理資料。
- 風險:如果 PC 跑到資料區,CPU 會把資料當成指令執行;如果有人把惡意資料寫進記憶體再讓 CPU 跳過去執行,就是一種攻擊手法。 所以現代作業系統會把放資料的記憶體區域標成「不可執行」。
下面的程式會改寫自己的第一條指令:每輸出一個數,就把位址 0 的 LDA list 加 1,讓它下一次讀下一格。
哈佛架構與馮紐曼瓶頸
馮紐曼架構不是唯一的做法。另一種設計把指令和資料放在分開的記憶體,各走各的通道,通常稱為哈佛架構(Harvard architecture)。 這個名稱來自 1944 年啟用的哈佛 Mark I:它從打孔紙帶一條一條讀指令,資料則存在機器內部,兩者是分開的。 (「哈佛架構」這個說法是後來才出現的分類名稱,Mark I 當時並沒有這樣稱呼自己。)
| 馮紐曼架構 | 哈佛架構 | |
|---|---|---|
| 指令與資料 | 放在同一個記憶體 | 放在分開的記憶體 |
| 通道 | 共用一組匯流排,一次只能拿指令或資料其中一個 | 指令和資料可以同時傳送 |
| 彈性 | 程式可以被當成資料載入、修改 | 程式區和資料區分得清楚,不容易誤把資料當指令 |
| 常見於 | 一般電腦的整體設計 | 許多微控制器、數位訊號處理器 |
馮紐曼瓶頸(von Neumann bottleneck):CPU 算得再快,每條指令和每筆資料都要經過同一組匯流排從記憶體搬過來, 搬運速度就成了限制。這個詞因電腦科學家 John Backus 在 1977 年圖靈獎演講〈程式設計能從馮紐曼風格中解放出來嗎?〉而廣為人知。
今天的電腦其實混用兩種想法:主記憶體裡程式和資料放在一起(馮紐曼), 但 CPU 內部的快取(cache)常分成「指令快取」和「資料快取」兩份,讓指令和資料能同時取得(哈佛的精神),通常稱為修改式哈佛架構。 快取會在第 12 課「記憶體階層與快取」細談。
想一想:馮紐曼架構
「儲存程式」這個想法的核心是什麼?
下列哪一個不是馮紐曼架構描述的電腦主要組成部分?
哈佛架構和馮紐曼架構最主要的差別是?
想一想:指令週期與 UC8 程式
下面這段 UC8 程式執行到 HLT,會輸出什麼?
00 LDA x
01 SUB y
02 JN neg ; 結果是負數就跳到 neg
03 OUT
04 HLT
05 neg: LDA y
06 OUT
07 HLT
08 x: DATA 3
09 y: DATA 8
為什麼 PC 跑進資料區時,CPU 會把資料當成指令執行?
重點整理
- 儲存程式:指令編成數字,和資料一起存在記憶體裡;換程式只要換位元。1945 年的〈EDVAC 報告初稿〉有系統地描述了這種設計,後來稱為馮紐曼架構。
- 電腦的組成:運算單元、控制單元(合稱 CPU)、記憶體、輸入、輸出;CPU 和記憶體之間用位址匯流排、資料匯流排、控制訊號連接。
- 指令週期不斷重複:擷取(PC → MAR → 記憶體 → MDR → IR,PC 加 1)、解碼(看運算碼和位址)、執行。
- 暫存器各有分工:PC 記下一條指令在哪、IR 放正在執行的指令、MAR/MDR 負責和記憶體交換、ACC 放計算結果。
- 記憶體裡的位元沒有標記是指令還是資料:程式可以修改自己,PC 跑錯地方也會把資料當成指令執行。
- 哈佛架構把指令和資料的記憶體分開;馮紐曼瓶頸指 CPU 和記憶體之間的搬運速度成了限制。現代 CPU 的快取把指令和資料分開,混用兩種想法。
下一課:UC8 的 10 條指令怎麼設計?組合語言怎麼變成機器碼?我們會用這些指令自己寫程式:相加、倒數迴圈、找出較大的數。