FPGA从Xilinx的7系列学起(6)


用户必须要认识到,学习一下技巧可以让更多的逻辑放在更少的Slice中,使工具能够达到既实现设计时序要求又满足用户对功耗的要求。而现在很多用户缺乏代码编写的想法,编写出一个有时序问题的设计。为了满足要求,就会不停修改,再综合再布局布线来满足自己的时序目标。其实,他们需要的是重新评估他们的HDL代码技术以及他们的控制信号。

例如,如何使用这些D触发器呢?首先说明几个概念:

所有触发器为D类型,所有的触发器具有一个时钟输入(CLK),所有触发器都有一个使能信号(CE),所有触发器有一个高有效的复位信号(SR),SR可以是同步或异步控制来设置触发器值,所有触发器的值在配置过程中将会被确定下来。

所有D触发器在7系列FPGA有一个芯片使能(CE)引脚,并通过时钟进行相关操作。当使能信号有效的时候,D触发器的输出等于前一个输入,当使能信号无效时,D触发器保持当前值。下面的代码就是一个很好的例子来说明FPGA的D触发器会被工具推断出来是寄存器。

Verilog:
always @ (posedge CLK )
begin
if (CE)
Q <= D;
end

VHDL:
FF: process (CLK)
begin
if (rising_edge CLK) then
if (CE = ‘1’) then
Q <= D;
end if;
end if;
end

对于XILINX的器件来说,大部分都是高有效。如果用户想使用低有效,需要占用LUT作为一个反转逻辑来使用,这也无疑增加了资源的使用。
D触发器有一个高有效的SR端口信号,SR端口可以被配置成同步置位/复位、异步置位或者复位、清零端口。它是基于用户的编码风格被配置为同步或异步的。置位时,触发器输出将被强制为触发器的置位/复位值,综合工具会自动从用户的HDL代码中推断出初始值进行设置的。

介绍一下异步复位。首先必须指出,虽然XILINX的器件支持异步复位,但是XILINX不建议使用异步复位。当用户必须使用异步复位的时候,想使工具能够推断出异步复位逻辑,那么必须把异步复位信号放在敏感列表里面,当该复位信号有效的时候,输出的信号必须立即等于复位的值。下面的例子可以很好的阐述该问题。

Verilog
always @ (posedge CLK or posedge RST )
begin
if (RST)
Q <= 1’b0;
else
Q <= D;
End

VHDL
FF: process (CLK, RST)
begin
if (RST = ‘1’) then
Q <= ‘0’;
elsif (rising_edge CLK) then
Q <= D;
end if;
end

上图这个例子很好的说明了如何同步用户的异步复位设计。这个电路设置能够有效的避免亚稳态。在用户必须使用异步复位的时候,应该使用该例子推荐的方法来进行设计,也就是异步复位必须同步化。如果没有把异步复位同步化,那么将可能引起亚稳态或者增加亚稳态的风险。

Verilog
always @ (posedge CLK)
begin
if (RST)
Q <= 1’b0;
else
Q <= D;
end

VHDL
FF: process (CLK)
begin
if (rising_edge CLK) then
if (RST = ‘1’) then
Q <= ‘0’;
else
Q <= D;
end if;
end

再说说同步复位。这个例子说明了如何HDL代码中正确的使用同步复位。有几个关键点很重要,首先就是复位信号不能在敏感列表里面,其次,只有当时钟有效的翻转的时候,输出信号才能有效赋值。当复位信号有效的时候,只是得到了一个同步复位的行为,只有当有复位效后的第一个有效时钟翻转才能传递复位信息。这个Reset信号就是一个时序路径上的一个节点,会被工具自动进行相对应的时序约束。这意味这工具会根据你的时序目标进行优化的布线。这也是XILINX推荐的复位的方法。


下面这个例子说明在用户的Flip-Flops设置初始值。大多数设计师这样写代码为的是仿真能够更加简便。否则当用户开始运行仿真的时候,用户设计中使用的寄存器等都处于一个不确定态。可以,按照例子来写的代码,可以让寄存器等有一个确定的初始值,处于一个确定态。

