当前位置: 首页 > news >正文

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输入请求参数032位
i_req_reg_1输入请求参数132位
i_req_reg_2输入请求参数232位
i_req_reg_3输入请求参数332位
o_res_reg_0输出响应结果032位
o_res_reg_1输出响应结果132位
o_res_reg_2输出响应结果232位
o_res_reg_3输出响应结果332位
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
http://www.dtcms.com/a/307696.html

相关文章:

  • 消息队列学习-----消息消失与积压
  • 操作系统数据格式相关(AI回答)
  • 性能优化(二):JS内存泄漏“探案”:从闭包到事件监听的隐形杀手
  • 经典屏保问题 - 华为OD机试真题(Java 题解)
  • uniapp Vue3版本使用pinia存储持久化插件pinia-plugin-persistedstate对微信小程序的配置
  • Django模型迁移指南:从命令用法到最佳实践
  • 分布式微服务--万字详解 微服务的各种负载均衡全场景以注意点
  • Vue3 + Electron 技术栈下 MAC 地址获取的方法、准确性优化与应对策略
  • mac操作笔记
  • nuxt3: trpc-nuxt和sqlite导致的503错误
  • Python 动态属性和特性(使用动态属性转换数据)
  • 【烧脑算法】Dijkstra 算法:解决最短路问题
  • PHP开发
  • SAP Datasphere 02 - 建模
  • 文件无法复制到u盘,提示0x80071ac3错误
  • SpringBoot原理揭秘--自动装配(终)
  • Cesium 快速入门(二)底图更换
  • Spring Cloud『学习笔记』
  • 前端项目如何同时导入一个库的不同版本
  • SpringMVC的核心架构与请求处理流程
  • React中的this绑定
  • 网关 + MDC 过滤器方案,5分钟集成 日志 traceid
  • Java学习-----SpringBoot的常用注解(下)
  • 嵌入式硬件中瓷片电容的基本原理与详解
  • WebRTC 多媒体 SDP 示例与解析
  • 嵌入式硬件学习(十)—— LED驱动+杂项设备驱动
  • 2025电商CPS分销与推客系统小程序开发:趋势、架构与实战解析
  • SpringBoot3.x引入Quartz,持久化到MySQL数据库
  • npm 设置国内镜像源
  • 中宇联:以“智云融合+AI”赋能全栈云MSP服务,深化阿里云生态合作