FPGA 进阶实战 · 03 多位跨域握手与复位契约

让 payload 保持到确认返回,理解双向 toggle、数据一致性与共同复位要求。

先修知识完成 FPGA FIFO 六章基础实战;理解同步时序、非阻塞赋值与 ready/valid。

适用环境Basys 3 / xc7a35tcpg236-1;板上 100/50 MHz 同源时钟;Icarus Verilog、Python 3;Vivado 2024.1 命令参考。

验证状态三组时钟 RTL 测试、两类负向测试及板上自检器的软件模型已通过;Vivado、物理 CDC 和 ILA 上板尚需实测。

本文目录同步控制,不逐位拼接数据按时钟沿追踪一次事务共同断言复位,各域同步释放典型故障:忙时数据被覆盖

同步控制,不逐位拼接数据

多位总线即使每位都经过两级同步器,也可能在接收端组合成不同次更新的混合值。本工程使用稳定的数据寄存器加往返握手,接收方通过同步后的请求知道何时可以取数,发送方在确认回来前保持原值。数据完整性由协议与物理稳定时间共同保证,不能把两级触发器本身当成多位总线正确性的证明。

单槽邮箱限制了吞吐,但使数据何时能改变可以被清楚验证。源端锁存held_data + req目的域同步req_meta → req_syncFIFO 操作写 → 读 → 捕获返回确认return_data + ackreq 与 ack 各经过两级同步;数据保持覆盖整个往返过程
单槽邮箱限制了吞吐,但使数据何时能改变可以被清楚验证。(原创示意,非器件引脚图;窄屏可横向滑动)

按时钟沿追踪一次事务

源端启动后翻转请求,目的域检测同步后的请求与已确认值不同,才开始工作。状态一写入经过变换的数据,状态二读取 FIFO,状态三捕获注册输出并更新确认。源端看到同步后的确认变化后保存结果并产生有效脉冲,AXI 外设再更新完成状态;每一层都有独立的时序含义,排障时不能把相邻信号视为同拍事件。

工程接口与验收依据
事件必须保持的条件
start 且 ready锁存 payload,翻转 req
等待 ackheld_data 不变,即使 DATA 寄存器被重写
工作域 state=3FIFO 注册输出稳定后锁存 return_data,再确认
ack_sync 变化源端采样返回数据,形成 result_valid
下一次启动只能在前次完成后,不能覆盖尚未消费的握手状态

手写教学邮箱;不是 AMD XPM 宏。物理实现必须配套检查时序约束。

systemverilog
`timescale 1ns/1ps
// Bundled-data request/acknowledge. Both domains MUST share reset assertion.
(* KEEP_HIERARCHY="yes" *)
module cdc_mailbox #(parameter INJECT_ERROR=0)(
 input wire s_clk,s_rst,d_clk,d_rst,start,input wire [31:0] payload,
 output wire ready,output reg result_valid,output reg [31:0] result,
 output wire [79:0] debug_s,debug_d);
 reg [31:0] held_data;
 reg req,ack,seen_ack;
 (* ASYNC_REG="TRUE" *) reg ack_meta,ack_sync;
 (* ASYNC_REG="TRUE" *) reg req_meta,req_sync;
 reg [31:0] return_data;
 reg [2:0] state;
 wire [31:0] fifo_out;wire full,empty;wire [2:0] count;
 wire [31:0] transformed=held_data ^ 32'h5a5aa5a5 ^ (INJECT_ERROR?32'h1:32'h0);
 course_fifo u_fifo(d_clk,d_rst,state==1,state==2,transformed,fifo_out,full,empty,count);
 assign ready=(req==ack_sync);
 always @(posedge s_clk or posedge s_rst) begin
  if(s_rst) begin held_data<=0;req<=0;seen_ack<=0;ack_meta<=0;ack_sync<=0;result_valid<=0;result<=0;end
  else begin
   ack_meta<=ack;ack_sync<=ack_meta;result_valid<=0;
   if(start && ready) begin held_data<=payload;req<=~req;end
   if(ack_sync!=seen_ack) begin result<=return_data;result_valid<=1;seen_ack<=ack_sync;end
  end
 end
 always @(posedge d_clk or posedge d_rst) begin
  if(d_rst) begin req_meta<=0;req_sync<=0;ack<=0;return_data<=0;state<=0;end
  else begin
   req_meta<=req;req_sync<=req_meta;
   case(state)
    0:if(req_sync!=ack)state<=1;
    1:if(!full)state<=2;
    2:if(!empty)state<=3;
    3:begin return_data<=fifo_out;ack<=req_sync;state<=0;end
   endcase
  end
 end
 assign debug_s={8'b0,held_data,result,req,ack_sync,ready,result_valid,start,s_rst,2'b0};
 assign debug_d={7'b0,fifo_out,return_data,req_sync,ack,state,count,d_rst};
endmodule

共同断言复位,各域同步释放

本工程把按钮或时钟失锁作为共同复位请求,两域分别通过复位同步器释放。复位取消在途命令及未完成总线响应,板上 manager 同时复位后重新开始;若系统只重置外设而让 manager 永远等待旧响应,就超出了此接口契约。单独复位其中一个 toggle 域会破坏历史状态,产品需要这种能力时应另行设计重连或代际编号机制。

  1. 观察锁存

    预期与判定:启动后 held_data 保持到往返确认完成。

  2. 制造忙状态

    预期与判定:仿真暂停工作时钟,再写 DATA 和 START,检查保持与 SLVERR。

  3. 共同复位

    预期与判定:取消等待中的命令,确认没有过期结果重新出现。

  4. 恢复运行

    预期与判定:重发一笔完整命令,校验新结果。

用于本工程的共同异步断言、各域同步释放;不能支持任意单域热复位。

systemverilog
(* KEEP_HIERARCHY="yes" *)
module reset_domain(input wire clk,async_reset,output wire rst);
 (* ASYNC_REG="TRUE" *)reg [2:0] pipe=3'b111;
 always @(posedge clk or posedge async_reset)
  if(async_reset)pipe<=3'b111;else pipe<={pipe[1:0],1'b0};
 assign rst=pipe[2];
endmodule

典型故障:忙时数据被覆盖

若目的域直接使用不断变化的 DATA 寄存器,主机为下一笔准备数据时可能改变正在传输的命令。仅测试发送一笔后等待很久往往发现不了这一错误,因此本实验在工作时钟暂停时修改 DATA,确认恢复后返回的仍是先前锁存值。暂停时钟是仿真压力场景,不表示板上设计提供动态关钟功能。

完成检查

  • 能够逐沿解释请求、FIFO 输出与确认。
  • 忙时覆盖测试保留原始 payload。
  • 共同复位取消在途事务且可恢复。

延伸阅读与来源

本文为黄河电子原创教学说明。产品命令与支持范围请以实际安装版本的官方文档为准。

继续阅读

FPGA

从 RTL 到板上运行的完整流程

建立约束、实现、下载与观测的最小工程闭环。

10 分钟
FPGA

第一个工程的引脚与时钟约束

区分电气连接与时间要求,避免“能生成位流就算正确”。

11 分钟
FPGA

如何读懂时序报告

从起点、终点、时钟和裕量找出失败路径的原因。

10 分钟