05.3 期末專題:微架構效能量測與改進#
學習目標#
讀完本節後,你應該能夠:
- 說明基準測試(benchmark)的意義,並將 Dhrystone 與 CoreMark 移植到自製 RISC-V SoC 上跑分。
- 設計自訂指令/協同處理器(co-processor),以 CRC 與 FFT 為例完成 Verilog 實作與軟體整合。
- 量化「改進前 vs 改進後」的效能差異,並用 Amdahl 定律分析加速的理論上限。
- 規劃期末專題方向,包含流水線加寬、快取相聯度、SIMD 自訂指令等加分主題。
5.3.1 效能量測的方法論#
為什麼要量測?#
前面章節我們能「跑起來」了,但計算機結構的最終問題是:這樣的微架構,效能好不好?改哪裡最划算? 直覺猜測經常是錯的——例如你以為加個硬體乘法器會大幅加速,實測發現程式 90% 時間在等 UART,加速器根本沒幫助。因此必須:
- 建立基準(baseline):用相同的測試程式量測改進前的效能。
- 找出瓶頸:用硬體計數器或軟體計時,找出時間花在哪。
- 單一變數改進:一次只改一個地方,重新量測。
- 對比與分析:用表格量化差異,並解釋為什麼。
Dhrystone 與 CoreMark#
兩者是嵌入式處理器最常用的整數基準測試:
| 項目 | Dhrystone | CoreMark |
|---|---|---|
| 年代 | 1988 | 2009(EEMBC) |
| 內容 | 字串處理、指標運算 | 狀態機、鏈結串列、矩陣運算、CRC |
| 輸出單位 | DMIPS/MHz | CoreMark/MHz |
| 常見問題 | 易被編譯器優化「作弊」 | 較不易作弊,更現代 |
| 移植難度 | 極低 | 低(需提供计时函数) |
移植 CoreMark 到自製 SoC#
CoreMark 原始碼極小(約 12KB),只需提供三個掛勾函式。核心修改在 core_portme.c:
/* core_portme.c —— 移植到自製 RISC-V SoC */
#include "coremark.h"
#include "uart.h"
#define NSECS_PER_SEC 100000000 /* 100MHz SoC */
/* 時間來源:CSR cycle 計數器(見下方) */
static unsigned long long begin_cycles, stop_cycles;
__attribute__((noinline))
void start_time(void) {
unsigned hi, lo;
asm volatile ("rdcycleh %0" : "=r"(hi));
asm volatile ("rdcycle %0" : "=r"(lo));
begin_cycles = ((unsigned long long)hi << 32) | lo;
}
__attribute__((noinline))
void stop_time(void) {
unsigned hi, lo;
asm volatile ("rdcycleh %0" : "=r"(hi));
asm volatile ("rdcycle %0" : "=r"(lo));
stop_cycles = ((unsigned long long)hi << 32) | lo;
}
CORETIMETYPE get_time(void) {
return (CORETIMETYPE)(stop_cycles - begin_cycles);
}
void portable_fini(core_results *res) {
double secs = (double)res->time / NSECS_PER_SEC;
double score = (double)res->iterations / secs;
uart_puts("CoreMark 1.0 : ");
print_u32((uint32_t)score); /* 自行實作整數轉字串 */
uart_puts(" / 100MHz = ");
print_u32((uint32_t)(score / 100));
uart_puts(" CoreMark/MHz\r\n");
}
前提:CPU 必須實作 cycle CSR(位址 0xC00 與 0xC80)。這是 RISC-V 標準規定的效能計數器,在 05.1 的 SoC 中加入:
// 在 CPU 內部(或 CSR 單元)加上:
reg [63:0] cycle_count;
always @(posedge clk) cycle_count <= cycle_count + 64'd1;
// Zicsr 讀取:0xC00 -> cycle, 0xC80 -> cycleh
教學註記:若你的 CPU 尚未實作 Zicsr,可用 memory-mapped 計數器(如
0x1000_0020)替代,軟體用lw讀取即可,概念相同。
編譯旗標(與 05.2 相同基礎):
riscv64-unknown-elf-gcc -march=rv32im -mabi=ilp32 -O2 \
-ffunction-sections -fdata-sections -frename-registers \
-DFLAGS_STR='"-O2 rv32im"' \
core_main.c core_list_join.c core_matrix.c core_state.c \
core_util.c core_portme.c uart_min.c crt0.S \
-T link.ld -nostdlib -ffreestanding -o coremark.elf
執行後得到第一組基準數據(範例,單週期 CPU、無快取):
| 配置 | CoreMark/MHz | 說明 |
|---|---|---|
| 單週期 CPU @ 25MHz(BRAM 限制) | 0.35 | 每指令 1 週期但時脈低 |
| 5 級流水線 @ 80MHz | 1.20 | 時脈提升,但分支冒險損失 |
| 流水線 + 分支預測 | 1.45 | 減少分支代價 |
| 流水線 + 預測 + 自訂 CRC 指令 | 1.58 | 加速器只幫助 CRC 部分 |
5.3.2 自訂指令與協同處理器#
概念:RoCC 式協同處理器#
RISC-V 預留了自訂指令編碼空間(custom-0=opcode 0x0B、custom-1=0x2B),允許設計者加入硬體加速指令。常見形式:
- Co-processor 介面(如 Rocket 的 RoCC):CPU 解碼自訂指令,將 rs1/rs2 值交給協同器,協同器回傳結果。
- 緊耦合功能單元:直接在 ALU 旁加一個單元,最省延遲。
協定:CPU 發出 cop_valid, cop_func[2:0], cop_rs1, cop_rs2;協同器算完後以 cop_ready, cop_result 回應;期間 CPU 停等(stall)。
範例一:CRC32 指令#
CRC(循環冗餘校驗)是通訊與儲存必備。軟體逐位元計算需 32 次迴圈;硬體可一拍算 8 位元。定義指令:
- 語意:
rd = crc32_update(rd, rs1)—— 以 rs1 為輸入 byte,更新 rd 中的 CRC 狀態(多項式 0x04C11DB7)。
// crc_cop.v —— CRC32 協同處理器(每拍處理 8 位元)
module crc_cop (
input wire clk,
input wire valid, // CPU 發出自訂指令
input wire [2:0] func,
input wire [31:0] rs1_data, // 新資料 byte(低 8 位元)
input wire [31:0] rs2_data, // 目前的 CRC 狀態
output reg ready,
output reg [31:0] result
);
function [31:0] crc32_byte;
input [31:0] crc;
input [7:0] data;
reg [31:0] x;
integer i;
begin
x = crc ^ {data, 24'b0};
for (i = 0; i < 8; i = i + 1)
x = x[31] ? (x << 1) ^ 32'h04C11DB7 : (x << 1);
crc32_byte = x;
end
endfunction
always @(posedge clk) begin
ready <= 1'b0;
if (valid && func == 3'b000) begin
ready <= 1'b1;
result <= crc32_byte(rs2_data, rs1_data[7:0]);
end
end
endmodule
CPU 中的解碼(在 ID 級):
wire is_custom = (instr[6:0] == 7'b0001011); // custom-0
wire [2:0] cust_func = instr[14:12];
wire crc_sel = is_custom && (cust_func == 3'b000);
// 使流水線停等 1 拍,然後將 crc_result 寫回 rd
軟體端(內嵌組語包裝):
static inline uint32_t crc32_acc(uint32_t crc, uint8_t byte) {
uint32_t out;
asm volatile (".insn r 0x0B, 0, 0, %0, %1, %2"
: "=r"(out) : "r"(byte), "r"(crc));
return out;
}
uint32_t crc32_buf(const uint8_t *p, int n) {
uint32_t crc = 0xFFFFFFFF;
for (int i = 0; i < n; i++)
crc = crc32_acc(crc, p[i]);
return ~crc;
}
.insn r opcode, func3, func7, rd, rs1, rs2 是 GCC 的自訂指令語法,讓你在 C 中直接使用硬體指令,無需修改編譯器。
範例二:FFT 蝶形運算指令#
FFT 的核心是蝶形(butterfly):
Xr' = Xr + (Wr*Xi_r - Wi*Xi_i)
Xi' = Xi + (Wr*Xi_i + Wi*Xi_r)
可設計一條指令完成「複數乘法的一部分」,或更簡單地提供 cmul(16 位元定點複數乘法):
// cmul_cop.v —— 16-bit 定點複數乘法:(ar+ai j)*(br+bi j)
module cmul_cop (
input wire clk,
input wire valid,
input wire [31:0] a, // {ai[15:0], ar[15:0]}
input wire [31:0] b, // {bi[15:0], br[15:0]}
output reg ready,
output reg [31:0] real_part // 僅回傳實部;虛部由第二條指令算
);
wire signed [15:0] ar = a[15:0], ai = a[31:16];
wire signed [15:0] br = b[15:0], bi = b[31:16];
wire signed [31:0] pr = ar*br - ai*bi;
always @(posedge clk) begin
ready <= 1'b0;
if (valid && func == 3'b001) begin
ready <= 1'b1;
real_part <= pr[29:14]; // Q15 定點,右移 14 回到 Q15
end
end
endmodule
(完整 FFT 引擎也可以做成 DMA 式加速器:CPU 丟進資料指標、啟動後輪詢完成旗標。兩種架構各有取捨:自訂指令延遲低但佔用 CPU;DMA 加速器可並行但需匯流排仲裁。)
為什麼加速「有效」?——Amdahl 定律#
若程式在 CRC 部分花費比例為 \(f\) ,該部分加速 \(s\) 倍,則整體加速:
實測中,CoreMark 的 CRC 單元約佔總時間 20%,若 CRC 硬體使其加速 10 倍,理論整體加速為 \(1/(0.8+0.02)\approx 1.22\) 倍。這提醒我們:加速冷門程式碼效果有限,選對目標(f 大的部分)才有意義。
5.3.3 效能前後對比(範例數據)#
以下為 5 級流水線 RV32IM 在 Arty A7-35T 上的量測範例(100MHz 目標):
| 改進項目 | CoreMark/MHz | Dhrystone DMIPS/MHz | LUT 數 | 最高時脈 |
|---|---|---|---|---|
| 基準(5 級流水,無預測) | 1.20 | 0.90 | 2400 | 80 MHz |
| + 分支預測(BTB 16 項 + BHT) | 1.45 | 1.10 | 2750 | 78 MHz |
| + CRC 自訂指令 | 1.58 | 1.12 | 2950 | 78 MHz |
| + cmul 自訂指令 | 1.61 | 1.13 | 3150 | 76 MHz |
| + 2 路組相聯 I-Cache(4KB) | 1.78 | 1.40 | 4100 | 76 MHz |
觀察:
- 分支預測 CP 值最高——分支在 CoreMark 中出現頻率高(約 15–20% 指令)。
- 自訂指令提升有限但穩定,符合 Amdahl 分析。
- 快取在純 BRAM 系統提升不若 DRAM 系統明顯,但為將來外接 SDRAM 打基礎。
5.3.4 期末專題建議清單#
基本要求(擇一)#
- 微架構改進:在既有流水線 CPU 上實作一項改進(分支預測/轉發優化/load-use 氣泡消除),量測前後 CoreMark。
- 自訂指令加速器:選一個演算法(CRC、AES S-Box、FFT 蝶形、popcount、位元反轉),設計自訂指令並完成軟硬整合與對比量測。
- 周邊整合:為 SoC 加上新周邊(SPI、PS/2 鍵盤、VGA 文字模式、Timer+中斷),並寫出對應驅動程式。
加分項目#
- 流水線加寬:從 5 級擴充為 6–7 級(分離 MEM 級、加入額外轉發路徑),或實作雙發射(dual-issue):同一拍發射兩條無相依指令。量測 IPC(每週期指令數)變化。
- 快取相聯度實驗:實作直接映射 vs 2 路/4 路組相聯 I-Cache/D-Cache,用 CoreMark 與矩陣乘法(不同資料走勢)比較 miss rate 與 CPI,討論相聯度與面積/時序的取捨。
- SIMD 自訂指令:定義一組 32-bit 內打包 4×8-bit 或 2×16-bit 的 SIMD 指令(如飽和加法、乘加),實作硬體並用它加速影像灰階轉換或 FIR 濾波,對比純標量版本的速度。
- 中斷與例外:實作完整的 CSR、mtvec、中斷嵌套,寫一個 timer 中斷驅動的多工迷你排程器。
- 軟體工具鏈:寫一個小型 bootloader,支援從 UART 載入程式(見 05.2 習題 5)並結合 QSPI flash 存放多個應用。
評分建議#
| 項目 | 比重 |
|---|---|
| 功能正確性(上板 demo) | 40% |
| 量測嚴謹度(基準/對比/分析) | 25% |
| 報告品質(理論分析、取捨討論) | 25% |
| 加分創意 | 10%+ |
本章小結#
- 效能改進必須「先量測、再改進、再驗證」;CoreMark/MHz 是可比較的標準指標,移植只需提供 start_time/stop_time 等掛勾與 cycle CSR。
- RISC-V 的 custom opcode 加上
.insn內嵌組語,是最低成本的自訂指令整合路徑;CRC 一拍一字元是典型範例。 - Amdahl 定律提醒:加速必須針對時間占比大的部分,否則事倍功半。
- 好的期末專題 = 明確的改進假設 + 可重現的量測 + 深入的取捨分析,而非功能堆疊。
實作題#
- cycle CSR:為你的 CPU 加入
cycle/cyclehCSR,並寫一段程式量測 100 萬次迴圈所耗時脈數,計算出 CPI,與理論值比較。 - CRC 加速:完整實作 5.3.2 的 CRC32 自訂指令,用 1KB 資料量測「軟體版 vs 硬體版」的執行週期數,並代入 Amdahl 定律驗證整體加速是否符合預測。
- FFT 加速:實作 cmul 指令(含虛部指令),完成一個 64 點定點 FFT,比較使用前後的週期數;若時間充裕,將 FFT 做成 DMA 式加速器,比較兩種架構的總時間與 CPU 佔用率。
- 快取相聯度:實作 1KB 直接映射與 1KB 2 路組相聯 I-Cache,用 CoreMark 與轉置矩陣乘法兩種 workload 量測 miss rate,解釋為何不同 workload 下結果不同。
- SIMD 指令:定義並實作
simd_add16(32-bit 內兩個 16-bit 飽和加法)與simd_mul16,用它加速 64-tap FIR 濾波,繪製「標量 vs SIMD」的週期數對比長條圖。