Verilog FIFO設(shè)計(jì)

2022-05-20 14:35 更新

FIFO(First In First Out)是異步數(shù)據(jù)傳輸時(shí)經(jīng)常使用的存儲(chǔ)器。該存儲(chǔ)器的特點(diǎn)是數(shù)據(jù)先進(jìn)先出(后進(jìn)后出)。其實(shí),多位寬數(shù)據(jù)的異步傳輸問題,無(wú)論是從快時(shí)鐘到慢時(shí)鐘域,還是從慢時(shí)鐘到快時(shí)鐘域,都可以使用 FIFO 處理。

FIFO 原理

工作流程

復(fù)位之后,在寫時(shí)鐘和狀態(tài)信號(hào)的控制下,數(shù)據(jù)寫入 FIFO 中。RAM 的寫地址從 0 開始,每寫一次數(shù)據(jù)寫地址指針加一,指向下一個(gè)存儲(chǔ)單元。當(dāng) FIFO 寫滿后,數(shù)據(jù)將不能再寫入,否則數(shù)據(jù)會(huì)因覆蓋而丟失。

FIFO 數(shù)據(jù)為非空、或滿狀態(tài)時(shí),在讀時(shí)鐘和狀態(tài)信號(hào)的控制下,可以將數(shù)據(jù)從 FIFO 中讀出。RAM 的讀地址從 0 開始,每讀一次數(shù)據(jù)讀地址指針加一,指向下一個(gè)存儲(chǔ)單元。當(dāng) FIFO 讀空后,就不能再讀數(shù)據(jù),否則讀出的數(shù)據(jù)將是錯(cuò)誤的。

FIFO 的存儲(chǔ)結(jié)構(gòu)為雙口 RAM,所以允許讀寫同時(shí)進(jìn)行。典型異步 FIFO 結(jié)構(gòu)圖如下所示。端口及內(nèi)部信號(hào)將在代碼編寫時(shí)進(jìn)行說(shuō)明。


讀寫時(shí)刻

關(guān)于寫時(shí)刻,只要 FIFO 中數(shù)據(jù)為非滿狀態(tài),就可以進(jìn)行寫操作;如果 FIFO 為滿狀態(tài),則禁止再寫數(shù)據(jù)。

關(guān)于讀時(shí)刻,只要 FIFO 中數(shù)據(jù)為非空狀態(tài),就可以進(jìn)行讀操作;如果 FIFO 為空狀態(tài),則禁止再讀數(shù)據(jù)。

不管怎樣,一段正常讀寫 FIFO 的時(shí)間段,如果讀寫同時(shí)進(jìn)行,則要求寫 FIFO 速率不能大于讀速率。

讀空狀態(tài)

開始復(fù)位時(shí),F(xiàn)IFO 沒有數(shù)據(jù),空狀態(tài)信號(hào)是有效的。當(dāng) FIFO 中被寫入數(shù)據(jù)后,空狀態(tài)信號(hào)拉低無(wú)效。當(dāng)讀數(shù)據(jù)地址追趕上寫地址,即讀寫地址都相等時(shí),F(xiàn)IFO 為空狀態(tài)。

因?yàn)槭钱惒?nbsp;FIFO,所以讀寫地址進(jìn)行比較時(shí),需要同步打拍邏輯,就需要耗費(fèi)一定的時(shí)間。所以空狀態(tài)的指示信號(hào)不是實(shí)時(shí)的,會(huì)有一定的延時(shí)。如果在這段延遲時(shí)間內(nèi)又有新的數(shù)據(jù)寫入 FIFO,就會(huì)出現(xiàn)空狀態(tài)指示信號(hào)有效,但是 FIFO 中其實(shí)存在數(shù)據(jù)的現(xiàn)象。

嚴(yán)格來(lái)講該空狀態(tài)指示是錯(cuò)誤的。但是產(chǎn)生空狀態(tài)的意義在于防止讀操作對(duì)空狀態(tài)的 FIFO 進(jìn)行數(shù)據(jù)讀取。產(chǎn)生空狀態(tài)信號(hào)時(shí),實(shí)際 FIFO 中有數(shù)據(jù),相當(dāng)于提前判斷了空狀態(tài)信號(hào),此時(shí)不再進(jìn)行讀 FIFO 數(shù)據(jù)操作也是安全的。所以,該設(shè)計(jì)從應(yīng)用上來(lái)說(shuō)是沒有問題的。