reg Q =1’b1;

always @ (posedge CLK or posedge RST )

begin
if (RST)

Q <= 1’b0;

else

Q <= D;

end

signal Q: std_logic:=‘1’;

FF: process (CLK, RST)

begin

if (RST = ‘1’) then

Q <= ‘0’;

elsif (rising_edge CLK) then

Q <= D;

end if;

end

触发器中的Flip-flops和flip-flop/latches有着相同的控制信号,CLK,SR, CE等等。那么对用户来说最重要的事情就是尝试着减少用户设计中的控制信号的数量。用户需要加强对控制信号的行为的理解:所有Flip-flops和flip-flop/latches共享所有这些控制信号,这些控制信号主要是时钟,置位和复位。如果一个组中的一个flip-flop用了时钟使能信号,那么所有其他的Flip-Flop必须使用相同的时钟使能或者不用时钟使能。如果一个组中的一个flip-flop用了置位或者复位信号,那么所有其他的Flip-Flop必须使用相同的置位或者复位信号。

在同一个Slice中的Flip-Flops会在同一时间进入复位或者置位的状态。但是有一点需要强调一下:所有Flip-Flops都有自己单独的SRVAL属性,也就是说信号在统一复位的时候,可以有自己的值。

对于整个slice来说,使用它的设计者要切记注意控制信号,工具的推断功能非常非常重要的。特别重要的,因为触发器中每个切片将共享所有相同的控制信号,因为被占用后,其他和它使用不一致控制信号的触发器将无法使用剩下来的资源。那么对用户来说,设计要尽量使用少的控制信号:关键是要评估是否有未使用的寄存器的一些情况,如果有,就尝试使用相同控制信号。

另外,用户要记住控制信号的置位、复位、时钟使能信号是能连接到了LUT的输入端,而不一定是连接到相应的flip-flop的相对应的端口。不像老的FPGA的架构有独立的置位和复位信号端口,7系列的置位和复位端口没有独立的端口,但是可以只被被编程使用两个、一个或者一个也不使用。因此如果你想两个都是用的话,它必须用LUT去生成置位或者复位功能。以上说的都是同步的。置位或者复位信号可以被设置为异步信号。但是异步信号的置位或者复位信号端口不能使用LUT来生成,因此综合工具不可能使用LUT来实现、映射异步控制信号到LUT的输入信号中,它直接就会映射到寄存器的端口。

下图展示了工具如何通过使用同步置位和复位信号获得好处。工具转移置位和复位端口连接到LUT输入上,并将复位推断为同步,那么这些触发器可以被放置在相同的Slice。

这一功能可以通过使用综合过程的“Reduce ControlSets”属性来实现。

在这个例子中,三个触发器中的原始设计必须放在单独的Slice中,因为它们每一个使用不同的控制集合。所有三个触发器共享相同的时钟,但是第一个触发器没有置位/复位,第二个触发器使用“置位(set)”同步地将其输出设置为1,而另一个使用“复位(reset)”将输出重置为0。如果同步置位和复位功能被使用的LUT逻辑实现,那么所有三个触发器可以被置于同一个Slice中。整个实现过程,图例中显示的非常明显。那么用户可以看到增加了组合逻辑将一些控制信号放在未使用的LUT中去,却减少了Slice的总体的使用量。


上图的例子中,两个触发器在原始设计必须放在单独的Slice里面,因为它们每一个使用不同的控制集合。但是一个时钟使能可以被工具推断为触发器输入或可能被工具推断LUT的输入。当他使用LUT的输入的时候,这两个寄存器可以被放置在同一个Slice里面。在VIVADO和ISE下面都有选项来帮助大家自动实现该功能。大家在做设计的时候可以选用。


做设计的时候有些原则还应该注意:

在控制信号的规划的时候,XILINX的原语和IP的例化可能增加额外的控制信号到用户的设计中,那么肯定有些设计要和原语、IP共享这些共享信号。建议用户自己建立和仿真自己IP——特别是用户的设计非常大——并以此结果来评估使用这些控制信号。

