FPGA設計心得(6)Aurora IP核例子簡析與仿真(framing版)


  • GEN模塊分析web

  • CHECK模塊分析微信

  • 整體仿真併發

  • 發送模塊仿真dom

  • 接收模塊仿真編輯器


背景

博客首發地址:aurora佈局

因爲微信公衆號的編輯器太難用,我已經差很少放棄在微信公衆號上寫東西了,這篇博客主要是測試mdnice這個編輯器怎麼樣,本文直接從CSDN的MD上覆制的內容(有點卡)。學習


下面是原文:測試

熬夜繼續寫Aurora系列博文!今天明顯狀態不如昨天,眼皮有點澀澀的,堅持一下。若是不是寫博客,我根本沒有動力那麼認真看,認真思考這個東西,這可能就是寫博客輸出的魅力吧。儘管streaming格式很簡單,可是確定不如framing強大,不少項目仍是用framing用戶接口,所以有必要對此進行仿真分析來認識下。flex

博客首頁完成時間:次日晚凌晨4點左右。spa

定製framing接口的IP核

很簡單,靈活配置以下參數便可:

在這裏插入圖片描述

本例選擇小端模式。

FLow Control 暫時選擇爲None。(有必要後面專門研究,暫時最主要的仍是弄懂用戶接口信號的用法!)

爲分析方面,選擇單通道傳輸數據。

生成示例工程並分析

在這裏插入圖片描述

如圖,右擊IP核,打開例子程序,保存到一個位置,便可自動打開例子工程。

對於咱們用戶來講,最重要仍是咱們的用戶程序,經過用戶程序模塊與Aurora IP核交互,生成數據發出以及接收IP核傳輸的數據。

在這裏插入圖片描述

文末一樣會分享示例工程,因此這裏就不把源碼貼出來,佔用篇幅,給閱讀帶來不便。

GEN模塊分析

先打開gen模塊,對於該模塊,有一段描述:

// Description: This module is a pattern generator to test the Aurora// designs in hardware. It generates data and passes it// through the Aurora channel. If connected to a framing// interface, it generates frames of varying size and// separation. LFSR is used to generate the pseudo-random// data and lower bits of LFSR are connected to REM bus.

翻譯過來:

該模塊是一個模式生成器,用於在硬件中測試Aurora設計。它生成數據並將其經過Aurora通道。若是鏈接到成幀接口,它將生成大小和間隔不一樣的幀。LFSR用於生成僞隨機數據,而且LFSR的低位鏈接到REM總線。

首先,讀了這段描述,通常確定不知道具體幹啥的,可是大概知道是生成一系列數據,併發送出去,並且用的是framing數據格式。

讓咱們看看具體內容:

看程序首先看輸入輸出:

 // User Interfaceoutput [0:15] TX_D;output TX_REM;output TX_SOF_N;output TX_EOF_N;output TX_SRC_RDY_N;input TX_DST_RDY_N;
// System Interfaceinput USER_CLK;input RESET; input CHANNEL_UP;

從這幾個信號用戶接口,可見,有一些咱們熟悉的接口,可是和axi接口的名字起的不同罷了;

在這裏插入圖片描述

讓咱們對應下:

從以下接口方向能夠判定:

s_axi_tx_tready爲

input TX_DST_RDY_N;

兩者之間的關係:

做爲gen模塊用戶邏輯的條件,不管是tready仍是RDY_N,有效便可。

tready爲RDY_N的反,從以N結尾也該明白了。

也不賣關子了,其餘的等價:

 // User Interfaceoutput [0:15] TX_D; //dataoutput TX_REM; //?output TX_SOF_N; // start of frameoutput TX_EOF_N; // end of frameoutput TX_SRC_RDY_N; // validinput TX_DST_RDY_N; //tready

當Aurora通道還未準備好時,須要設計復位,讓通道出於復位狀態:

 always @ (posedge USER_CLK) begin if(RESET) channel_up_cnt <= `DLY 5'd0; else if(CHANNEL_UP) if(&channel_up_cnt) channel_up_cnt <= `DLY channel_up_cnt; else  channel_up_cnt <= `DLY channel_up_cnt + 1'b1; else channel_up_cnt <= `DLY 5'd0; end
