SoC之 异步FIFO(async fifo)

1.0 简介

在大规模SoC设计中,多时钟系统往往是不可避免的,这样就产生了不同时钟域数据传输的问题,其中一个比较好的解决方案就是使用异步FIFO来作不同时钟域数据传输的缓冲区,这样既可以使异步时钟域数据传输的时序要求变得宽松,也提高了它们之间的传输效率。FIFO通常用于安全地将数据从一个时钟域传递到另一个异步时钟域。 使用FIFO将数据从一个时钟域传递到另一时钟域需要多异步时钟设计技术。 

异步FIFO是指一种FIFO设计,其中,数据值从一个时钟域写入FIFO缓冲区,数据值从另一个时钟域从同一FIFO缓冲区读取,两个时钟域彼此异步。 异步FIFO用于将数据从一个时钟域安全地传递到另一时钟域。
2.0传递多个异步信号

试图将多个变化的信号从一个时钟域同步到一个新的时钟域,并确保所有变化的信号都同步到新的时钟域中的同一时钟周期已被证明是有问题的。  FIFO在设计中用于将多位数据字从一个时钟域安全地传递到另一个时钟域。
  通过一个时钟域中的控制信号将数据字放置到FIFO缓冲存储器阵列中,并通过来自第二时钟域的控制信号将数据字从同一FIFO缓冲存储器阵列的另一个端口中删除。 从概念上讲,使用这些假设设计FIFO的任务似乎很容易。与进行FIFO设计相关的困难与生成FIFO指针以及找到一种可靠的方法来确定FIFO上的满和空状态有关。

2.1同步FIFO指针

对于同步FIFO设计(在同一时钟域中进行FIFO缓冲区的读写操作的FIFO),一种实现方式是对FIFO缓冲区的写入和读取次数进行递增计数(在FIFO上 写入但不读取)减量(在FIFO读取但不写入的情况下)保持(不进行写入和读取,或同时进行读写操作)FIFO缓冲区的当前填充值。 当FIFO计数器达到预定的满值时,FIFO充满,而当FIFO计数器为零时,FIFO为空。
   不幸的是,对于异步FIFO设计,不能使用增减FIFO填充计数器,因为将需要两个不同的异步时钟来控制计数器。 为了确定异步FIFO设计的满状态和空状态,必须比较写指针和读指针。

2.2异步FIFO指针

为了理解FIFO设计,需要了解FIFO指针如何工作。 写指针总是指向下一个要写的字; 因此,在复位时,两个指针都设置为零,这也恰好是下一个要写入的FIFO字位置。 在FIFO写入操作中,写入指针所指向的存储位置,然后将写入指针递增以指向要写入的下一个位置。
   同样,读取指针始终指向要读取的当前FIFO字。 再次复位时,两个指针均复位为零,FIFO为空,而读指针指向无效数据(因为FIFO为空且声明了空标志)。 一旦第一个数据字被写入FIFO,写入指针就会递增,清除空标志,并且仍在寻址第一个FIFO存储字内容的读取指针会立即将第一个有效字驱动到FIFO 数据输出端口,由接收器逻辑读取。 读取指针始终指向要读取的下一个FIFO字这一事实意味着,接收器逻辑不必使用两个时钟周期来读取数据字。 如果接收器在读取FIFO数据字之前首先必须递增读取指针,则接收器将计时一次以从FIFO输出数据字,并再次计时以将数据字捕获到接收器中。 那将是不必要的低效。
   当读写指针相等时,FIFO为空。 当两个指针在复位操作期间都复位为零时,或者在从FIFO读取了最后一个字的情况下,读指针赶上写指针时,就会发生这种情况。
   当指针再次相等时,也就是说,当写指针缠绕并追上读指针时,FIFO已满。 这是个问题。 当指针相等时,FIFO为空或已满,但是指的是哪个?
   一种用来区分是满还是空的设计技术是为每个指针添加一个额外的位。 当写指针增加到最终FIFO地址之后,写指针将使未使用的MSB递增,同时将其余位设置回零,如图1所示(FIFO已包装并切换了指针MSB)。 读取指针也是如此。 如果两个指针的MSB不同,则意味着写指针比读指针多缠绕了一次。 如果两个指针的MSB相同,则意味着两个指针的换行次数相同。

 

使用n位指针,其中(n-1)是访问整个FIFO存储缓冲区所需的地址位数,当两个指针(包括MSB)相等时,FIFO为空。 当两个指针(MSB除外)相等时,FIFO已满。
FIFO设计将n位指针用于具有2^(n-1)个可写位置的FIFO,以帮助处理满full和空empty条件。

