【FPGA】AXI4-Lite总线读写BRAM

这篇具有很好参考价值的文章主要介绍了【FPGA】AXI4-Lite总线读写BRAM。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

博主参考和学习的博客

  1. AXI协议基础知识 。这篇博客比较详细地介绍了AXI总线,并且罗列了所有的通道和端口,写代码的时候可以方便地进行查表。
  2. AXI总线,AXI_BRAM读写仿真测试 。 这篇文章为代码的书写提供大致的思路,比如状态机和时序的控制问题,可以参考。

valid-ready双向握手机制

  1. 双向握手机制的实质是:
    数据接收方R告诉数据发送方T“我准备好接收数据了”,并拉高ready;同样的,数据发送方T告诉数据接收方R“我准备好发送数据了”,并拉高valid。

数据发送方给出valid,数据接收方给出ready

  1. 重点:只有在valid和ready同时拉高时,表面成功握手,数据才得以传输。
    【例】比如下图,当前clk上升沿检测到awvalid和awready都拉高,aw握手成功,此时32位的awaddr地址得以写入bram。
    bram读写,XILINX-FPGA实战开发经验,AXI,FPGA,高速总线,BRAM
    【例】比如下图,当前clk上升沿检测到wvalid和wready都拉高,w握手成功,此时32位的wdata数据得以写入bram。
    bram读写,XILINX-FPGA实战开发经验,AXI,FPGA,高速总线,BRAM

  2. 读写BRAM需要用到四个的AXI通道。每一个通道下都有很多端口,包括常规的数据线和valid-ready线
    ① 写地址通道AW(address write)

//写地址通道AW
reg [31:0]  awaddr;    //in(视角:bram)
reg         awvalid;   //in
wire        awready;   //out

② 写数据通道W(write)

//写数据通道W
reg [31:0]  wdata;     //in
reg         wvalid;    //in
wire        wready;    //out
reg [3:0]   wstrb;     //in

③ 写响应通道B

//写响应通道B
reg         bready;    //in:表示主机准备好接收bram的数据
wire        bvalid;    //out:表示bram可以给master发送数据
wire [1:0]  bresp;     //out

④ 读地址通道AR(address read)

//读地址通道AR
reg [31:0]  araddr;    //in
reg         arvalid;   //in
wire        arready;   //out

⑤ 读数据通道R(read)

//读数据通道R
wire [31:0] rdata;     //out
wire        rvalid;    //out
reg         rready;    //in
wire [1:0]  rresp;     //out

博主画了一张图方便理解和查询:
bram读写,XILINX-FPGA实战开发经验,AXI,FPGA,高速总线,BRAM

这是BRAM的IP核设置界面,可以清晰地看到各个通道。
bram读写,XILINX-FPGA实战开发经验,AXI,FPGA,高速总线,BRAM


突发读和突发写时序


有关AXI进行BRAM读写的细节问题

【主从valid-ready互不干扰】
正常工作情况下valid不等待ready,ready也不等待valid。因为主机和从机的逻辑判断是独立的,都是根据自己的情况拉高valid或者ready,换句话说就是:“主机想发数据就把自己的valid拉高,从机能接收数据就把自己的ready拉高”。正好握上了数据就开始传。

【有一边出现繁忙,才会出现等待握手的情况】
如果现在主机可以发送数据,拉高了valid,但是从机处于繁忙状态不能接收数据,没有拉高ready。这个时候主机valid信号就要等待从机退出繁忙并将ready拉高。


代码详解

我们书写代码,是站在“主机master”的视角,去操控bram的。

我们的程序中关于valid-ready双向握手协议的部分,只需要操纵主机的三个valid信号(awvalid,wvalid,arvalid)和一个ready信号(rready),从机bram的ready不需要我们操心。如果主从握手成功,数据便会成功传输,即对于数据传输的过程只要握手成功便会进行,也不需要我们操心。

  1. 状态机——状态空间:
    由于AXI读写时序较为复杂,为了更加好地操控时序和debug,博主使用状态机进行书写。
