在我刚接触FPGA的时候,我的师傅就告诉我不要写latch,并且告诉我latch来源于分支不完全的组合条件逻辑.不要用latch的原因我不太明白,网上的说法主要有两个:
- 对毛刺不敏感,会产生毛刺并且传递毛刺.
- 不利于STA.
第一个很好理解,但是第二个并不明白.在网络上搜索答案的时候虽然没有马上找到答案,却收获了一些其他的知识.
Latch设计之Time borrowing
虽然在FPGA设计中要尽量避免latch,但是在ASIC设计中却应用得很普遍,原因在于latch比FF消耗更少的资源,所以能带来更小的面积.
这个主要是因为FF可以理解为增强功能的Latch,它们都有存储的功能,但是latch是电平触发,而FF只有在Clock有效沿才触发,多了一个Clock控制,自然会消耗更多的门电路资源.
除了面积的优势,Latch还能带来更快的速度,这就涉及到我们说的time borrowing.所谓time borrowing,其本质是因为锁存器是电平触发,即使在有效沿没有准备好,只要能在有效电平准备好,同样可以收到数据.所以它的时序要求比FF要宽泛.例如,可以在电路存在时延最长为8ns的信号通路的情况下,满足5ns的时钟周期.
那么borrow了,还得还,怎么还,请看下图.
为了便于分析,我们设FF和Latch本身的器件传播延迟Tpd为0.这并不会影响我们的分析结果.
我们设时钟周期为10ns,也就是100Mhz,这在FPGA上是非常常见的频率,设F1到F2的Tcomb为12ns,F2到F3的Tcomb为5ns,那么很显然,我们的时序存在不满足的路径,因为12ns超过了10ns,而后面的路径comb延迟仅为5ns,可以认为有较大的余量,但是在一般的FPGA设计中,这个余量似乎浪费掉了.所以假如我们整体地看F1到F3,comb延迟是17ns,符合20ns的要求.也就是说,假如我们能把后面5ns路径的余量挪到前面,就可以满足STA的要求.
那么此时我们再次考虑触发器和锁存器的特性,触发器要求数据其实主要是不能太晚,必须在时钟有效沿的建立时间之前到达,而锁存器不一样,锁存器在SR输入有效电平时会变成透明状态,因此假如我们把FF换成Latch,如下图
假设这个Latch的有效电平正好在时钟的有效沿,同样,这个设定也是不影响核心分析的.
那么当F1的输出错过有效时钟沿到达L2时,虽然错过了有效时钟沿,但是Latch并不是靠时钟沿接收数据的,而是有效电平,有效电平长达半个时钟周期,那么这个时候latch就可以接受到数据,并且在后半个周期将数据锁存,输出给下一级F3,因为L2到F3时间仅有5ns,所以F3必定能成功在有效时钟沿建立时间之前接收到数据.成功地满足了10ns的周期要求,这也就是我们说的borrowing time,显然,假如L2到F3同样超时,那就要另外再考虑了,这个时候再用Latch也不一定能满足要求.
在上面的例子中,F1到F3要添加多周期时钟约束?多周期也不一定能解决问题,因为它只是时序变得宽松,但是还是有要求的,如果有效沿过去了,信号还没到,那锁存器也不好使。
在这里是两个.这里提一下,Vivado的时序分析 都是以一个周期为准.什么意思呢,就是说认为源信号只有一个周期,接收端要在一个周期内接受到这个信号.除了在上面例子的情况下,多周期时钟约束主要出现在慢时钟域信号到快时钟域上,其本质就是发送端给的源信号不止一个周期,对时序要求其实比较松.
同步设计综合出锁存器?
1 module Decode(
2 input A,
3 input B,
4 input C,
5 output reg[31:0] edata,
6 output reg[31:0] eCapData,
7 input bCap,
8 output reg CapSt,
9 input n_rst,
10 input [31:0] rstVal,
11 input clk);
12
13 reg[1:0] state;
14
15 always@(posedge clk or negedge n_rst)
16 begin
17 if(!n_rst)
18 begin
19 edata <= rstVal;
20 state <= {A,C};
21 end
22 else
23 begin
24 state <= {A,B};
25 edata <= {31'd0,A};
26 *****此处省略一万*****
27 end
所谓同步设计出现锁存器,在于复位时信号给错,我们先看触发器在FPGA中怎么实现的
这个就是实现触发器的东西,锁存器也可以用这个实现
ce是使能,一般用在触发器赋值时的条件判断语句中
sr是锁存输入,有效时变成透明锁存器,无效时锁存数据,配置为FF时作为复位端口
触发器
异步复位 | FDCE | 复位后Q输出0 |
FDPE | 复位后Q输出1 | |
同步复位 | FDRE | 复位后Q输出0 |
FDSE | 复位后Q输出1 |
锁存器
异步复位 | LDCE | 复位后Q输出0 |
LDPE | 复位后Q输出1 |
FF复位的输入应该是来自接地或者参考电平,所以把另外的信号作为复位肯定不会综合得到FF.根本就狗屁不通不可能实现的东西.
另外驳斥一个所谓Latch比FF省资源的说法,至少在xilinx7系的板子上实现之后,都可以发现是同一个资源实现FF或者Latch的.而纯用门电路实现FF和Latch,肯定是Latch省资源.
这里再补充一下所谓异步复位同步释放,其本质就是一个异步复位信号两级同步避免亚稳态的设计,说的玄乎.唯一有一点值得记录的就是,复位信号都是电平有效,因此生效沿一般无需考虑亚稳态,而失效沿假如和时钟有效沿的建立保持时间区间有重叠,就有可能引起亚稳态状态的产生.
基本代码长这样:
1 module moduleName (
2 input clk,
3 input rst,
4 output rst_r
5 );
6
7 reg [1:0] rst_ff;
8
9 assign rst_r = rst_ff[1];
10
11 always @(posedge(rst),posedge(clk)) begin
12 if(rst)
13 begin
14 rst_ff <= 2'b0;
15 end
16 else
17 begin
18 rst_ff <= {rst_ff[0],rst};
19 end
20 end
21
22 endmodule
对于xilinx的触发器,一般直接综合出来rst信号的连接没有区别,而综合出来的触发器有区别,如下图
代码:
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 -- Uncomment the following library declaration if using
5 -- arithmetic functions with Signed or Unsigned values
6 --use IEEE.NUMERIC_STD.ALL;
7
8 -- Uncomment the following library declaration if instantiating
9 -- any Xilinx leaf cells in this code.
10 --library UNISIM;
11 --use UNISIM.VComponents.all;
12
13 entity case_test is
14 port (
15 clk : in STD_LOGIC;
16 rst : in STD_LOGIC;
17 ce_lc : in STD_LOGIC;
18 ce_ff : in STD_LOGIC;
19 data_i : in std_logic_vector(2 downto 0);
20 dout_lc : out std_logic_vector(2 downto 0);
21 dout_ff : out std_logic_vector(2 downto 0);
22 dout_ffs : out std_logic_vector(2 downto 0)
23 );
24 end case_test;
25
26 architecture Behavioral of case_test is
27
28 begin
29 proc_name : process (ce_lc)
30 begin
31 if ce_lc = '1' then
32 dout_lc <= data_i;
33 -- case ce_lc is
34 -- when B"000" =>
35 -- dout_lc <= data_i;
36 -- when B"001" =>
37 -- dout_lc <= B"10";
38 -- when B"010" =>
39 -- dout_lc <= B"01";
40 -- when B"011" =>
41 -- dout_lc <= B"11";
42 -- when B"100" =>
43 -- dout_lc <= B"00";
44 -- when B"101" =>
45 -- dout_lc <= B"10";
46 -- when B"110" =>
47 -- dout_lc <= B"11";
48 -- when B"111" =>
49 -- dout_lc <= B"00";
50 -- when others =>
51 -- null;
52 -- end case;
53 end if;
54 end process proc_name;
55
56 ff_proc : process (clk, rst)
57 begin
58 if rst = '1' then
59 dout_ff <= (others => '0');
60 elsif rising_edge(clk) then
61 if ce_ff = '1' then
62 dout_ff <= data_i;
63 end if;
64 end if;
65 end process ff_proc;
66
67 ffs_proc : process (clk)
68 begin
69 if rising_edge(clk) then
70 if rst = '1' then
71 dout_ffs <= (others => '0');
72 else
73 if ce_ff = '1' then
74 dout_ffs <= data_i;
75 end if;
76 end if;
77 end if;
78 end process ffs_proc;
79 end Behavioral;
可以看到, 第一个进程综合出了锁存器,LDCE,我没有写复位.第二个进程是异步复位FDCE,第三个进程是同步复位FDRE,但是查看综合出来的结果,rst都是直接连到了FDCE和FDRE的复位端口之上,连接方式没有任何区别.