不建议使用低电平有效的控制信号,这是因为触发器(flip-flops)本身没有翻转逻辑,而需要使用LUT来完成该功能。虽然可能很多人在上大学求学期间学习的原则都是低电平有效。但是这个真的是一个非常落伍的设计理念。当然除非要进行ASIC的流片工作或者使用非常古老的芯片,否则用户必须修改使用低电平有效的陈旧的思想。

低电平有效,必须增加使用更多的LUT,他们需要LUT才能把低电平有效翻转为高电平有效然后再连接到触发器的控制端口,这样就需要白白浪费一个LUT资源。

如果听说过层次设计(hierarchicaldesign)的方法学(意味着用户在使用 partitions功能,使用保持hierarchy功能, 使用IP核, 使用多网表,或者使用自下而上的编程),那么这个问题会变得更加严重:会有一个很差的资源使用情况,一个非常长的运行时间,一个糟糕的时序问题,一个差劲的功耗问题。

举一个例子,如图所示的例子准确的展示使用低有效作为控制信号,并且使用了层次结构等等的设计方法学。大家可以清晰的看到,因为要保持一个结构就无法跨越这些边界。那么如何修正这些代码呢?在这个案例中,XILINX的建议是直接修改代码或者重新整合IP核变为高有效,然后重新综合。大家一定请记住,综合工具和实现工具不能合并这种逻辑到同一个Slice的,因为用户是不允许跨层次边界优化。

再给大家提问一个问题,大家先不要看答案:

1) 以下这些案例能不能把这两个寄存器放在同一个slice里面?

注:所有控制信号都是直接驱动触发器的控制信号。

– 案例 1
• FF1: Clock, CE, Set
• FF2: CLK, CE, Set

– 案例 2
• FF1: CLK, CE, Reset
• FF2: CLK, Reset

– 案例3
• FF1: CLK, CE, Reset
• FF2: CLK, CE, not Reset

– 案例 4
• FF1: CLK, CE, Reset
• FF2: CLK, CE, Reset

案例1:不能。触发器有不同的时钟,所以它们有不同的控制组的。

案例2:不能。FF2具有时钟使能,所以它们有不同的控制组的。

案例3:不能。FF2无法提供翻转逻辑,所以它们有不同的控制组的。

案例4:能。两个触发器具有完全相同的控制信号,所以它们是相同的控制组的成员。可以被放在同一个Slice里面。

减少使用控制信号可以提高用户的资源使用率。复位逻辑的正确使用,可以提高设计的效率。如果不是设计必须要求使用异步复位,所有的设计都使用同步复位。对更大更复杂的设计,只有必须的关键的逻辑使用同步复位,其他逻辑直接不使用复位,而是依靠芯片自由GSR来进行复位就够了(这个想法够胆大,不放心的还是按照老办法来设计吧)。异步复位也只是在万不得已的情况才使用。

Reset分为全局Reset和局部Reset。全局Reset可以让所有存储类的元素处于一个已知的状态。全局的Reset一般是通过外部的引脚或者一个标准进程的完成或者等待PLL/MMCM锁定信号的有效来实现的。局部复位一般是有内部信号产生,他是让一些存储类的元素处于一个已知的状态,如利用内部的计数器。一般来说局部复位都是同步信号。

Reset又分为同步复位和异步复位,一个同步的局部复位是一个标准设计里面的一部分,一般都是使用状态机或者计数器让一些信号进入一个固定的状态,例如经常会用的0状态。综合工具一般是建议使用同步设计,不建议使用异步设计的。当使用异步复位设计的时候,可能会产生一个亚稳态,让电路处于一个不确定的状态。因此局部的异步复位,最好不要使用。对于全局的异步复位,需要一些方法来处理它,以前提过异步的同步化。