寫滿狀態(tài)

開始復(fù)位時(shí),F(xiàn)IFO 沒有數(shù)據(jù),滿信號(hào)是無(wú)效的。當(dāng) FIFO 中被寫入數(shù)據(jù)后,此時(shí)讀操作不進(jìn)行或讀速率相對(duì)較慢,只要寫數(shù)據(jù)地址超過讀數(shù)據(jù)地址一個(gè) FIFO 深度時(shí),便會(huì)產(chǎn)生滿狀態(tài)信號(hào)。此時(shí)寫地址和讀地址也是相等的,但是意義是不一樣的。


此時(shí)經(jīng)常使用多余的 1bit 分別當(dāng)做讀寫地址的拓展位,來(lái)區(qū)分讀寫地址相同的時(shí)候,F(xiàn)IFO 的狀態(tài)是空還是滿狀態(tài)。當(dāng)讀寫地址與拓展位均相同的時(shí)候,表明讀寫數(shù)據(jù)的數(shù)量是一致的,則此時(shí) FIFO 是空狀態(tài)。如果讀寫地址相同,拓展位為相反數(shù),表明寫數(shù)據(jù)的數(shù)量已經(jīng)超過讀數(shù)據(jù)數(shù)量的一個(gè) FIFO 深度了,此時(shí) FIFO 是滿狀態(tài)。當(dāng)然,此條件成立的前提是空狀態(tài)禁止讀操作、滿狀態(tài)禁止寫操作。

同理,由于異步延遲邏輯的存在,滿狀態(tài)信號(hào)也不是實(shí)時(shí)的。但是也相當(dāng)于提前判斷了滿狀態(tài)信號(hào),此時(shí)不再進(jìn)行寫 FIFO 操作也不會(huì)影響應(yīng)用的正確性。

FIFO 設(shè)計(jì)

設(shè)計(jì)要求

為設(shè)計(jì)應(yīng)用于各種場(chǎng)景的 FIFO,這里對(duì)設(shè)計(jì)提出如下要求:

  1. FIFO 深度、寬度參數(shù)化,輸出空、滿狀態(tài)信號(hào),并輸出一個(gè)可配置的滿狀態(tài)信號(hào)。當(dāng) FIFO 內(nèi)部數(shù)據(jù)達(dá)到設(shè)置的參數(shù)數(shù)量時(shí),拉高該信號(hào)。
  2. 輸入數(shù)據(jù)和輸出數(shù)據(jù)位寬可以不一致,但要保證寫數(shù)據(jù)、寫地址位寬與讀數(shù)據(jù)、讀地址位寬的一致性。例如寫數(shù)據(jù)位寬 8bit,寫地址位寬為 6bit(64 個(gè)數(shù)據(jù))。如果輸出數(shù)據(jù)位寬要求 32bit,則輸出地址位寬應(yīng)該為 4bit(16 個(gè)數(shù)據(jù))。
  3. FIFO 是異步的,即讀寫控制信號(hào)來(lái)自不同的時(shí)鐘域。輸出空、滿狀態(tài)信號(hào)之前,讀寫地址信號(hào)要用格雷碼做同步處理,通過減少多位寬信號(hào)的翻轉(zhuǎn)來(lái)減少打拍法同步時(shí)數(shù)據(jù)的傳輸錯(cuò)誤。 格雷碼與二進(jìn)制之間的轉(zhuǎn)換如下圖所示。


雙口 RAM 設(shè)計(jì)

RAM 端口參數(shù)可配置,讀寫位寬可以不一致。建議 memory 數(shù)組定義時(shí),以長(zhǎng)位寬地址、短位寬數(shù)據(jù)的參數(shù)為參考,方便數(shù)組變量進(jìn)行選擇訪問。

Verilog 描述如下。

