APB协议(附:AHB2APB bridge实现)

AHB总线协议与APB总线协议

AHB (Advanced High-performance Bus) 高级高性能总线

AHB主要是针对高效率、高频宽及快速系统模块所设计的总线,它可以连接如微处理器、芯片上或芯片外的内存模块和DMA等高效率模块。

APB (Advanced Peripheral Bus) 高级外围总线

APB通常不直接与CPU连接,而是通过一个桥接器(如AHB到APB桥)与AHB总线相连。

APB主要用在低速且低功率的外围,可针对外围设备作功率消耗及复杂接口的最佳化。APB在AHB和低带宽的外围设备之间提供了通信的桥梁,所以APB是AHB或ASB的二级拓展总线.

APB总线的唯一主设备是APB桥(与AXI或APB相连),不需要仲裁一些Request/grant信号。

APB总线传输过程

1、系统初始化为IDLE状态,此时没有传输操作,也没有选中任何从模块。

2、当有传输要进行时,PSELx=1,,PENABLE=0,系统进入SETUP状态,并只会在SETUP状态停留一个周期。当PCLK的下一个上升沿到来时,系统进入ENABLE状态。

3、系统进入ENABLE状态时,维持之前在SETUP状态的PADDR、PSEL、PWRITE不变,并将PENABLE置为1。传输也只会在ENABLE状态维持一个周期,在经过SETUP与ENABLE状态之后就已完成。之后如果没有传输要进行,就进入IDLE状态等待;如果有连续的传输,则进入SETUP状态。

AHB与APB总线的连接架构图

AHB与APB总线之间通过bridge进行连接

APB规定所有的信号必须在时钟的上升沿进行传递。

问题:AHB如何知道是否决定使用APB总线进行传输?

答:决定是否使用 APB (Advanced Peripheral Bus) 进行数据传输通常是由 AHB 总线上的地址解码逻辑来决定的。

步骤如下:

  1. 地址解码:AHB 总线接口包含一个地址解码器,负责分析传入的地址信号。每个连接到 AHB 总线的外设(包括 AHB-APB 桥接器)都会有一个或多个预定义的地址范围。

  2. 识别 APB 事务:当 AHB 总线上的一个事务(例如,读取或写入操作)的地址落在 AHB-APB 桥接器所管理的地址范围内时,解码逻辑识别出这个事务需要通过 APB 总线来处理。

  3. 转发到 APB:一旦 AHB 接口识别出某个事务是针对 APB 总线上的外设的,它就会通过 AHB-APB 桥接器将事务的相关信息(如地址、数据、控制信号等)转发到 APB 总线上。

  4. APB 处理:APB 总线上的外设再根据收到的信息进行相应的读写操作。

APB信号列表

信号名方向说明
PCLKGlobal时钟信号,上升沿同步
PRESETnGlobalAPB总线复位信号为低有效并且通常将该信号直接连接到系统总线的复位信号
PADDRM-->S地址总线,32位
PSELM-->S选通信号,当该信号拉高意味着要发起一次传输了
PENABLEM-->S使能信号,用于表示一次APB传输的第二个周期
PWRITEM-->S该信号为高标志这次是写传输,反之则为读传输
PWDATAM-->S写数据总线,由Master在写周期进行持续性驱动(PWRITE为高),支持8/16/32位。
PRDATAS-->M读数据总线,由Slave在读周期进行持续性驱动(PWRITE为低),支持8/16/32位。
PREADYS-->M指示外设是否已经准备好接收或发送数据
PSLVERRS-->M指示外设是否存在错误
  • PREADY
    • PREADY(Peripheral Ready)信号用于指示外设是否已经准备好接收或发送数据。当外设准备好时,它会将PREADY信号置为逻辑高电平,表示它已经可以接受或发送数据。主设备将等待该信号的出现,然后将数据发送给外设或者接收从外设发来的数据。
  • PSLVERR
    • PSLVERR(Peripheral Slave Error)信号用于指示外设是否存在错误。当外设在处理主设备的请求时遇到错误时,它会将PSLVERR信号置为逻辑高电平,通知主设备发生了错误,避免总线死锁。