reg [3:0]   STATE;
parameter   wr_addr             = 4'b0000;
parameter   wr_addr_shakehand   = 4'b0001;
parameter   wr_data             = 4'b0010;
parameter   wr_data_shakehand   = 4'b0011;
parameter   write               = 4'b0100;          
parameter   rd_addr             = 4'b0101;
parameter   rd_addr_shakehand   = 4'b0110;
parameter   rd_data             = 4'b0111;
parameter   rd_data_shakehand   = 4'b1000;
parameter   read                = 4'b1001;
parameter   init                = 4'b1010;
parameter   buffer              = 4'b1011;
parameter   stop                = 4'b1111;
  1. 状态机——状态跳转:
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)    STATE <= init;
    else    begin
        case(STATE)
            init:   begin
                if(init_cnt == 5)   STATE <= wr_addr;
                    else            STATE <= init;                    
            end
            wr_addr:            STATE <= wr_addr_shakehand;
            wr_addr_shakehand:  begin
                if(awvalid == 1'b1 && awready == 1'b1)  STATE <= wr_data;
                    else                                STATE <= wr_addr_shakehand;     
            end
            wr_data:            STATE <= wr_data_shakehand;
            wr_data_shakehand:  begin
                if(wvalid == 1'b1 && wready == 1'b1)    STATE <= write;
                    else                                STATE <= wr_data_shakehand;
            end
            write:              STATE <= buffer;
            buffer:             STATE <= rd_addr;
            rd_addr:            STATE <= rd_addr_shakehand;
            rd_addr_shakehand:  begin
                if(arvalid == 1'b1 && arready == 1'b1)  STATE <= rd_data;
                    else                                STATE <= rd_addr_shakehand;
            end
            rd_data:            STATE <= rd_data_shakehand;
            rd_data_shakehand:  begin
                if(rready == 1'b1 && rvalid == 1'b1)    STATE <= read;
                    else                                STATE <= rd_data_shakehand;
            end
            read:               STATE <= stop;
            stop:               STATE <= stop;
            default:           STATE <= init;
        endcase
    end    
end
  1. 写地址通道AW:

① 给出要写入的地址ADDRESS,并将awvalid拉高,等待bram拉高awready进行握手。
(准确来说不是等待,因为bram只要满足条件就会拉高awready,主从逻辑独立。此处为方便表达。)
② awvalid-awready握手后,数据awaddr传输完成,此时主机要主动拉低awvalid。

//写地址通道AW
parameter   ADDRESS = 114;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        awaddr  <= 32'b0;
        awvalid <= 1'b0;
    end     
    else if(STATE == wr_addr)    begin
        awaddr <= ADDRESS;
        awvalid <= 1'b1;
    end
    else    begin
        awaddr <= awaddr;
        awvalid <= awvalid;
    end
end
always@(posedge clk or negedge rst_n)
begin
    if(STATE == wr_addr_shakehand)  begin
        if(awvalid == 1'b1 && awready == 1'b1)  begin
            awvalid <= 1'b0;     //成功握手,valid拉低
            awaddr <= 32'b0;
        end
    end
end
  1. 写数据通道W:

① 给出要写入的数据WR_DATA,同时拉高wvalid,等待bram拉高wready进行握手
② wvalid-wready握手后,数据wdata传输完成,此时主机要主动拉低wvalid。
③ 对于wstrb,在数据传输时赋为 4’b1111,表示wdata32位数据均有效。
(wstrb:写数据有效的字节线,用来表明哪8bits数据是有效的。)

//写数据通道W
parameter   WR_DATA = 514;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        wdata <= 32'b0;
        wvalid <= 1'b0;
        wstrb <= 4'b0;
    end
    else if(STATE == wr_data)   begin
        wdata <= WR_DATA;
        wvalid <= 1'b1;
        wstrb <= 4'b1111;
    end    
    else    begin
        wdata <= wdata;
        wvalid <= wvalid;
        wstrb <= wstrb;
    end
