《硬件架构的艺术》笔记(三)

处理多个时钟

3.1 单时钟域

        单时钟设计(即同步设计)。易于实现,并且更少产生与亚稳态、建立和保持时间违背方面的问题。

 3.2 多时钟域

时钟的频率不同; 时钟频率相同,但是相位不同。

3.3 多时钟域设计的难题 

多时钟域设计面临问题:

        1. 建立时间和保持时间的违背

        2. 亚稳态

3.3.1 违背建立时间和保持时间

        建立时间:在时钟脉冲到来前,输入数据需要保持稳定的时间。
        保持时间:在时钟脉冲到达后,输入数据仍需保持稳定的时间。
        图3.4解释了相对于时钟升沿的建立时间和保持时间。

         在多时钟域情况下,很容易出现一个时钟域的输出在另一个时钟域中的时钟上升沿到来时发生改变的现象。这将会引起第二个时钟域中触发器的输出处于亚稳态,由此导致一系列错误的结果。

 如图3.5所示,xclk_output1 (属于 xclk 时钟城)在yclk的上升沿附近发生了变化。即 xclk_output1 信号在变化期间被 yclk 时钟域采样。这会导致相对于 yclk的建立时间和保持时间违背现象的发生。因此,在 yclk 时钟城中,依赖于 xclk_output1 的信号将进入亚稳态并产生错误的结果。然而,xclk_output2(属于 xclk 时钟城)在yclk 的上升沿处是稳定的。所以不会出现建立时间和保持时间违背的问题。因此,对于 yclk 时钟城中的信号,如果依赖xclk_utput2 信号,将会产生正确的输出。

3.3.2 亚稳态

        由多个时钟域引起的亚稳态问题在笔记(一)中已经学习。

3.4 多时钟设计的处理技术

        通用准则: 时钟命名规则;分模块设计

3.4.1 时钟命名法

        系统时钟可以命名为sys_clk,发送时钟可以命名为tx_clk,接收时钟可以命名为rx_clk,这样可以在脚本中使用通配符来对所有时钟进行操作。同样,属于同一个时钟域的信号,在命名时使用同样的前缀。

3.4.2 分块化设计

  • 每个模块在单个时钟下工作
  • 在信号跨时钟域传输时,使用同步器模块,使进入某个时钟域内的模块信号,与该模块时钟保持同步
  • 同步器模块规模尽可能小

        将整个设计分割成模块的优点在于使得静态时序分析变得很简单,因为所有输出或输人某时钟域的信号都与使用该时钟的模块保持同步。所以,设计就成为完全同步的。另外,对于同步模块是不需要做静态时序分析的。但是,要保证满足保持时间的要求。

 3.4.3 跨时钟域

跨异步时钟域的传输可以分为两类:

  • 控制信号的传输
  • 数据信号的传输

控制信号的传输(同步化)

        为减少亚稳态的影响,设计者最常用的方式是使用多级同步器,即两个或多个触发器串联起来组成的同步电路(使用两级同步电路就足以避免亚稳态的出现,只有在时钟频率非常高的设计中才要求使用三级同步器电路)

         如果同步器的第一级触发器产生亚稳态输出,那么这个亚稳态会在同步器的第二个触发器取样前进人稳态。这种方法无法保证第二级触发器的输出一定不会出现亚稳态,但是它确实降低出现亚稳态的可能性。同理,如果为同步器增加更多级触发器,就会进一步降低出现亚稳态的可能性。这种方法的一个缺点,也可以看做同步器电路不可避免的开销,就是增加了电路的整体延时

数据信号的传输

  • 使用握手信号的方式
  • 使用异步FIFO

3.5 跨时钟域

        如果多个时钟都起源于同一时钟,并且它们的相位和频率关系是已知的,那么这些时钟可以看成是跨同步时钟域的时钟。按照相位和频率的关系,可以将这些时钟分成以下类型:

  • 同频零相位差时钟
  • 同频恒定相位差时钟
  • 非同频可变相位差时钟(倍时钟;有理数倍时钟)