module  ramdp
    #(  parameter       AWI     = 5 ,
        parameter       AWO     = 7 ,
        parameter       DWI     = 64 ,
        parameter       DWO     = 16
        )
    (
        input                   CLK_WR , //寫時(shí)鐘
        input                   WR_EN ,  //寫使能
        input [AWI-1:0]         ADDR_WR ,//寫地址
        input [DWI-1:0]         D ,      //寫數(shù)據(jù)
        input                   CLK_RD , //讀時(shí)鐘
        input                   RD_EN ,  //讀使能
        input [AWO-1:0]         ADDR_RD ,//讀地址
        output reg [DWO-1:0]    Q        //讀數(shù)據(jù)
     );
   //輸出位寬大于輸入位寬,求取擴(kuò)大的倍數(shù)及對(duì)應(yīng)的位數(shù)
   parameter       EXTENT       = DWO/DWI ;
   parameter       EXTENT_BIT   = AWI-AWO > 0 ? AWI-AWO : 'b1 ;
   //輸入位寬大于輸出位寬,求取縮小的倍數(shù)及對(duì)應(yīng)的位數(shù)
   parameter       SHRINK       = DWI/DWO ;
   parameter       SHRINK_BIT   = AWO-AWI > 0 ? AWO-AWI : 'b1;

   genvar i ;
   generate
      //數(shù)據(jù)位寬展寬(地址位寬縮?。?      if (DWO >= DWI) begin
         //寫邏輯,每時(shí)鐘寫一次
         reg [DWI-1:0]         mem [(1<<AWI)-1 : 0] ;
         always @(posedge CLK_WR) begin
            if (WR_EN) begin
               mem[ADDR_WR]  <= D ;
            end
         end

         //讀邏輯,每時(shí)鐘讀 4 次
         for (i=0; i<EXTENT; i=i+1) begin
            always @(posedge CLK_RD) begin
               if (RD_EN) begin
                  Q[(i+1)*DWI-1: i*DWI]  <= mem[(ADDR_RD*EXTENT) + i ] ;
               end
            end
         end
      end

      //=================================================
      //數(shù)據(jù)位寬縮小(地址位寬展寬)
      else begin
         //寫邏輯,每時(shí)鐘寫 4 次
         reg [DWO-1:0]         mem [(1<<AWO)-1 : 0] ;
         for (i=0; i<SHRINK; i=i+1) begin
            always @(posedge CLK_WR) begin
               if (WR_EN) begin
                  mem[(ADDR_WR*SHRINK)+i]  <= D[(i+1)*DWO -1: i*DWO] ;
               end
            end
         end

         //讀邏輯,每時(shí)鐘讀 1 次
         always @(posedge CLK_RD) begin
            if (RD_EN) begin
                Q <= mem[ADDR_RD] ;
            end
         end
      end
   endgenerate

endmodule

計(jì)數(shù)器設(shè)計(jì)

計(jì)數(shù)器用于產(chǎn)生讀寫地址信息,位寬可配置,不需要設(shè)置結(jié)束值,讓其溢出后自動(dòng)重新計(jì)數(shù)即可。Verilg 描述如下。

module  ccnt
  #(parameter W )
   (
    input              rstn ,
    input              clk ,
    input              en ,
    output [W-1:0]     count
    );

   reg [W-1:0]          count_r ;
   always @(posedge clk or negedge rstn) begin
      if (!rstn) begin
         count_r        <= 'b0 ;
      end
      else if (en) begin
         count_r        <= count_r + 1'b1 ;
      end
   end
   assign count = count_r ;

endmodule

FIFO 設(shè)計(jì)

該模塊為 FIFO 的主體部分,產(chǎn)生讀寫控制邏輯,并產(chǎn)生空、滿、可編程滿狀態(tài)信號(hào)。

鑒于篇幅原因,這里只給出讀數(shù)據(jù)位寬大于寫數(shù)據(jù)位寬的邏輯代碼,寫數(shù)據(jù)位寬大于讀數(shù)據(jù)位寬的代碼描述詳見附件。

