• 高级FPGA设计实现结构和优化_(四)综合编码


    高级FPGA设计实现结构和优化_综合编码

    判决树

      在FPGA设计的范围内,把一系列用来决定逻辑应该采取什么动作的条件称做一个判决树。通常,这个分解成if/elsecase结构。考虑一个十分简单的寄存器写的例子。

    module regwrite (
        input clk,
        input [3:0] in,
        input [3:0] ctrl,
        output rout
    );
    
    reg rout;
    always @(posedge clk) begin
        if(ctrl[0])
            rout <= in[0];
        else if(ctrl[1])
            rout <= in[1];
        else if(ctrl[2])
            rout <= in[2];
        else if(ctrl[3])
            rout <= in[3];
    end 
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

      而它所描述的电路结构如下图所示:
    在这里插入图片描述
      从上图不难看出,if/else结构固有的特权的概念,在上述结构中更高的特权对应靠近链的末尾和更接近寄存器的多选择器。如果控制字的位0被设置,in[0]将被寄存,而不管控制字的其他位的状态。如果控制字的位0没有被设置,则利用其他位的状态来决定通过寄存器的信号。通常,只有一位在它(在此情况是最低位LSB)前面的所有位没有设置时利用来选择输出。另一方面,case结构常常是(不是总是)利用在所有条件互不相容的情况。换言之,他们可以在任何时间只有一个条件成立时利用来优化判决树。例如,根据一些其他的多位网线或寄存器进行判决时,在一个时间只有一个条件成立。为了在Verilog中实现完全相同的功能,一个case语句可以利用。

    case(1)
        ctrl[0]:rout <= in[0];
        ctrl[1]:rout <= in[1];
        ctrl[2]:rout <= in[2];
        ctrl[3]:rout <= in[3];
    endcase
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      总结就是:特权编码应该用if/else语句来实现,设计并行的结构应该利用case语句编码
      如果case语句的条件没有一个是成立的,综合工具已经把寄存器的输出返回送到判决树作为一个缺省条件。对设计者有效的一个选择是添加缺省条件,这个缺省条件可能或不可能是当前数值,但是,假设在每一个case条件下分配输出一个数值。它避免了工具自动地锁存当前值。用这个缺省条件将消除寄存器使能,如以下修改的case语句所示:

    module regwrite (
        input clk,
        input [3:0] in,
        input [3:0] ctrl,
        output      rout
    );
    reg rout;
    always @(posedge clk) begin
        case(1)
            ctrl[0]:rout <= in[0];
            ctrl[1]:rout <= in[1];
            ctrl[2]:rout <= in[2];
            ctrl[3]:rout <= in[3];
            default:rout <= 0;
        endcase
    end    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      上面两段代码所对应的电路图

      可以看到缺省条件现在作为到多路选择器的一个可供选择的输入实现,虽然触发器不再要求一个使能信号,总的逻辑资源也不一定少。为了保证总有一个数值分配到寄存器,我们也可以在`case`语句之前利用初始赋值分配给寄存器一个数值。就比如:
    module regwrite (
        input clk,
        input [3:0] in,
        input [3:0] ctrl,
        output  rout
    );
    reg rout;
    always @(posedge clk) begin
        rout <= 0;
        case(1)
            ctrl[0]:rout <= in[0];
            ctrl[1]:rout <= in[1];
            ctrl[2]:rout <= in[2];
            ctrl[3]:rout <= in[3];
        endcase
    end
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      这类编码风格消除了对缺省情况的需要,也保证了如果没有其他赋值定义时,寄存器分配到这个缺省值。
      下面再来看多控制分支的一种差的编码风格,代码如下:

    module separated (
        input iClk,
        input iDat1,
        input iDat2,
        input iCtrl1,
        input iCtrl2,
        output oDat
    );
    reg oDat;
    always @(posedge iClk) begin
        if(iCtrl2)
            oDat <= iDat2;
        if(iCtrl1)
            oDat <= iDat1;
    end
        
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      因为没有方法告诉iCtrl1iCtrl2是否是互不相容的,这个编码风格是模糊的,综合工具必须为实现做一定的假设。当二者的条件同时成立时,没有明显的方式管理特权。因此综合工具必须基于这些条件发生的顺序赋予特权。在这种情况下,如果条件最后出现,它将获得优于第一个条件的特权。

    陷阱

    阻塞与非阻塞

      当我最开始接触阻塞与非阻塞的概念时总是迟迟不能理解,希望借这一章的学习,对这一处做一个复习和更深的理解。在软件设计世界,按照预定的顺序执行规定的操作来产生功能。在HDL的设计世界里,这类执行可以想象为阻塞。这意味着进一步的操作一直到当前的操作完成之后才不被阻塞(之前他们都没被执行)。所有进一步的操作是在所有前面的操作已经完成和存储器中的所有变量已被更新的假设之下。非阻塞的操作执行起来与次序无关,更新是被专门的事件所触发,当触发的事件发生时所有的更新同时发生。举个例子:

    module blockingnonblocking (
        input clk,
        input in1,in2,in3,
        output out
    );
    reg out,logicfun;
    always @(posedge clk) begin
        logicfun <= in1 & in2;
        out <= logicfun | in3;
    end  
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

      这段代码按照逻辑设计预期的实现,如图所示:
    在这里插入图片描述
      其中信号“logicfun”“out”是触发器,在“in1”“in2”上的任何变化将花费两个时钟周期传播到“out”。转到阻塞赋值只是出现微小的变化。

    //BAD CODING STYLE
    logicfun = in1 & in2;
    out = logicfun | in3;
    
    • 1
    • 2
    • 3

      若改为使用非阻塞赋值,这意味着out直到logicfun已经更新之后才不更新,二者的更新必须在一个时钟周期内发生。还有一种写法是:

    //BAD CODING STYLE
    out = logicfun | in3;
    logicfun = in1 & in2;
    
    • 1
    • 2
    • 3

      在这种写法中,强迫out寄存器在logicfun之前更新,它迫使输入in1in2经两个时钟周期的延时传播到out,这将得到预期的逻辑实现,但是这种方法不是好的编码风格。
      还有一种编码风格也应当被避免,就是为每个赋值利用独立的always语句,例如:

    //BAD CODING STYLE
    always @(posedge clk) begin
        logicfun = in1 & in2;
    end
    
    always @(posedge clk) begin
        out  = logicfun | in3;
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      这里我记得时序电路中是不能用阻塞赋值的,不符合verilog规范。
      当涉及到阻塞和非阻塞赋值为综合编码时的广泛接受的准则是:
      利用阻塞赋值模拟组合逻辑;
      利用非阻塞赋值模拟时序逻辑;
      从不把阻塞和非阻塞赋值混合在一个always块里;

    for环路

      软件设计者可能会利用for-loop获得XN次幂,就如下面的片段展示的一样:

    PowerX = 1;
    for(i=0;i<N;i++)
    	PowerX = PowerX * X;
    
    • 1
    • 2
    • 3

      这个算法在每次环路迭代时都会用PowerX的当前值来更新一个内部寄存器。对比之下,可综合的HDL没有任何隐含的寄存器在迭代环路期间出现。相反,所有的寄存器操作都被清楚地定义。如果设计者试图用可综合的HDL以类似的方式产生上述结构,可能最后的结果看起来像以下的代码段:

    //BAD CODING STYLE
    module forloop (
        input [7:0] X,
        input [7:0] N,
        output [7:0] PowerX
    );
    
    integer i
    reg [7:0] PowerX;
    
    always @(*) begin
        PowerX = 1;
        for(i=0;i<N;i=i+1)
            PowerX = PowerX * X;
    end
        
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      该程序可以在行为仿真中工作,并与综合工具有关可能可以综合到门电路,Xilinx综合工具(XST)不可以综合没有固定N值的代码,但是Synplify将基于最坏条件的N值综合到这个环路。如果它确实可综合,最后的结果将完全展开成运行极其慢的大量逻辑块的环路。在环路每次迭代期间管理这些寄存器的设计可能利用控制信号,如以下的例子所示:

    module forloop (
        input Clk,
        input Start,
        input [7:0] X,
        input [7:0] N,
        output Done,
        output [7:0] PowerX
    );
    
    reg Done;
    reg [7:0] PowerX;
    integer i;
    always @(posedge Clk) begin
        if(Start) begin
            i <=0;
            Done <= 0;
            PowerX <= 1;   
        end
        else if(i < N) begin
            PowerX <= PowerX * X;
            i <= i + 1;
        end
        else
            Done <= 1;
    end
        
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

      在上面的设计中,幂函数将是一个较小的数量级,并运行比“类似软件”实现更快的数量级。但是呢,forloop不应该用在这种场合,forloop常常利用短的形式来减少只是并行代码段重复的长度,例如,以下的代码取X的每一位与Y的偶数位应用异或操作产生一个输出。

    Out[0] <= Y[0] ^ X[0];
    Out[0] <= Y[2] ^ X[1];
    ...
    Out[31] <= Y[62] ^ X[31];
    
    • 1
    • 2
    • 3
    • 4

      以长格式写这个out可能要求32位,反复的输入。为了压缩它和使他更好读,forloop可以利用来重复每一位的操作。而且在这个环路中,是没有反馈机构的。

    always @(posedge Clk) begin
        for(i =0;i < 32;i = i+1)
            Out[i] <= Y[i*2] ^ X[i];
    end
    
    • 1
    • 2
    • 3
    • 4

    组合环路

      组合环路是逻辑结构,其中包含的反馈没有任何居中的同步元件。当一组组合逻辑的输出不带中间寄存器反馈回本身时出现组合环路,这类行为很少遇到,一般表示设计或者实现中的一个错误,就如下图所示:

    在这里插入图片描述
      下面来看这段代码:

    //BAD CODING STYLE
    module combfeedback (
        input a,
        output out
    );
    
    reg b;
    //BAD CODIING STYLE:this will feed b back to b
    assign out = b;
    //BAD CODING STYLE:incomplete sensitivity list
    always @(a) begin
        b = out ^ a; 
    end
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

      上面的模块表示一个行为描述,在仿真中它可能表现为:当wire “a”改变时,输出被赋值当前输出异或“a”的结果,输出只在“a”改变时改变,不呈现出任何反馈或振荡的行为。但是,在FPGA综合中,一个always结构描述或者寄存器或者组合逻辑的行为。在这种情况下,综合工具将扩展敏感清单(当前只包含“a”),要包含假设结构为组合的所有输入,当这些发生时,反馈环路闭合,将作为一个反馈到自身的异或门来实现,如图所示:
    在这里插入图片描述
      这类结构是十分有问题的,因为输入“a”为逻辑1的任何时间它将振荡。作为好的编码实践,所有的组合结构应该编码使得在always模块内的表达式中所包含的全部输入都列在敏感清单中。

  • 相关阅读:
    高等数学(第七版)同济大学 习题7-4 个人解答
    51单片机复位电容计算与分析(附带Proteus电路图)
    违约金过高”的认定依据
    前台线程与后台线程
    28岁转行软件测试真的很难吗?按照我整理出的这份3000字学习指南就没问题...
    springboot下的mybatis打印sql语句
    嵌入式Linux中内存管理详解分析
    linux系统shell脚本开发之循环的使用
    校园日 | 看高校数据安全建设典型案例
    云服务器部署Neo4j
  • 原文地址:https://blog.csdn.net/m0_59681237/article/details/127559721