3.5.1 同频零相位差时钟

        clk1 和 clk2 具有相同的频率和零相位差 。

         只要在源触发器和目的触发器之间的组合逻辑的延迟能满足电路建立和保持时间的要求,数据就能正确地传输。在这种情况下,对设计的唯一要求只是保证 STA (静态时序分析) 通过。如果这一条件满足,就不会出现亚稳态问题和数据丢失或不一致的问题。

3.5.2  同频恒定相位差时钟       

        有相同时钟周期,但是相位差恒定。

         每当数据从“clk1”传输到“clk2”时,由于更小的建立时间/保持时间裕量,对组合逻辑的延时约束都会变得更紧。如果组合逻辑的延时能保证满足采样沿处建立时间和保持时间的要求,数据就可以正确地传输,并且不会有亚稳态产生。在这种情况下是不需要同步器的。只需要使设计的STA 通过就可以了。
        一般会在 STA 中创建这种情况以保证满足时序要求。如果组合逻辑有更多延时,通过在发射边沿和捕获边沿加入偏移(例如,使时序有相同频率和不同相位),会有助于满足时序的要求。

3.5.3  非同频、可变相位差时钟

  • 整数倍频率的时钟

        两个时钟的有效边沿的相位差是可变的,这两个时钟的有效边沿的最小相位差始终等于其中较快的那个时钟的时间周期

        下图的数据是由快时钟到慢时钟,可能会出现数据丢失,所以必须要将源数据保持至少一个目标时钟周期,可以使用一个简单的FSM实现

        clk1的频率是时钟clk2的三倍。clk2捕获数据的时间可能是T、2T或3T,取决于数据是在clk1哪一个边沿发送出来,所以应满足任意路径的最差延迟都应在时钟边沿相位差为T时满足建立时间的要求。
        由于至少都有较快时钟的一个完整的周期用来传输数据,所以通常可以保证满足建立时间和保持时间的要求,基本不会存在亚稳态或数据不一致的问题,也就无需使用同步器。 

  • 非整数倍频率的时钟

        由于一个时钟的频率是另一个时钟的非整数倍,所以有效边沿的相位差是可变的。此时两时钟之间的最小相位差足以使亚稳态发生(取决于实际的频率倍数和设计工艺)。

1. 在源时钟和目的时钟有效沿之间有足够大的相位差,所以不会有亚稳态

        例如,clk1和clk2分别是clk=30ns的三分频与二分频,即clk1的周期是15ns,clk2的周期是10ns。这两个时钟的最小相位差为2.5ns,对于满足建立时间和保持时间足够了。由于相位差很小,避免在跨越两个时钟的未知使用任何组合逻辑,必须要使用同步器。

 2. 源时钟和目的时钟有效沿非常接近,导致产生亚稳态问题。

        一旦有时钟边沿接近这种情况出现,下一个时钟周期就会留出足够大的时间冗余,使得数据的捕获不会出现违背建立时间或保持时间的要求。
        例如,时钟clk1和clk2的周期分别为10ns和7ns,之间的最小相位差是0.5ns。必须要用同步器。

3. 两个时钟的时钟沿在许多连续的周期中都非常接近

        在这种情况下时钟的相位差差异很小,并且连续存在几个周期。除了变化的相位差异和周期性重复现象,其余都与异步时钟很相似。clk1=10ns,clk2=9ns 。在前两个周期中可能违背建立时间(源时钟在目的时钟之前),而在后两个时钟周期中可能会违背保持时间(目的时钟在源时钟之前)(除了亚稳态问题,数据从慢时钟域传递至快时钟域也可能丢失)。

         为了不丢失数据,数据需要保持稳定至少两个目的时钟周期,既适用于从快到慢的传输,也适用于从慢到快的传输。

