Verilog实现以太网接收部分的学习笔记

前言

最近需要进行aurora接口的报文解析,所以学了一下以太网的报文解析方法。
基于verilog实现以太网收发通信时,主要包括四个模块:GMII 转 RGMII接口模块(4bit转8bit)、以太网接收模块、以太网发送模块以及以太网的控制模块。

1 以太网基础知识的掌握

首先大概了解一下以太网的基础概念以及相关的IP五层模型

如下图所示,为模型的各个层。各层之间可以简单理解成不断组包和拆包的过程。
另外对于数据包以及报文或者帧差不多,主要是在不同的层有不同的称号。比如说一般在数据链路层,我们称为数据帧,而在网络层一般称为报文。
在这里插入图片描述


理解七层之后,我们要了解每层要重点掌握的东西。

  • 物理层的话,我理解的是主要掌握通信接口的外部输入输出引脚的分配,比如说RX和TX端的连接等。
  • 另外重点掌握以太网的传输格式(数据链路层、网络层、传输层),这也是后续设计收发状态机以及编写代码的关键。
  • 应用层是计算机用户,以及各种应用程序和网络之间的接口。我的理解是这方面又软件或驱动来控制,主要负责在应用层下报文。

2 以太网的数据传输格式

2.1 数据链路层的数据帧

如下图所示,为整个以太网的数据传输格式:
其存在于数据链路层。前面说了,各层之间就是组包和拆包的过程,所以将数据链路层的前导码、SFD、以太网帧头以及FCS去掉之后,剩下的以太网数据就是网络层的报文了。

在这里插入图片描述

每个字段的含义(重点看一下加粗部分即可):
前导码(Preamble):帧头,用于数据同步。物理层使用固定的连续7Byte的0x55实现。

帧起始定界符(SFD,Start Frame Delimiter):用于区分前导码与数据段,为固定1Byte的0xd5

目的MAC地址:即接收端物理MAC地址,占6Byte,为固定值,因为每个设备都对应唯一个MAC地址。MAC地址从应用上可分为单播地址、组播地址和广播地址。
单播地址:第一个字节的最低位为0,比如0-00-00-11-11-11,一般用于标志唯一的设备;
组播地址:第一个字节的最低位为1,比如01-00-00-11-11-11,一般用于标志同属一组的多个设备;
广播地址:全为1,即FF-FF-FF-FF-FF-FF,用于标志同一网段中的所有设备。

源MAC地址:即发送端物理MAC地址6Byte

数据帧类型/长度2B,当这两个字节的值小于1536 (十六进制为0x0600)时代表该以太网中数据段的长度;如果这两个字节的值大于1536,则表示与以太网帧相关的MAC客户端协议的类型,例如0x0800代表IP协议(网际协议)、0x0806 代表ARP协议(地址解析协议) 等

以太网数据长度为46-1500Byte。最大值1500称为以太网的最大传输单元(MTU,Maximum Transmission Unit)。接收到的数据包如果少于64字节会被认为发生冲突,数据包被自动丢弃

校验(FCS,Frame Check Sequence):确保数据的正确传输,在数据的尾部加入了4Byte的循环冗余校验码(CRC)来检验数据是否传输错误。CRC数据校验从目的MAC地址开始。

帧间隙(IFG,Interpacket Gap):以太网相邻两帧之间的时间间隔,即网络设备和组件在接收一帧之后,需要短暂的时间来恢复并为接收下一帧做准备的时间,最小值是96 bit time(媒介中发送96位原始数据所需要的时间)。


2.2 网络层的IP报文格式

接下来分析以太网数据部分,即网络层报文。由于网络层需要进行IP寻址操作,因此传入网络层的以太网数据中包含20 字节的IP协议首部(IP报文头) + 报文数据。同样,将IP报文头拆掉之后,传入传输层。