module  fifo
    #(  parameter       AWI        = 5 ,
        parameter       AWO        = 3 ,
        parameter       DWI        = 4 ,
        parameter       DWO        = 16 ,
        parameter       PROG_DEPTH = 16) //可設(shè)置深度
    (
        input                   rstn,  //讀寫使用一個(gè)復(fù)位
        input                   wclk,  //寫時(shí)鐘
        input                   winc,  //寫使能
        input [DWI-1: 0]        wdata, //寫數(shù)據(jù)

        input                   rclk,  //讀時(shí)鐘
        input                   rinc,  //讀使能
        output [DWO-1 : 0]      rdata, //讀數(shù)據(jù)

        output                  wfull,    //寫滿標(biāo)志
        output                  rempty,   //讀空標(biāo)志
        output                  prog_full //可編程滿標(biāo)志
     );

   //輸出位寬大于輸入位寬,求取擴(kuò)大的倍數(shù)及對(duì)應(yīng)的位數(shù)
   parameter       EXTENT       = DWO/DWI ;
   parameter       EXTENT_BIT   = AWI-AWO ;
   //輸出位寬小于輸入位寬,求取縮小的倍數(shù)及對(duì)應(yīng)的位數(shù)
   parameter       SHRINK       = DWI/DWO ;
   parameter       SHRINK_BIT   = AWO-AWI ;

   //==================== push/wr counter ===============
   wire [AWI-1:0]      waddr ;
   wire                wover_flag ; //多使用一位做寫地址拓展
   ccnt         #(.W(AWI+1))            
   u_push_cnt(
      .rstn           (rstn),
      .clk            (wclk),
      .en             (winc && !wfull), //full 時(shí)禁止寫
      .count          ({wover_flag, waddr})
        );

   //============== pop/rd counter ===================
   wire [AWO-1:0]            raddr ;
   wire                      rover_flag ;  //多使用一位做讀地址拓展
   ccnt         #(.W(AWO+1))    
   u_pop_cnt(
      .rstn           (rstn),
      .clk            (rclk),
      .en             (rinc & !rempty), //empyt 時(shí)禁止讀
      .count          ({rover_flag, raddr})
      );

   //==============================================
   //窄數(shù)據(jù)進(jìn),寬數(shù)據(jù)出
generate
   if (DWO >= DWI) begin : EXTENT_WIDTH

      //格雷碼轉(zhuǎn)換
      wire [AWI:0] wptr    = ({wover_flag, waddr}>>1) ^ ({wover_flag, waddr}) ;
      //將寫數(shù)據(jù)指針同步到讀時(shí)鐘域
      reg [AWI:0]  rq2_wptr_r0 ;
      reg [AWI:0]  rq2_wptr_r1 ;
      always @(posedge rclk or negedge rstn) begin
         if (!rstn) begin
            rq2_wptr_r0     <= 'b0 ;
            rq2_wptr_r1     <= 'b0 ;
         end
         else begin
            rq2_wptr_r0     <= wptr ;
            rq2_wptr_r1     <= rq2_wptr_r0 ;
         end
      end

      //格雷碼轉(zhuǎn)換
      wire [AWI-1:0] raddr_ex = raddr << EXTENT_BIT ;
      wire [AWI:0]   rptr     = ({rover_flag, raddr_ex}>>1) ^ ({rover_flag, raddr_ex}) ;
      //將讀數(shù)據(jù)指針同步到寫時(shí)鐘域
      reg [AWI:0]    wq2_rptr_r0 ;
      reg [AWI:0]    wq2_rptr_r1 ;
      always @(posedge wclk or negedge rstn) begin
         if (!rstn) begin
            wq2_rptr_r0     <= 'b0 ;
            wq2_rptr_r1     <= 'b0 ;
         end
         else begin
            wq2_rptr_r0     <= rptr ;
            wq2_rptr_r1     <= wq2_rptr_r0 ;
         end
      end

      //格雷碼反解碼
      //如果只需要空、滿狀態(tài)信號(hào),則不需要反解碼
      //因?yàn)榭删幊虧M狀態(tài)信號(hào)的存在,地址反解碼后便于比較
      reg [AWI:0]       wq2_rptr_decode ;
      reg [AWI:0]       rq2_wptr_decode ;
      integer           i ;
      always @(*) begin
         wq2_rptr_decode[AWI] = wq2_rptr_r1[AWI];
         for (i=AWI-1; i>=0; i=i-1) begin
            wq2_rptr_decode[i] = wq2_rptr_decode[i+1] ^ wq2_rptr_r1[i] ;
         end
      end
      always @(*) begin
         rq2_wptr_decode[AWI] = rq2_wptr_r1[AWI];
         for (i=AWI-1; i>=0; i=i-1) begin
            rq2_wptr_decode[i] = rq2_wptr_decode[i+1] ^ rq2_wptr_r1[i] ;
         end
      end

      //讀寫地址、拓展位完全相同是,為空狀態(tài)
      assign rempty    = (rover_flag == rq2_wptr_decode[AWI]) &&
                         (raddr_ex >= rq2_wptr_decode[AWI-1:0]);
      //讀寫地址相同、拓展位不同,為滿狀態(tài)
      assign wfull     = (wover_flag != wq2_rptr_decode[AWI]) &&
                         (waddr >= wq2_rptr_decode[AWI-1:0]) ;
      //拓展位一樣時(shí),寫地址必然不小于讀地址
      //拓展位不同時(shí),寫地址部分比如小于讀地址,實(shí)際寫地址要增加一個(gè)FIFO深度
      assign prog_full  = (wover_flag == wq2_rptr_decode[AWI]) ?
                          waddr - wq2_rptr_decode[AWI-1:0] >= PROG_DEPTH-1 :
                          waddr + (1<<AWI) - wq2_rptr_decode[AWI-1:0] >= PROG_DEPTH-1;

      //雙口 ram 例化
      ramdp
        #( .AWI     (AWI),
           .AWO     (AWO),
           .DWI     (DWI),
           .DWO     (DWO))
      u_ramdp
        (
         .CLK_WR          (wclk),
         .WR_EN           (winc & !wfull), //寫滿時(shí)禁止寫
         .ADDR_WR         (waddr),
         .D               (wdata[DWI-1:0]),
         .CLK_RD          (rclk),
         .RD_EN           (rinc & !rempty), //讀空時(shí)禁止讀
         .ADDR_RD         (raddr),
         .Q               (rdata[DWO-1:0])
         );

   end

   //==============================================
   //big in and small out
   /*
   else begin: SHRINK_WIDTH
      ……
   end
   */
 endgenerate
