以下内容仅记录本人工作中发现的问题以及解决方法,仅供参考。

在基于zynq平台PL端开发的工程中,全局时钟采用PS端的100M,未进行时序约束以及逻辑优化前,编译成功但时序报告有严重警告如图:

通过图一、图二可以定位时钟未约束。

通过图三、图四可以定位程序中代码有需要计算除法的部分,在程序中直接使用除法符号的写法,引起了延时超时。

接下来逐一解决。

第一个问题:时钟约束

尝试在网上搜索时钟约束方法及概念,简单概括一下就是把主时钟、从时钟、时钟分频or倍频这些信息按照约束语法写成一行约束语句添加进xdc文件中,对于我的工程而言,主时钟即为PS输出的100M时钟我命名为FCLK_CLK0,从时钟为100M分频后的250K我命名为div_aclk,但按照约束语法加进去以后编译频频警告或报错,原因是未找到我命名的主时钟FCLK_CLK0,从时钟div_aclk,最后发现PS的时钟系统已经将全局时钟做了约束,自动生成约束语句,自动命名为clk_fpga_0,如图:点击vivado界面IMPLEMENTATION---Open Implemented Design---Edit Timing Constraints可以看到PS的100M时钟已经被系统自动约束。

这句约束体现在系统自动生成的design_1_processing_system7_0_0.xdc文件里,如图右上角提示只读不可修改。

现在思路已经很明确了,只需要将自定义的FCLK_CLK0作为全局时钟clk_fpga_0的从时钟进行约束,再将自定义的div_aclk作为生成时钟FCLK_CLK0的从时钟进行约束即可,经过试验发现在Timing Constraints界面中通过选项设置生成约束语句比自己按照约束语法写约束语句会更简单准确一些,方法如下。

打开Timing Constraints界面,点击Create Generated Clock,点击上方加号或右键,进入生成时钟界面:

在Clock name一栏手动输入从时钟的名称FCLK_CLK0

在Master pin(source)一栏选择主时钟的起点路径,注意主从时钟命名类似但起点不同,一个在BD内部PS_IP的输出,一个是BD模块的输出,点击选项框

进入界面如图所示

FCLK_CLK0不是IO pins,查找类型选择Cell Pins,在搜索框手动输入自定义时钟名FCLK_CLK0

点击Find,如图选择时钟路径,点击向右箭头添加到右边空白框,点击OK

在Master clock一栏选择主时钟,点击选项框

不用输入名称直接查找,如图选择主时钟clk_fpga_0,点击向右箭头添加到右边空白框,点击OK

如图倍频系数与分频系数设置为1,FCLK_CLK0与clk_fpga_0时钟均为100M

在Source obiects一栏选择从时钟的起点路径,注意主从时钟命名类似但起点不同,一个在BD内部PS_IP的输出,一个是BD模块的输出,点击选项框

进入界面如图所示

 FCLK_CLK0不是IO pins,查找类型选择Cell Pins,在搜索框手动输入自定义时钟名FCLK_CLK0

点击Find,如图选择时钟路径,点击向右箭头添加到右边空白框,点击Set

各选项添加完毕的界面如图所示,点击OK,这里关于这个add选项的勾选,我目前还没有搞清楚具体的原因,不勾选会提示报错。

上述操作已经成功地将自定义的FCLK_CLK0作为全局时钟clk_fpga_0的从时钟进行约束,

重复以上操作,将自定义的div_aclk作为生成时钟FCLK_CLK0的从时钟进行约束,这里不过多阐述,设置完毕的界面如图:

在Timing Constraints界面中可以看到,已经成功添加两条时钟约束,点击下方Apply

可以看到xdc文件中已经自动生成两条语句。

编译测试一下,再次打开时序报告,点击屏幕下方Methodology界面,可以看到这里的严重警告已经全部消除

点击屏幕下方Timing界面,时钟树如图:

第二个问题:时序违例

此时时钟约束已完成,但时序报告中的时序违例依然存在:

文章开头已经通过时序违例详细报告定位到违例的原因是由于代码中使用除法的方式不对,在除数和被除数的位宽比较大的情况下,一个时钟无法完成对数据的计算,因此严重超时。试过使用除法器IP核同样存在超时违例的现象。

原代码如下图所示:

将代码改成下图所示,编译后时序违例大大减少但仍然存在超时

