FPGA的时序问题(1)

       FPGA的时序问题,包括两种时序问题。一种是FPGA内部的寄存器到寄存器之间的时序,另外一种是FPGA接口,外部的寄存器到达FPGA内部的寄存器,也就是接口IO的时序问题。

    FPGA的内部寄存器的时序问题,包括建立时间与保持时间。如下图所示。

    建立时间里包含两条路径,一个是数据走的路径,一个是时钟走的路径。我们要保证的是,在时钟来临之前,数据已经准备好了一段时间。

   数据时间Tdata=Tclk1+Tco+Tdata

   时钟时间Tclk=Tclk2+Tcycle-Tsu

       Tclk1是时钟源到达第一个寄存器的时间,Tco是时钟沿到数据稳定输出的时间,Tdata是两个寄存器之间的组合逻辑加上布线的延迟, Tclk2是时钟源到达第二个寄存器的时间,Tsu是一个最小的建立时间,是时钟沿到达之前,数据需要保持的最小时间,Tcycle是一个时钟周期。

      上面的公式需要满足数据的时间需要小于时钟时间,Tdata<Tclk,即Tclk1+Tco+Tdata<Tclk2+Tcycle-Tsu;那通过这个公式,就可以算出slack是多大

slack=Tclk2+Tcycle-Tsu-Tclk1-Tco-Tdata;

那么能做到的一个最大的频率为Tcycle

举个例子,比如Tcycle=10ns,Tclk2=2ns,Tclk1=3ns, Tsu=0.5ns,Tdata=3ns,Tco=1ns那么

slack=2+10-0.5-3-3-1=4.5ns;

那么最大的就是slack=0的时候Tcycle的值,那么就是5.5ns。就是Fmax=1/(Tcycle-slack)。

按照余量来讲,我用的芯片是xcvu9p-flga2104-3-e,周期可以再设置为10-4.735ns=5.5ns,但实际上设置到4.5ns的时钟周期,还是WNS还是正的。那再设置一下,4.5ns-0.478ns=4ns左右。

时钟周期设置为4ns,WNS还是为正数,那周期再设置小一点

时钟周期设置为3.6ns,WNS还是为正好为0

再设置小一点,设置为3ns,那么

那下面先去看一下有哪些违例的路径,并且看一下xcvu9p-flga2104-3-e这个芯片的Tco,Tsu的大小等。

如上图所示,违例路径就是从cnt的32位寄存器到dout的32位寄存器之间的路径。我们看一下两个寄存器的组合逻辑有多少级。下面点开一条路径的原理图,可以看到组合逻辑非常长,有14级的组合逻辑,14级正好是勉强可接受,后续优化的方向是把14级组合逻辑拆分为2个7级的组合逻辑。

组合逻辑级数(Levels)的可接受范围,完全取决于时钟频率,核心公式:
单级逻辑延迟 ≈ 0.1ns(xcvu9p-3-e 高速器件)
可接受级数 ≈ 时钟周期 (ns) ÷ 0.1ns ÷ 2(留 50% 裕量)

下面看里的这几个参数。下面是这是时钟从源端 → 发送寄存器 cnt_reg[3]/C 的延迟,也就是公式里的 Tclk1

这是数据从发送寄存器 cnt_reg[3]/Q → 接收寄存器 dout_reg[30]/D 的完整延迟,包含 Tco(寄存器输出延迟)和 Tdata(组合逻辑 + 布线延迟)。
1. 读出 Tco(寄存器时钟→输出延迟)
第一行:
FDCE (Prop DFF2 SLICEL C Q) → Tco = 0.072ns
这是发送寄存器 cnt_reg[3] 的时钟沿到数据输出 Q 的延迟,xcvu9p-3-e 的典型值就是 0.07ns 左右,远小于你公式里假设的 1ns!
2. 读出 Tdata(组合逻辑 + 布线延迟)
从第二行开始,到最后一行 FDCE (dout_reg[30]/D) 之前,所有延迟的总和,就是 Tdata。
我们把关键段加起来:
第一行 net 延迟:0.312ns
LUT2 + net:0.047 + 0.010 = 0.057ns
CARRY8 + net:0.133 + 0.048 = 0.181ns
后续所有 CARRY8、LUT、net 延迟累加...
最终数据到达接收寄存器 D 端的总延迟(Arrival Time)= 3.994ns
✅ 结论:
Tco = 0.072ns
Tdata = 3.994ns - 0.500ns (Tclk1) - 0.072ns (Tco) = 3.422ns
(完全对应你之前路径表的 Total Delay=3.494ns,误差来自小数位截断)