assign dly_data_xfer = (&channel_up_cnt);
//Generate RESET signal when Aurora channel is not ready assign reset_c = RESET || !dly_data_xfer;

從上面的計數條件,可見CHANNEL_UP爲通道準備好的標誌,當其有效時,channel_up_cnt則從0一直計數到5'b1111_1並保持;不然,channel_up_cnt爲0;這樣的話,當CHANNEL_UP無效時,dly_data_xfer爲0,那麼reset_c爲1,即處於復位狀態。

當CHANNEL_UP爲1,也即有效時,也會計數一段時間,確保穩定,以後dly_data_xfer爲1,那麼reset_c的值取決於RESET,RESET無效時,中止復位。這樣確保了,RESET早就中止了復位,而通道還未準備好,等通道準備好了以後,才中止復位,發送邏輯開始有效執行。

下面繼續分析數據傳輸的部分:

//______________________________ Transmit Data __________________________________  //Generate random data using XNOR feedback LFSR always @(posedge USER_CLK) if(reset_c) begin data_lfsr_r <= `DLY 16'hABCD; //random seed value end else if(!TX_DST_RDY_N && !idle_r) begin data_lfsr_r <= `DLY {!{data_lfsr_r[3]^data_lfsr_r[12]^data_lfsr_r[14]^data_lfsr_r[15]}, data_lfsr_r[0:14]}; end    //Connect TX_D to the DATA LFSR assign TX_D = {1{data_lfsr_r}};

可見,要發送的數據是一個有規則產生的隨機數據,data_lfsr_r的初始值做爲隨機數的種子,以後經過異或非的方式產生隨機數。

這種產生隨機數的方式屬於線性反饋移位寄存器

上面出現了一個陌生的變量idle_r:

 //State registers for one-hot state machinereg idle_r;reg single_cycle_frame_r;reg sof_r;reg data_cycle_r;reg eof_r;  wire reset_c;//*********************************Wire Declarations********************************** wire ifg_done_c;  //Next state signals for one-hot state machinewire next_idle_c;wire next_single_cycle_frame_c;wire next_sof_c;wire next_data_cycle_c;wire next_eof_c;

它是狀態機變量,衆多爲了描述狀態機而設的變量之一。下面即是狀態機部分,能夠看出,是一個三段式狀態機,很講究!

使用狀態機的目的在於肯定 frame的起始,結束以及要發送數據仍是什麼也不發送等。

 //_____________________________ Framing State machine______________________________ //Use a state machine to determine whether to start a frame, end a frame, send //data or send nothing  //State registers for 1-hot state machine always @(posedge USER_CLK) if(reset_c) begin idle_r <= `DLY 1'b1; single_cycle_frame_r <= `DLY 1'b0; sof_r <= `DLY 1'b0; data_cycle_r <= `DLY 1'b0; eof_r <= `DLY 1'b0; end else if(!TX_DST_RDY_N) begin idle_r <= `DLY next_idle_c; single_cycle_frame_r <= `DLY next_single_cycle_frame_c; sof_r <= `DLY next_sof_c; data_cycle_r <= `DLY next_data_cycle_c; eof_r <= `DLY next_eof_c; end   //Nextstate logic for 1-hot state machine assign next_idle_c = !ifg_done_c && (single_cycle_frame_r || eof_r || idle_r);  assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) && (idle_r || single_cycle_frame_r || eof_r);  assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) && (idle_r || single_cycle_frame_r || eof_r);  assign next_data_cycle_c = (frame_size_r != bytes_sent_r) && (sof_r || data_cycle_r);  assign next_eof_c = (frame_size_r == bytes_sent_r) && (sof_r || data_cycle_r);   //Output logic for 1-hot state machine always @(posedge USER_CLK) if(reset_c) begin TX_SOF_N <= `DLY 1'b1; TX_EOF_N <= `DLY 1'b1; TX_SRC_RDY_N <= `DLY 1'b1;  end else if(!TX_DST_RDY_N) begin TX_SOF_N <= `DLY !(sof_r || single_cycle_frame_r); TX_EOF_N <= `DLY !(eof_r || single_cycle_frame_r); TX_SRC_RDY_N <= `DLY idle_r; end