如下为IP报文的格式:
在这里插入图片描述
重点掌握IP报文头中各字段的含义:

  1. 版本:定义IP协议版本,二进制0100表示IPv4,二进制0110表示IPv6.

  2. 首部长度:IP报头长度,单位为DW。协议头最小值为5DW,最大值为15DW。

  3. 服务类型 :定义上层协议对处理当前数据报所期望的服务质量,并对数据报按照重要性级别进行分配。前3位成为优先位,后面4位成为服务类型,最后1位预留位。即这8位字段用于分配优先级、延迟、吞吐量以及可靠性。

  4. 总长度定义整个IP报文的字节长度,即包括IP报文头及数据,最大不超过65535字节

  5. 标识:用于标识主机发送的数据报。即报文序号,每个报文递增1

  6. 标记:由3位字段构成,其中最低位为ME (用于表示是否为最后一个分片报文。若当前报文不是最后一个分片报文,ME=1,否则若当前报文是最后一个分片报文,则ME=0。中间位为DF,用于表示当前数据报文是否为分片报文(分片报文使能),若为1表示单片报文,否则为分片报文。最高位为预留位。

  7. 分段偏移:在接收方进行数据报重组时用来标识分段的顺序,即分片报文序号

  8. 生存时间:一种计数器,在丢弃数据报的每个点值依次减1直至减少为0。这样确保数据报拥有有限的环路过程(即TTL),限制了数据报的寿命。

  9. 协议 :该字段表示处理完成后,由哪种上层协议(UDP、ARP)接收报文。

  10. 首部校验和:以确保IP报文头的完整性。进行首部校验和计算,计算后将值填充到该字段即可。【计算方法

  11. 源地址发送端的IP地址,该地址在传输期间必须保持不变。

  12. 目的地址接收端的IP地址,同上。


2.3 传输层的UDP协议

传输层使用UDP协议,那么除去协议头之外,传入传输层的数据包为UDP数据包。
前面的字段和上面一样,我们直接看UDP首部及数据段。其中UDP首部即UDP数据包头,数据段则是最终的用户数据。
在这里插入图片描述

2.3.1 UDP数据传输的格式

在这里插入图片描述
首部包含:

● 源端口号:用于区分不同服务的端口,范围:0~ 65535。
● 目的端口号:接收端端口号。
UDP长度整个UDP数据报的长度,含 UDP 首部长度+数据长度
● UDP校验和:注意需要对UDP伪首部、首部以及数据进行校验和计算。


如下是UDP校验和的计算范围:(注意伪首部的内容)
在这里插入图片描述

2.3.2 UDP以太网传输的设计方法

本篇主要讲UDP以太网接收部分的状态机设计(单片报文的情况):
由于以太网头部的前导码、MAC地址以及IP地址都是固定值,所以如果固定值信息错误,当前以太网数据帧可以直接丢弃,源主机重发。所以在状态跳转时,各头状态下错误会跳转到空闲状态。

在这里插入图片描述


2.3.3 ARP协议(地址解析协议)

ARP协议分为ARP请求和ARP应答
具体的通信方式:源主机发起查询目的MAC地址和目的IP地址的报文(ARP请求报文)目的主机响应源主机发送包含本地MAC地址的报文称为ARP应答

2.3.3.1 ARP传输数据格式

由于以太网数据段最少为46个字节,而ARP数据包总长度为28个字节,因此在ARP数据段后面需要填充18字节的数据,以满足以太网传输格式的要求,填充的数据可以为任意值,但一般为0。
在这里插入图片描述
下面分析ARP数据段的内容:
在这里插入图片描述
● 硬件类型(Hardware type):硬件地址的类型,1 表示以太网地址。

● 协议类型:要映射的协议地址类型,ARP协议的上层协议为IP协议,因此该协议类型为IP协议,其值为0x0800

● 硬件地址长度 (Hardware size):MAC 地址长度,为6Byte。

● 协议地址长度 (Protocol size):IP地址的长度,为4Byte。

OP(Opcode):操作码,用于表示该数据包为ARP请求或者ARP应答。1表示ARP请求,2表示ARP应答

● 目的MAC地址:接收端的硬件地址,在ARP请求时由于不知道接收端MAC地址,因此该字段为广播地址,即48’hff_ff_ff_ff_ff_ff。广播后,局域网内所有的主机都会接收这个地址并处理该请求报文,根据请求进行验证,从而查看接收到的ip地址是不是自己,是的话,ARP应答的时候就把接收方的MAC地址和IP地址返回去。

关于广播:
在这里插入图片描述


3 UDP以太网传输的接收部分的verilog代码编写

下面重点对UDP以太网传输的接收部分进行verilog代码编写:
主要用计数器数节拍来实现(不够灵活),是一种锻炼吧,后续尽量在实际应用中学习更好的实现方法。


注意:
由于IP报文的传输单位是32bit,所以我们要进行数据的8bit到32bit转换。这里转换时要注意,如果IP报文的数据部分不是整32bit的情况

这里我们可以用接受完成时对应的计数器值来分析。比如说8bit转32bit。应该是每4个8bit数据得到一个32bit。

那么设计一个0-3计数器,每次cnt = 3得到一个32bit数据。
当完成时,判断对应的计数器值.
如果是0,说明还差三个8bit数据,则低位补24‘b0
如果是1,表示还差两个8bit数据,则低位补16’b0
以此类推…

下面的整体的代码:

//规定IP头的长度为5DW
module udp_rx(
    input                               CLK                                     ,    //时钟信号
    input                               RST_N                                   ,    //复位信号,低电平有效
    
    input                               GMII_RX_DV                              ,    //GMII输入数据有效信号
    input        [ 7:0]                 GMII_RXD                                ,    //GMII输入数据
    output                              REC_PKT_DONE                            ,    //以太网单包数据接收完成信号

    output       [31:0]                 REC_DATA                                ,     //以太网接收的数据
    output       [ 3:0]                 KEEP_NUM                                      //最后一个移位数据的有效字节数

);

//定义参数                                                  
parameter           BOARD_MAC           = 48'h00_11_22_33_44_55                   ; //目的MAC地址 00-11-22-33-44-55                                  
parameter           BOARD_IP            = {8'd192,8'd168,8'd1,8'd10}              ; //目的IP地址 192.168.1.10  
                                                     
localparam           ST_IDLE             = 7'b000_0001                             ; //初始状态,等待接收有效前导码0x55
localparam           ST_PREAMBLE         = 7'b000_0010                             ; //接收前导码状态     
localparam           ST_ETH_HEAD         = 7'b000_0100                             ; //接收以太网帧头     
localparam           ST_IP_HEAD          = 7'b000_1000                             ; //接收IP头      
localparam           ST_UDP_HEAD         = 7'b001_0000                             ; //接收UDP头     
localparam           ST_RX_DATA          = 7'b010_0000                             ; //接收有效用户数据 (UDP数据)     
localparam           ST_RX_END           = 7'b100_0000                             ; //接收结束        
                                                     
localparam           ETH_TYPE            = 16'h0800                                ; //以太网协议类型 IP协议
localparam           UDP_TYPE            = 8'h11                                   ; //UDP协议类型   

//定义内部信号 
reg                 r_GMII_RX_DV        = 1'h0                                     ;  
reg  [7:0]          r_GMII_RXD          = 8'h0                                     ;

reg  [6:0]          r_CUR_ST            = ST_IDLE                                  ;
reg  [6:0]          r_NXT_ST                                                       ;

reg  [ 7:0]         r_CNT              = 8'h0                                      ; //传输期间字节计数器
reg  [47:0]         r_des_mac          = 48'h0                                     ; //解析出来的目的MAC地址
reg  [15:0]         r_eth_type         = 16'hff                                    ; //解析出来的以太网类型
reg  [ 5:0]         r_ip_head_byte_num = 6'h0                                      ; //解析出来的IP首部长度,单位DW
reg  [31:0]         r_des_ip           = 32'h0                                     ; //解析出来的IP地址
reg  [ 7:0]         r_ip_type          = 8'h0                                      ; //解析出来的IP协议类型
reg  [15:0]         r_udp_byte_num     = 16'h0                                     ; //解析出来的UDP长度
reg  [ 7:0]         r_data_cnt         = 8'h0                                      ; //有效数据计数器
reg                 r_rec_pkt_done     = 1'h0                                      ; //有效数据接收完成信号
reg                 r_shift_en         = 1'h0                                      ; //移位输出使能信号
reg  [ 7:0]         r_shif_cnt         = 8'h0                                      ; //移位输出计数器  
reg  [31:0]         r_rec_data         = 32'h0                                     ; //有效32bit移位数据
reg  [31:0]         r_rec_data1        = 32'h0                                     ; //非有效字数补0后的32bit移位数据
wire [31:0]         REC_DATA                                                       ; //有效udp数据转成32bit
reg  [ 3:0]         r_keep_num         = 4'h0                                      ; //最后一个移位数据的有效字节数
reg                 r_shift_done                                                   ; //全部接收数据移位完成
      
//输出       
    assign REC_PKT_DONE                =  r_rec_pkt_done                            ;       
    assign REC_DATA                    =  r_rec_data1                               ;         
    assign KEEP_NUM                    =  r_keep_num                                ;  
    
//以太网接收有效信号及数据打一拍
    always @ (posedge CLK)
        if(~RST_N)
        begin                              
            r_GMII_RX_DV                <= 1'h0;  
            r_GMII_RXD                  <= 8'h0;  
        end             
        else          
        begin                                      
            r_GMII_RX_DV                <= GMII_RX_DV; 
            r_GMII_RXD                  <= GMII_RXD;
        end
    
//传输字节计数器
    always @ (posedge CLK)
    begin
        if(r_NXT_ST == ST_IDLE)
            r_CNT                      <= 1'h0;
        else
        begin
            if(r_GMII_RX_DV)
                r_CNT                  <= r_CNT + 1'h1; 
            else
               r_CNT                   <= r_CNT; 
        end
    end

//有效数据计数器
    always @ (posedge CLK)
        if(r_NXT_ST == ST_RX_DATA)
            r_data_cnt                 <= r_data_cnt + 1'h1;
        else
            r_data_cnt                 <= 8'h0;
    
//解析所需信息——目的MAC地址、以太网类型
//IP头长度、IP协议类型、目的IP地址
//UDP长度
always @ (posedge CLK)
begin
    case(r_NXT_ST)
        ST_ETH_HEAD : //目的MAC地址、以太网类型
        begin
            if((r_CNT >= 8'd8) && (r_CNT <= 8'd13)) //8-13共6B目的MAC地址
                r_des_mac           <= {r_des_mac[39:0],r_GMII_RXD};
            else if(r_CNT == 8'd20)
                r_eth_type[15:8]    <= r_GMII_RXD; 
            else if(r_CNT == 8'd21)
                r_eth_type[7:0]     <= r_GMII_RXD; 
            else
            begin
                r_des_mac           <= r_des_mac; 
                r_eth_type          <= r_eth_type;
            end  
         end
        ST_IP_HEAD :
        begin
            if(r_CNT == 8'd22)
                r_ip_head_byte_num <= {r_GMII_RXD[3:0],2'b0}; //解析出来IP头长度放高位20B,由于IP头最大15DW,即60字节,所以位宽设成6bit
            else if(r_CNT == 8'd31)
                r_ip_type          <= r_GMII_RXD;
            else if((r_CNT >= 8'd38) && (r_CNT <= 8'd41))
                r_des_ip <= {r_des_ip[23:0],r_GMII_RXD};      //寄存目的IP地址 4B,先进来放高位
            else
            begin
                r_ip_head_byte_num <= r_ip_head_byte_num; 
                r_ip_type          <= r_ip_type;
                r_des_ip           <= r_des_ip;
            end
        end   
        ST_UDP_HEAD :
        begin
            if(r_CNT == 8'd46)
                r_udp_byte_num[15:8] <= r_GMII_RXD;
            else if(r_CNT == 8'd47)
                r_udp_byte_num[7:0] <= r_GMII_RXD;
        end  
        ST_RX_DATA : 
        begin
            r_udp_byte_num <= r_udp_byte_num;
        end   
        default :
        begin
            r_des_mac          <= 0;
            r_eth_type         <= 16'hffff;
            r_ip_head_byte_num <= 0;
            r_des_ip           <= 0;
            r_ip_type          <= 0;
            r_udp_byte_num     <= 0;
        end
    endcase
end

//状态机
    always @ (posedge CLK)
        if(~RST_N)                              
            r_CUR_ST                                 <= ST_IDLE;               
        else                                                                    
            r_CUR_ST                                 <= r_NXT_ST;            

    always @(*) begin                                                                                                                                                                 
        case(r_CUR_ST)                                                                 
            ST_IDLE : 
            begin                                              //等待接收第一个有效数据8'h55             
                if( r_GMII_RX_DV && (r_GMII_RXD == 8'h55) )     
                    r_NXT_ST = ST_PREAMBLE;                                           
                else                                                                    
                    r_NXT_ST = ST_IDLE;                                               
            end                                                                         
            ST_PREAMBLE : 
            begin                                              //接收前导码 +SFD   
                if(r_CNT == 8'd7) 
                begin           
                    if(r_GMII_RX_DV && (r_GMII_RXD == 8'hd5) )                                                             
                        r_NXT_ST = ST_ETH_HEAD;  
                    else
                        r_NXT_ST = ST_IDLE;                    //SFD接收错误
                end                                          
                else if(r_GMII_RX_DV && (r_GMII_RXD != 8'h55))                                                       
                    r_NXT_ST = ST_IDLE;                        //前导码接收错误                                        
                else                                                                    
                    r_NXT_ST = ST_PREAMBLE;                                           
            end                                                                         
            ST_ETH_HEAD : begin                                //接收以太网帧头(比对目的MAC地址和类型) 
                if(r_GMII_RX_DV && (r_CNT == 8'd22))  
                begin      
                    //判断MAC地址是否为开发板MAC地址或者公共广播地址                                       
                    if((r_eth_type == ETH_TYPE) &&( (r_des_mac == BOARD_MAC)||(r_des_mac == 48'hff_ff_ff_ff_ff_ff)))                                                            
                        r_NXT_ST = ST_IP_HEAD;                                            
                    else                                                      
                        r_NXT_ST = ST_IDLE;                   //目的MAC地址或类型错误                                 
                end
                else
                begin
                    r_NXT_ST = ST_ETH_HEAD; 
                end                                          
            end                                                                         
            ST_IP_HEAD : begin                                  //接收IP头(比对IP头的协议类型以及IP目的地址 )                
                if(r_CNT == 8'd42)  //r_GMII_RX_DV && (r_CNT == 8'd42)
                    begin  
                        if((r_des_ip == BOARD_IP) && (r_ip_type == UDP_TYPE))                                                          
                            r_NXT_ST = ST_UDP_HEAD;  
                        else   
                            r_NXT_ST = ST_IDLE  ;               //IP头协议类型或目的地址错误  
                    end                                                                               
                else                                                                    
                    r_NXT_ST = ST_IP_HEAD;                                            
            end                                                                         
            ST_UDP_HEAD : begin                                 //接收UDP头               
                if(r_GMII_RX_DV &&  (r_CNT == 8'd49))           //跳转接收UDP数据                                                          
                    r_NXT_ST = ST_RX_DATA;                                            
                else                                                                    
                    r_NXT_ST = ST_UDP_HEAD;                                           
            end                                                                         
            ST_RX_DATA : begin                                  //接收有效数据                
                if( r_GMII_RX_DV && (r_data_cnt == (r_udp_byte_num - 16'd8)))    //接受完所有的有效数据 (当前:r_udp_byte_num = 26B)                                                   
                    r_NXT_ST = ST_RX_END;                                                    
                else                                                                    
                    r_NXT_ST = ST_RX_DATA;                                            
            end                                                                         
            ST_RX_END : 
            begin 
                if(r_rec_pkt_done)                              //单包数据接收结束                                                                 
                    r_NXT_ST = ST_IDLE;
                else
                    r_NXT_ST = ST_RX_END; 
            end                                               
                                                                            
            default : r_NXT_ST = ST_IDLE;                                             
        endcase                                                                         
    end   

//生成单包数据接收完成信号
always @ (posedge CLK)
    if(r_data_cnt == (r_udp_byte_num - 16'd8))
        r_rec_pkt_done <=  1'h1;
    else
        r_rec_pkt_done <= 1'h0;
        
//生成输出移位使能、计数器及移位数据
    always @ (posedge CLK)
        if(r_CUR_ST == ST_RX_DATA)
        begin
             r_shift_en                <= 1'h1;
             r_rec_data <= {r_rec_data[23:0],r_GMII_RXD}; //先入放高位
        end
        else
        begin
            r_shift_en                 <= 1'h0;
            r_rec_data                 <= 32'h0;
        end
                   
    always @ (posedge CLK)
        if(r_shift_en)
        begin
            if(r_shif_cnt == 8'd3)
                r_shif_cnt             <= 8'h0;
            else 
                r_shif_cnt             <= r_shif_cnt + 1'h1;
        end
        else
            r_shif_cnt                 <= 8'h0;

//最后一个移位数据不满32bit判断
    always @ (posedge CLK)
        if(r_CUR_ST == ST_RX_DATA)
        begin
            if(r_shif_cnt == 8'h3)
                r_rec_data1           <= r_rec_data;
            else
                r_rec_data1           <= r_rec_data1;
        end
        else if(r_CUR_ST == ST_RX_END)
        begin
            case(r_shif_cnt) //判断最后一个32bit数据的字节有效
                8'd0   : r_rec_data1  <= {r_rec_data[7:0],24'h0} ; //仅1个有效字节
                8'd1   : r_rec_data1  <= {r_rec_data[15:0],16'h0}; //仅2个有效字节
                8'd2   : r_rec_data1  <= {r_rec_data[23:0],8'h0} ; //仅3个有效字节
                8'd3   : r_rec_data1  <= r_rec_data              ; //全字节有效  
                default: r_rec_data1  <= 0;
            endcase
        end   
        else
            r_rec_data1 <=r_rec_data1;
    
//最后一个移位数据的有效字节数
     always @ (posedge CLK)
         if(r_CUR_ST == ST_RX_END)  
         begin
            r_shift_done           <= 1'h1;
            case(r_shif_cnt)
                8'd0   : r_keep_num <= 4'd1 ; //仅1个有效字节 
                8'd1   : r_keep_num <= 4'd2 ; //仅2个有效字节 
                8'd2   : r_keep_num <= 4'd3 ; //仅3个有效字节 
                8'd3   : r_keep_num <= 4'd4 ; //全字节有效   
                default: r_keep_num <= 0;
            endcase
         end        
         else
         begin
             r_keep_num             <= r_keep_num; 
             r_shift_done           <= 1'h0;
         end
endmodule  
//已仿真

以上是对于以太网接收部分的学习笔记,发送部分还没学,就是拼数据,等有时候在记录~
FPGA小白,若有不足或错误之处请指正。

实现以太网组播,可以使用Verilog编写一个简单的组播过滤器模块。该模块将通过MAC地址比较来确定接收到的帧是否为组播帧,并将组播帧转发到相应的接收方。 以下是一个简单的Verilog代码示例,用于实现以太网组播: ``` module ethernet_multicast_filter ( input clk, input rst, input [47:0] mac_addr, input [47:0] frame_dest, output frame_filter ); // Declare internal signals wire multicast; wire [23:0] multicast_hash; reg [23:0] filter_table [0:255]; // Implement the multicast filter table function void build_filter_table(); integer i, j; for (i = 0; i < 256; i = i + 1) begin filter_table[i] = 0; for (j = 0; j < 6; j = j + 1) begin filter_table[i] = filter_table[i] ^ mac_addr[(i*8)+j+:8]; end end endfunction // Implement the multicast hash function function void multicast_hash_function(); integer i; multicast_hash = 24'h000000; for (i = 0; i < 6; i = i + 1) begin multicast_hash = multicast_hash ^ frame_dest[(i*8)+:8]; end endfunction // Implement the multicast filter logic always @(posedge clk or posedge rst) begin if (rst) begin multicast = 0; build_filter_table(); end else begin multicast_hash_function(); multicast = filter_table[multicast_hash[7:0]]; end end // Implement the frame filter logic assign frame_filter = (frame_dest[0:23] == 24'h0180c2) && multicast; endmodule ``` 上述代码实现了一个简单的组播过滤器模块,该模块使用组播哈希函数和组播过滤表来确定接收到的帧是否为组播帧。在实现过程中,需要注意以下几点: 1. 组播哈希函数和组播过滤表的实现应该符合以太网规范。 2. 在实现组播哈希函数和组播过滤表时,需要注意性能和资源占用。 3. 在实现组播过滤器模块时,需要注意时序约束和时钟域划分。 希望这个简单的Verilog代码示例可以帮助您实现以太网组播。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting_FPGA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值