这是时钟从源端 → 接收寄存器 dout_reg[30]/C 的延迟,以及接收寄存器的建立时间 Tsu

1. 读出 Tclk2(接收端时钟延迟)

2. 读出 Tsu(接收寄存器建立时间)
最后一行:
FDCE (Setup HFF2 SLICEM C D) → Tsu = 0.321ns
这是接收寄存器 dout_reg[30] 要求的建立时间,是器件的固有参数,工具从器件库直接读取。
注意:这里包含了时钟不确定性(-0.300ns)的修正,真实 Tsu 约 0.02ns,工具加了安全裕量。
3. 读出 Required Time(要求到达时间)
最终 Required Time = 3.521ns
完全对应公式:
Required Time=T clk2+T cycle−Tsu=0.5+3.0−(−0.321)=3.521ns
(这里的负号是因为 Tsu 是 “提前稳定的时间”,工具计算时做了修正)

时序优化:

优化的原代码如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2026/04/15 13:29:03
// Design Name: 
// Module Name: module_timing
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

/*
module module_timing(
    input  clk,
    input  rst_n,
    output reg [31:0] dout
    );
    reg [31:0] cnt;

// 计数器(正常时序)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 32'd0;
    else
        cnt <= cnt + 1'b1;
end

// 关键:超长组合逻辑链 → 制造大 Tdata
wire [31:0] comb_delay;
assign comb_delay = (((((cnt * 1234) + 5678) ^ 9876) 
                    * 4321) + 13579) ^ 24680
                    * cnt[15:0] + cnt[31:16];

// 组合逻辑结果直接打一拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        dout <= 32'd0;
    else
        dout <= comb_delay;
end
endmodule*/

优化后的代码如下:

module module_timing(
    input  clk,
    input  rst_n,
    output reg [31:0] dout
);

reg [31:0] cnt;

// 计数器不变
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 32'd0;
    else
        cnt <= cnt + 1'b1;
end

// ===================== 优化1:寄存器复制(解决扇出大 → 布线延迟高)=====================
reg [31:0] cnt_a;
reg [31:0] cnt_b;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_a <= 0;
        cnt_b <= 0;
    end else begin
        cnt_a <= cnt;  // 复制一份 cnt
        cnt_b <= cnt;  // 再复制一份 cnt
    end
end

// ===================== 优化2:插入流水线寄存器(砍半逻辑延迟)=====================
reg [31:0] pipe_stage;

// 前半段组合逻辑(只使用 cnt_a,扇出减半)
wire [31:0] comb_part1;
assign comb_part1 = (((cnt_a * 1234) + 5678) ^ 9876);

// 插入一级寄存器 → 最关键!
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        pipe_stage <= 32'd0;
    else
        pipe_stage <= comb_part1;
end

// 后半段组合逻辑(只使用 cnt_b,扇出减半)
wire [31:0] comb_part2;
assign comb_part2 = (pipe_stage * 4321) + 13579 ^ 24680 * cnt_b[15:0] + cnt_b[31:16];

// 最终输出
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        dout <= 32'd0;
    else
        dout <= comb_part2;
end

endmodule

把组合逻辑的长度变短,优化结果如下:

组合逻辑的长度减半

那现在我们去对比代码优前后的时序:

首先Tclk1这里没有变化,两者都是一样的。

其次是逻辑延迟与布线延迟,如下图,可以看到,把14级的组合逻辑拆分为7级的组合逻辑,逻辑延迟反而变大了,布线延迟大幅压缩。综合器为了塞得更紧凑,甚至会把逻辑合并得更密,导致单级更慢、总逻辑延迟更高。这也就是流水线主要优化布线,其次才是逻辑级数。

优化前

优化后:

同时你会发现时钟走线的FDCE这个值从0.321ns变为0.023ns,这不是Tsu变了,Tsu(Setup Time)是FPGA 寄存器的「硬件固有参数」,由器件工艺、速度等级决定,工具在计算Required Time时,叠加了不同的「时序悲观性 / 不确定性」修正,最终呈现的「有效建立时间要求」变了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值