程序設計的是一個所謂的獨熱碼狀態機,且不是通常的獨熱碼設計方法,相似於:hdlbits,獨熱碼狀態機設計,很是重要

這個狀態機有5個狀態,每循環一次,就能夠發送一幀數據。

五個狀態以下:

reg idle_r; // 空閒狀態reg single_cycle_frame_r; //單字幀,也就是一幀數據只有一個字或者少於一個字長reg sof_r; //幀起始reg data_cycle_r; //有效賦值數據reg eof_r; //幀結束

因爲是獨熱碼,故都是一位變量;

次態變量:

 //Next state signals for one-hot state machinewire next_idle_c;wire next_single_cycle_frame_c;wire next_sof_c;wire next_data_cycle_c;wire next_eof_c;

對應的次態部分代碼:

//Nextstate logic for 1-hot state machine assign next_idle_c = !ifg_done_c && (single_cycle_frame_r || eof_r || idle_r);  assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) && (idle_r || single_cycle_frame_r || eof_r);  assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) && (idle_r || single_cycle_frame_r || eof_r);  assign next_data_cycle_c = (frame_size_r != bytes_sent_r) && (sof_r || data_cycle_r);  assign next_eof_c = (frame_size_r == bytes_sent_r) && (sof_r || data_cycle_r);

可見,出現了不少條件來組成獨熱碼:如:

ifg_done_cframe_size_rbytes_sent_r

要搞清楚這些輸入的含義,才能更好理解,根據次態生成部分以及輸出生成部分,還能夠畫出狀態轉移圖或者狀態轉移表,這是後面的工做(看心情)。

都在這裏   :

//Use a counter to determine the size of the next frame to send always @(posedge USER_CLK) if(reset_c)  frame_size_r <= `DLY 8'h00; else if(single_cycle_frame_r || eof_r) frame_size_r <= `DLY frame_size_r + 1;  //Use a second counter to determine how many bytes of the frame have already been sent always @(posedge USER_CLK) if(reset_c) bytes_sent_r <= `DLY 8'h00; else if(sof_r) bytes_sent_r <= `DLY 8'h01; else if(!TX_DST_RDY_N && !idle_r) bytes_sent_r <= `DLY bytes_sent_r + 1;   //Use a freerunning counter to determine the IFG always @(posedge USER_CLK) if(reset_c) ifg_size_r <= `DLY 4'h0; else ifg_size_r <= `DLY ifg_size_r + 1;  //IFG is done when ifg_size register is 0 assign ifg_done_c = (ifg_size_r == 4'h0);

frame_size_r是一個計數器變量,使用計數器肯定要發送的一幀數據的大小;

同理,bytes_sent_r   使用第二個計數器來肯定已經發送了多少個幀字節;

最難理解的屬於ifg了?

這是什麼玩意?

經過查閱資料恍然大悟:IFG是Interframe Gap的縮寫,意思是幀與幀之間的間距。

爲何要有這個東西呢?簡單地說,就是爲了防止幀間距太小,而致使丟幀等,也就是說發送完一幀數據後,給下一幀數據的發送預留緩衝時間。

從程序中也能看出來:

第一部分:

 //Use a freerunning counter to determine the IFG always @(posedge USER_CLK) if(reset_c) ifg_size_r <= `DLY 4'h0; else ifg_size_r <= `DLY ifg_size_r + 1;  //IFG is done when ifg_size register is 0 assign ifg_done_c = (ifg_size_r == 4'h0);

第二部分:

 //Nextstate logic for 1-hot state machine assign next_idle_c = !ifg_done_c && (single_cycle_frame_r || eof_r || idle_r);  assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) && (idle_r || single_cycle_frame_r || eof_r);  assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) && (idle_r || single_cycle_frame_r || eof_r);

ifg_size_r爲計數變量,一直計數,計數滿了以後溢出,自身 變爲零,繼續計數,一直如此。

當ifg_size_r不爲零的時候,狀態機出於idle狀態,也就是空閒狀態,等溢出以後的下一個週期,就能夠進入下一個狀態了,發送數據。

