专栏首页根究FPGADDR3篇第三讲、DDR3读写测试项目分析

DDR3篇第三讲、DDR3读写测试项目分析

本节介绍一个米联客DDR3读写测试的工程,把一些难以理解的代码进行了注释,如果哪里有问题的话,感谢大家指出,最后对波形进行分析。

一、DDR3读写测试代码

前面的内容基本不需要看,重点在后面的assign赋值语句与两个状态机模块。

`timescale 1ps/1ps
module example_top #
  (
   //***************************************************************************
   // Traffic Gen related parameters
   //***************************************************************************
   parameter PORT_MODE             = "BI_MODE",
   parameter DATA_MODE             = 4'b0010,
   parameter TST_MEM_INSTR_MODE    = "R_W_INSTR_MODE",
   parameter EYE_TEST              = "FALSE",
                                     // set EYE_TEST = "TRUE" to probe memory
                                     // signals. Traffic Generator will only
                                     // write to one single location and no
                                     // read transactions will be generated.
   parameter DATA_PATTERN          = "DGEN_ALL",
                                      // For small devices, choose one only.
                                      // For large device, choose "DGEN_ALL"
                                      // "DGEN_HAMMER", "DGEN_WALKING1",
                                      // "DGEN_WALKING0","DGEN_ADDR","
                                      // "DGEN_NEIGHBOR","DGEN_PRBS","DGEN_ALL"
   parameter CMD_PATTERN           = "CGEN_ALL",
                                      // "CGEN_PRBS","CGEN_FIXED","CGEN_BRAM",
                                      // "CGEN_SEQUENTIAL", "CGEN_ALL"
   parameter CMD_WDT               = 'h3FF,
   parameter WR_WDT                = 'h1FFF,
   parameter RD_WDT                = 'h3FF,
   parameter SEL_VICTIM_LINE       = 0,
   parameter BEGIN_ADDRESS         = 32'h00000000,
   parameter END_ADDRESS           = 32'h00ffffff,
   parameter PRBS_EADDR_MASK_POS   = 32'hff000000,

   //***************************************************************************
   // The following parameters refer to width of various ports
   //***************************************************************************
   parameter CK_WIDTH              = 1,
                                     // # of CK/CK# outputs to memory.
   parameter nCS_PER_RANK          = 1,
                                     // # of unique CS outputs per rank for phy
   parameter CKE_WIDTH             = 1,
                                     // # of CKE outputs to memory.
   parameter DM_WIDTH              = 4,
                                     // # of DM (data mask)
   parameter ODT_WIDTH             = 1,
                                     // # of ODT outputs to memory.
   parameter BANK_WIDTH            = 3,
                                     // # of memory Bank Address bits.
   parameter COL_WIDTH             = 10,
                                     // # of memory Column Address bits.
   parameter CS_WIDTH              = 1,
                                     // # of unique CS outputs to memory.
   parameter DQ_WIDTH              = 32,
                                     // # of DQ (data)
   parameter DQS_WIDTH             = 4,
   parameter DQS_CNT_WIDTH         = 2,
                                     // = ceil(log2(DQS_WIDTH))
   parameter DRAM_WIDTH            = 8,
                                     // # of DQ per DQS
   parameter ECC                   = "OFF",
   parameter ECC_TEST              = "OFF",
   //parameter nBANK_MACHS           = 4,
   parameter nBANK_MACHS           = 4,
   parameter RANKS                 = 1,
                                     // # of Ranks.
   parameter ROW_WIDTH             = 15,
                                     // # of memory Row Address bits.
   parameter ADDR_WIDTH            = 29,
                                     // # = RANK_WIDTH (1)+ BANK_WIDTH(3)
                                     //     + ROW_WIDTH(15) + COL_WIDTH;(10)
                                     // Chip Select is always tied to low for
                                     // single rank devices

   //***************************************************************************
   // The following parameters are mode register settings
   //***************************************************************************
   parameter BURST_MODE            = "8",
                                     // DDR3 SDRAM:
                                     // Burst Length (Mode Register 0).
                                     // # = "8", "4", "OTF".
                                     // DDR2 SDRAM:
                                     // Burst Length (Mode Register).
                                     // # = "8", "4".
   //***************************************************************************
   // The following parameters are multiplier and divisor factors for PLLE2.
   // Based on the selected design frequency these parameters vary.
   //***************************************************************************
   parameter CLKIN_PERIOD          = 5000,
                                     // Input Clock Period
   parameter CLKFBOUT_MULT         = 8,
                                     // write PLL VCO multiplier
   parameter DIVCLK_DIVIDE         = 1,
                                     // write PLL VCO divisor
   parameter CLKOUT0_PHASE         = 337.5,
                                     // Phase for PLL output clock (CLKOUT0)
   parameter CLKOUT0_DIVIDE        = 2,
                                     // VCO output divisor for PLL output clock (CLKOUT0)
   parameter CLKOUT1_DIVIDE        = 2,
                                     // VCO output divisor for PLL output clock (CLKOUT1)
   parameter CLKOUT2_DIVIDE        = 32,
                                     // VCO output divisor for PLL output clock (CLKOUT2)
   parameter CLKOUT3_DIVIDE        = 8,
                                     // VCO output divisor for PLL output clock (CLKOUT3)
   parameter MMCM_VCO              = 800,
                                     // Max Freq (MHz) of MMCM VCO
   parameter MMCM_MULT_F           = 4,
                                     // write MMCM VCO multiplier
   parameter MMCM_DIVCLK_DIVIDE    = 1,
                                     // write MMCM VCO divisor

   //***************************************************************************
   // Simulation parameters
   //***************************************************************************
   parameter SIMULATION            = "FALSE",
                                     // Should be TRUE during design simulations and
                                     // FALSE during implementations

   //***************************************************************************
   // IODELAY and PHY related parameters
   //***************************************************************************
   parameter TCQ                   = 100,
   
   parameter DRAM_TYPE             = "DDR3",
   //***************************************************************************
   // System clock frequency parameters
   //***************************************************************************
   parameter nCK_PER_CLK           = 4,
                                     // # of memory CKs per fabric CLK
   //***************************************************************************
   // Debug parameters
   //***************************************************************************
   parameter DEBUG_PORT            = "OFF",
                                     // # = "ON" Enable debug signals/controls.
                                     //   = "OFF" Disable debug signals/controls.
      
   parameter RST_ACT_LOW           = 1
                                     // =1 for active low reset,
                                     // =0 for active high.
   )
  (

   // Inouts
   inout [31:0]                       ddr3_dq,
   inout [3:0]                        ddr3_dqs_n,
   inout [3:0]                        ddr3_dqs_p,

   // Outputs
   output [14:0]                      ddr3_addr,
   output [2:0]                       ddr3_ba,
   output                             ddr3_ras_n,
   output                             ddr3_cas_n,
   output                             ddr3_we_n,
   output                             ddr3_reset_n,
   output [0:0]                       ddr3_ck_p,
   output [0:0]                       ddr3_ck_n,
   output [0:0]                       ddr3_cke,
   
   output [0:0]           ddr3_cs_n,
   
   output [3:0]           ddr3_dm,
   
   output [0:0]           ddr3_odt,
   // Inputs
   output                       tg_compare_error,
   output                       init_calib_complete,
   input                        clk100m_i,
   input                        rst_key
   
   );
   wire sys_rst;
   wire locked;
   wire clk_ref_i;
   wire sys_clk_i;
   wire clk_200;
      
   assign sys_rst = ~rst_key;//复位信号
   assign clk_ref_i = clk_200;//200M的参考时钟
   assign sys_clk_i = clk_200;//200M的系统时钟
   
   //时钟管理产生DDR需要的时钟   
   clk_wiz_0 CLK_WIZ_DDR( .clk_out1(clk_200),
   .reset(sys_rst),
   .locked(locked),
   .clk_in1(clk100m_i)
   ); 

function integer clogb2 (input integer size);
    begin
      size = size - 1;
      for (clogb2=1; size>1; clogb2=clogb2+1)
        size = size >> 1;
    end
  endfunction // clogb2

  function integer STR_TO_INT;
    input [7:0] in;
    begin
      if(in == "8")
        STR_TO_INT = 8;
      else if(in == "4")
        STR_TO_INT = 4;
      else
        STR_TO_INT = 0;
    end
  endfunction


  localparam DATA_WIDTH            = 32;
  localparam RANK_WIDTH = clogb2(RANKS);
  localparam PAYLOAD_WIDTH         = (ECC_TEST == "OFF") ? DATA_WIDTH : DQ_WIDTH;
  localparam BURST_LENGTH          = STR_TO_INT(BURST_MODE);
  localparam APP_DATA_WIDTH        = 2 * nCK_PER_CLK * PAYLOAD_WIDTH;
  localparam APP_MASK_WIDTH        = APP_DATA_WIDTH / 8;

  //***************************************************************************
  // Traffic Gen related parameters (derived)
  //***************************************************************************
  localparam  TG_ADDR_WIDTH = ((CS_WIDTH == 1) ? 0 : RANK_WIDTH)
                                 + BANK_WIDTH + ROW_WIDTH + COL_WIDTH;
  localparam MASK_SIZE             = DATA_WIDTH/8;
      
  // Wire declarations
      
  wire [(2*nCK_PER_CLK)-1:0]            app_ecc_multiple_err;
  wire [(2*nCK_PER_CLK)-1:0]            app_ecc_single_err;
  wire [ADDR_WIDTH-1:0]                 app_addr;
  wire [2:0]                            app_cmd;
  wire                                  app_en;
  wire                                  app_rdy;
  wire [APP_DATA_WIDTH-1:0]             app_rd_data;
  wire                                  app_rd_data_end;
  wire                                  app_rd_data_valid;
  wire [APP_DATA_WIDTH-1:0]             app_wdf_data;
  wire                                  app_wdf_end;
  wire [APP_MASK_WIDTH-1:0]             app_wdf_mask;
  wire                                  app_wdf_rdy;
  wire                                  app_sr_active;
  wire                                  app_ref_ack;
  wire                                  app_zq_ack;
  wire                                  app_wdf_wren;
  wire [(64+(2*APP_DATA_WIDTH))-1:0]      error_status;
  wire [(PAYLOAD_WIDTH/8)-1:0] cumlative_dq_lane_error;
  wire                                  mem_pattern_init_done;
  wire [47:0]                           tg_wr_data_counts;
  wire [47:0]                           tg_rd_data_counts;
  wire                                  modify_enable_sel;
  wire [2:0]                            data_mode_manual_sel;
  wire [2:0]                            addr_mode_manual_sel;
  wire [APP_DATA_WIDTH-1:0]             cmp_data; 
  wire                                  cmp_data_valid;
  reg                                   cmp_data_valid_r;
  wire                                  cmp_error;
  wire [(PAYLOAD_WIDTH/8)-1:0]          dq_error_bytelane_cmp;

  wire                                  clk;
  wire                                  rst;
     
  wire [11:0]                           device_temp;
  
 

//***************************************************************************


// Start of User Design top instance
//***************************************************************************
// The User design is instantiated below. The memory interface ports are
// connected to the top-level and the application interface ports are
// connected to the traffic generator module. This provides a reference
// for connecting the memory controller to system.
//***************************************************************************
 mig_7series_0 u_mig_7series_0
     (
// Memory interface ports
      .ddr3_addr                      (ddr3_addr),
      .ddr3_ba                        (ddr3_ba),
      .ddr3_cas_n                     (ddr3_cas_n),
      .ddr3_ck_n                      (ddr3_ck_n),
      .ddr3_ck_p                      (ddr3_ck_p),
      .ddr3_cke                       (ddr3_cke),
      .ddr3_ras_n                     (ddr3_ras_n),
      .ddr3_we_n                      (ddr3_we_n),
      .ddr3_dq                        (ddr3_dq),
      .ddr3_dqs_n                     (ddr3_dqs_n),
      .ddr3_dqs_p                     (ddr3_dqs_p),
      .ddr3_reset_n                   (ddr3_reset_n),
      .init_calib_complete            (init_calib_complete),
      .ddr3_cs_n                      (ddr3_cs_n),
      .ddr3_dm                        (ddr3_dm),
      .ddr3_odt                       (ddr3_odt),
// Application interface ports
      .app_addr                       (app_addr),
      .app_cmd                        (app_cmd),
      .app_en                         (app_en),
      .app_wdf_data                   (app_wdf_data),
      .app_wdf_end                    (app_wdf_end),
      .app_wdf_wren                   (app_wdf_wren),
      .app_rd_data                    (app_rd_data),
      .app_rd_data_end                (app_rd_data_end),
      .app_rd_data_valid              (app_rd_data_valid),
      .app_rdy                        (app_rdy),
      .app_wdf_rdy                    (app_wdf_rdy),
      .app_sr_req                     (1'b0),
      .app_ref_req                    (1'b0),
      .app_zq_req                     (1'b0),
      .app_sr_active                  (app_sr_active),
      .app_ref_ack                    (app_ref_ack),
      .app_zq_ack                     (app_zq_ack),
      .ui_clk                         (clk),
      .ui_clk_sync_rst                (rst),
      .app_wdf_mask                   (32'd0),
// System Clock Ports
      .sys_clk_i                      (sys_clk_i),
// Reference Clock Ports
      .clk_ref_i                      (clk_ref_i),
      .device_temp                    (device_temp),
      .sys_rst                        (locked)
      );
      
//以下是读写测试       
// End of User Design top instance
parameter    [2:0]CMD_WRITE    =3'd0;
parameter    [2:0]CMD_READ    =3'd1;
//parameter TEST_DATA_RANGE=24'd16777215;//全地址测试
parameter TEST_DATA_RANGE=24'd2000;//部分测试

(*mark_debug="true"*)    wire init_calib_complete;
(*mark_debug="true"*)    reg    [3:0]state=0;
(*mark_debug="true"*)    reg    [23:0]Count_64=0;// 128M*2*16/256
(*mark_debug="true"*)    reg    [23:0]Count_64_1=0;
(*mark_debug="true"*)    reg    ProsessIn=0;//表示读写操作的包络
(*mark_debug="true"*)    reg    WriteSign=0;//表示是写操作
(*mark_debug="true"*)    reg    ProsessIn1=0;//表示写操作的包络
reg    [ADDR_WIDTH-1:0]app_addr_begin=0; 
reg    [29:0]CountRead_tem=0;

(*mark_debug="true"*)    wire   error_rddata=0;

assign    app_wdf_end                        =app_wdf_wren;//两个相等即可
//根据是否允许数据写入,之后等待app_wdf_rdy为一,再等待app_rdy的应答信号,在输入请求中使用
assign    app_en                            =ProsessIn?(WriteSign?app_rdy&&app_wdf_rdy:app_rdy):1'd0;//控制命令使能
//根据是否写数据使能,发送数据写入指令,或者数据读取指令
assign    app_cmd                            =WriteSign?CMD_WRITE:CMD_READ;
//数据操作地址为begin~~~~~后续自加8
assign    app_addr                        =app_addr_begin;
assign    app_wdf_data                    =Count_64_1;//写入的数据是计数器
//根据是否允许数据写入,且DDR3准备好&MIG内部fifo准备好,使能写入数据
assign    app_wdf_wren                    =ProsessIn1?app_rdy&&app_wdf_rdy:1'd0;

//app_rdy:用户提交的请求已经被接受
//app_wdf_rdy:写入数据fifo已经准备好接收数据
always@(posedge clk)
   if(rst&!init_calib_complete)//MIG复位&未初始化结束
       begin
       state                            <=4'd0;  //初始状态
       app_addr_begin                    <=28'd0; //起始地址
       WriteSign                        <=1'd0;  //写标志
       ProsessIn                        <=1'd0;  //输入写入
       Count_64                        <=24'd0;   //数据写入个数计数器
       end
else case(state)
4'd0:    begin
       state                            <=4'd1; //在状态0时跳转到状态1
       app_addr_begin                    <=28'd0;  //传输起始地址为0
       WriteSign                        <=1'd0;     //写数据清零
       ProsessIn                        <=1'd0;     //写指令清零
       Count_64                        <=24'd0;     //数据写入个数计数器清零  
       end
4'd1:    begin
       state                            <=4'd2;
       WriteSign                        <=1'd1; //允许写入数据
       ProsessIn                        <=1'd1; //允许发送写指令   
       Count_64                        <=24'd0; //发送数据次数计数器清零   
       app_addr_begin                    <=28'd0; //写数据的起始地址 
       end
4'd2:    begin//写整片的DDR3
      //未发送完指定长度时,在该状态保持
       state                            <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?4'd3:4'd2;//最后一个地址写完之后跳出状态
     //未写入到指定长度时,一直写数据使能  
       WriteSign                        <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;//写数据使能
    //未写入到指定长度时,每次MIG准备好且MIG数据fifo准备好,写指令使能   
       ProsessIn                        <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;//写命令使能
       //MIG准备好且接收数据fifo已经准备好,则发送数据寄存器自加一
       Count_64                        <=app_rdy&&app_wdf_rdy?(Count_64+24'd1):Count_64;
     // 当提交给UI的指令已经被接受且写入数据fifo准备好接收数据,发送给数据,DDR的工作频率是800Mhz,数据宽度32,上下沿都写入数据,每次需要256bit数据
     //之后需要跳转到下一个数据地址+8   
       app_addr_begin                    <=app_rdy&&app_wdf_rdy?(app_addr_begin+28'd8):app_addr_begin;//跳到下一个(8*32=256)bit数据地址
  
       end
4'd3:    begin
       state                            <=(state1==4'd0)?4'd4:state;  //等待数据全部写入
       WriteSign                        <=1'd0;                        //将写数据使能清零,表示读取数据使能
       ProsessIn                        <=(state1==4'd0)?1'd1:1'd0;    //将写数据指令清零  表示读取指令使能
       Count_64                        <=24'd0;                        //写数据计数器清零
       app_addr_begin                    <=28'd0;                      //操作起始地址清零
       end
4'd4:    begin//读整片的DDR3
       state                            <=(Count_64==TEST_DATA_RANGE)&&app_rdy?4'd0:state;
       WriteSign                        <=1'd0;
       ProsessIn                        <=(Count_64==TEST_DATA_RANGE)&&app_rdy?1'd0:1'd1;    
       Count_64                        <=app_rdy?(Count_64+24'd1):Count_64;    
       app_addr_begin                    <=app_rdy?(app_addr_begin+28'd8):app_addr_begin; 
       end
default:begin
       state                            <=4'd1;
       app_addr_begin                    <=28'd0;
       WriteSign                        <=1'd0;
       ProsessIn                        <=1'd0;
       Count_64                        <=24'd0;
       end        
   endcase
   
(*mark_debug="true"*)    reg    [3:0]state1=0;
always@(posedge clk)//单独将写操作从上面的状态机提出来,当然也可以和上面的状态机合并到一起
   //当DDR处于复位状态且为初始化完成
   if(rst&!init_calib_complete)//
       begin
       state1                            <=4'd0;  //状态机初始状态
       ProsessIn1                        <=1'd0; //写指令不使能
       end
else case(state1)
4'd0:    begin
    //空闲状态时,等待主状态机跳转到state==1
       state1                            <=(state==4'd1)?4'd1:4'd0;  
       ProsessIn1                        <=(state==4'd1)?1'd1:1'd0;
       Count_64_1                        <=24'd0;
       end
4'd1:    begin
    //当写入数据写到指定长度,且UI接受写指令&MIG接收数据fifo准备好时返回到状态0,否则在状态0维持
       state1                            <=(Count_64_1==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?4'd0:4'd1;
       //当写入数据写到指定长度,且UI接受写指令&MIG接收数据fifo准备好时返回到状态0,否则不断使能输入写入指令
       ProsessIn1                        <=(Count_64_1==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;    
       Count_64_1                        <=app_rdy&&app_wdf_rdy?(Count_64_1+24'd1):Count_64_1;    
       end
default:begin
       state1                            <=(state==4'd1)?4'd1:4'd0;
       ProsessIn1                        <=(state==4'd1)?1'd1:1'd0;
       Count_64_1                        <=24'd0;
       end
       endcase
   
(*mark_debug="true"*)    reg    [23:0]app_rd_data_tem=0;
(*mark_debug="true"*)    reg    [23:0]cmp_data_r1=0;
reg    [23:0]cmp_data_r;
always@(posedge clk)
    if(rst&!init_calib_complete)begin
        cmp_data_r <= 24'd0;
    end 
    else if(cmp_data_r==TEST_DATA_RANGE)begin
        cmp_data_r <= 24'd0;
    end
    else if(app_rd_data_valid) begin
        cmp_data_r<=cmp_data_r+1'b1;  //每次检测到有效数据标志时,将数据计数器自加一
    end

reg app_rd_data_valid_r=1'b0;
always@(posedge clk) begin
    app_rd_data_valid_r <= app_rd_data_valid;  //寄存读取数据有效标志
    app_rd_data_tem     <= app_rd_data;         //寄存读取得到的数据
    cmp_data_r1 <= cmp_data_r;                  //寄存计数器产生的数据
end
 
/*
错误校验,当读取得到的数据与计数器产生的数据大小不一致时,且经过寄存的数据有效信号为一时,将错误标志位置一
*/ 
assign error_rddata=(app_rd_data_tem!=cmp_data_r1)&app_rd_data_valid_r; 

endmodule

二、DDR3波形分析

1、状态分析

state=4表示处于数据读取状态,state=2表示处于数据写入状态。

2、读取数据的有效标志

数据读取阶段,发出读取指令后,经过一段时间的延迟,读取数据显示到数据总线,同时data_valid信号为1.

3、数据读取操作

此处仍在进行数据单独,但是后半部分与数据写入部分重合,这说明从DDR中读取得到的数据保存在MIG内部的fifo中,从而不会影响到数据写入操作。

4、数据的写入与读取

当WriteSign为0时进行数据读取,为1时进行数据写入操作

本文分享自微信公众号 - 根究FPGA(gh_08b5d93f8fa5),作者:叫什么好呢啊

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • DDR3篇第一讲、MIG用户接口介绍

    核心板搭载了4块镁光DDR3内存,2片与PS相连,另外2片与PL相连,单片DDR3内存大小为512MB,其型号为:MT41K256M16XX-125。

    根究FPGA
  • DDR3篇第四讲、DDR3操作时序与关键参数

    app_cmd和app_addr有效,且app_en拉高,app_rdy拉高,则该指令成功发送给DDR3控制器,若是在app_cmd、app_addr、app_...

    根究FPGA
  • UDP的FPGA实现(下) | 基于UDP的图像传输工程分析

    本章节分析基于以太网图像传输工程,其实上周就已经做完,只不过实在是难以总结,代码的理解有时候真的要自己去逐词逐句的分析,不然也就只能理解其过程,无法重新复现,工...

    根究FPGA
  • Flask阶段(一)代码

    小闫同学啊
  • 109个提高App下载量的营销策略(上)

    引言:本文介绍了如何提高APP下载量的109个适用的营销策略中的前36个策略,本系列全长共109个策略。

    iCDO互联网数据官
  • App出海又遇困局,乘风破浪的互联网企业何时上岸

    App Annie统计显示,2020年6月全球热门应用,TikTok(抖音海外版)继续霸榜,无论是下载量还是收入依旧稳拿第一。今年受到新冠疫情的影响,在一二季度...

    APICloud
  • 慕课网Flask高级编程实战-4.flask核心机制

    在 3.8节我们通过db.create_all(app=app)的方式解决了working outside application context的错误,下面我...

    Meet相识
  • 慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

    应该讲一些初始化工作,放在对应层级的包的初始化文件 __init__.py 中。比如Flask核心应用app对象初始化应该放在应用层级app包的 __init_...

    Meet相识
  • mig IP用户读写时序

    FPGA开源工作室将通过五篇文章来给大家讲解xilinx FPGA 使用mig IP对DDR3的读写控制,旨在让大家更快的学习和应用DDR3。

    FPGA开源工作室
  • 怎样才能做一款好的App,即做app时最需要注意的六个因素

    移动互联网时代,app已经成为了我们生活中不可或缺的一部分了。根据当前情况来看,app在未来几年内,至少两年内,还将会有新一波的火爆增长时期。尤其是我们中国市场...

    非著名程序员

扫码关注云+社区

领取腾讯云代金券