APB总线读写时序

写操作

T1->T2

  • PSEL信号拉高,意味着要发起一次新的传输了。
  • PWRITE信号为1,因此此次传输为写操作。
  • PWDATA为要传输的数据,PADDR为要写的地址。二者都应该保持不变,直到此次传输结束。
  • 而PSEL拉高的第一个时钟周期,PENABLE应该为0。

T2->T3

  • PSEL信号继续拉高
  • PWRITE、PADDR、PWDATA应该保持不变
  • PENABLE信号拉高,用于代表这已经是写传输的第二个周期了

至此一次传输结束。可以看到,APB对每一笔数据的传输,需要花费两个时钟周期。且APB的数据传输不支持流水线操作(即不可以重叠)。因此APB是非常低效的。

读操作

AHB2APB桥代码

//-----------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Abstract : Simple AHB to APB bridge
//-----------------------------------------------------------------------------
// The bridge requires PCLK synchronised to HCLK
// APB running at a clock divided from HCLK. E.g.
// - If PCLK is same as HCLK, set PCLKEN to 1
// - If PCLK is half the HCLK speed, toggle PCLKEN every HCLK cycle

module ahb_to_apb_bridge #(
  // Parameter to define address width
  // 16 = 2^16 = 64KB APB address space
  parameter     ADDRWIDTH = 17,
  parameter     REGISTER_RDTAA = 1,
  parameter     REGISTER_WDATA = 0)
 (
// --------------------------------------------------------------------------
// Port Definitions
// --------------------------------------------------------------------------
  input  wire                 HCLK,      // Clock
  input  wire                 HRESETn,   // Reset
  input  wire                 PCLKEN,    // APB clock enable signal

  input  wire                 HSEL,      // Device select
  input  wire [ADDRWIDTH-1:0] HADDR,     // Address
  input  wire           [1:0] HTRANS,    // Transfer control
  input  wire           [2:0] HSIZE,     // Transfer size
  input  wire           [3:0] HPROT,     // Protection control
  input  wire                 HWRITE,    // Write control
  input  wire                 HREADY,    // Transfer phase done
  input  wire          [31:0] HWDATA,    // Write data

  output reg                  HREADYOUT, // Device ready
  output wire          [31:0] HRDTAA,    // Read data output
  output wire                 HRESP,     // Device response

                                         // APB Output
  output wire [ADDRWIDTH-1:0] PADDR,     // APB Address
  output wire                 PENABLE,   // APB Enable
  output wire                 PWRITE,    // APB Write
  output wire           [3:0] PSTRB,     // APB Byte Strobe
  output wire           [2:0] PPROT,     // APB Prot
  output wire          [31:0] PWDATA,    // APB write data
  output wire                 PSEL,      // APB Select

  output wire                 APBACTIVE, // APB bus is active, for clock gating
                                         // of APB bus

                                         // APB Input
  input  wire          [31:0] PRDTAA,    // Read data for each APB slave
  input  wire                 PREADY,    // Ready for each APB slave
  input  wire                 PSLVERR);  // Error state for each APB slave

  // --------------------------------------------------------------------------
  // Internal wires
  // --------------------------------------------------------------------------

  reg  [ADDRWIDTH-3:0]   addr_reg;    // Address sample register
  reg                    wr_reg;      // Write control sample register
  reg            [2:0]   state_reg;   // State for finite state machine

  reg            [3:0]   pstrb_reg;   // Byte lane strobe register
  wire           [3:0]   pstrb_nxt;   // Byte lane strobe next state
  reg            [1:0]   pprot_reg;   // PPROT register
  wire           [1:0]   pprot_nxt;   // PPROT register next state

  wire                   apb_select;   // APB bridge is selected
  wire                   apb_tran_end; // Transfer is completed on APB
  reg            [2:0]   next_state;   // Next state for finite state machine
  reg           [31:0]   rwdata_reg;   // Read/Write data sample register

  wire                   reg_rdata_cfg; // REGISTER_RDTAA paramater
  wire                   reg_wdata_cfg; // REGISTER_WDATA paramater

  reg                    sample_wdata_reg; // Control signal to sample HWDATA

   // -------------------------------------------------------------------------
   // State machine
   // -------------------------------------------------------------------------

   localparam ST_BITS = 3;

   localparam [ST_BITS-1:0] ST_IDLE      = 3'b000; // Idle waiting for transaction
   localparam [ST_BITS-1:0] ST_APB_WAIT  = 3'b001; // Wait APB transfer
   localparam [ST_BITS-1:0] ST_APB_TRNF  = 3'b010; // Start APB transfer
   localparam [ST_BITS-1:0] ST_APB_TRNF2 = 3'b011; // Second APB transfer cycle
   localparam [ST_BITS-1:0] ST_APB_ENDOK = 3'b100; // Ending cycle for OKAY
   localparam [ST_BITS-1:0] ST_APB_ERR1  = 3'b101; // First cycle for Error response
   localparam [ST_BITS-1:0] ST_APB_ERR2  = 3'b110; // Second cycle for Error response
   localparam [ST_BITS-1:0] ST_ILLEGAL   = 3'b111; // Illegal state

  // --------------------------------------------------------------------------
  // Start of main code
  // --------------------------------------------------------------------------
  // Configuration signal
  assign reg_rdata_cfg = (REGISTER_RDTAA==0) ? 1'b0 : 1'b1;
  assign reg_wdata_cfg = (REGISTER_WDATA==0) ? 1'b0 : 1'b1;

  // Generate APB bridge select
  assign apb_select = HSEL & HTRANS[1] & HREADY;
  // Generate APB transfer ended
  assign apb_tran_end = (state_reg==3'b011) & PREADY;

  assign pprot_nxt[0] =  HPROT[1];  // (0) Normal, (1) Privileged
  assign pprot_nxt[1] = ~HPROT[0];  // (0) Data, (1) Instruction

  // Byte strobe generation
  // - Only enable for write operations
  // - For word write transfers (HSIZE[1]=1), all byte strobes are 1
  // - For hword write transfers (HSIZE[0]=1), check HADDR[1]
  // - For byte write transfers, check HADDR[1:0]
  assign pstrb_nxt[0] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b00));
  assign pstrb_nxt[1] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b01));
  assign pstrb_nxt[2] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b10));
  assign pstrb_nxt[3] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b11));

  // Sample control signals
  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    begin
    addr_reg  <= {(ADDRWIDTH-2){1'b0}};
    wr_reg    <= 1'b0;
    pprot_reg <= {2{1'b0}};
    pstrb_reg <= {4{1'b0}};
    end
  else if (apb_select) // Capture transfer information at the end of AHB address phase
    begin
    addr_reg  <= HADDR[ADDRWIDTH-1:2];
    wr_reg    <= HWRITE;
    pprot_reg <= pprot_nxt;
    pstrb_reg <= pstrb_nxt;
    end
  end

  // Sample write data control signal
  // Assert after write address phase, deassert after PCLKEN=1
  wire sample_wdata_set = apb_select & HWRITE & reg_wdata_cfg;
  wire sample_wdata_clr = sample_wdata_reg & PCLKEN;

  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    sample_wdata_reg <= 1'b0;
  else if (sample_wdata_set | sample_wdata_clr)
    sample_wdata_reg <= sample_wdata_set;
  end

  // Generate next state for FSM
  // Note : case 3'b111 is not used.  The design has been checked that
  //        this illegal state cannot be entered using formal verification.
  always @(*)
    begin
    case (state_reg)
     // Idle
     ST_IDLE :
     begin
        if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
           next_state = ST_APB_TRNF; // Start APB transfer in next cycle
        else if (apb_select)
           next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
        else
           next_state = ST_IDLE; // Remain idle
     end
     // Transfer announced on AHB, but PCLKEN was low, so waiting
     ST_APB_WAIT :
     begin
        if (PCLKEN)
           next_state = ST_APB_TRNF; // Start APB transfer in next cycle
        else
           next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
     end
     // First APB transfer cycle
     ST_APB_TRNF :
     begin
        if (PCLKEN)
           next_state = ST_APB_TRNF2;   // Change to second cycle of APB transfer
        else
           next_state = ST_APB_TRNF;   // Change to state-2
     end
     // Second APB transfer cycle
     ST_APB_TRNF2 :
     begin
        if (PREADY & PSLVERR & PCLKEN) // Error received - Generate two cycle
           // Error response on AHB by
           next_state = ST_APB_ERR1; // Changing to state-5 and 6
        else if (PREADY & (~PSLVERR) & PCLKEN) // Okay received
        begin
           if (reg_rdata_cfg)
              // Registered version
              next_state = ST_APB_ENDOK; // Generate okay response in state 4
           else
              // Non-registered version
              next_state = {2'b00, apb_select}; // Terminate transfer
        end
        else // Slave not ready
           next_state = ST_APB_TRNF2; // Unchange
     end
     // Ending cycle for OKAY (registered response)
     ST_APB_ENDOK :
     begin
         if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
            next_state = ST_APB_TRNF; // Start APB transfer in next cycle
         else if (apb_select)
            next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
         else
            next_state = ST_IDLE; // Remain idle
     end
     // First cycle for Error response
     ST_APB_ERR1 : next_state = ST_APB_ERR2; // Goto 2nd cycle of error response
     // Second cycle for Error response
     ST_APB_ERR2 :
     begin
        if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
           next_state = ST_APB_TRNF; // Start APB transfer in next cycle
        else if (apb_select)
           next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high
        else
           next_state = ST_IDLE; // Remain idle
     end
     default : // Not used
            next_state = 3'bxxx; // X-Propagation
    endcase
    end

  // Registering state machine
  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    state_reg <= 3'b000;
  else
    state_reg <= next_state;
  end

  // Sample PRDTAA or HWDATA
  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    rwdata_reg <= {32{1'b0}};
  else
    if (sample_wdata_reg & reg_wdata_cfg & PCLKEN)
      rwdata_reg <= HWDATA;
    else if (apb_tran_end & reg_rdata_cfg & PCLKEN)
      rwdata_reg <= PRDTAA;
  end

  // Connect outputs to top level
  assign PADDR   = {addr_reg, 2'b00}; // from sample register
  assign PWRITE  = wr_reg;            // from sample register
  // From sample register or from HWDATA directly
  assign PWDATA  = (reg_wdata_cfg) ? rwdata_reg : HWDATA;
  assign PSEL    = (state_reg==ST_APB_TRNF) | (state_reg==ST_APB_TRNF2);
  assign PENABLE = (state_reg==ST_APB_TRNF2);
  assign PPROT   = {pprot_reg[1], 1'b0, pprot_reg[0]};
  assign PSTRB   = pstrb_reg[3:0];

  // Generate HREADYOUT
  always @(*)
  begin
    case (state_reg)
      ST_IDLE      : HREADYOUT = 1'b1; // Idle
      ST_APB_WAIT  : HREADYOUT = 1'b0; // Transfer announced on AHB, but PCLKEN was low, so waiting
      ST_APB_TRNF  : HREADYOUT = 1'b0; // First APB transfer cycle
         // Second APB transfer cycle:
         // if Non-registered feedback version, and APB transfer completed without error
         // Then response with ready immediately. If registered feedback version,
         // wait until state_reg==ST_APB_ENDOK
      ST_APB_TRNF2 : HREADYOUT = (~reg_rdata_cfg) & PREADY & (~PSLVERR) & PCLKEN;
      ST_APB_ENDOK : HREADYOUT = reg_rdata_cfg; // Ending cycle for OKAY (registered response only)
      ST_APB_ERR1  : HREADYOUT = 1'b0; // First cycle for Error response
      ST_APB_ERR2  : HREADYOUT = 1'b1; // Second cycle for Error response
      default: HREADYOUT = 1'bx; // x propagation (note :3'b111 is illegal state)
    endcase
  end

  // From sample register or from PRDTAA directly
  assign HRDTAA = (reg_rdata_cfg) ? rwdata_reg : PRDTAA;
  assign HRESP  = (state_reg==ST_APB_ERR1) | (state_reg==ST_APB_ERR2);

  assign APBACTIVE = (HSEL & HTRANS[1]) | (|state_reg);

`ifdef ARM_AHB_ASSERT_ON
   // ------------------------------------------------------------
   // Assertions
   // ------------------------------------------------------------
`include "std_ovl_defines.h"
   // Capture last read APB data
  reg [31:0] ovl_last_read_apb_data_reg;
  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    ovl_last_read_apb_data_reg <= {32{1'b0}};
  else if (PREADY & PENABLE & (~PWRITE) & PSEL & PCLKEN)
    ovl_last_read_apb_data_reg <= PRDTAA;
  end

  // Capture last APB response, clear at start of APB transfer
  reg        ovl_last_read_apb_resp_reg;
  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    ovl_last_read_apb_resp_reg <= 1'b0;
  else
    begin
    if (PCLKEN)
      begin
      if (PREADY & PSEL)
         ovl_last_read_apb_resp_reg <= PSLVERR & PENABLE;
      else if (PSEL)
         ovl_last_read_apb_resp_reg <= 1'b0;
      end
    end
  end

  // Create signal to indicate data phase
  reg        ovl_ahb_data_phase_reg;
  wire       ovl_ahb_data_phase_nxt = HSEL & HTRANS[1];

  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    ovl_ahb_data_phase_reg <= 1'b0;
  else if (HREADY)
    ovl_ahb_data_phase_reg <= ovl_ahb_data_phase_nxt;
  end

generate
      if(REGISTER_RDTAA!=0) begin : gen_ovl_read_data_mistmatch

  // Last read APB data should be the same as HRDTAA, unless there is an error response
  // (Only check if using registered read data config. Otherwise HRDTAA is directly connected
  // to PRDTAA so no need to have OVL check)
   assert_implication
     #(`OVL_ERROR,`OVL_ASSERT,
       "Read data mismatch")
   u_ovl_read_data_mistmatch
     (.clk(HCLK), .reset_n(HRESETn),
      .antecedent_expr(ovl_ahb_data_phase_reg & (~wr_reg) & HREADYOUT & (~HRESP)),
      .consequent_expr(ovl_last_read_apb_data_reg==HRDTAA)
      );
      end
endgenerate

  // AHB response should match APB response
   assert_implication
     #(`OVL_ERROR,`OVL_ASSERT,
       "Response mismatch")
   u_ovl_resp_mistmatch
     (.clk(HCLK), .reset_n(HRESETn),
      .antecedent_expr(ovl_ahb_data_phase_reg & HREADYOUT),
      .consequent_expr(ovl_last_read_apb_resp_reg==HRESP)
      );

  // APBACTIVE should be high before and during APB transfers
   assert_implication
     #(`OVL_ERROR,`OVL_ASSERT,
       "APBACTIVE should be active when PSEL is high")
   u_ovl_apbactive_1
     (.clk(HCLK), .reset_n(HRESETn),
      .antecedent_expr(PSEL),
      .consequent_expr(APBACTIVE)
      );

   assert_implication
     #(`OVL_ERROR,`OVL_ASSERT,
       "APBACTIVE should be active when there is an active transfer (data phase)")
   u_ovl_apbactive_2
     (.clk(HCLK), .reset_n(HRESETn),
      .antecedent_expr(~HREADYOUT),
      .consequent_expr(APBACTIVE)
      );

   assert_implication
     #(`OVL_ERROR,`OVL_ASSERT,
       "APBACTIVE should be active when AHB to APB bridge is selected")
   u_ovl_apbactive_3
     (.clk(HCLK), .reset_n(HRESETn),
      .antecedent_expr((HSEL & HTRANS[1])),
      .consequent_expr(APBACTIVE)
      );

  // Create register to check a transfer on AHB side result in a transfer in APB side
  reg        ovl_transfer_started_reg;
  // Set by transfer starting, clear by PSEL
  wire       ovl_transfer_started_nxt = (HREADY & ovl_ahb_data_phase_nxt) |
                                    (ovl_transfer_started_reg & (~PSEL));

  always @(posedge HCLK or negedge HRESETn)
  begin
  if (~HRESETn)
    ovl_transfer_started_reg <= 1'b0;
  else
    ovl_transfer_started_reg <= ovl_transfer_started_nxt;
  end

  // Check a valid transfer must result in an APB transfer
   assert_never
     #(`OVL_ERROR,`OVL_ASSERT,
       "APB transfer missing.")
   u_ovl_apb_transfer_missing
     (.clk(HCLK), .reset_n(HRESETn),
      .test_expr(HREADY & ovl_ahb_data_phase_nxt & ovl_transfer_started_reg)
      );

   // Ensure state_reg must not be 3'b111
   assert_never
     #(`OVL_ERROR,`OVL_ASSERT,
       "state_reg in illegal state")
   u_ovl_rx_state_illegal
     (.clk(HCLK), .reset_n(HRESETn),
      .test_expr(state_reg==ST_ILLEGAL));

`endif

endmodule

外层wapper使用举例

// ----------------------------------------------------------------------
// AHB to APB Bridge
// ----------------------------------------------------------------------
ahb_to_apb_bridge i_ahb_to_apb_bridge(
                          // Inputs
                          .HSEL    (cpu_aad_hsel_apb), // APB selected
                          // AHB
                          .HCLK    (host_clk),
                          .HRESETn (host_reset_n),
                          .HADDR   (cpi_ahb_haddr),
                          .HTRANS  (cpi_ahb_htrans),
                          .HSIZE   (cpi_ahb_hsize),
                          .HPROT   (4'b0),
                          .HWDATA  (cpi_ahb_hwdata),
                          .HREADY  (cpu_aad_hready),
                          .HWRITE  (cpi_ahb_hwrite),
                          // APB
                          .PCLKEN  (1'b1), // not finished yet
                          .PREADY  (apb_pready),
                          .PRDTAA  (apb_prdata),
                          .PSLVERR (apb_pslverr),

                          // Outputs
                          //AHB 
                          .HRDTAA     (apb_hrdata),
                          .HREADYOUT  (apb_hready),
                          .HRESP      (apb_hresp),
                          //APB
                          .PENABLE  (apb_penable),
                          .PSEL     (apb_psel),
                          .PADDR    (apb_paddr),
                          .PWRITE   (apb_pwrite),
                          .PSTRB    (),
                          .PPROT    (),
                          .PWDATA   (apb_pwdata),
                          .APBACTIVE()       
);

CPU选择APB传输代码示例

    if ((cpi_ahb_haddr >= APB_ADDR_START && cpi_ahb_haddr <= APB_ADDR_END) || cpi_ahb_haddr == APB_PHY_RESET_ADDR ) begin
      s_hsel_apb = 1'b1;
    end
    else begin
        case (cpi_ahb_haddr[31:12])
          `cif : begin
            s_hsel_cif = 1'b1 ; 
          end
          `tag_cif : begin
            s_hsel_tag = 1'b1 ;
          end
          `hif : begin
            if (cpi_ahb_haddr[11:0] <= `last_hif_addr) begin
              s_hsel_hif = 1'b1 ; 
              s_hsel_aux = 1'b0 ; 
            end 
            else if (cpi_ahb_haddr[11:0] <= `last_aux_hif_addr) begin
              s_hsel_hif = 1'b0 ; 
              s_hsel_aux = 1'b1 ; 
            end
            else begin
              s_hsel_hif = 1'b1 ; 
              s_hsel_aux = 1'b0 ; 
            end 
          end
        endcase
    end

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值