說了這麼多,其實狀態機 描述的就是一個幀數據的發送過程:

在這裏插入圖片描述

idle_r狀態不發送數據;

single_cycle_frame_r這個狀態何時會進入呢?就是要發送的數據就一個字或者更少,就進入這個狀態,由於framing協議要求,其實標誌sof和結束標誌eof都要有效(此時);

sof_r:若是要發送的數據多於一個字,那麼安裝狀況就要分爲幾個週期完成數據發送;那麼此時就會進入發送首字的狀態;

data_cycle_r:這個狀態,繼續發送中間字;

eof_r:這就是要發送最後一個字了。

好了,咱們的發送過程就講完了。

CHECK模塊分析

若是你知道了發送的過程,那收還不容易嗎?

通訊雙方要按規矩辦事,這個規矩就是協議!

首先看下輸入輸出定義,這也是看程序的第一步:

 // User Interfaceinput [0:15] RX_D;input RX_REM;input RX_SOF_N;input RX_EOF_N;input RX_SRC_RDY_N;
// System Interfaceinput USER_CLK;input RESET; input CHANNEL_UP;
output [0:7] ERR_COUNT;

經過發送的過程,這裏咱們也心領神會地領會到,CHANNEL_UP和復位有關;

RX_D是要收的數據;

RX_SOF_N是首字;

RX_EOF_N是末字;

RX_SRC_RDY_N爲有效信號,即Valid,它有效的時候咱們才能採樣到有效的數據。

若是SOF以及EOF同時有效,那麼咱們知道這個幀就一個字,或者更少。

繼續看:

// SLACK registers
always @ (posedge USER_CLK)begin RX_D_SLACK <= `DLY RX_D; RX_SRC_RDY_N_SLACK <= `DLY RX_SRC_RDY_N; RX_REM_1SLACK <= `DLY RX_REM; RX_REM_2SLACK <= `DLY RX_REM; RX_SOF_N_SLACK <= `DLY RX_SOF_N; RX_EOF_N_SLACK <= `DLY RX_EOF_N;end

程序對輸入的變量都寄存了一拍,爲何呢?很簡單,還不是爲了改善時序,這樣讓佈局佈線更加容易。

接着給出了一些標誌信號:

 assign data_in_frame_c = data_in_frame_r || (!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK);

其中有:

//Start a multicycle frame when a frame starts without ending on the same cycle. End //the frame when an EOF is detected always @(posedge USER_CLK) if(reset_c)  data_in_frame_r <= `DLY 1'b0; else if(CHANNEL_UP) begin if(!data_in_frame_r && !RX_SOF_N_SLACK && !RX_SRC_RDY_N_SLACK && RX_EOF_N_SLACK) data_in_frame_r <= `DLY 1'b1; else if(data_in_frame_r && !RX_SRC_RDY_N_SLACK && !RX_EOF_N_SLACK) data_in_frame_r <= `DLY 1'b0; end

先解釋下data_in_frame_r:

有條件:!data_in_frame_r && !RX_SOF_N_SLACK && !RX_SRC_RDY_N_SLACK && RX_EOF_N_SLACK

可知,在幀開始後,爲1;

又:data_in_frame_r && !RX_SRC_RDY_N_SLACK && !RX_EOF_N_SLACK 表示,在幀結束後,又變爲0;

這點,在後面的行爲仿真中,咱們能夠拉出來看看。

由這段分析能夠知道data_in_frame_r在幀內(不包括sof有效的第一個週期)爲1;那麼:

data_in_frame_c呢?

assign data_in_frame_c = data_in_frame_r || (!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK);

表示若是數據是單週期幀或已啓動多週期幀,則數據在該幀中。

它把幀的第一個週期也納進去了。

怎麼理解呢?

它等於data_in_frame_r與 !RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK的或,也就是兩者有其一就爲1;

在幀的第一個週期內,!RX_SRC_RDY_N_SLACK && !RX_SOF_N_SLACK有效;在後面的週期內,兩者均有效。這兩者都有效了,確定數據就在幀內了。那麼這個信號data_in_frame_c就有效;

assign data_valid_c = data_in_frame_c && !RX_SRC_RDY_N_SLACK;

