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 上板尚需实测。
同步控制,不逐位拼接数据
多位总线即使每位都经过两级同步器,也可能在接收端组合成不同次更新的混合值。本工程使用稳定的数据寄存器加往返握手,接收方通过同步后的请求知道何时可以取数,发送方在确认回来前保持原值。数据完整性由协议与物理稳定时间共同保证,不能把两级触发器本身当成多位总线正确性的证明。
按时钟沿追踪一次事务
源端启动后翻转请求,目的域检测同步后的请求与已确认值不同,才开始工作。状态一写入经过变换的数据,状态二读取 FIFO,状态三捕获注册输出并更新确认。源端看到同步后的确认变化后保存结果并产生有效脉冲,AXI 外设再更新完成状态;每一层都有独立的时序含义,排障时不能把相邻信号视为同拍事件。
| 事件 | 必须保持的条件 |
|---|---|
| start 且 ready | 锁存 payload,翻转 req |
| 等待 ack | held_data 不变,即使 DATA 寄存器被重写 |
| 工作域 state=3 | FIFO 注册输出稳定后锁存 return_data,再确认 |
| ack_sync 变化 | 源端采样返回数据,形成 result_valid |
| 下一次启动 | 只能在前次完成后,不能覆盖尚未消费的握手状态 |
手写教学邮箱;不是 AMD XPM 宏。物理实现必须配套检查时序约束。
`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 域会破坏历史状态,产品需要这种能力时应另行设计重连或代际编号机制。
- 观察锁存
预期与判定:启动后 held_data 保持到往返确认完成。
- 制造忙状态
预期与判定:仿真暂停工作时钟,再写 DATA 和 START,检查保持与 SLVERR。
- 共同复位
预期与判定:取消等待中的命令,确认没有过期结果重新出现。
- 恢复运行
预期与判定:重发一笔完整命令,校验新结果。
用于本工程的共同异步断言、各域同步释放;不能支持任意单域热复位。
(* 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。
- 共同复位取消在途事务且可恢复。
延伸阅读与来源
本文为黄河电子原创教学说明。产品命令与支持范围请以实际安装版本的官方文档为准。
- Arm AMBA AXI / ACE IHI0022H
A3 通道握手;B1 AXI4-Lite。寄存器行为与示例实现为本文原创设计。
- AMD UG903 · Constraining Asynchronous Signals
跨域数据路径最大延时与时钟组例外的优先级。
- AMD UG908 · Programming and Debugging
Hardware Manager 与 ILA 配置、触发及采集。
继续阅读
FPGA从 RTL 到板上运行的完整流程
建立约束、实现、下载与观测的最小工程闭环。
第一个工程的引脚与时钟约束
区分电气连接与时间要求,避免“能生成位流就算正确”。
如何读懂时序报告
从起点、终点、时钟和裕量找出失败路径的原因。
黄河电子