end
always@(posedge clk or negedge rst_n)
begin
   if(STATE == wr_data_shakehand)   begin
        if(wvalid == 1'b1 && wready == 1'b1)    begin
            wvalid <= 1'b0;
            wstrb  <= 4'b0000;
            wdata  <= 0;
        end
        else    begin
            wvalid <= wvalid;
            wstrb <= wstrb;
        end
   end
end
  1. 写响应通道B:

在AXI-Lite中,我们只需要管bready这一个端口就行。我们只需要一直拉高bready就行。
(bready:主机接受响应就绪信号 。该信号表示主机是否能够接受响应信息。1 = 主机就绪,0 = 主机未就绪。)
对于bvalid和bresp不需要我们操心,因为那是bram给到主机的,不由我们控制。

//写响应通道B
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        bready <= 1'b1;
    end
    else if(STATE == write || STATE == read)  begin
        bready <= 1'b1;
    end
end
  1. 读地址通道AR:

① 给出要读取的地址ADDRESS,并将arvalid拉高,等待bram拉高arready进行握手。
② arvalid-arready握手后,数据araddr传输完成,此时主机要主动拉低arvalid。

//读地址通道AR
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        araddr <= 32'b0;
        arvalid <= 1'b0;
    end
    else if (STATE == rd_addr)  begin
        araddr <= ADDRESS;
        arvalid <= 1'b1;
    end
    else    begin
        araddr <= araddr;
        arvalid <= arvalid;
    end
end
always@(posedge clk or negedge rst_n)
begin
    if(STATE == rd_addr_shakehand)  begin
        if(arvalid == 1'b1 && arready == 1'b1)  begin
            arvalid <= 1'b0;
        end
        else    arvalid <= arvalid;
    end
end
  1. 读数据通道R:

① 将rready拉高,等待bram拉高rvalid进行握手。
② rready-rvalid握手后,数据rdata传输完成,此时主机要主动拉低rready。
(注意:此时的数据流向是bram→主机,因此主机端为ready信号,bram端为valid信号。)

//读数据通道R
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        rready <= 1'b0;
    end
    else if(STATE == rd_data)    begin
        rready <= 1'b1;
    end
end
always@(posedge clk or negedge rst_n)
begin
    if(STATE == rd_data_shakehand)  begin
        if(rready == 1'b1 && rvalid == 1'b1)    begin
            rready <= 1'b0;
        end
        else    rready <= rready;
    end
end
  1. 实例化block memory generator IP核模块:
blk_mem_gen_0   mybram(
    .s_aclk(clk),
    .s_aresetn(rst_n),
    
    .s_axi_awaddr(awaddr),
    .s_axi_awvalid(awvalid),
    .s_axi_awready(awready),
    .s_axi_wdata(wdata),
    .s_axi_wvalid(wvalid),
    .s_axi_wready(wready),
    .s_axi_wstrb(wstrb),
    
    .s_axi_bready(bready),
    .s_axi_bvalid(bvalid),
    .s_axi_bresp(bresp),
    
    .s_axi_araddr(araddr),
    .s_axi_arvalid(arvalid),
    .s_axi_arready(arready),
    .s_axi_rready(rready),
    .s_axi_rvalid(rvalid),
    .s_axi_rdata(rdata)
     
);

完整代码(包含Testbench)

module axi4_light_bram(
input   wire    clk,
input   wire    rst_n
    );

reg [3:0]   STATE;
parameter   wr_addr             = 4'b0000;
parameter   wr_addr_shakehand   = 4'b0001;
parameter   wr_data             = 4'b0010;
parameter   wr_data_shakehand   = 4'b0011;
parameter   write               = 4'b0100;          
parameter   rd_addr             = 4'b0101;
parameter   rd_addr_shakehand   = 4'b0110;
parameter   rd_data             = 4'b0111;
parameter   rd_data_shakehand   = 4'b1000;
parameter   read                = 4'b1001;
parameter   init                = 4'b1010;
parameter   buffer              = 4'b1011;
parameter   stop                = 4'b1111;

