赞
踩
1. 应用场景
异步FIFO用来在两个异步时钟域间传输数据。如图所示是两个系统,分别为“system X”和“system Y”,从“system X”向“system Y”传输数据,两个系统工作在不同时钟域。
“system X”使用“xclk”将数据写入FIFO,“system Y”使用“yclk”将数据读出,“fifo_full”和“fifo_empty”分别负责监控上溢(overflow)和下溢(underflow)情况。
“fifo_full”指示上溢情况,拉高时数据不应再写入FIFO,否则会将FIFO内的数据覆盖掉。
“fifo_empty”指示上溢情况,拉高时不应读取FIFO,否则会读出垃圾数据。
与握手信号不用,异步FIFO用于对性能要求较高的设计中,尤其是时钟延迟比系统资源更为重要的环境中。
2. 异步FIFO结构
与同步FIFO设计原理相似,异步FIFO的结构也可以分为三个部分,如图所示,fifo write control、fifo read control和fifo memory,其中fifo write control和fifo read control分别工作在wr_clk和rd_clk时钟域,fifo memory工作在wr_clk和rd_clk时钟域。
fifo write control:写指针产生、满信号产生和读指针同步器;
fifo read control:读指针产生、空信号产生和写指针同步器;
fifo memory:缓存数据。
3. 关键设计
3.1 异步FIFO与同步FIFO差异
异步FIFO相较于同步FIFO,主要有两点不同:
Fifo memory工作在两个时钟域;
Wr_ctrl和rd_ctrl工作在不同时钟域;
读、写指针经过两级同步器会存在延迟。
异步FIFO不能再使用“计数器”产生fifo空、满信号,原因在于计数器不能被两个时钟同时驱动,此时只能通过比较读、写指针来产生空、满信号,将读指针同步到写时钟域,与写指针比较产生满信号;将写指针同步到读时钟域,与读指针比较产生空信号。
读写指针的同步都需要跨时钟域传输,若使用握手信号的方式同步指针,效率很低;假如指针仍使用“二进制编码”,使用两级同步器同步指针,可能会出现亚稳态导致用于比较的指针出错。
如指针从“111”→“000”,经过两级同步器,可能的结果有8种,每一个比特都可能存在采样错误,错误的指针会导致fifo空、满信号异常。
读指针同步错误,FIFO满信号未正常拉高,继续写FIFO会覆盖原数据,导致数据传输错误;
写指针同步错误,FIFO空信号为正常拉高,继续读FIFO会读出垃圾数据,导致数据传输错误。
异步FIFO的指针同步应该避免使用二进制编码。
3.2 格雷码
实现异步FIFO指针同步的一种方式是使用格雷码,格雷码是“单位间距码”,即相邻值之间只有1bit不同。格雷码与二进制码的对应关系如图所示。格雷码有以下几个特点:
单位间距码:相邻值之间只有1bit不同
中心对称:除MSB外,其余bit中心对称
指针采用格雷码计数,使用两级同步器同步指针很少会出现亚稳态,此外取样后的值最多只有1bit出现错误,但该错误不会影响空、满信号的正确产生。考虑格雷码读、写指针采样错误的情况:
读指针采样错误:相邻格雷码只有1bit不同,若采样出现亚稳态会出现两种情况,一是采样值保持不变,同步的读指针小于当前的真实值,fifo_full会提前拉高,此时不会出现overflow;二是采样值采样成功,同步的读指针等于当前的真实值。两种结果都不会影响“fifo_full”的正确产生;
写指针采样错误:写指针同理,一是同步的写指针小于当前的真实值, fifo_empty会提前拉高,虽然还有数据未读出,但这种情况也不会造成underflow;二是同步的写指针等于当前的真实值。两种结果都不会影响“fifo_empty”的正确产生。
3.3 同步指针的影响
比较读、写指针的目的是产生FIFO的空满信号,相较于同步FIFO,异步FIFO在两个时钟域分别比较读、写指针,读、写指针都需要经过两级同步器。
3.3.1 FIFO的“假满”
由于读指针同步到写时钟域存在2*T_wclk的延迟,此时写时钟域采样到的rd_ptr_sync小于等于读时钟域的rd_ptr。rd_ptr_sync小于rd_ptr的情况如图所示,wr_ptr追赶上rd_ptr_sync(wr_ptr和rd_ptr_sync都转换为二进制码),最高位不同其余位相同,此时会拉高fifo_full信号,但实际上FIFO内还有两个地址空间可以写入数据,这种情况就是FIFO的“假满”。FIFO的“假满”虽然阻止了数据的写入,但是对数据的准确性是没有影响的。只有在FIFO实际已经满了但没阻止数据写入才会出现overflow(原数据未读出就被覆盖)。
3.3.2 FIFO的“假空”
由于写指针同步到读时钟域存在2*T_rclk的延迟,此时读时钟域采样到的wr_ptr_sync小于等于读时钟域的wr_ptr。wr_ptr_sync小于等于wr_ptr的情况如图所示,wr_ptr_sync等于rd_ptr(指针均以转换为二进制码),所有比特位均相等,此时会拉高fifo_empty信号。FIFO的“假空”虽然在实际有数据的情况下拉高了FIFO的空信号,阻止了数据的读出,但是对数据的准确性是没有影响的,直到读时钟域看到的写指针变化才会拉低FIFO的空信号。只有在FIFO为空的时候没有阻止读操作才会产生问题,即underflow问题。
3.4 格雷码与二进制码转换
读、写指针有二进制码转为格雷码后经两级同步器同步到写、读时钟域,在读、写时钟域有时还需要将格雷码转为二进制码进行指针的比较以产生FIFO的空、满信号,下面将给出格雷码与二进制码相互转换的方法。
3.4.1 二进制码转格雷码
二进制码转格雷码的公式如下所示,其中n表示位宽。
3.4.2 格雷码转二进制码
格雷码转二进制码的公式如下所示,其中n表示位宽。
3.5 读、写指针产生
产生读、写指针有两种方法,一种是直接使用格雷码计数器产生读写指针,一种是使用二进制码计数器产生读写指针,再将读写指针转换为格雷码用于跨时钟域的同步,下面将分析这两种设计方法。
3.5.1 格雷码计数器
格雷码计数器的结构如图所示,由格雷码转二进制码、二进制加法器和二进制码转格雷码组成。
从面积和工作频率两个方面分析该设计。
面积:格雷码转二进制、二进制转格雷码的组合逻辑,格雷码指针寄存器;
工作频率:寄存器输入信号的组合逻辑较为复杂,该电路工作在较高频率可能存在时序违例的情况。
3.5.2 二进制码计数器
二进制码计数器的结构如图所示,由二进制转格雷码、二进制码寄存器和格雷码寄存器组成。
从面积和工作频率两个方面分析该设计。
面积:二进制转格雷码、两个寄存器;
工作频率:寄存器输入只有加法器或二进制转格雷码,组合逻辑延迟相对较小,该电路可以工作在较高频率。
3.6 空、满信号产生
FIFO的空、满信号通过比较读、写指针可以得到,下文将讨论两种计数器设计下FIFO的空、满信号产生。
3.6.1 格雷码计数器
3.6.1.1 二进制码产生空、满信号
N位指针可以覆盖FIFO中的2^N个地址。在两个指针相等时,因为FIFO可能处于空状态也可能处于满状态,所以需要使用额外的位对这两种情况进行区分。
FIFO满:二进制码指针的最高有效位不同,其余比特位相同;
FIFO空:二进制码指针所有比特位相等。
格雷码计数器设计下,使用二进制码产生空、满信号的结构如图所示。由于读写指针的值都是以格雷码保存,而所有比较和递增是以二进制码形式进行,实现和纠错比较简单。
读指针经过两级同步后得到rd_ptr_sync,将格雷码转为二进制码rd_ptr_sync_b,在写时钟域还需要将格雷码的wr_ptr转换为二进制码的wr_ptr_b,将rd_ptr_sync_b和wr_ptr_b进行比较,得到FIFO的满信号。
写指针经过两级同步后得到wr_ptr_sync,将格雷码的wr_ptr_sync转换为二进制码的wr_ptr_sync_b,在读时钟域还需要将格雷码的rd_ptr转为rd_ptr_b,最后将wr_ptr_sync_b和rd_ptr_b进行比较,得到FIFO的空信号。
该设计共使用了4个“格雷码转二进制码”,组合逻辑路径较长,较高频率下工作可能会存在时序违例的情况。如果直接使用格雷码指针产生FIFO的空、满信号,就不需要这些转换器,下文将分析使用格雷码指针产生FIFO的空、满信号。
3.6.1.2 格雷码产生空、满信号
使用格雷码产生FIFO的空满信号要求使用两个格雷码计数器,分别是N位和N-1位。由于格雷码除最高位镜像对称的特性,可以使用N位格雷码计数器产生N-1位格雷码计数器。N-1位格雷码计数器的产生如图所示,将N位格雷码计数器的2个MSB异或得到的值作为N-1位格雷码计数器的最高位,其余N-2位与N位格雷码的N-2位完全一致。
格雷码计数器产生空信号使用N位计数器与二进制码相同,当读写指针完全相同时FIFO为空。
格雷码计数器产生满信号与二进制码不同。如图所示,当rd_ptr和wr_ptr相等时FIFO为空。wr_ptr加一后,wr_ptr和rd_ptr的最高为不同、其余位相同,按照二进制码的判断标准此时FIFO为满,但此时FIFO并没有满,所以二进制码的FIFO满判断方法不再适用于格雷码。
双格雷码计数器可以很好地解决该问题。N位计数器用于区分写指针比读指针多回绕一次,N-1位计数器用于确定写指针的真实位置。FIFO的满需要满足三个条件:
N位格雷码指针最高有效位不同;
N-1位格雷码指针最高有效位相同;(该条件等效于N位格雷码指针的次高位不同)
N位格雷码指针剩余N-2位全部相等。
3.6.2 二进制码计数器
格雷码计数器的两种实现方式都略显复杂,二进制码计数器提供了一种简单的实现方法。如图所示,二进制码计数器使用两个寄存器分别缓存二进制指针和格雷码指针。二进制码计数器产生空、满信号如图所示。
仅需将同步后的指针转为二进制码,然后与二进制码指针比较,按照二进制码FIFO空、满方法产生FIFO空、满信号。
4. 大容量异步FIFO设计
大容量同步FIFO是使用SRAM作为FIFO memory实现的,大容量异步FIFO仍可以使用该方法,不过更为常用的方法是使用“大容量同步FIFO和小容量异步FIFO”级联来实现的。原因在于异步FIFO的功能只是跨时钟域传输数据,同步FIFO更适合缓存数据,结合这两种FIFO的特点将其级联,得到大容量异步FIFO。
5. 代码实现
5.1 async_fifo_top
- //=================================================================================
- // module : async_fifo_gray_cnt.v
- // description : asynchronous fifo , pointer generate by gray counter
- // data : 2022/2/27
- // author : souther meditating
- //=================================================================================
-
-
-
-
- module async_fifo(
- wclk,
- wrst_n,
- wr_en,
- wr_data,
- rclk,
- rrst_n,
- rd_en,
- rd_data,
- fifo_full,
- fifo_empty
- );
-
-
-
-
- //=================================================================================
- // parameter & localparam
- //=================================================================================
-
-
-
-
- //=================================================================================
- // parameter
- parameter FIFO_DATA_WIDTH = 16;
- parameter FIFO_DEPTH = 16;
-
-
-
-
- //=================================================================================
- // localparam
- localparam FIFO_ADDR_WIDTH = clog2(FIFO_DEPTH);
- localparam FIFO_PTR_WIDTH = FIFO_ADDR_WIDTH + 1;
-
-
-
-
- //=================================================================================
- // I/O
- //=================================================================================
- input wclk;
- input wrst_n;
- input wr_en;
- input [FIFO_DATA_WIDTH-1:0] wdata;
-
-
-
-
- input rclk;
- input rrst_n;
- input rd_en;
- output [FIFO_DATA_WIDTH-1:0] rdata;
-
-
-
-
- output fifo_full;
- output fifo_empty;
-
-
-
-
- //=================================================================================
- // signal
- //=================================================================================
-
-
-
-
- // ---- fifo mem ----
- wire [FIFO_ADDR_WIDTH-1:0] waddr;
- wire [FIFO_ADDR_WIDTH-1:0] raddr;
-
-
-
-
- // ---- wr_sync_cell ----
- wire [FIFO_PTR_WIDTH-1:0] rptr_g;
- wire [FIFO_PTR_WIDTH-1:0] rptr_g_sync;
-
-
-
-
- // ---- rd_sync_cell ----
- wire [FIFO_PTR_WIDTH-1:0] wptr_g;
- wire [FIFO_PTR_WIDTH-1:0] wptr_g_sync;
-
-
-
-
- // ---- wr_ptr_full ----
-
-
-
-
-
-
-
-
-
-
-
-
- //=================================================================================
- // main body
- //=================================================================================
-
-
-
-
- //=================================================================================
- // 1. fifo_mem
- // 2. wr_sync_cell
- // 3. rd_sync_cell
- // 4. wptr_full
- // 5. rptr_empty
-
-
-
-
- //=================================================================================
- // fifo_mem
-
-
-
-
- fifo_mem #(
- .DSIZE (FIFO_DATA_WIDTH ),
- .FIFO_DEPTH (FIFO_DEPTH )
- )
- u_fifo_mem(
- .raddr (raddr ),
- .rdata (rdata ),
- .wclk (wclk ),
- .wr_en (wr_en ),
- .waddr (waddr ),
- .wdata (wdata ),
- .fifo_full (fifo_full )
- );
-
-
-
-
- //=================================================================================
- // wr_sync_cell
-
-
-
-
- sync_cell #(
- .DSIZE (FIFO_DATA_WIDTH)
- )
- u_rd_ptr_sync(
- .dat_i (rptr_g ),
- .clk_o (wclk ),
- .rst_n_o (wrst_n ),
- .dat_o (rptr_g_sync )
- );
-
-
-
-
- //=================================================================================
- // rd_sync_cell
-
-
-
-
- sync_cell #(
- .DSIZE (FIFO_DATA_WIDTH)
- )
- u_wr_ptr_sync(
- .dat_i (wptr_g ),
- .clk_o (rclk ),
- .rst_n_o (rrst_n ),
- .dat_o (wptr_g_sync )
- );
-
-
-
-
- //=================================================================================
- // wptr_full
-
-
-
-
- wptr_full #(
- .FIFO_DEPTH (FIFO_DEPTH )
- )
- u_wptr_full (
- .wclk (wclk ),
- .wrst_n (wrst_n ),
- .wr_en (wr_en ),
- .waddr (waddr ),
- .wptr_g (wptr_g ),
- .rptr_g_sync (rptr_g_sync ),
- .fifo_full (fifo_full )
- );
-
-
-
-
- //=================================================================================
- // rptr_empty
-
-
-
-
- rptr_empty #(
- .FIFO_DEPTH (FIFO_DEPTH )
- )
- u_rptr_empty(
- .rclk (rclk ),
- .rrst_n (rrst_n ),
- .rd_en (rd_en ),
- .raddr (raddr ),
- .wptr_g_sync (wptr_g_sync ),
- .rptr_g (rptr_g ),
- .fifo_empty (fifo_empty )
- );
-
-
-
-
- endmodule;
5.2 async_fifo_mem
- //=================================================================================
- // module : fifo_mem.v
- // description : register dpram
- // data : 2022/2/27
- // author : souther meditating
- //=================================================================================
-
-
-
-
- module fifo_mem (
- raddr,
- rdata,
- wclk,
- wr_en,
- fifo_full,
- waddr,
- wdata
- );
-
-
-
-
- // ==========================================================================
- // parameter
- // ==========================================================================
- parameter DSIZE = 8;
- parameter FIFO_DEPTH = 16;
-
-
-
-
- // ==========================================================================
- // localpara
- // ==========================================================================
- localparam FIFO_ADDR_WIDTH = $clog2(FIFO_DEPTH);
-
-
-
-
- // ==========================================================================
- // I/O
- // ==========================================================================
- input [FIFO_ADDR_WIDTH-1:0] raddr;
- output [DSIZE-1:0] rdata;
-
-
-
-
- input wclk;
- input wr_en;
- input [FIFO_ADDR_WIDTH-1:0] waddr;
- input [DSIZE-1:0] wdata;
-
-
-
-
- // ==========================================================================
- // signal define
- // ==========================================================================
- reg [DSIZE-1:0] fifo_mem[FIFO_DEPTH-1:0];
-
-
-
-
- // ==========================================================================
- // main body
- // ==========================================================================
-
-
-
-
- // ==========================================================================
- // write fifo_mem
- always@(posedge wclk) begin
- if((wr_en == 1'b1) && (fifo_full != 1'b1)) begin
- fifo_mem[waddr] <= wdata;
- end
- end
-
-
-
-
- // ==========================================================================
- // read fifo_mem
- assign rdata = fifo_mem[raddr];
-
-
-
-
-
-
-
-
- endmodule
5.3 sync_cell
- //=================================================================================
- // module : sync_cell.v
- // description : 2-stage synchronizer
- // data : 2022/2/27
- // author : souther meditating
- //=================================================================================
-
-
-
-
- module sync_cell(
- dat_i,
- clk_o,
- rst_n_o,
- dat_o
- );
-
-
-
-
- //=================================================================================
- // parameter & localparam
- //=================================================================================
- parameter DSIZE = 16;
-
-
-
-
- //=================================================================================
- // I/O
- //=================================================================================
- input [DSIZE-1:0] dat_i;
-
-
-
-
- input clk_o;
- input rst_n_o;
- output [DSIZE-1:0] dat_o;
-
-
-
-
- //=================================================================================
- // signal
- //=================================================================================
-
-
-
-
- // ---- temp flip-flop ----
- reg [DSIZE-1:0] dat_i_ff1;
- reg [DSIZE-1:0] dat_i_ff2;
-
-
-
-
- //=================================================================================
- // main body
- //=================================================================================
-
-
-
-
- always@(posedge clk_o or negedge rst_n_o) begin
- if(rst_n_o == 1'b0) begin
- dat_i_ff1 <= {DSIZE{1'b0}};
- dat_i_ff2 <= {DSIZE{1'b0}};
- end
- else if() begin
- dat_i_ff1 <= dat_i;
- dat_i_ff2 <= dat_i_ff1;
- end
- end
-
-
-
-
- assign dat_o = dat_i_ff2;
-
-
-
-
- endmodule
5.4 wptr_full
- //=================================================================================
- // module : wptr_full.v
- // description : async_fifo write ctrl
- // data : 2022/2/27
- // author : souther meditating
- //=================================================================================
-
-
-
-
- module wptr_full(
- wclk,
- wrst_n,
- wr_en,
- waddr,
- wptr_g,
- rptr_g_sync,
- fifo_full
- );
-
-
-
-
- //=================================================================================
- // parameter & localparam
- //=================================================================================
- parameter FIFO_DEPTH = 16;
-
-
-
-
- localparam FIFO_ADDR_WIDTH = clog2(FIFO_DEPTH) ;
- localparam FIFO_PTR_WIDTH = FIFO_ADDR_WIDTH + 1;
-
-
-
-
- //=================================================================================
- // I/O
- //=================================================================================
- input wclk;
- input wrst_n;
- input wr_en;
- output [FIFO_ADDR_WIDTH-1:0] waddr;
-
-
-
-
- output [FIFO_PTR_WIDTH-1:0] wptr_g;
- input [FIFO_PTR_WIDTH-1:0] rptr_g_sync;
-
-
-
-
- output fifo_full;
-
-
-
-
- // ==========================================================================
- // signal define
- // ==========================================================================
- reg [FIFO_PTR_WIDTH-1:0] wptr_b;
- reg [FIFO_PTR_WIDTH-1:0] wptr_g;
- reg fifo_full;
-
-
-
-
- wire [FIFO_PTR_WIDTH-1:0] wptr_bnext;
- wire [FIFO_PTR_WIDTH-1:0] wptr_gnext;
-
-
-
-
- //=================================================================================
- // main body
- //=================================================================================
-
-
-
-
- // ---- binary count ----
-
-
-
-
- assign wptr_bnext = wbin + (wr_en & (~fifo_full));
- assign waddr = wptr_b[FIFO_ADDR_WIDTH-1:0];
-
-
-
-
- always @(posedge wclk or negedge wrst_n) begin
- if(wrst_n == 1'b0)begin
- wptr_b <= {FIFO_PTR_WIDTH{1'b0}};
- end
- else begin
- wptr_b <= wptr_bnext;
- end
- end
-
-
-
-
- // ---- binary to gray ----
- assign wptr_gnext = (wptr_bnext >> 1) ^ (wptr_bnext);
-
-
-
-
- always @(posedge wclk or negedge wrst_n) begin
- if(wrst_n == 1'b0) begin
- wptr_g <= {FIFO_PTR_WIDTH{1'b0}};
- end
- else begin
- wptr_g <= wptr_gnext;
- end
-
-
-
-
- end
-
-
-
-
- // ---- fifo full -----
- // three necessary condition
- assign fifo_full_val = (wptr_gnext == {~rptr_g_sync[FIFO_PTR_WIDTH-1:FIFO_PTR_WIDTH-2],
- rptr_g_sync[FIFO_PTR_WIDTH-3:0]});
-
-
-
-
- always@(posedge wclk or negedge wrst_n) begin
- if(wrst_n == 1'b0) begin
- fifo_full <= 1'b0;
- end
- else begin
- fifo_full <= fifo_full_val;
- end
- end
-
-
-
-
-
-
-
-
-
-
-
-
- endmodule
5.5 rptr_empty
- //=================================================================================
- // module : rptr_empty.v
- // description : async_fifo read ctrl
- // data : 2022/3/2
- // author : souther meditating
- //=================================================================================
-
-
-
-
- module rptr_empty(
- rclk,
- rrst_n,
- rd_en,
- raddr,
- wptr_g_sync,
- rptr_g,
- fifo_empty
- );
-
-
-
-
- //=================================================================================
- // parameter & localparam
- //=================================================================================
- parameter FIFO_DEPTH = 16;
-
-
-
-
- localparam FIFO_ADDR_WIDTH = clog2(FIFO_DEPTH) ;
- localparam FIFO_PTR_WIDTH = FIFO_ADDR_WIDTH + 1;
-
-
-
-
- //=================================================================================
- // I/O
- //=================================================================================
- input rclk;
- input rrst_n;
-
-
-
-
- input rd_en;
- output [FIFO_ADDR_WIDTH-1:0] raddr;
-
-
-
-
- input [FIFO_PTR_WIDTH-1:0] wptr_g_sync;
- output [FIFO_PTR_WIDTH-1:0] rptr_g;
-
-
-
-
- output fifo_empty;
-
-
-
-
- // ==========================================================================
- // signal define
- // ==========================================================================
- reg [FIFO_PTR_WIDTH-1:0] rptr_b;
- reg [FIFO_PTR_WIDTH-1:0] rptr_g;
-
-
-
-
- wire [FIFO_PTR_WIDTH-1:0] rptr_bnext;
- wire [FIFO_PTR_WIDTH-1:0] rptr_gnext;
-
-
-
-
- wire fifo_empty_val;
- reg fifo_empty;
-
-
-
-
- //=================================================================================
- // main body
- //=================================================================================
-
-
-
-
- // ---- binary count ----
-
-
-
-
- assign rptr_bnext = rptr_b + (rd_en & (~fifo_empty));
- assign raddr = rptr_b[FIFO_ADDR_WIDTH-1:0];
-
-
-
-
- always @(posedge rclk or negedge rrst_n) begin
- if(rrst_n == 1'b0) begin
- rptr_b <= {FIFO_PTR_WIDTH{1'b0}};
- end
- else begin
- rptr_b <= rptr_bnext;
- end
- end
-
-
-
-
- // ---- binary to gray ----
-
-
-
-
- assign rptr_gnext = (rptr_bnext >> 1) ^ rptr_bnext;
-
-
-
-
- always@(posedge rclk or negedge rrst_n) begin
- if(rrst_n == 1'b0) begin
- rptr_g <= {FIFO_PTR_WIDTH{1'b0}};
- end
- else begin
- rptr_g <= rptr_gnext;
- end
- end
-
-
-
-
- // ---- fifo empty ----
- assign fifo_empty_val = (rptr_gnext == wptr_g_sync);
-
-
-
-
- always @(posedge rclk or posedge rrst_n) begin
- if(rrst_n == 1'b0) begin
- fifo_empty <= 1'b1;
- end
- else begin
- fifo_empty <= fifo_empty_val;
- end
- end
-
-
-
-
- endmodule
6. 参考资料
[1] 《硬件架构的艺术》
[2] Simulation and Synthesis Techniques for Asynchronous FIFO Design
[3] 同步FIFO设计
来源:https://blog.csdn.net/shiwq1127/article/details/123102904
作者:南风在冥想 版权归作者所有
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。