這個就是把data_in_frame_c與!RX_SRC_RDY_N_SLACK進行一個與操做。做用於data_in_frame_c無異。

不管是單字幀(單週期幀)仍是多週期幀,這個data_valid_c有效,數據必定是幀內有效數據。

//Register and decode the RX_D data with RX_REM bus always @ (posedge USER_CLK) begin  if((!RX_EOF_N_SLACK) && (!RX_SRC_RDY_N_SLACK)) begin  case(RX_REM_1SLACK) 1'd0 : RX_D_R <= `DLY {RX_D_SLACK[0:7], 8'b0}; 1'd1 : RX_D_R <= `DLY RX_D_SLACK; default : RX_D_R <= `DLY RX_D_SLACK;  endcase  end  else if(!RX_SRC_RDY_N_SLACK) RX_D_R <= `DLY RX_D_SLACK; end

這段代碼,包括後面的幾乎都不用說了,就是把數據接收過來處理,換作你的工程,確定按照本身的方式處理接收的數據。

那CHECK的分析到此結束吧。

示例工程仿真

仿真文件也就是例化兩次例子程序,以後將兩者的收發相接,造成一個環路。

整體仿真

這裏直接仿真看咱們想看的結果。

首先仍是從宏觀上看:

在這裏插入圖片描述

能夠看出,1發2收,2發1收;

不過串行數據只能看到一個大概狀況,更多 的細節,繼續拉出來看:

可見,發的第一個數據和收的第一個數據一致!

後面的數據也是一致的。

發送模塊仿真

從這裏開始,我將關注gen模塊的幀組成狀況:

在這裏插入圖片描述

第一幀數據只有一個字,所以在發送的時候sof以及eof同時有效;第二幀:

在這裏插入圖片描述

第二幀數據有兩個字:如上圖,所以,第一個字sof有效,第二字eof有效。

我還想看看第一幀數據和第二幀數據之間的間隔是否是ifg_size_r 進行了計數:

在這裏插入圖片描述

確實在計數!

和代碼對應起來:

 //Use a freerunning counter to determine the IFG always @(posedge USER_CLK) if(reset_c) ifg_size_r <= `DLY 4'h0; else ifg_size_r <= `DLY ifg_size_r + 1;  //IFG is done when ifg_size register is 0 assign ifg_done_c = (ifg_size_r == 4'h0);

計數是一直進行的過程。

 assign next_idle_c = !ifg_done_c && (single_cycle_frame_r || eof_r || idle_r);

計數值不爲0的時候,一直處於空閒狀態。

 assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) && (idle_r || single_cycle_frame_r || eof_r);  assign next_sof_c = (ifg_done_c && (frame_size_r != 0)) && (idle_r || single_cycle_frame_r || eof_r);

計數值爲0的時候,若是是單週期幀,則進入單週期幀狀態,發送單週期數據。對於第一幀數據就是如此,直接進入單週期幀狀態發送數據。將當前狀態變量拉出來看看:

在這裏插入圖片描述

可見,一開始處於idle狀態,以後進入單週期幀狀態,在下一個週期便發送數據了。

 assign next_single_cycle_frame_c = (ifg_done_c && (frame_size_r == 0)) && (idle_r || single_cycle_frame_r || eof_r);

因爲進入單週期幀,須要另外一個計數,就是幀長計數frame_size_r == 0;這個計數量的條件是:

//Use a counter to determine the size of the next frame to send always @(posedge USER_CLK) if(reset_c)  frame_size_r <= `DLY 8'h00; else if(single_cycle_frame_r || eof_r) frame_size_r <= `DLY frame_size_r + 1;

可見,確實應該進入了單週期幀狀態:

在這裏插入圖片描述

在分析下,下一幀不是單週期幀的狀況:

在這裏插入圖片描述

在sof以後直接進入eof也很顯而易見:

 assign next_eof_c = (frame_size_r == bytes_sent_r) && (sof_r || data_cycle_r);

