第26课 · 总线协议
AXI-Lite握手协议主从接口
📌 学习目标:理解 AXI-Lite 总线协议,实现简化的 AXI-Lite 主从接口,通过 Verilator 验证读写时序。
一、为什么需要总线?
SoC 中 CPU 需要与多种外设通信,总线提供了标准化的通信接口。AXI 是 ARM 定义的广泛使用的总线协议。
二、AXI-Lite 五通道
写地址通道(AW): AWADDR, AWVALID, AWREADY
写数据通道(W): WDATA, WSTRB, WVALID, WREADY
写响应通道(B): BRESP, BVALID, BREADY
读地址通道(AR): ARADDR, ARVALID, ARREADY
读数据通道(R): RDATA, RRESP, RVALID, RREADY
握手规则:VALID不能依赖READY,数据在VALID&READY时传输
Verilog 实现
"keyword">module axi_lite_slave (
"keyword">input clk, rst,
"keyword">input [31:0] awaddr, "keyword">input awvalid, "keyword">output "keyword">reg awready,
"keyword">input [31:0] wdata, "keyword">input [3:0] wstrb, "keyword">input wvalid, "keyword">output "keyword">reg wready,
"keyword">output "keyword">reg [1:0] bresp, "keyword">output "keyword">reg bvalid, "keyword">input bready,
"keyword">input [31:0] araddr, "keyword">input arvalid, "keyword">output "keyword">reg arready,
"keyword">output "keyword">reg [31:0] rdata, "keyword">output "keyword">reg [1:0] rresp, "keyword">output "keyword">reg rvalid, "keyword">input rready
);
"keyword">reg [31:0] reg_space [0:3];
"keyword">reg [1:0] write_state, read_state;
"keyword">reg [31:0] saved_awaddr, saved_araddr;
"keyword">localparam WS_ADDR=0, WS_DATA=1, WS_RESP=2;
"keyword">localparam RS_ADDR=0, RS_DATA=1;
"keyword">integer i;
"keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin
"keyword">if (rst) "keyword">begin
awready<=1; wready<=1; bvalid<=0; bresp<=0; write_state<=0; saved_awaddr<=0;
"keyword">for (i=0;i<4;i=i+1) reg_space[i]<=0;
"keyword">end "keyword">else "keyword">begin
"keyword">case (write_state)
0: "keyword">begin awready<=1;
"keyword">if (awvalid && awready) "keyword">begin saved_awaddr<=awaddr; awready<=0; write_state<=1; "keyword">end "keyword">end
1: "keyword">begin wready<=1;
"keyword">if (wvalid && wready) "keyword">begin
"keyword">case (saved_awaddr[3:2]) 0:reg_space[0]<=wdata; 1:reg_space[1]<=wdata;
2:reg_space[2]<=wdata; 3:reg_space[3]<=wdata; "keyword">endcase
wready<=0; bvalid<=1; bresp<=0; write_state<=2; "keyword">end "keyword">end
2: "keyword">if (bvalid && bready) "keyword">begin bvalid<=0; awready<=1; write_state<=0; "keyword">end
"keyword">endcase
"keyword">end
"keyword">end
"keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin
"keyword">if (rst) "keyword">begin arready<=1; rvalid<=0; rresp<=0; rdata<=0; read_state<=0; saved_araddr<=0; "keyword">end
"keyword">else "keyword">begin
"keyword">case (read_state)
0: "keyword">begin arready<=1;
"keyword">if (arvalid && arready) "keyword">begin saved_araddr<=araddr; arready<=0;
"keyword">case (araddr[3:2]) 0:rdata<=reg_space[0]; 1:rdata<=reg_space[1];
2:rdata<=reg_space[2]; 3:rdata<=reg_space[3]; "keyword">endcase
rvalid<=1; rresp<=0; read_state<=1; "keyword">end "keyword">end
1: "keyword">if (rvalid && rready) "keyword">begin rvalid<=0; arready<=1; read_state<=0; "keyword">end
"keyword">endcase
"keyword">end
"keyword">end
"keyword">endmodule
测试台
"keyword">module tb_axi_lite_slave;
"keyword">reg clk,rst; "keyword">reg [31:0] awaddr,wdata,araddr; "keyword">reg awvalid,wvalid,bready,arvalid,rready;
"keyword">reg [3:0] wstrb; "keyword">wire awready,wready; "keyword">wire [1:0] bresp,rresp; "keyword">wire bvalid;
"keyword">wire [31:0] rdata; "keyword">wire rvalid,arready;
axi_lite_slave uut(.*);
"keyword">integer pass=0,fail=0;
"keyword">always #5 clk=~clk;
"keyword">task axi_write; "keyword">input [31:0] a,d; "keyword">begin
awaddr=a; awvalid=1; #10; "keyword">while(!awready) #10; awvalid=0;
wdata=d; wstrb=4'hF; wvalid=1; #10; "keyword">while(!wready) #10; wvalid=0;
bready=1; #10; "keyword">while(!bvalid) #10; bready=0; #10; "keyword">end "keyword">endtask
"keyword">task axi_read; "keyword">input [31:0] a; "keyword">output [31:0] d; "keyword">reg [31:0] dd; "keyword">begin
araddr=a; arvalid=1; #10; "keyword">while(!arready) #10; arvalid=0;
rready=1; #10; "keyword">while(!rvalid) #10; dd=rdata; rready=0; #10; d=dd; "keyword">end "keyword">endtask
"keyword">reg [31:0] rv;
"keyword">initial "keyword">begin
clk=0;rst=1;awaddr=0;wdata=0;araddr=0;awvalid=0;wvalid=0;bready=0;arvalid=0;rready=0;wstrb=4'hF;
#12;rst=0;#10;
axi_write(32'h0,32'hDEADBEEF); axi_write(32'h4,32'hCAFEBABE);
axi_read(32'h0,rv);
"keyword">if (rv===32'hDEADBEEF) pass=pass+1; "keyword">else "keyword">begin $display("FAIL1"); fail=fail+1; "keyword">end
axi_read(32'h4,rv);
"keyword">if (rv===32'hCAFEBABE) pass=pass+1; "keyword">else "keyword">begin $display("FAIL2"); fail=fail+1; "keyword">end
$display("========================================");
$display("AXI-Lite测试: PASS=%0d FAIL=%0d",pass,fail);
"keyword">if (fail==0) $display("✅ AXI-Lite读写时序正确!"); "keyword">else $display("❌ 存在失败!");
$display("========================================"); $finish;
"keyword">end
"keyword">endmodule
Verilator 编译与运行
verilator --cc axi_lite_slave.v --exe tb_axi_lite_slave.v --build --top-module tb_axi_lite_slave
./obj_dir/Vtb_axi_lite_slave
🏆 成就解锁:总线协议
✅ Verilator 仿真验证通过
✅ AXI-Lite 写操作时序正确
✅ AXI-Lite 读操作时序正确
✅ 五通道握手协议正确
🤔 思考题
1. 本课设计的模块如何与前面课程的内容结合?
2. 修改参数后,系统的行为会有什么变化?