//写地址通道AW
reg [31:0]  awaddr;    //in(视角:bram)
reg         awvalid;   //in
wire        awready;   //out
//写数据通道W
reg [31:0]  wdata;     //in
reg         wvalid;    //in
wire        wready;    //out
reg [3:0]   wstrb;     //in
//写响应通道B
reg         bready;    //in:表示主机准备好接收bram的数据
wire        bvalid;    //out:表示bram可以给master发送数据
wire [1:0]  bresp;     //out
//读地址通道AR
reg [31:0]  araddr;    //in
reg         arvalid;   //in
wire        arready;   //out
//读数据通道R
wire [31:0] rdata;     //out
wire        rvalid;    //out
reg         rready;    //in
wire [1:0]  rresp;     //out

//初始缓冲init
reg [3:0] init_cnt;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) init_cnt <= 0;
    else    init_cnt <= (init_cnt == 5) ? 0 : (init_cnt + 1);
end

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)    STATE <= init;
    else    begin
        case(STATE)
            init:   begin
                if(init_cnt == 5)   STATE <= wr_addr;
                    else            STATE <= init;                    
            end
            wr_addr:            STATE <= wr_addr_shakehand;
            wr_addr_shakehand:  begin
                if(awvalid == 1'b1 && awready == 1'b1)  STATE <= wr_data;
                    else                                STATE <= wr_addr_shakehand;     
            end
            wr_data:            STATE <= wr_data_shakehand;
            wr_data_shakehand:  begin
                if(wvalid == 1'b1 && wready == 1'b1)    STATE <= write;
                    else                                STATE <= wr_data_shakehand;
            end
            write:              STATE <= buffer;
            buffer:             STATE <= rd_addr;
            rd_addr:            STATE <= rd_addr_shakehand;
            rd_addr_shakehand:  begin
                if(arvalid == 1'b1 && arready == 1'b1)  STATE <= rd_data;
                    else                                STATE <= rd_addr_shakehand;
            end
            rd_data:            STATE <= rd_data_shakehand;
            rd_data_shakehand:  begin
                if(rready == 1'b1 && rvalid == 1'b1)    STATE <= read;
                    else                                STATE <= rd_data_shakehand;
            end
            read:               STATE <= stop;
            stop:               STATE <= stop;
            default:           STATE <= init;
        endcase
    end    
end


//写地址通道AW
parameter   ADDRESS = 114;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        awaddr  <= 32'b0;
        awvalid <= 1'b0;
    end     
    else if(STATE == wr_addr)    begin
        awaddr <= ADDRESS;
        awvalid <= 1'b1;
    end
    else    begin
        awaddr <= awaddr;
        awvalid <= awvalid;
    end
end
always@(posedge clk or negedge rst_n)
begin
    if(STATE == wr_addr_shakehand)  begin
        if(awvalid == 1'b1 && awready == 1'b1)  begin
            awvalid <= 1'b0;     //成功握手,valid拉低
            awaddr <= 32'b0;
        end
    end
end

//写数据通道W
parameter   WR_DATA = 514;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        wdata <= 32'b0;
        wvalid <= 1'b0;
        wstrb <= 4'b0;
    end
    else if(STATE == wr_data)   begin
        wdata <= WR_DATA;
        wvalid <= 1'b1;
        wstrb <= 4'b1111;
    end    
    else    begin
        wdata <= wdata;
        wvalid <= wvalid;
        wstrb <= wstrb;
    end
end
always@(posedge clk or negedge rst_n)
begin
   if(STATE == wr_data_shakehand)   begin
        if(wvalid == 1'b1 && wready == 1'b1)    begin
            wvalid <= 1'b0;
            wstrb  <= 4'b0000;
            wdata  <= 0;
        end
        else    begin
            wvalid <= wvalid;
            wstrb <= wstrb;
        end
   end
