ARM 的文件裡說 Cortex-M4 是一顆 Harvard 架構的處理器(DUI 0553B)。
可是在〈memory map 是誰規劃的?〉裡我們也看到,指令和資料明明都住在同一張 4 GB 的位址表上,Harvard 架構不是應該把指令和資料分開放嗎?我突然又搞不清楚 Von Neumann 跟 Harvard 這兩種架構了,趕快來查一下維基百科。
兩種教科書上的架構
依照維基百科的說明,這兩種架構的分界粗略來說在於指令和資料怎麼存放:
| 架構 | 指令和資料 | 特性 |
|---|---|---|
| Von Neumann(馮·諾伊曼結構) | 存放在同一個記憶體裡 | CPU 和記憶體之間的傳輸率,相對於 CPU 的處理能力顯得太小,這被稱為馮·諾伊曼瓶頸 |
| Harvard(哈佛結構) | 分開存放在程式記憶體和資料記憶體 | 取指令和存取資料可以同時進行 |
看起來,兩種「純血統」的架構區別在於指令和資料是否放在同一塊記憶體。Harvard 把指令和資料兩者分開存放,自然也就可以開闢出同時存取它們的路來。
現在很少有純的
現代的處理器大多採用折衷的做法,維基百科稱之為改良型哈佛結構(modified Harvard):放寬指令和資料必須嚴格分開的要求,但仍讓 CPU 可以同時存取兩條以上的記憶體匯流排。最常見的形式,是在共用的位址空間上,配一組分開的指令快取和資料快取。維基百科把 ARM、x86 等現代處理器都列為這一型,也提到它們常常還是被直接稱為哈佛結構。
Cortex-M4 也屬於改良型。它的 core 本身沒有快取,改良的方式是把取指令和存取資料的匯流排分開,再接到同一張 4 GB 的位址表上。ARM 文件稱它為 Harvard,指的是這種分開的存取路徑;位址表只有一張,講的是定址的方式。
Cortex-M4 的三條路
以 STM32F401 為例,Cortex-M4 有三條匯流排接到 ST 的 bus matrix(RM0368):
| 匯流排 | 用途 | 通往哪裡 |
|---|---|---|
| I-bus | 取指令 | 放程式碼的記憶體(Flash;開機區對應到 SRAM 時也可以是 SRAM) |
| D-bus | 讀 literal pool 這類常數、除錯存取 | 放程式碼或資料的記憶體(Flash、SRAM) |
| S-bus | 存取 SRAM 和周邊的資料;也可以取指令,但效率比 I-bus 差 | SRAM、AHB1(含 APB)與 AHB2 上的周邊 |
要走哪一條路,由位址和存取的種類決定。Code 區(從 0x0000 0000 開始,Flash 就放在這一區)的取指令走 I-bus、取資料走 D-bus;SRAM(從 0x2000 0000 開始)和周邊則走 S-bus。
對照〈CPU 暫存器和周邊暫存器差在哪裡?〉的說法:周邊暫存器是周邊開給 CPU 的窗口,CPU 是經由 S-bus 去存取它們;CPU 自己的通用暫存器就在工作台上,不需要經過任何一條匯流排。另外,NVIC、SysTick 這些 ARM 自己的周邊,放在 Private Peripheral Bus 那一區,它們並不在 ST 的 bus matrix 上。
為什麼這樣規劃
最直接的好處,是 CPU 可以一邊從 Flash 取下一條指令,一邊透過 S-bus 讀寫 SRAM 裡的變數或周邊暫存器。ARM 在文件裡也是用這個理由,建議程式盡量放在 Code 區:處理器有分開的匯流排,可以讓取指令和存取資料同時發生(DUI 0553B)。
反過來說,如果把程式搬到 SRAM,並且從 0x2000 0000 起的 SRAM 區執行,取指令和存取資料就得擠在同一條 S-bus 上,所以文件才會說從 S-bus 取指令的效率比 I-bus 差。如果透過 BOOT 腳位或 SYSCFG_MEMRMP 暫存器,把 SRAM 重新映射到 0x0000 0000 起的 Code 區,再從這個別名位址執行,取指令就可以走 I-bus(RM0368)。
那 Flash 為什麼要分 I-Code、D-Code 兩個入口呢?程式在執行時,除了取指令,也常常要從 Flash 讀常數。分成兩個入口,取指令和讀常數就有各自的路可以走。這些常數當中,最常見的一種放在 literal pool 裡。
Literal pool
CPU 要讀寫 0x4002 0014 這個窗口,得先把位址本身搬上工作台,也就是放進某個暫存器。可是 Thumb-2 的指令只有 16 或 32 位元長,一條指令放不下任意的 32 位元常數。
Literal pool 就是為了這件事而設計的:compiler 把這類常數集中放在程式碼附近的一小塊 Flash 裡,執行時再用一條以 PC 為基準的 ldr 把常數讀進暫存器。讀它是一次資料存取,所以走的是 D-Code。
從 Flash 取東西
Flash 的速度跟不上 CPU。F401 在 84 MHz、供電 2.7–3.6 V 時,讀 Flash 需要 2 個 wait state,也就是每次讀取要花 3 個 CPU cycle(RM0368)。如果每一條指令都這樣等,CPU 會有很多時間在空轉。
ST 在 Flash 介面裡放了 ART Accelerator 來補這個落差(RM0368):
- 每次從 Flash 讀出 128 bits,一次就能拿到 4 條 32 位元或 8 條 16 位元的指令。
- 在 I-Code 上預取(prefetch)下一行指令。
- 用 64 行、每行 128 bits 的 instruction cache,減少遇到跳躍時的等待。
- 用 8 行的 data cache,存放經常用到的 literal pool。
依 ST 的說法,有了 ART Accelerator,在 84 MHz 下跑 CoreMark 的表現,相當於從 0 wait state 的 Flash 執行程式。
{alertWarning}「Cortex-M4 沒有 cache」這句話要小心使用。就 core 本身來說是對的,但 STM32F401 的 Flash 介面裡就有 instruction cache 和 data cache,它們屬於 ST 的 ART Accelerator。
模型的邊界
三條路的模型很好用,但有兩個地方要先知道:
- CPU 不是唯一在用這些路的人。F401 的 bus matrix 接了六個 master:CPU 的三條匯流排,加上 DMA1、DMA2 的三條。兩個 master 要存取同一個目的地時(例如 CPU 和 DMA 同時要讀寫 SRAM),bus matrix 會用 round-robin 的方式輪流處理。所以「同時存取」只在走不同的路、到不同的目的地時才成立。
- Flash 在寫入或抹除時,路會被卡住。這段時間去讀 Flash,匯流排會停下來等,取指令和取資料都沒辦法進行。
想一想
{collapse}1. 為什麼 ARM 建議把程式放在 Code 區,而不是 SRAM?
程式放在 Code 區時,取指令走 I-bus,存取 SRAM 和周邊的資料走 S-bus,兩件事可以同時進行。如果程式從0x2000 0000起的 SRAM 區執行,取指令和存取資料都要走 S-bus,就得互相等待。(SRAM 重新映射到 Code 區、從別名位址執行時例外。)
{collapse}2. CPU 讀取一張放在 Flash 裡的 const 查表,走的是哪一條匯流排?
D-bus。RM0368 Figure 2 把 Flash 中資料與 literal pool 的存取都歸給 D-Code。如果開啟了 ART Accelerator 的 data cache,常用的資料也可能直接從 cache 拿到。
現在文章開頭的問題得到解釋了:Cortex-M4 的 Harvard 架構,分開的是存取的路。指令和資料共用同一張位址表,取指令和存取資料則各有自己的匯流排,所以 ARM 稱它為 Harvard 架構。學習了!
本文屬於「補破網」專欄:由我和 AI 協作寫成,內容經把關後發佈。關於本專欄,請見〈補破網〉。