endmodule

FIFO 調(diào)用

下面可以調(diào)用設(shè)計(jì)的 FIFO,完成多位寬數(shù)據(jù)傳輸?shù)漠惒教幚怼?

寫數(shù)據(jù)位寬為 4bit,寫深度為 32。

讀數(shù)據(jù)位寬為 16bit,讀深度為 8,可配置 full 深度為 16。

module  fifo_s2b(
        input                   rstn,
        input [4-1: 0]          din,     //異步寫數(shù)據(jù)
        input                   din_clk, //異步寫時(shí)鐘
        input                   din_en,  //異步寫使能

        output [16-1 : 0]       dout,      //同步后數(shù)據(jù)
        input                   dout_clk,  //同步使用時(shí)鐘
        input                   dout_en ); //同步數(shù)據(jù)使能

   wire         fifo_empty, fifo_full, prog_full ;
   wire         rd_en_wir ;
   wire [15:0]  dout_wir ;

   //讀空狀態(tài)時(shí)禁止讀,否則一直讀
   assign rd_en_wir     = fifo_empty ? 1'b0 : 1'b1 ;

   fifo  #(.AWI(5), .AWO(3), .DWI(4), .DWO(16), .PROG_DEPTH(16))
     u_buf_s2b(
        .rstn           (rstn),
        .wclk           (din_clk),
        .winc           (din_en),
        .wdata          (din),

        .rclk           (dout_clk),
        .rinc           (rd_en_wir),
        .rdata          (dout_wir),

        .wfull          (fifo_full),
        .rempty         (fifo_empty),
        .prog_full      (prog_full));

   //緩存同步后的數(shù)據(jù)和使能
   reg          dout_en_r ;
   always @(posedge dout_clk or negedge rstn) begin
      if (!rstn) begin
         dout_en_r       <= 1'b0 ;
      end
      else begin
         dout_en_r       <= rd_en_wir ;
      end
   end
   assign       dout    = dout_wir ;
   assign       dout_en = dout_en_r ;

endmodule

testbench

`timescale 1ns/1ns
`define         SMALL2BIG
module test ;