end

//写响应通道B
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        bready <= 1'b1;
    end
    else if(STATE == write || STATE == read)  begin
        bready <= 1'b1;
    end
end

//读地址通道AR
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        araddr <= 32'b0;
        arvalid <= 1'b0;
    end
    else if (STATE == rd_addr)  begin
        araddr <= ADDRESS;
        arvalid <= 1'b1;
    end
    else    begin
        araddr <= araddr;
        arvalid <= arvalid;
    end
end
always@(posedge clk or negedge rst_n)
begin
    if(STATE == rd_addr_shakehand)  begin
        if(arvalid == 1'b1 && arready == 1'b1)  begin
            arvalid <= 1'b0;
        end
        else    arvalid <= arvalid;
    end
end

//读数据通道R
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
        rready <= 1'b0;
    end
    else if(STATE == rd_data)    begin
        rready <= 1'b1;
    end
end
always@(posedge clk or negedge rst_n)
begin
    if(STATE == rd_data_shakehand)  begin
        if(rready == 1'b1 && rvalid == 1'b1)    begin
            rready <= 1'b0;
        end
        else    rready <= rready;
    end
end

blk_mem_gen_0   mybram(
    .s_aclk(clk),
    .s_aresetn(rst_n),
    
    .s_axi_awaddr(awaddr),
    .s_axi_awvalid(awvalid),
    .s_axi_awready(awready),
    .s_axi_wdata(wdata),
    .s_axi_wvalid(wvalid),
    .s_axi_wready(wready),
    .s_axi_wstrb(wstrb),
    
    .s_axi_bready(bready),
    .s_axi_bvalid(bvalid),
    .s_axi_bresp(bresp),
    
    .s_axi_araddr(araddr),
    .s_axi_arvalid(arvalid),
    .s_axi_arready(arready),
    .s_axi_rready(rready),
    .s_axi_rvalid(rvalid),
    .s_axi_rdata(rdata)
     
);

endmodule

Testbench如下:

