高级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

  而它所描述的电路结构如下图所示:
在这里插入图片描述
  从上图不难看出,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

  总结就是:特权编码应该用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

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

  可以看到缺省条件现在作为到多路选择器的一个可供选择的输入实现,虽然触发器不再要求一个使能信号,总的逻辑资源也不一定少。为了保证总有一个数值分配到寄存器,我们也可以在`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

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

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

  因为没有方法告诉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

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

//BAD CODING STYLE
logicfun = in1 & in2;
out = logicfun | in3;

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

//BAD CODING STYLE
out = logicfun | in3;
logicfun = in1 & in2;

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

//BAD CODING STYLE
always @(posedge clk) begin
    logicfun = in1 & in2;
end

always @(posedge clk) begin
    out  = logicfun | in3;
end

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

for环路

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

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

  这个算法在每次环路迭代时都会用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

  该程序可以在行为仿真中工作,并与综合工具有关可能可以综合到门电路,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

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

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

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

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

组合环路

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

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

//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

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

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值