全局的同步置位/复位信号可以让存储类的元素进入一个已知的状态, 那么他带来的好处是什么?全局的同步置位/复位信号实现非常简单,可以在工程师需要的时候控制设计中的控制信号或者触发控制信号,确保设计万无一失,更可以使用让综合工具来进行减少控制信号功能。那么它的缺点是什么?当时钟不能保证正常工作的时候,同步复位信号将不能够生。另外,同步复位会占用一些同步资源,可能会影响用户的时序设计的目标。

全局的异步使用,以前我们就讲过,必须异步的同步化。这样才能保障用户的设计不进入到亚稳态。但是有时候,用户会必须使用异步复位,它的优点就是任何时候生效的时候都能工作,例如,当用户使用一个热插拔影响时候,这可能需要一个不依靠时钟产生复位来保障整个工作的启动。缺点也很明显,它不能使用减少控制信号的功能,也会大量占用布线资源。目前很多综合工具都是支持把异步复位转换为同步复位。

既然异步复位不应该使用,那么对于用户已经设计的代码我们应该怎么处理呢?有三种选择,第一,留下设计中的异步复位,清楚的了解他的缺陷。第二,你使用工具转换(如下图所示)。这个是非常危险的,因为异步复位毕竟和同步复位不一样,转换后,代码的综合结果可能和行为仿真出来的结果完全不同。第三,修改你的代码或者使用脚本来修改代码让异步变成同步,这个是我们推荐的方法。要记住只是顶层修改一下复位代码并不一定能够有一个正确的结果,还是应该把所有的代码检查一遍,然后把从HDL级上把所有的异步复位清除掉。

如何选择使用哪种方式来处理已有设计呢?如果已有设计的代码完全没有问题,能用满足时序要求,能够使用当前选用的器件,那么工程师就没有必须去做什么改动。综合工具不可能像修改代码一样的有效果,那么一定要仔细考虑使用该选项,虽然该功能可能会有一些问题,但是毕竟是简单易使用。

对大型的特别复杂的设计且时序等等都不满足要求,就只能修改代码了。再强调一下不要只是简单的修改顶层,因为综合工具都是从底层开始综合的,所以用户必须从Flip-Flops这个级别进行修改。

下图是一个用户的例子,他除了有专门的GSR的复位外,额外使用了同步全局复位,可以看到他大量的占用了布线资源,最后造成了用户设计的时序目标无法完成。要记住复位信号是需要和设计中的其他信号来竞争占用相同的设计资源,包括关键路径的时序,如果这些设计资源给其他信号使用能够更好满足设计的时序要求。有心的用户可以检查一下一个典型的复位,就会知道有多少扇出数量,这个扇出的数量会让用户觉得可怕。这也是为什么要在设计中删除复位的最大原因了。

在芯片中有一个GSR资源,有专门的慢速布线资源,他能够在几个毫秒的级别里面激活。如果用户需要时刻驾驭自己设计,同时需要在系统运行过程中去复位,那么GSR对该用户是没有意义的。这因为GSR还是太慢,毕竟它不是基于时间来复位的,这个条件下我们也不推荐用户使用GSR。然而,GSR将自动作为配置过程的一部分。配置完成后,所有的触发器都会处于一个已知状态。如果用户想尝试和使用,或以某种方式控制它,那么需要例化一个UPE2的原语进入到用户的设计中,并连接到GSR的输入上。但是这个实在是没有意义,因为它太慢了,因此不推荐它作为一个复位系统的方式。

DSP 资源的灵活性远远超过用户认知,加减乘除、累加、计数器、比较器,移位寄存器、复用器、模式匹配等等全部可以使用DSP实现。DSP如何使用复位呢?每一个DSP的Slice有超过250多个寄存器,但是没有一个使用了异步复位。用户使用同步复位可以令综合工具更加容易的推断出使用DSP资源。