3.6 握手信号方法

 使用握手信号“xack”和“yreq”,“系统X” 将数据发送给 “系统Y”

下面是使用握手信号传输数据的例子。
        1)发送器 “系统X” 将数据放到数据总线上并发出“xreq”请求信号,表示有效数据已经发到接收器“系统Y”的数据总线上。

        2)把“xreq”信号同步到接收器的时钟域“yclk”上。

        3)接收器在识别“xreq”同步的信号“yreq2”后,锁存数据总线上的信号。
        4)接收器发出确认信号“yack”,表示其已经接受了数据。
        5)接收器发出的“yack”信号同步到发送时钟“xclk”上。
        6)发送器在识别同步的“xack2”信号后,将下一个数据放到数据总线上。

        所以安全地将一个数据从发送端传输至接受端需要5个时钟周期。由此看来握手信号的缺点也十分明显,传输单个数据的延迟太长。

握手信号要求:

  • 数据应该在发送时钟域内稳定至少两个时钟上升沿
  • 请求信号“xreq”的宽度应该超过两个上升沿时钟,否则从高速时钟域向低速时钟域传递可能无法捕捉到该信号。

握手信号的缺点:传输单个数据延迟高。

3.7 使用同步FIFO传输数据

        图3.19展示了一个同步FIFO的通用架构。DPRAM(双端口RAM)用作 FIFO的存储器以使读、写可以独立进行。

3.7.1 同步FIFO的工作原理

        在同一个时钟信号下,通过读、写指针产生各自的读、写地址,再通过产生的读、写地址分别从FIFO存储器中读取和写入数据。使用状态模块产生的fifo_empty和fifo_full信号。如果 fifo_empty 有效,说明FIFO已经“读空”,如果fifo_full有效,说明FIFO已经“写满”。而在读写指针相等时,FIFO要么空要么满,所以需要对这两种情况进行区分。

3.7.2 FIFO 空和满的产生

  •  写操作使两个指针在下个时钟保持相等,即FIFO满,这时会发出fifo_full信号