`timescale 1ns / 1ps
module axi4_light_bram_tb();

reg clk;
reg rst_n;

initial begin
    clk = 1'b0;
    rst_n = 1'b0;
    #150
    rst_n = 1'b1; 
end

always # 10  clk <= ~clk;

axi4_light_bram axi4_light_bram_uut(
    .clk(clk),
    .rst_n(rst_n)
);

endmodule

仿真结果分析

bram读写,XILINX-FPGA实战开发经验,AXI,FPGA,高速总线,BRAM文章来源地址https://www.toymoban.com/news/detail-607444.html


心得和后续工作

  1. AXI总线算是比较复杂的总线,虽然后续开发的过程中大多不需要自己去写读写时序,但是经过这么一个过程能加深我对AXI协议的理解。“握手”是协议的精髓所在,它有效地控制着整个时序,并且保证主从机互相了解对方的状态,方便数据传输的开始和终止。
  2. 本程序只是最简单的AXI-Lite,如果书写常规的AXI协议,将多出一些端口需要配置(比如id,len,size),后续博主会在这个代码的基础上写常规AXI协议。
  3. 本程序只进行了一次数据的读和写。但是使用AXI协议可以同时写很多数据,然后用last指示最后一个数据,此类云云。后续我会去写这样的代码的。

到了这里,关于【FPGA】AXI4-Lite总线读写BRAM的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FPGA-基于AXI4接口的DDR3读写顶层模块

    AXI4(Advancede Xtensible Interface 4)是一种高性能、高带宽的总线接口协议,用于在系统级芯片设计中连接不同的IP核(Intellectual Property)或模块。它是由ARM公司开发的,被广泛应用于各种SoC(System-on-Chip)设计中。 AXI4接口协议定义了一组规范,用于描述数据传输、地址传输、控

    2024年04月15日
    浏览(42)
  • 带你快速入门AXI4总线--AXI4-Full篇(1)----AXI4-Full总线

    目录 1、什么是AXI4-Full? 2、通道(Channel) 2.1、AXI 读取传输事务 2.2、AXI 写入传输事务

    2024年02月01日
    浏览(38)
  • AXI4总线学习心得(一)

    AXI4:(For high-performance memory-mapped requirements.)主要面向高性能地址映射通信的需求,是面向地址映射的接口,允许最大 256 轮的数据突发传输; AXI4-Lite:(For simple, low-throughput memory-mapped communication )是一个轻量级的地址映射单次传输接口,占用很少的逻辑单元。 AXI4-Stream:

    2024年02月12日
    浏览(33)
  • 【正点原子FPGA连载】第二十章AXI4接口之DDR读写实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

    1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html Xilinx从Spartan-6和Virtex-6系列开始使用AXI协议来连接IP核。在ZYNQ MPSOC器件中,Xilinx在IP核中继续使用AXI协议。本章

    2024年02月02日
    浏览(37)
  • ZYNQ AXI4总线访问DDR3实现图像数据乒乓存储与显示

    目录 前言 一、添加端口 二、添加局部变量 三、例化读写FIFO 四、内部变量修改,设置一次读写进行多少次突发操作 五、写地址 六、读地址 七、状态机 1.写状态机 2.读状态机 总结 在Altera FPGA进行图像处理时,我们采用的存储芯片为SDRAM,当时参照正点原子的例程是封装SDR

    2024年02月02日
    浏览(42)
  • ZYNQ使用AXI4-HP接口总线读取DDR中的数据

    最近笔者在做项目的时候需要使用zynq中的AXI4-HP总线在PL端读取DDR中的数据这种功能,但是网上很多历程对于这方面只是创建了一个官方提供的IP核用于测试,并且每次写入和读取的长度为4K字节。所以为了满足我自己的项目需求,笔者将官方提供的测试IP核上做修改,主要实现

    2023年04月15日
    浏览(31)
  • DDR3 AXI4 IP核读写仿真实验(2)

    上篇blog中记录了DDR3 AXI4接口的IP配置详情,这一文章则是记录自己在项目工程以及学习中对于DDR3的读写测试。先讲一下大概的工程架构:产生16位的自加数写进写FIFO中,当FIFO中的数达到一次突发长度后将其全部读出写进DDR3中,再检测到DDR3中数达到1024之后全部读出写入到读

    2024年02月13日
    浏览(33)
  • 【FPGA】 xilinx vivado中AXI4通信协议详解

    AXI是ARM 1996年提出的微控制器总线家族AMBA中的一部分。AXI的第一个版本出现在AMBA3.0,发布于2003年。当前的最新的版本发布于2010年。AXI 4总线和别的总线一样,都用来传输bits信息 (包含了数据或者地址) 。AXI4总线有三种类型,分别是AXI4、AXI4-Lite、AXI4-Stream AXI4:主要面向高性能

    2024年04月28日
    浏览(32)
  • 仿真通过AXI_lite接口读写寄存器时axi_awready信号无法拉高的一种原因

            本人初次接触AXI接口,在了解了AXI接口读写时序后,计划使用AXI接口对BRAM进行读写,并进行仿真测试,AXI接口有三种类型:AXI4、AXI-lite、AXI-stream,我一开始成功对AXI4进行了读写测试,在了解读写时序后这是很简单的,但是在对AXI-lite进行读写测试时,本以为读写

    2024年02月16日
    浏览(47)
  • AXI4-Full Xilinx FPGA使用理解---信号定义理解

             一、AXI4 signal dir Xilinx 中文理解 ID类 AWID M2S Masters need only output the set of ID bits that it varies (if any) to indicate re-orderable transaction threads. Single-threaded master interfaces can omit this signal. Masters do not need to output the constant portion that comprises the Master ID, as this is appended by the AXI Interco

    2024年02月22日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包