2.3二进制FIFO指针的考虑

尝试将二进制计数值从一个时钟域同步到另一个时钟域是有问题的,因为n位计数器的每个位都可以同时更改(示例7-> 8的二进制数为0111-> 1000,所有位均已更改 )。 解决该问题的一种方法是将周期二进制计数值采样并保存在保持寄存器中,然后将同步就绪信号传递到新的时钟域。 识别就绪信号后,接收时钟域将同步的确认信号发送回发送时钟域。 在从接收时钟域接收到确认信号之前,不得更改采样指针。 使用此技术,可以将具有多个更改位的计数值安全地传输到新的时钟域。 收到确认信号后,发送时钟域有权清除就绪信号并重新采样二进制计数值。
   使用此技术,将定期采样二进制计数器值,并且并非所有二进制计数器值都可以传递到新的时钟域。
   当写指针追上同步和采样的读指针时,FIFO满。
   当读指针赶上同步采样的写指针时,FIFO空。 
   FIFO计数器指针的一种常见方法是使用格雷码计数器。 格雷码在每次时钟转换时只允许改变一位,从而消除了与试图在同一时钟沿同步多个变化信号相关的问题。

3.0格雷码计数器-方法1

格雷码以1953年最初为该代码申请专利的人Frank Gray 的名字命名。 有多种设计格雷码计数器的方法。 本节详细介绍了一种简单而直接的方法来进行设计。 本文描述的技术仅将一组触发器用于格雷码计数器。 第4.0节详细介绍了使用两组触发器实现更高速度的第二种方法。

3.1格雷码模式

理想的是创建一个n位格雷码计数器和一个(n-1)位格雷码计数器。 分别创建两个计数器肯定很容易,但是创建一个通用的n位格雷码计数器然后修改第二个MSB以形成一个共享的(n-1)位格雷码计数器也很容易且有效。  

 为了更好地理解将n位格雷码转换为(n-1)位格雷码的问题,请考虑创建双4位和3位格雷码计数器的示例,如图2所示。
  如图2所示,最常见的格雷码是一种反射码,其中除MSB以外的任何列中的位均关于序列中点对称。 这意味着4位格雷码的后半部分是MSB反转的前半部分的镜像。
   要将4位转换为3位格雷码,我们不希望4位序列的后半部分的LSB是前半部分的LSB的镜像,而是希望将4位序列的后半部分的LSB镜像。 下半部分重复上半部分的4位LSB序列。
 经过仔细检查,很明显将4位格雷码后半部分的第二个MSB取反将在4位序列的三个LSB中产生所需的3位格雷码序列。 唯一的另一个问题是带有额外MSB的3位格雷码不再是真正的格雷码,因为当序列从7(0100)变为8(1000),然后又从15(1100)变为 0(0000),两位在变化,而不仅仅是一位。 真正的格雷码只会在计数之间改变一位。
 

3.2格雷码计数器基础知识

关于格雷码,要记住的第一个事实是,任何两个相邻字之间的码距仅为1(从一个格雷计数到下一个格雷计数,只能改变一位)。 要记住的格雷码计数器的第二个事实是,最有用的格雷码计数器必须在序列中具有2的幂。 可以使格雷码计数器计数偶数个序列,但是与这些序列之间的转换通常不像标准格雷码那样简单。 另请注意,由于没有奇数长度的格雷码序列,因此无法制作23深的格雷码。 这意味着本文描述的技术用于制作2^n深的FIFO。
   图3是样式1的双n位格雷码计数器的框图。 样式#1格雷码计数器假定寄存器位的输出是格雷码值本身(ptr,wptr或rptr)。 然后,将格雷码输出传递到格雷二进制转换程序(bin),将其传递给条件二进制值增量器,以生成下一个二进制计数值(bnext),并将其传递给二进制代码。 到格雷码转换器,生成下一个格雷码计数值(gnext),该值传递到寄存器输入。 图3方框图的上半部分显示了所描述的逻辑流程,而下半部分则显示了与第二格雷码计数器相关的逻辑,如下一节所述。

  双n位格雷码计数器是一个格雷码计数器,它生成n位格雷码序列和一个(n-1)位格雷码序列。
  通过对n位格雷码的两个MSB进行异或运算,即可生成(n-1)位格雷码的MSB,从而简单地生成(n-1)位格雷码。 它与n位   格    雷码计数器的(n-2)个LSB组合在一起,形成(n-1)位格雷码计数器[5]。

一些考虑:

如上面图3所示,对二进制值计数器进行“不为满”或“不为空”的条件限制,以确保适当的FIFO指针在FIFO满或FIFO期间不会递增。 否则可能导致FIFO缓冲区上溢over flow或下溢under flow。如果在声明FIFO满状态时将数据发送到FIFO的逻辑块能可靠地停止发送数据,则可以通过从FIFO写指针中删除这个条件限制逻辑来简化FIFO设计。
   FIFO指针本身并不能保护FIFO缓冲区不被覆盖,但是可以将其他条件逻辑添加到FIFO存储缓冲区中,以确保在FIFO满状态下不能激活write_enable信号。

可以将额外的状态位ovf(上溢)或unf(下溢)添加到指针设计中,以指示在满时发生了另外的FIFO写操作,或者在空时发生了另外的FIFO读操作以指示错误情况 ,只能在复位期间清除。

一个安全的通用FIFO设计将包括上述保护措施,但代价是执行起来会稍大一些,甚至可能更慢。 

格雷码计数器-方式2

该方式实际上使用了两组寄存器,从而无需将格雷指针值转换为二进制值。 第二组寄存器(二进制寄存器)也可用于直接寻址FIFO存储器,而无需将存储器地址转换为格雷码。 仍然需要n位格雷码指针将指针同步到相反的时钟域,但是n-1位二进制指针可用于直接寻址内存。 如果需要,二进制指针还使运行计算更容易以生成“几乎满almost full”和“几乎为空 almost empty”的位。

 

FIFO框图1

 

为了方便 FIFO设计的静态时序分析,该设计已划分为以下六个具有以下功能和时钟域的Verilog模块:

•fifo1.v

此为top模块,包括所有时钟域。 顶部模块仅用作包装连接,以实例化设计中使用的所有其他FIFO模块。 如果将此FIFO用作较大的ASIC或SoC设计的一部分,则可能会舍弃此顶级包装,以允许将其他FIFO模块分组到各自的时钟域中,以改进综合和静态时序分析。
•fifomem.v

这是FIFO内存缓冲区,可通过写入和读取时钟域进行访问。 该缓冲区很可能是实例化的同步双端口RAM。 可以修改其他存储器样式以用作FIFO缓冲区。
•sync_r2w.v

这是一个同步器模块,用于将读指针同步到写时钟域中。  wptr_full模块将使用同步的读取指针来生成FIFO已满条件。 该模块仅包含与写时钟同步的触发器。 此模块中没有其他逻辑。
•sync_w2r.v

这是一个同步器模块,用于将写指针同步到读时钟域中。  rptr_empty模块将使用同步的写指针来生成FIFO空条件。 该模块仅包含与读取时钟同步的触发器。 此模块中没有其他逻辑。
•rptr_empty.v

此模块与读取时钟域完全同步,并包含FIFO读取指针和空标志逻辑。

•wptr_full.v

-此模块与写时钟域完全同步,并包含FIFO写指针和全标志逻辑。

为了使用此FIFO执行FIFO满和FIFO空测试,必须将读取和写入指针传递到相反的时钟域以进行指针比较。
与其他FIFO设计一样,由于两个指针是从两个不同的时钟域生成的,因此需要将这些指针“安全地”传递到相对的时钟域。 利用同步格雷码指针,以确保一次只能更改一个指针位。
 

5.0处理满和空条件

   究竟如何实现FIFO满和FIFO空取决于设计。
   本文中的FIFO设计假定将在读时钟域中生成空标志,以确保当FIFO缓冲区为空时(即,读指针赶上写操作的瞬间)立即检测到空标志。 指针(包括指针MSB)
   本文中的FIFO设计假定将在写时钟域中生成满标志,以确保当FIFO缓冲区已满时,即写指针赶上读取的瞬间,立即检测到满标志。 指针(指针MSB除外)

5.1生成空 flag

   如图1所示,当读指针和同步写指针相等时,FIFO为空。
   空比较很容易做到。 使用的指针比寻址FIFO存储缓冲区所需的指针大一位。 如果两个指针(指针的MSB)的额外位相等,则指针的换行次数相同,并且如果其余读取指针等于同步写入指针,则FIFO为空。
   格雷码写指针必须通过sync_w2r模块中的一对同步器寄存器同步到读时钟域中。 由于使用格雷码指针一次只更改一位,因此在时钟域之间同步多位转换没有问题。
   为了寄存器输出输出,实际上将同步写指针与rgraynext(将被注册到rptr中的下一个格雷码)进行比较。 rptr_empty.v中判断空的代码如下所示:

5.2生成满flag

由于在写时钟域中通过在写指针和读指针之间进行比较来生成满标志,因此一种进行FIFO设计的安全技术要求在进行指针比较之前将读指针同步到写时钟域中。

