第07课 · 寄存器
时序逻辑移位寄存器寄存器文件
📌 学习目标:实现通用寄存器(加载、清零)、移位寄存器(左移、右移、串入串出),以及 RISC-V 寄存器文件,通过 Verilator 验证。
一、寄存器基础
寄存器是多个 D 触发器的组合,用于存储多位数据。RISC-V 的 x0-x31 就是 32 个 32 位寄存器。
1.1 通用寄存器功能
- 加载(Load):时钟边沿写入新数据
- 保持(Hold):不加载时保持原值
- 清零(Clear):异步或同步复位为 0
- 输出(Read):随时可读(组合逻辑)
二、移位寄存器
移位寄存器能在时钟驱动下将数据左移或右移,广泛用于:
- 串并转换(UART 通信)
- 乘除法(左移×2,右移÷2)
- 延迟线
- 伪随机数生成(LFSR)
2.1 通用移位寄存器
支持:并行加载、左移、右移、保持。
| 模式 | s1 | s0 | 功能 |
| 保持 | 0 | 0 | Q 不变 |
| 右移 | 0 | 1 | Q >> 1, MSB ← si |
| 左移 | 1 | 0 | Q << 1, LSB ← si |
| 加载 | 1 | 1 | Q ← D |
三、RISC-V 寄存器文件
RISC-V 有 32 个 32 位寄存器(x0-x31),其中 x0 硬连线为 0。寄存器文件支持2读1写。
📋 RISC-V 寄存器约定:
- x0 (zero) — 恒为 0
- x1 (ra) — 返回地址
- x2 (sp) — 栈指针
- x5-x7 (t0-t2) — 临时寄存器
- x10-x17 (a0-a7) — 函数参数/返回值
- x8-x9, x18-x27 (s0-s11) — 保存寄存器
- x28-x31 (t3-t6) — 临时寄存器
四、Verilog 实现
module shift_register #(parameter WIDTH = 8) (
input clk, rst,
input [1:0] mode,
input si,
input [WIDTH-1:0] din,
output [WIDTH-1:0] q
);
reg [WIDTH-1:0] q;
always @(posedge clk or posedge rst) begin
if (rst)
q <= {WIDTH{1'b0}};
else
case (mode)
2'b00: q <= q;
2'b01: q <= {si, q[WIDTH-1:1]};
2'b10: q <= {q[WIDTH-2:0], si};
2'b11: q <= din;
endcase
end
endmodule
module regfile (
input clk,
input [4:0] rs1, rs2,
input we,
input [4:0] rd,
input [31:0] wd,
output [31:0] rd1, rd2
);
reg [31:0] regs [31:0];
assign rd1 = (rs1 != 0) ? regs[rs1] : 32'b0;
assign rd2 = (rs2 != 0) ? regs[rs2] : 32'b0;
always @(posedge clk) begin
if (we && rd != 0)
regs[rd] <= wd;
end
endmodule
4.1 测试台
module tb_register;
reg clk, rst;
reg [1:0] mode;
reg si;
reg [7:0] din;
wire [7:0] q;
shift_register #(.WIDTH(8)) u_sr (
.clk(clk), .rst(rst), .mode(mode), .si(si), .din(din), .q(q)
);
integer pass = 0, fail = 0;
always #5 clk = ~clk;
initial begin
clk = 0; rst = 1; mode = 2'b00; si = 0; din = 8'b0;
#12; rst = 0;
mode = 2'b11; din = 8'hA5; #10;
if (q !== 8'hA5) begin $display("FAIL: load"); fail=fail+1; end else pass=pass+1;
mode = 2'b10; si = 0; #10;
if (q !== 8'h4A) begin $display("FAIL: left shift got %h", q); fail=fail+1; end else pass=pass+1;
mode = 2'b11; din = 8'h0F; #10;
if (q !== 8'h0F) begin $display("FAIL: reload"); fail=fail+1; end else pass=pass+1;
mode = 2'b01; si = 0; #10;
if (q !== 8'h07) begin $display("FAIL: right shift got %h", q); fail=fail+1; end else pass=pass+1;
mode = 2'b00; #10;
if (q !== 8'h07) begin $display("FAIL: hold"); fail=fail+1; end else pass=pass+1;
rst = 1; #10;
if (q !== 8'h00) begin $display("FAIL: clear"); fail=fail+1; end else pass=pass+1;
$display("========================================");
$display("寄存器测试: PASS=%0d FAIL=%0d", pass, fail);
if (fail == 0) $display("✅ 移位+加载+清零 全部验证通过!");
else $display("❌ 存在失败!");
$display("========================================");
$finish;
end
endmodule
五、Verilator 编译命令
verilator --cc register.v --exe tb_register.v \
--build --top-module tb_register
./obj_dir/Vtb_register
🤔 思考题:RISC-V 的 x0 寄存器为什么硬连线为 0?如果允许写 x0 会有什么问题?
💡 提示:x0=0 可以作为常量来源,简化指令编码;写 x0 无意义但可能导致意外行为
🏆 成就解锁:寄存器设计师
✅ Verilator 仿真验证通过
✅ 移位寄存器:左移、右移验证正确
✅ 并行加载、保持、清零验证正确
✅ 寄存器文件:x0=0、2读1写逻辑正确
🎯 下一目标:计数器 → 第08课:计数器