二进制除法器原理,参考链接http://基于FPGA的高效除法器 - 电路fpga的文章 - 知乎 https://zhuanlan.zhihu.com/p/679056099

下图为例,八位(M位[M-1:0])二进制被除数10011001,四位(N位[N-1:0])除数1010。

第一步先给被除数高位借0

第二步循环判断被除数高五位([M:N])与除数的大小,能减则减,不能则继续向左移位。

每次进行相减操作:移位次数不变,商+1

每次进行移位操作,移位次数+1,商左移1位

当移位次数等于被除数与除数的位宽差(M-N)时,计算结束,注意最后一次移位后若被除数高五位([M:N])大于等于除数的大小,还需要再减一次,否则计算错误。

使用组合逻辑实现二进制除法器,替换掉原先的两行代码,逻辑代码虽然多了几十行,但资源利用率已经降到最低。编译后时序违例已经全部消除

除法器顶层例化

除法器源码

module div_module#(
    parameter integer Data_width = 25,
    parameter integer Width_sub  = 16//除数和被除数的位宽差值 25-9
)(
    input  wire                          adc_aclk               ,
    input  wire                          adc_aresetn            ,
    input  wire signed [Data_width-1:0]  data_sum               ,
    input  wire                          data_sum_valid         ,
    output reg  signed [15:0]            data_aver              ,
    output reg                           data_aver_valid         
);

//以下计算适用除数为定值400
localparam           Divisor = 'b110010000;//400
reg  [Data_width:0]  Dividend    ;
reg           [3:0]  div_state   ;
reg           [7:0]  shift_cnt   ;
reg           [15:0] remainder   ;//余数
reg           [15:0] quotient    ;//商
always@(posedge adc_aclk or negedge adc_aresetn)begin
    if(!adc_aresetn)begin
        data_aver        <= 'd0;
        data_aver_valid  <= 'd0;
        div_state        <= 'd0;
        shift_cnt        <= 'd0;
        Dividend         <= 'd0;
        remainder        <= 'd0;
        quotient         <= 'd0;
    end 
    else begin 
        case(div_state)
            0:begin
                data_aver       <= data_aver;
                data_aver_valid <= 'd0;
                if(data_sum_valid)begin 
                    if(data_sum == Divisor)begin
                        div_state       <= 'd1; 
                    end
                    else if(data_sum < Divisor)begin
                        div_state       <= 'd2; 
                    end
                    else begin
                        div_state <= 'd3;
                        Dividend  <= {1'b0,data_sum};
                    end
                end 
            end
            1:begin
                data_aver       <= 'd1;
                remainder       <= 'd0;
                data_aver_valid <= 'd1;
                div_state       <= 'd0; 
            end
            2:begin
                data_aver       <= 'd0;
                remainder       <= data_sum[15:0] ;
                data_aver_valid <= 'd1;
                div_state       <= 'd0; 
            end
            3:begin
                if(shift_cnt == Width_sub )begin//左移25-9次计算结束
                    div_state       <= 'd4; 
                    if(Dividend[Data_width:Width_sub] >= Divisor)begin
                        Dividend        <= {Dividend[Data_width:Width_sub]-Divisor,Dividend[Width_sub-1:0]};
                        quotient        <= quotient + 1'b1;
                    end
                end
                else begin
                    if(Dividend[Data_width:Width_sub] < Divisor)begin
                        Dividend        <= {Dividend[Data_width-1:0],1'b0};
                        quotient        <= {quotient[14:0],1'b0};
                        shift_cnt       <= shift_cnt + 'd1;
                    end
                    else begin
                        Dividend        <= {Dividend[Data_width:Width_sub]-Divisor,Dividend[Width_sub-1:0]};
                        quotient        <= quotient + 1'b1;
                    end
                end
            end
            4:begin
                shift_cnt       <= 'd0;
                remainder       <= Dividend[Data_width-1:Width_sub] ;
                data_aver_valid <= 'd1;
                div_state       <= 'd0; 
                data_aver       <= quotient;
            end
        endcase
    end
end
endmodule

编译后的时序报告:

结语:第一次遇到这样的问题,网上搜到的资料很碎片化,能搜到的大多是概念和原理,要在很短的时间内理解并应用到实际工程中,对我来说还是有点困难,很多细节还没完全理解,有些选项编译报错就换一个,不断测试,一知半解的情况下问题解决了,有空再看看,欢迎各位大佬指正,也希望这篇文章能帮助到跟我一样的人。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