完全比较不像空比较那样简单。 比寻址FIFO存储器所需的地址大一点的指针仍用于比较,但是仅使用带有额外位的格雷码计数器进行比较并不能确定完全条件。 问题在于,格雷码是除MSB之外的对称码。

 

考虑图6所示的8深度的FIFO的示例。 在此示例中,使用3位格雷码指针寻址存储器,并添加了一个额外的位(4位格雷码的MSB)以测试满和空条件。

 如果允许FIFO填充前七个位置(字0-6),然后如果通过回读相同的七个字来清空FIFO,则两个指针将相等并指向地址Gray-7(FIFO为空)  。 在下一次写操作中,写指针将使4位格雷码指针递增(请记住,只有3个LSB用于地址存储器),使MSB在4位指针上有所不同,但其余部分在写指针上有所不同。 这些位将与读指针位匹配,因此将声明FIFO满标志。 错了! 不仅FIFO未满,而且3个LSB均未更改,这意味着寻址的存储器位置将覆盖最后写入的FIFO存储器位置。 这也是错的!

   这就是为什么使用双n位格雷码计数器的原因之一。
   通过将rptr同步到wclk域中,可以完成执行完全比较的正确方法,然后需要满足以下三个条件才能使FIFO充满:

   (1)wptr和同步的rptr MSB不相等(因为 wptr必须比rptr多走了一个循环)。
   (2)wptr和同步rptr第二个MSB不相等。
   (3)所有其他wptr和同步rptr位必须相等。


   为了有效地wfull寄存器输出,实际上将同步读取的指针与wgnext(将在wptr中寄存的下一个格雷码)进行比较。 如下所示,这是从示例7的wptr_full.v代码中提取的顺序始终块中的:

在上面的代码中,测试了检查FIFO已满的三个必要条件,并将结果分配给wfull_val信号,然后将其注册到随后的顺序始终块中。
   可以使用串联进一步简化对wfull_val的连续分配,如下所示:

 

 

 

 

RTLCode:

1. top

顶层FIFO模块是参数化的FIFO设计,其所有子块均采用建议的命名端口连接做法进行实例化。 另一种常见的编码实践是为顶层模块实例化提供与模块名称相同的名称。 这样做是为了方便调试,因为如果实例名称与模块名称匹配,则在层次结构路径中引用模块名称将很简单。

module async_fifo #(parameter DSIZE = 8,
                    parameter ASIZE = 4)
(
	output [DSIZE-1:0] rdata,
        output wfull,
        output rempty,
        input [DSIZE-1:0] wdata,
        input winc, wclk, wrst_n,
        input rinc, rclk, rrst_n);

wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;

sync_r2w sync_r2w (.wq2_rptr(wq2_rptr), .rptr(rptr),
.wclk(wclk), .wrst_n(wrst_n));

sync_w2r sync_w2r (.rq2_wptr(rq2_wptr), .wptr(wptr),
.rclk(rclk), .rrst_n(rrst_n));

fifomem #(DSIZE, ASIZE) fifomem
(.rdata(rdata),
 .wdata(wdata),
 .waddr(waddr), 
 .raddr(raddr),
 .wclken(winc), 
 .wfull(wfull),
 .wclk(wclk));

rptr_empty #(ASIZE) rptr_empty
(.rempty(rempty),
 .raddr(raddr),
 .rptr(rptr), 
 .rq2_wptr(rq2_wptr),
 .rinc(rinc), 
 .rclk(rclk),
 .rrst_n(rrst_n));

wptr_full #(ASIZE) wptr_full
(.wfull(wfull),
 .waddr(waddr),
 .wptr(wptr),
 .wq2_rptr(wq2_rptr),
 .winc(winc),
 .wclk(wclk),
 .wrst_n(wrst_n));



endmodule

2.fifomem

FIFO存储缓冲区通常是实例化的ASIC或FPGA双端口同步存储设备。 也可以使用该模块中的RTL代码将存储缓冲区合成为ASIC或FPGA寄存器。

module fifomem #(parameter DATASIZE = 8, // Memory data word width
                 parameter ADDRSIZE = 4) // Number of mem address bits
(	output [DATASIZE-1:0] rdata,
	input [DATASIZE-1:0] wdata,
	input [ADDRSIZE-1:0] waddr, raddr,
	input wclken, wfull, wclk);
`ifdef VENDORRAM
// instantiation of a vendor's dual-port RAM
vendor_ram mem (
.dout(rdata), 
.din(wdata),
.waddr(waddr), 
.raddr(raddr),
.wclken(wclken),
.wclken_n(wfull), 
.clk(wclk));
`else
// RTL Verilog memory model

