Verilog实现RPC从机(配合AXI_Slave使用)
本文介绍如何使用 Verilog 实现一个 RPC 从机模块,并通过 AXI-Lite 总线 与外部主机系统(如 Zynq PS 端)进行RPC调用。
具体地,我们在 AXI Slave 模块中实例化 rpc_processor
模块,并将 AXI Slave的寄存器映射到 rpc_processor
的参数/结果接口,使得主机可以通过 AXI 接口:
- 触发 RPC 调用
- 传递调用方法与输入参数
- 接收处理结果与完成标志
该模块需配合我另一篇博客 《Zynq AXI-Lite 总线原理与实现》 一起使用,读者可结合使用 AXI-Lite 接口读写代码与本模块逻辑,实现完整的软硬件交互。
模块功能
rpc_processor
模块是RPC从机,外部主机通过AXI Slave接口发送RPC请求,并通过读取AXI Slave寄存器获取处理结果。rpc_processor
支持的RPC方法包括:
- 回显功能(Echo):返回请求的参数。
- 加法功能(Add): 将请求参数相加并返回结果。
引脚定义
引脚名 | 方向 | 描述 | 位数 |
---|---|---|---|
i_clk | 输入 | 时钟信号 | - |
i_rst_n | 输入 | 复位信号(低有效) | - |
i_method_reg | 输入 | 功能码寄存器,选择RPC方法 | 32位 |
i_req_reg_0 | 输入 | 请求参数0 | 32位 |
i_req_reg_1 | 输入 | 请求参数1 | 32位 |
i_req_reg_2 | 输入 | 请求参数2 | 32位 |
i_req_reg_3 | 输入 | 请求参数3 | 32位 |
o_res_reg_0 | 输出 | 响应结果0 | 32位 |
o_res_reg_1 | 输出 | 响应结果1 | 32位 |
o_res_reg_2 | 输出 | 响应结果2 | 32位 |
o_res_reg_3 | 输出 | 响应结果3 | 32位 |
i_rpc_start | 输入 | RPC启动信号(触发处理) | 1位 |
o_rpc_valid | 输出 | RPC请求有效标志 | 1位 |
i_rpc_ready | 输入 | 外部处理就绪信号 | 1位 |
o_rpc_done | 输出 | RPC处理完成标志 | 1位 |
执行流程:
- 主机通过写请求传递RPC功能码和参数。
rpc_processor
模块根据功能码执行操作。- 主机通过读请求获取RPC处理结果。
rpc_processor.v
`timescale 1ns/1ps// 宏定义:RPC方法(32位功能码)
`define RPC_FUNC_ECHO 32'h00000000 // 回显功能(返回输入参数)
`define RPC_FUNC_ADD 32'h00000001 // 加法功能(参数相加)module rpc_processor (input wire i_clk, // 时钟信号input wire i_rst_n, // 复位信号(低有效)// 寄存器接口(直接映射到axi_slave寄存器)input wire [31:0] i_method_reg, // 方法选择寄存器input wire [31:0] i_req_reg_0, // 请求参数0input wire [31:0] i_req_reg_1, // 请求参数1input wire [31:0] i_req_reg_2, // 请求参数2input wire [31:0] i_req_reg_3, // 请求参数3output reg [31:0] o_res_reg_0, // 响应结果0output reg [31:0] o_res_reg_1, // 响应结果1output reg [31:0] o_res_reg_2, // 响应结果2output reg [31:0] o_res_reg_3, // 响应结果3// RPC控制信号input wire i_rpc_start, // RPC启动信号(1=触发处理,上升沿有效)output reg o_rpc_valid, // RPC请求有效(处理中保持高)input wire i_rpc_ready, // 外部处理就绪(1=可接收请求)output reg o_rpc_done // RPC处理完成(1=结果有效)
);// --------------------------// 启动信号边沿检测(防止持续触发)// --------------------------reg r_rpc_start_dly;wire w_rpc_start_posedge; // 启动信号上升沿(真正的触发点)always @(posedge i_clk or negedge i_rst_n) beginif (!i_rst_n) beginr_rpc_start_dly <= 1'b0;end else beginr_rpc_start_dly <= i_rpc_start; // 延迟一拍用于边沿检测endendassign w_rpc_start_posedge = i_rpc_start && !r_rpc_start_dly; // 上升沿检测// --------------------------// 内部锁存寄存器(处理期间保持参数稳定)// --------------------------reg [31:0] r_method_latch;reg [31:0] r_req_latch_0, r_req_latch_1, r_req_latch_2, r_req_latch_3;// --------------------------// RPC处理状态机// --------------------------localparam S_IDLE = 2'b00;localparam S_PROCESSING = 2'b01;localparam S_DONE = 2'b10;reg [1:0] r_state;reg [3:0] r_proc_cnt; // 模拟处理延迟(0~15周期)always @(posedge i_clk or negedge i_rst_n) beginif (!i_rst_n) beginr_state <= S_IDLE;r_proc_cnt <= 4'h0;o_rpc_valid <= 1'b0;o_rpc_done <= 1'b0;r_method_latch <= 32'h0;r_req_latch_0 <= 32'h0;r_req_latch_1 <= 32'h0;r_req_latch_2 <= 32'h0;r_req_latch_3 <= 32'h0;o_res_reg_0 <= 32'h0;o_res_reg_1 <= 32'h0;o_res_reg_2 <= 32'h0;o_res_reg_3 <= 32'h0;end else begincase (r_state)S_IDLE: begino_rpc_done <= 1'b0; // 空闲状态下完成标志清0// 检测到启动信号上升沿,且外部就绪时,启动处理if (w_rpc_start_posedge && i_rpc_ready) begin// 锁存当前寄存器值(处理期间参数不变)r_method_latch <= i_method_reg;r_req_latch_0 <= i_req_reg_0;r_req_latch_1 <= i_req_reg_1;r_req_latch_2 <= i_req_reg_2;r_req_latch_3 <= i_req_reg_3;o_rpc_valid <= 1'b1; // 置位请求有效r_state <= S_PROCESSING; // 进入处理状态r_proc_cnt <= 4'h0; // 重置延迟计数器end else begino_rpc_valid <= 1'b0;r_state <= S_IDLE;endendS_PROCESSING: begin// 模拟处理延迟(例如10个时钟周期,可修改)if (r_proc_cnt >= 4'd9) begin// 根据方法号执行不同处理(示例逻辑)case (r_method_latch)`RPC_FUNC_ECHO: begin // 方法0:返回请求参数o_res_reg_0 <= r_req_latch_0;o_res_reg_1 <= r_req_latch_1;o_res_reg_2 <= r_req_latch_2;o_res_reg_3 <= r_req_latch_3;end`RPC_FUNC_ADD: begin // 方法1:参数相加o_res_reg_0 <= r_req_latch_0 + r_req_latch_1;o_res_reg_1 <= r_req_latch_2 + r_req_latch_3;o_res_reg_2 <= r_req_latch_0 + r_req_latch_2;o_res_reg_3 <= r_req_latch_1 + r_req_latch_3;enddefault: begin o_res_reg_0 <= 32'h0;o_res_reg_1 <= 32'h0;o_res_reg_2 <= 32'h0;o_res_reg_3 <= 32'h0;endendcaser_state <= S_DONE;end else beginr_proc_cnt <= r_proc_cnt + 1'b1;r_state <= S_PROCESSING;endendS_DONE: begino_rpc_valid <= 1'b0; // 清除请求有效o_rpc_done <= 1'b1; // 置位完成标志(通知结果就绪)r_state <= S_IDLE; // 返回空闲状态,等待下一次启动enddefault: r_state <= S_IDLE;endcaseendendendmodule
tb.v
`timescale 1ns/1ps// 接口定义:抽象DUT的所有信号
interface rpc_if;logic i_clk;logic i_rst_n;logic [31:0] i_method_reg;logic [31:0] i_req_reg_0;logic [31:0] i_req_reg_1;logic [31:0] i_req_reg_2;logic [31:0] i_req_reg_3;logic i_rpc_start;logic i_rpc_ready;logic [31:0] o_res_reg_0;logic [31:0] o_res_reg_1;logic [31:0] o_res_reg_2;logic [31:0] o_res_reg_3;logic o_rpc_valid;logic o_rpc_done;// 时钟生成(50MHz,周期20ns)initial begini_clk = 1'b0;forever #10 i_clk = ~i_clk;end
endinterface// RPC测试类:封装所有测试逻辑
class rpc_tester;// 虚拟接口:用于连接DUTvirtual rpc_if vif;// 宏定义:RPC方法(与DUT保持一致)localparam RPC_FUNC_ECHO = 32'h00000000;localparam RPC_FUNC_ADD = 32'h00000001;// 构造函数:绑定接口function new(virtual rpc_if ifc);vif = ifc;endfunction// 初始化所有信号task initialize();vif.i_rst_n = 1'b0;vif.i_method_reg = 32'h0;vif.i_req_reg_0 = 32'h0;vif.i_req_reg_1 = 32'h0;vif.i_req_reg_2 = 32'h0;vif.i_req_reg_3 = 32'h0;vif.i_rpc_start = 1'b0;vif.i_rpc_ready = 1'b0;endtask// 执行复位并等待稳定task reset();@(posedge vif.i_clk);vif.i_rst_n = 1'b0;#100; // 保持复位100ns@(posedge vif.i_clk);vif.i_rst_n = 1'b1; // 释放复位#20; // 等待复位释放稳定endtask// 发送RPC请求并等待完成task send_rpc(input logic [31:0] method,input logic [31:0] req0,input logic [31:0] req1,input logic [31:0] req2,input logic [31:0] req3,input logic ready);// 设置请求参数@(posedge vif.i_clk);vif.i_method_reg = method;vif.i_req_reg_0 = req0;vif.i_req_reg_1 = req1;vif.i_req_reg_2 = req2;vif.i_req_reg_3 = req3;vif.i_rpc_ready = ready;// 产生start上升沿@(posedge vif.i_clk);vif.i_rpc_start = 1'b1;@(posedge vif.i_clk);vif.i_rpc_start = 1'b0;// 等待处理完成(如果ready为1)if (ready) beginwait(vif.o_rpc_done == 1'b1);@(posedge vif.i_clk);endendtask// 验证ADD方法结果task verify_add_result(input logic [31:0] exp0,input logic [31:0] exp1,input logic [31:0] exp2,input logic [31:0] exp3);@(posedge vif.i_clk);if (vif.o_res_reg_0 == exp0 && vif.o_res_reg_1 == exp1 &&vif.o_res_reg_2 == exp2 && vif.o_res_reg_3 == exp3) begin$display("ADD method test passed %d",exp0);end else begin$display("ADD method test failed, Expected: %h %h %h %h, Actual: %h %h %h %h",exp0, exp1, exp2, exp3,vif.o_res_reg_0, vif.o_res_reg_1, vif.o_res_reg_2, vif.o_res_reg_3);endendtask// 运行完整测试序列task run_test();// 初始化并复位initialize();reset();// 执行ADD方法测试send_rpc(RPC_FUNC_ADD,32'h00000001, // req032'h00000002, // req132'h00000003, // req232'h00000004, // req31'b1 // ready);// 验证结果(1+2=3, 3+4=7, 1+3=4, 2+4=6)verify_add_result(32'h00000003,32'h00000007,32'h00000004,32'h00000006);// 测试完成#100;$display("All tests completed");$finish;endtask
endclass// 顶层测试平台
module tb;wire w_test;// 实例化接口rpc_if rpc_ifc();// 实例化DUT并连接接口rpc_processor uut (.i_clk (rpc_ifc.i_clk),.i_rst_n (rpc_ifc.i_rst_n),.i_method_reg (rpc_ifc.i_method_reg),.i_req_reg_0 (rpc_ifc.i_req_reg_0),.i_req_reg_1 (rpc_ifc.i_req_reg_1),.i_req_reg_2 (rpc_ifc.i_req_reg_2),.i_req_reg_3 (rpc_ifc.i_req_reg_3),.o_res_reg_0 (rpc_ifc.o_res_reg_0),.o_res_reg_1 (rpc_ifc.o_res_reg_1),.o_res_reg_2 (rpc_ifc.o_res_reg_2),.o_res_reg_3 (rpc_ifc.o_res_reg_3),.i_rpc_start (rpc_ifc.i_rpc_start),.o_rpc_valid (rpc_ifc.o_rpc_valid),.i_rpc_ready (rpc_ifc.i_rpc_ready),.o_rpc_done (rpc_ifc.o_rpc_done));// 启动测试initial begin// 创建测试实例并运行测试rpc_tester tester = new(rpc_ifc);tester.run_test();end
endmodule
测试结果
因为
32'h00000001, // req0
32'h00000002, // req1
所以测试结果为
ADD method test passed 3