module riscv_single_cycle (
input wire clk, rst_n,
output wire [31:0] imem_addr, // -> 指令記憶體
input wire [31:0] imem_data,
output wire dmem_read, dmem_write,
output wire [31:0] dmem_addr, // -> 資料記憶體
output wire [2:0] dmem_funct3,
output wire [31:0] dmem_wdata,
input wire [31:0] dmem_rdata,
output wire [31:0] dbg_pc // 除錯觀察
);CPU 核心只暴露兩組記憶體埠——把記憶體放在核外(top 模組接線),日後換成快取或匯流排介面(第 3、4 章)都不必改核心。
三段關鍵邏輯
PC 更新
wire [31:0] pc_plus4 = pc + 32'd4;
wire take_branch = branch & br_taken;
wire [31:0] branch_target = pc + imm; // B/J 型共用
wire [31:0] jalr_target = (rs1_data + imm) & ~32'b1; // I 型, 清 bit0
assign pc_next = jump ? branch_target : // jal
jalr ? jalr_target : // jalr
take_branch ? branch_target : // beq/bne/...
pc_plus4;
always @(posedge clk or negedge rst_n)
if (!rst_n) pc <= 32'b0; else pc <= pc_next;四路優先選擇正好對應第 10 章「下一指令參考」的四種來源。注意 jal 與分支共用 pc+imm 加法器——立即值產生器已經依格式(J/B)給出正確的 imm。
ALU 輸入選擇
wire [31:0] alu_a = alu_a_pc ? pc : rs1_data; // auipc 用 PC
wire [31:0] alu_b = alu_src ? imm : rs2_data; // I/S 型用 imm寫回選擇
assign wb_data = (wb_sel == 2'b01) ? dmem_rdata : // 載入
(wb_sel == 2'b10) ? pc_plus4 : // jal/jalr
(wb_sel == 2'b11) ? imm : // lui
alu_y; // 其餘逐類指令的資料流
| add x5,x6,x7 | IMEM\(\to\)解碼\(\to\)讀 x6,x7\(\to\)ALU(加)\(\to\)wb_sel=ALU\(\to\)寫 x5;PC+4 |
|---|---|
| addi x5,x6,-1 | 同上,但 alu_src=1:ALU 的 B 輸入改走 ImmGen(I 型符號延伸) |
| lw x5,8(x6) | 讀 x6\(\to\)ALU 算 EA=x6+8\(\to\)dmem 讀\(\to\)wb_sel=MEM\(\to\)寫 x5。走完最長的一條鏈 |
| sw x7,8(x6) | ALU 算 EA;rs2(x7)直接接 dmem_wdata;mem_write=1;不寫暫存器 |
| beq x5,x6,L | 讀 x5,x6\(\to\)branch_unit 比較;同時 PC+imm 備妥;taken 則 pc_next 改向 |
| jal ra,L | PC+imm\(\to\)pc_next;PC+4\(\to\)寫 ra(wb_sel=PC+4) |
| jalr x0,0(ra) | rs1+imm 清 bit0\(\to\)pc_next;PC+4 寫入 x0 被硬體丟棄 |
| lui x5,0x12345 | ImmGen 給 0x12345000;wb_sel=IMM 直接寫回——ALU 完全閒置 |
| auipc x5,0x1 | alu_a_pc=1:ALU 算 PC+0x1000\(\to\)寫回 |
第 26 章的 testbench 執行 81 條指令的整合測試(算邏、記憶體、分支、呼叫),單週期版在第 98 個週期寫入成功魔數 0xC0DE600D;暫存器傾印與手算期望值完全一致(例如 srl x12, -16, 3 = 0x1FFFFFFE、lh 符號延伸 0xFFFF8000)。
效能分析:為什麼要管線化
單週期的時脈週期 \(\tau\) 必須容納最慢指令的整條組合路徑。以載入為例:
\[\tau \ge t_{IMEM} + t_{DEC/RF} + t_{ALU} + t_{DMEM} + t_{MUX} + t_{setup}\]
假設各段 2/1.5/2/2.5/0.5 ns,則 \(\tau \ge 8.5\) ns(約 118 MHz);但 R 型只需約 6 ns、分支約 5 ns——所有指令都被最慢者拖累(CPI=1,但 \(\tau\) 大)。對照第 12 章公式 \(T = I_c \times \mathrm{CPI} \times \tau\):管線化把 \(\tau\) 降到單級延遲(約 2.5 ns),CPI 略升(危障停頓),總體大勝——這正是第 25 章的任務。
另一個代價:單週期中每個部件每週期只用一次,加法器就要三個(ALU、PC+4、PC+imm),資源利用率低;多週期/管線設計能分時共用。
本章重點回顧
頂層=積木+三段膠水邏輯:PC 四路選擇、ALU 兩輸入多工、寫回四路多工。
每類指令啟用不同的控制訊號組合,在同一張資料路徑上走不同的「高亮路徑」。
單週期:CPI=1、但 \(\tau\) 由載入指令的最長鏈決定;資源不可分時共用。
核心只暴露記憶體埠,記憶體在頂層接線——為未來接快取/匯流排留下介面。
練習
追蹤
sw x7, 12(x2)在資料路徑上的完整資料流,列出所有活躍控制訊號的值。若把 wb_sel=IMM(lui 專用路徑)移除,改讓 lui 走 ALU(A=0,B=imm),需要改哪些訊號?有何優缺點?
用本章的段延遲數字,計算 R 型、載入、分支各自的最短週期;若指令組成為 40%/25%/20%/15%(R/載入/分支/儲存),單週期與「理想變週期」機器的平均每指令時間各是多少?
為 CPU 增加
ebreak停機:解碼到 ebreak 時凍結 PC,輸出 halted 訊號。把 imem/dmem 合併成單一埠記憶體會發生什麼?這是哪一類危障?(第 12 章)
9 D. Patterson and J. Hennessy, Computer Organization and Design, RISC-V Edition, Ch. 4.3–4.4. S. Harris and D. Harris, Digital Design and Computer Architecture, RISC-V Edition, Ch. 7.3. 本教材原始碼:verilog/rtl/riscv_single_cycle.v、top_single_cycle.v。