always @(posedge clk or negedge rstn)begin 
    if(~rstn)
        fifo_full <= 1'b0;
    else if(wr_fifo && rd_fifo)
        //do nothing
    esle if(rd_fifo)
        fifo_full <= 1'b0;
    else if(wr_fifo && (rd_prt == wr_ptr + 1'b1))
        fifo_full <= 1'b1;
end 
  • 使读操作使两个指针在下个时钟保持相等,即FIFO空,这时会发送fifo_empty信号
always@(posedge clk or negedge rstn)begin 
    if(~rstn)
        fifo_empty <= 1'b1;
    else if(wr_fifo && rd_fifo)
        //do nothing
    else if(wr_fifo)
        fifo_empty <= 1'b0;
    else if(rd_fifo && (wr_prt == rd_ptr + 1'b1))
        fifo_empty <= 1'b1;
end 

此外,还可以使用计数器来实现。复位时计数器为0,写操作计数器增1,读操作计数器减1。

cnt = 0 时 fifo空; cnt = fifo深度 时 fifo 满。

缺点:效率慢,增加额外的硬件(比较器)。

3.8 异步FIFO

        异步 FIFO用来在两个异步时钟域间传输数据。图3.21中是两个系统,分别为“System X”和“System Y”,从“SystemX”向“SystemY”传输数据,两个系统工作在不同的时钟城中。

         “SystemX”使用“xclk”将数据写人 FIFO,并由“System Y”使用“yclk”读出。
        “fifo_full”和“ffo_empty”信号负责对上溢和下溢情况的监控。
        “ffo_full”信号指示上溢情况。在“ffo_full”置起时数据不应写人FIFO,否则会将 FIFO内的数据覆盖掉。
        由“ffo_empty”信号指示下溢情况,如在“ffo_empty”时不应读取FIFO,否则会读出垃圾数据。
        与握手信号不同,异步 FIFO 用于对性能要求较高的设计中,尤其是时钟延迟比系统资源更为重要的环境中。
        如3.7节所述,简单的同步 FIFO可以通过使用双端口 RAM和单独的读、写端口来实现,读写操作使用同一个时钟。可以用同样的原理设计异步 FIFO,但是要特别注意当产生 FIFO空和 FIFO满信号时要避免出现亚稳态现象。

3.8.1 避免使用二进制计数器实现读写指针

        在产生 FIFO 满信号时要将写指针与读指针进行比较,由于两个指针与分别与其各自的时钟同步,但是彼此之间异步,在使用二进制计数器实现指针时,就会导致用于比较的指针值取样错误。

        因为 FIFO 满和 FIFO空标记的产生要使用这些指针的值,所以错误的指针值将会误产生标记。可能是在 FIFO 满时没有产生FIFO满标记从而使数据丢失;或在 FIFO空时没有产生FIFO空标记,从而读出垃圾数据

3.8.2 使用格雷码取代二进制计数

        格雷码是单位间距码,每下一个值与前一个值的区别只有一位距离,所以在转换中最多只会出现一位错误。例如,如果计数器从“1010”变为“1011”,取样逻辑要么读到“1010”(旧值)要么读到“1011”(新值),但是不会出现其他值。

同步指针的影响

        在 FIFO满时会阻止进一步访间 FIFO。在FIFO 满时,按自时钟递增的读、写指针会进行比较。需要将读指针 (格雷码) 同步到写时钟上,下面用例子来说明这一点。
        如图 3.22 所示,最初在 t0 阶段读、写指针都为0。随着后续发生在FIFO上的写操作,写指针增加。当到达某个阶段时,读、写指针相等,这时 FIFO 满。在图3.23 中在 t5 阶段这种情况发生。

         如果在 t6 处发生了读操作,由于典型的同步电路至少包含两个触发器,将读指针同步到写时钟上将会导致同步后的读指针在两个写时钟后出现。这虽然增加了阻止数据写人的周期,但是对数据的准确性是无害的。只有在 FIFO实际上已经满了但是没有阻止写动作时才会出现问题。
        类似地,在 FIFO空时也会阻止对 FIFO 进一步的读访问。对于FIFO空计算,把写指针同步到读时钟并与读指针进行比较。因此在读一边延迟了写操作(延迟了两个时钟信号)并且在实际上有数据时仍然会指示 FIFO 为空。这会导致阻止读操作,直到读一边能看到写操作为止。

        如图3.24 所示,在 t0 处读、写指针初始值为0。随对 FIFO 的写操作写指针增加。在某个阶段写指针与读指针相等,这时 FIFO变为满。这在图3.25 中的 t3 阶段发生。

 注意:在未满时通知写一边FIFO 已满,或在未空时通知读一边 FIFO已空都是可以的。即使指针同步后的值 (写时同步读指针以及读时同步写指针)会持续一小段时间亚稳态,阻止写/读的影响会使 FIFO 挂起一段时间,但是不会导致任何错误。

3.8.3 用格雷码实现FIFO指针

        设计格雷码计数器步骤:

  1. 将格雷值转换为二进制值。
  2. 根据条件递增二进制值。
  3. 将二进制值转化为格雷码。
  4. 将计数器的最终格雷值保存到寄存器中。

 格雷码转换为二进制公式:

module gray2bin (bin , gray);
    parameter SIZE = 4;
    input    [SIZE-1:0]    gray;
    output   [SIZE-1:0]    bin;

    reg    [SIZE-1:0]    bin;
    integer    i;
    
    always @(gray)
        for(i=0; i<SIZE; i=i+1)
            bin[i] = ^(gray>>i);

endmodule                                    

二进制转化为格雷码:

module bin2gray (bin, gray);
    parameter SIZE = 4;
    input    [SIZE-1:0]    bin;
    output   [SIZE-1:0]    gray;

    assign gray = (bin>>1) ^ bin;

endmodule

格雷码计数器的实现:

module gray_cnt(clk, gray, inr, rstn)
    parameter SIZE = 4;
    input    clk, inr, rstn;
    output  [SIZE-1:0]    gray;

    reg    [SIZE-1:0]    gray_temp, gray, bin_temp, bin;
    integer    i;
    
    always @(gray or inr)begin: gray_bin_gray
        for(i=1; i<SIZE; i=i+1)
            bin[i] = ^(gray>>i);    //gray to binary conversion
            bin_temp = bin + inr;    //addition in binary
            gray_temp = (bin_temp>>1) ^ bin_temp;    //binary to gary conversion
    end

    //---gray_temp register ---//
    always@(posedge clk or negedge rstn)
        if(~rstn)
            gray <= {SIZE{1'b0}};
        else 
            gray <= gray_temp;

endmodule

 3.8.4 FIFO 满和 FIFO 空的产生

(1) 比较二进制读写指针

        当二进制码指针的最高有效位不同,而其余位相同时,FIFO 为满。

        当二进制码指针的所有位都相同时,FIFO 为空。

例如,一个深度为8 的 FIFO。使用3 位表示所有8 个位置,并用额外的一位来区分FIFO满或空。一开始 rd_ptr_bin 和w_ptr_bin 都是 “0000” FIFO 为空。在连续8次写 FIFO后得到读、指针的值为:
                                rd_ptr_bin =”0000"
                                wr_ptr_bin ="1000”

这就是图3.29中所示的FIFO满状态

 现在连续读8次,得到以下读、写指针值:
                                rd_ptr_bin ="1000"

                                wr_ptr_bin ="1000"

这就是图3.30 中所示的FIFO 空状态

         图3.31是FIFO空和FIFO满的模块原理图在这种情况下,由于存在 XOR 门链路,最大操作频率取决于格雷码计数器的速度。
        由于读/写指针的值以格雷码保存,而所有的比较和递增操作以二进制码形式进行,使得实现和纠错变得相当简单。在图3.31 中,需要4个格雷码到二进制码转换器。

 (2)比较格雷码读写指针

         格雷码计数器:

 FIFO 空条件的产生:

        当读指针与同步后的写指针相匹配时,FIFO为空,这时应该在 FIFO的读时钟城内马上产生 FIFO 空标记。
        这时比较读和同步化的写指针的格雷码。如果在比较前指针首先转化为自身的等效二进制码,这种方式就能节省所需要的4个格雷码到二进制码转换器。
        与之前的实现类似,指针宽度比寻址 FIFO存储器所需的宽度多一位同步后的写指针(wr_ptr_sync)与rd_gtemp进行比较(下一个格雷码将保存在 rd_ptr 内)。

always @(posedg rclk or negedge rstn) begin : fifo_empty_gen
    if(~rstn)
        fifo_empty <= 1'b1;
    else
        fifo_empty <= (rd_gtemp == wr_prt_sync);
    
end 
    

 FIFO 满条件的产生:

        当写指针与同步化的读指针相等时,FIFO为满,这时应该在 FIFO的写时钟域内马上产生FIFO满标记。
        注意,应直接比较写指针和同步化的读指针的格雷码。
        与之前的实现类似,指针宽度比寻址 FIFO存储器地址所需的宽度多一位。由于这次直接使用格雷码而不是二进制码对指针进行比较,因此产生该条件的逻辑与之前的实现方式不同。

always@(posedge wclk or negedge rstn)begin :generate fifo_full
    if(~rsnt)
        fifo_full <= 1'b0;
    else if(wr_gtemp == {~rd_prt_sync[SIZE], ~rd_prt_sync[SIZE-1], rd_prt_sync[SIZE-2:0]});
        fifo_full <= 1'b1;
end

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值