知足了frame_size = bytes_size的條件。這兩個計數器有什麼關係呢?

 //Use a counter to determine the size of the next frame to send always @(posedge USER_CLK) if(reset_c)  frame_size_r <= `DLY 8'h00; else if(single_cycle_frame_r || eof_r) frame_size_r <= `DLY frame_size_r + 1;  //Use a second counter to determine how many bytes of the frame have already been sent always @(posedge USER_CLK) if(reset_c) bytes_sent_r <= `DLY 8'h00; else if(sof_r) bytes_sent_r <= `DLY 8'h01; else if(!TX_DST_RDY_N && !idle_r) bytes_sent_r <= `DLY bytes_sent_r + 1;  

frame計數器呢?

若是發送單週期幀,則遇到單週期幀狀態加1;

若是發送多週期幀,則遇到eof狀態就加1;

可見,是不斷加的。

而bytes呢?

是每次的幀開始就置1,而後一直加到eof狀態;

assign next_data_cycle_c = (frame_size_r != bytes_sent_r) && (sof_r || data_cycle_r);

bytes計數的含義是已經發送的數據字數,如何和要發送的字數不符合,就處於next_data_cycle_c狀態,這個狀態是要一直髮送數據的狀態;

assign next_eof_c = (frame_size_r == bytes_sent_r) && (sof_r || data_cycle_r);

若是等於了,則進入最後一個eof狀態,發完最後一個字,結束。

//Output logic for 1-hot state machine always @(posedge USER_CLK) if(reset_c) begin TX_SOF_N <= `DLY 1'b1; TX_EOF_N <= `DLY 1'b1; TX_SRC_RDY_N <= `DLY 1'b1;  end else if(!TX_DST_RDY_N) begin TX_SOF_N <= `DLY !(sof_r || single_cycle_frame_r); TX_EOF_N <= `DLY !(eof_r || single_cycle_frame_r); TX_SRC_RDY_N <= `DLY idle_r; end

有輸出代碼可知,輸出都是在狀態的基礎上延遲一個時鐘。

當sof_r狀態的時候,下一個週期將TX_SOF_N置有效;

當eof_r狀態的時候,下一個週期置TX_EOF_N有效;

而TX_SRC_RDY_N則在非空閒狀態下有效,空閒狀態下無效。

若是處於空閒狀態,則下一個時鐘無效,若是不處於空閒狀態,則下一個週期有效。總之,等價於狀態延遲一個時鐘。

接收模塊仿真

有了上面的發送模塊仿真的分析,我想接收模塊的仿真也再也不話下了。

咱們就看看仿真結果就行了,至於結合程序分析,沒有必要了,由於咱們接收完數據後,按照本身的方式處理了。這個本身最清楚。

在這裏插入圖片描述

接收真的比發送要簡單多了。畢竟發送要設計狀態機來組合要發送的數據。

參考資料

pg046

FPGA設計心得(3)Aurora IP core 的理論學習記錄

FPGA設計心得(4)Aurora IP core 的定製詳情記錄

FPGA設計心得(5)Aurora 例子工程分析與仿真實例分析(streaming版)

高速串行總線系列(3)GTX/GTH 物理層結構分析

HDLBits 系列(21)LFSR(線性反饋移位寄存器)

HDLBits 系列(25)獨熱碼有限狀態機實現的簡單方式

交個朋友

進不進無所謂,同行交個朋友!

寫在最後

這篇博客,可真的花費了不少時間,熬夜兩個夜晚,說實話,若是沒有實際需求,我纔不會花這個心思去搞這個東西。

一方面是實驗室工程須要;另外一方面是之後工做也須要。

還有就是這個IP核的定製,雖然選擇了framing幀格式,可是並無選擇flow control,所以會不會有後續還說不許。

固然,還有數據手冊的進一步細節學習,也說不許,所以後續應該會有的。

寫的很差,還望見諒。

最後,寫老師不催之恩(幾天沒催了,我也幾天埋頭學習,沒有消息,你老是說我慢,我其實很用心!)。

2020/05/14

工程分享

連接:https://pan.baidu.com/s/1Tc2X6DWhOeO_AUiyE14UTQ 提取碼:aw4x 或者csdn下載:https://download.csdn.net/download/Reborn_Lee/12417421


本文分享自微信公衆號 - FPGA LAB(gh_af38c08c9983)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索