`ifdef SMALL2BIG
   reg          rstn ;
   reg          clk_slow, clk_fast ;
   reg [3:0]    din ;
   reg          din_en ;
   wire [15:0]  dout ;
   wire         dout_en ;

   //reset
   initial begin
      clk_slow  = 0 ;
      clk_fast  = 0 ;
      rstn      = 0 ;
      #50 rstn  = 1 ;
   end

   //讀時(shí)鐘 clock_slow 較快于寫時(shí)鐘 clk_fast 的 1/4
   //保證讀數(shù)據(jù)稍快于寫數(shù)據(jù)
   parameter CYCLE_WR = 40 ;
   always #(CYCLE_WR/2/4) clk_fast = ~clk_fast ;
   always #(CYCLE_WR/2-1) clk_slow = ~clk_slow ;

   //data generate
   initial begin
      din       = 16'h4321 ;
      din_en    = 0 ;
      wait (rstn) ;
      //(1) 測(cè)試 full、prog_full、empyt 信號(hào)
      force test.u_data_buf2.u_buf_s2b.rinc = 1'b0 ;
      repeat(32) begin
         @(negedge clk_fast) ;
         din_en = 1'b1 ;
         din    = {$random()} % 16;
      end
      @(negedge clk_fast) din_en = 1'b0 ;

      //(2) 測(cè)試數(shù)據(jù)讀寫
      #500 ;
      rstn = 0 ;
      #10 rstn = 1 ;
      release test.u_data_buf2.u_buf_s2b.rinc;
      repeat(100) begin
         @(negedge clk_fast) ;
         din_en = 1'b1 ;
         din    = {$random()} % 16;
      end

      //(3) 停止讀取再一次測(cè)試 empyt、full、prog_full 信號(hào)
      force test.u_data_buf2.u_buf_s2b.rinc = 1'b0 ;
      repeat(18) begin
         @(negedge clk_fast) ;
         din_en = 1'b1 ;
         din    = {$random()} % 16;
      end
   end

   fifo_s2b u_data_buf2(
        .rstn           (rstn),
        .din            (din),
        .din_clk        (clk_fast),
        .din_en         (din_en),

        .dout           (dout),
        .dout_clk       (clk_slow),
        .dout_en        (dout_en));

`else
`endif

   //stop sim
   initial begin
      forever begin
         #100;
         if ($time >= 5000)  $finish ;
      end
   end

endmodule

仿真分析

根據(jù) testbench 中的 3 步測(cè)試激勵(lì),分析如下:

測(cè)試 (1) : FIFO 端口及一些內(nèi)部信號(hào)時(shí)序結(jié)果如下。

由圖可知,F(xiàn)IFO 內(nèi)部開始寫數(shù)據(jù),空狀態(tài)信號(hào)拉低之前有一段時(shí)間延遲,這是同步讀寫地址信息導(dǎo)致的。

由于此時(shí)沒有進(jìn)行讀 FIFO 操作,相對(duì)于寫數(shù)據(jù)操作,full 和 prog_full 拉高幾乎沒有延遲。


測(cè)試 (2) : FIFO 同時(shí)進(jìn)行讀寫時(shí),數(shù)字頂層異步處理模塊的端口信號(hào)如下所示,兩圖分別顯示了數(shù)據(jù)開始傳輸、結(jié)束傳輸時(shí)的讀取過程。

由圖可知,數(shù)據(jù)在開始、末尾均能正確傳輸,完成了不同時(shí)鐘域之間多位寬數(shù)據(jù)的異步處理。



測(cè)試 (3) :整個(gè) FIFO 讀寫行為及讀停止的時(shí)序仿真圖如下所示。

由圖可知,讀寫同時(shí)進(jìn)行時(shí),讀空狀態(tài)信號(hào) rempty 會(huì)拉低,表明 FIFO 中有數(shù)據(jù)寫入。一方面讀數(shù)據(jù)速率稍高于寫速率,且數(shù)據(jù)之間傳輸會(huì)有延遲,所以中間過程中 rempty 會(huì)有拉高的行為。

讀寫過程中,full 與 prog_full 信號(hào)一直為低,說(shuō)明 FIFO 中數(shù)據(jù)并沒有到達(dá)一定的數(shù)量。當(dāng)停止讀操作后,兩個(gè) full 信號(hào)不久便拉高,表明 FIFO 已滿。仔細(xì)對(duì)比讀寫地址信息,F(xiàn)IFO 行為沒有問題。


完整的 FIFO 設(shè)計(jì)見附件,包括輸入數(shù)據(jù)位寬小于輸出數(shù)據(jù)位寬時(shí)的異步設(shè)計(jì)和仿真。

點(diǎn)擊這里下載源碼


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)