localparam DEPTH = 1<<ADDRSIZE;
reg [DATASIZE-1:0] mem [0:DEPTH-1];
	assign rdata = mem[raddr];
always @(posedge wclk)
		if (wclken && !wfull) mem[waddr] <= wdata;
`endif


endmodule

 

3 sync_r2w.v-读域到写域同步器

这是一个简单的同步器模块,用于通过FIFO时钟对的一对寄存器,将n位指针从读时钟域传递到写时钟域 写时钟。 注意always块的简单性,它将两个寄存器连接在一起以进行复位和移位。 同步器始终仅阻塞三行代码。
   所有模块输出均已注册,可使用时间预算进行简化的综合。 该模块的所有输出均与wclk完全同步,并且该模块的所有异步输入均来自rclk域,所有信号均使用“ r”前缀命名,从而易于在所有“ r *”信号上设置虚假路径,从而 简化的静态时序分析。
   

module sync_r2w #(parameter ADDRSIZE = 4)
(
output reg [ADDRSIZE:0] wq2_rptr,
input [ADDRSIZE:0] rptr,
input wclk, wrst_n);

reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n)
	if (!wrst_n) 
		{wq2_rptr,wq1_rptr} <= 0;
	else
	       	{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
endmodule

 

4 sync_w2r.v-写域到读域同步器

这是一个简单的同步器模块,用于通过FIFO时钟对的一对寄存器,将n位指针从写时钟域传递到读时钟域 读取时钟。 注意always块的简单性,它将两个寄存器连接在一起以进行复位和移位。 同步器始终仅阻塞三行代码。
   所有模块输出均已寄存器输出,可使用时间预算进行简化的综合。 该模块的所有输出都与rclk完全同步,并且该模块的所有异步输入均来自wclk域,所有信号均使用“ w”前缀命名,因此很容易在所有“ w *”信号上设置错误路径 简化的静态时序分析。

 

module sync_w2r
#(parameter ADDRSIZE = 4)
(
output reg [ADDRSIZE:0] rq2_wptr,
input [ADDRSIZE:0] wptr,
input rclk, rrst_n);

reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk or negedge rrst_n)
	if (!rrst_n)
	       	{rq2_wptr,rq1_wptr} <= 0;
	else 
		{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
endmodule

 

5 rptr_empty.v-读取指针和empty生成逻辑

    该模块封装了在读取时钟域内生成的所有FIFO逻辑(同步器除外)。
   读指针是一个双n位格雷码计数器。  n位指针(rptr)通过sync_r2w模块传递到写时钟域。  (n-1)位指针(raddr)用于寻址FIFO缓冲区。
   当下一个rptr值等于同步的wptr值时,将记录FIFO空输出并在下一个rclk上升沿声明该状态。 所有模块输出均已寄存器输出,可使用时间预算进行简化的综合。 该模块与rclk完全同步,以简化静态时序分析。

module rptr_empty #(parameter ADDRSIZE = 4)
(output reg rempty,
output [ADDRSIZE-1:0] raddr,
output reg [ADDRSIZE :0] rptr,
input [ADDRSIZE :0] rq2_wptr,
input rinc, rclk, rrst_n);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
//-------------------
// GRAYSTYLE2 pointer
//-------------------
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext;
//---------------------------------------------------------------
// FIFO empty when the next rptr == synchronized wptr or on reset
//---------------------------------------------------------------
assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val;
endmodule

 

6.wptr_full.v-写指针和full的生成逻辑

   该模块封装了在写时钟域内生成的所有FIFO逻辑(同步器除外)。
   写指针是一个双n位格雷码计数器。  n位指针(wptr)通过sync_w2r模块传递到读取时钟域。 第(n-1)位指针(waddr)用于寻址FIFO缓冲区。
   当下一个修改后的wgnext值等于同步和修改后的wrptr2值(MSB除外)时,将记录FIFO完整输出并在下一个wclk上升沿声明。 所有模块输出均已寄存器输出,可使用时间预算进行简化的综合。 该模块与wclk完全同步,以简化静态时序分析。

module wptr_full #(parameter ADDRSIZE = 4)
(output reg wfull,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
input [ADDRSIZE :0] wq2_rptr,
input winc, wclk, wrst_n);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
//------------------------------------------------------------------
// Simplified version of the three necessary full-tests:
// assign wfull_val=((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
// (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
// (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
//------------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],
wq2_rptr[ADDRSIZE-2:0]});
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值