这个事情的重要性,需要进一步强调一下。同步复位可以令工具更容易的使用专用的硬件资源。特别对DSP资源来说,用户特别喜欢DSP的输出的寄存器,这样可以增加pipeline,提高系统的性能。只要使用了异步的复位,那么综合工具将无法有效的推断出使用DSP资源。在实际的设计中,我们经常可以看到只要是使用了同步复位,DSP资源将被使用的更有效率。那么只要用户想建立一个算术的系统并且想有效使用DSP资源,那么一定要确保自己使用的同步复位。

Block RAMs可以通过使用输出寄存器,可以获得最小的输出的时间,提高性能。同样输出的寄存器也仅仅有同步服务的接口。没有被使用BlockRAM的资源,那么可以用来作很多功能,例如:ROM、大的LUT、复杂的逻辑、大状态机、深度移位寄存器。使用BlockRAM作为其他的功能,能够解放出大量的寄存器。同样,同步复位可以令工具更容易的推断出Block RAM硬件资源。只要使用了异步的复位,那么综合工具将无法有效的推断出使用Block RAM资源。

另外一个重要的事情,也要说明一下,就是移位寄存器。用户一定要记住要想直接使用LUT作为以为寄存器一定不能有复位端口。只要是在相关的HDL代码中使用了复位的信号,那么这个设计就不可能使用LUT作为SRL寄存器了。综合工具或者把用户的设计放在相关的CLB的寄存器里面了。这个会非常明显的增加使用的寄存器的数量。工具或者建立一个辅助的电路来模拟复位的功能。但是同样的,复位电路也需要大量的消耗资源。设计肯定也会更慢,这个肯定和设计的目的背道而驰。

很多用户可能会有一个疑问,那么这个移位寄存器到底应该怎么在代码中体现呢?给一个例子,当做设计的时候能够仿照这个来实现自己的功能。

VHDL版本的:

library IEEE;
useIEEE.STD_LOGIC_1164.ALL;
entity shifter is
generic (
C_DWIDTH : integer := 8;
C_NUM_CYCLES : integer := 32
);
Port ( Clk : in STD_LOGIC;
En : in STD_LOGIC;
Din : in STD_LOGIC_VECTOR(C_DWIDTH-1 downto 0);
Dout : out STD_LOGIC_VECTOR(C_DWIDTH-1 downto 0));
end shifter;
architecture Behavioral ofshifter is
signal din_d1 :std_logic_vector(C_DWIDTH-1 downto 0); -- Shift register Input
type array_slv is array(C_DWIDTH-1 downto 0) of std_logic_vector(C_NUM_CYCLES-1 downto 0);
signal din_shreg :array_slv;
begin
process (Clk)
begin
if Clk'event and Clk='1' then
if En = '1' then
for i in 0 to C_DWIDTH-1 loop
din_shreg(i) <=din_shreg(i)(C_NUM_CYCLES-2 downto 0) & Din(i);
end loop;
end if;
end if;
end process;
process (din_shreg)
begin
for i in 0 to C_DWIDTH-1 loop
Dout(i) <=din_shreg(i)(C_NUM_CYCLES-1);
end loop;
end process;
end Behavioral;

Verilog版本的:

`timescale 1 ps / 1 ps
module shifter
#(
parameter C_DWIDTH = 8,
parameter C_NUM_CYCLES = 32
)
(
Clk,
En,
Din,
Dout
);
input Clk;
input En;
input [C_DWIDTH-1:0] Din;
output [C_DWIDTH-1:0] Dout;
reg [C_NUM_CYCLES-1:0]din_shreg [C_DWIDTH-1:0];
integer srl_index;
initial
for (srl_index = 0; srl_index <c_dwidth; srl_index="srl_index" +="" 1)
din_shreg[srl_index] ={C_NUM_CYCLES{1'b0}};
genvar i;
generate
for (i=0; i < C_DWIDTH; i=i+1)
begin
always @(posedge Clk)
if (En)
din_shreg[i] <={din_shreg[i][C_NUM_CYCLES-2:0], Din[i]};
assign Dout[i] =din_shreg[i][C_NUM_CYCLES-1];
end
endgenerate

endmodule


猜你喜欢

转载自blog.csdn.net/reborn_lee/article/details/80394103