FPGA入門筆記011_B——搭建串列埠收發與存取雙口RAM簡易應用系統

Yamada_Ryo發表於2024-04-08

1、實驗現象

​ 透過串列埠傳送資料到 FPGA 中,FPGA 接收到資料後將資料儲存在雙口 ram 的一段連續空間中,透過 Quartus II 軟體提供的 In-System Memory Content Editor 工具檢視 RAM 中接收到的資料。當需要時,按下設計好的按鍵 ,則 FPGA 將 RAM 中儲存的資料透過串列埠傳送出去。

2、知識點:

​ 1、Altera 公司 Cyclone IV 系列器件的內部結構。

​ 2、儲存器(RAM) IP 核的使用。

​ 3、In-System Memory Content Editor 工具檢視記憶體的使用。

​ 4、串列埠收發+按鍵+雙口 RAM 組成的簡易系統設計。

3、系統模組功能劃分及介面設計

​ 在設計前,先進行整體功能劃分。本工程劃分為如下幾個模組:

​ (1)串列埠接收模組(UART_Byte_Rx);

​ (2)按鍵消抖模組(Key_Filter);

​ (3)RAM模組(dpram);

​ (4)串列埠傳送模組(UART_Byte_Tx);

​ (5)控制模組(CTRL)

​ 整個系統框圖如圖 1 所示。

image-20240407161301492

圖1——串列埠收發與儲存雙口RAM簡易應用系統整體框圖

​ 明確了系統的整體模組架構之後,即可以開始進行頂層檔案設計。

4、頂層檔案設計

​ 新建工程後,先將已編寫好的模組設計檔案新增進工程中,並新建一個以名為 UART_DPRAM.v 的設計檔案儲存在 rtl 下,並設定為頂層檔案。

​ 這樣對照圖1 用 Top-Down 的設計方式就可以先把頂層檔案寫出。結構圖左邊的埠 為 input 型別,右邊的為 output 型別,內部連線均為 wire 型。然後例化各個模組,這裡將波特率設定為 9600bps。其模組介面列表如表 1 所示。

image-20240408104844051

表1——UART_DPRAM模組介面列表

​ 程式碼如下:

module UART_DPRAM(
    Clk,
    Rst_n,

    Key_in,

    Rs232_Rx,
    Rs232_Tx
);

    input Clk;
    input Rst_n;
    input Key_in;

    input Rs232_Rx;
    output Rs232_Tx;

    wire [7:0]rx_data;
    wire [7:0]tx_data;

    wire key_flag;
    wire key_state;

    wire Rx_Done;
    wire Tx_Done;

    wire wren;
    wire [7:0]wraddress;
    wire [7:0]rdaddress;

    wire Send_En;

    uart_byte_tx uart_byte_tx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Send_En(Send_En),
        .Data_Byte(tx_data),
        .Baud_Set(3'd0),

        .Rs232_Tx(Rs232_Tx),
        .Tx_Done(Tx_Done),
        .UART_state()
    );

    uart_byte_rx uart_byte_rx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Baud_Set(3'd0),
        .Rs232_Rx(Rs232_Rx),

        .Data_Byte(rx_data),
        .Rx_Done(Rx_Done)
    );

    key_filter key_filter(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .key_in(Key_in),

        .key_flag(key_flag),
        .key_state(key_state)
    );

    dpram dpram(
        .clock(Clk),
        .data(rx_data),
        .rdaddress(rdaddress),
        .wraddress(wraddress),
        .wren(wren),

        .q(tx_data)
    );

    CTRL CTRL(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .key_flag(key_flag),
        .key_state(key_state),
        .Rx_Done(Rx_Done),
        .Tx_Done(Tx_Done),

        .wren(wren),
        .wraddress(wraddress),
        .rdaddress(rdaddress),
        .Send_En(Send_En)
    );

endmodule

5、控制模組設計

​ 現在編寫本系統的控制模組 CTRL.v,模組的介面可參照系統結構圖寫出。

module CTRL(
    Clk,
    Rst_n,
    key_flag,
    key_state,
    Rx_Done,
    Tx_Done,

    wren,
    wraddress,
    rdaddress,
    Send_En
);
    
    input Clk;
    input Rst_n;
    input key_flag;
    input key_state;
    input Rx_Done;
    input Tx_Done;

    output wren;
    output reg [7:0]wraddress;
    output reg [7:0]rdaddress;
    output reg Send_En;

endmodule

​ 為了實現 FPGA 接收到資料後將資料儲存在雙口 ram 的一段連續空間中的功能,需要設計一個可以實現寫地址資料自加的控制邏輯,且其控制訊號為串列埠接收模組輸出的 Rx_Done 訊號。每來一個 Rx_Done 也就是每接收成功一位元組數,地址數進行加一。

	assign wren = Rx_Done;	//將wren與Rx_Done相連,Rx_Done直接控制dpram的資料寫入

//控制寫資料地址訊號
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        wraddress <= 8'd0;
    else if(Rx_Done)
        wraddress <= wraddress + 1'b1;	//寫地址加一,延遲一拍,資料自動加入到dpram中
    else
        wraddress <= wraddress;

​ 當按下按鍵,FPGA 將 RAM 中儲存的資料透過串列埠傳送出去。也就是實現按鍵按下即啟動連續讀操作,再次按下即可暫停讀操作。

    reg do_send;    //輸出標誌訊號

//控制讀資料地址訊號
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        do_send <= 1'd0;
else if(key_flag && !key_state)	//當按鍵按下時
        do_send <= ~do_send;
    else
        do_send <= do_send;

    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        rdaddress <= 8'd0;
else if(do_send && Tx_Done)	//當"do_send == 1'b1"且"Tx_Done == 1'b1"(1位元組的資料傳送完成)
        rdaddress <= rdaddress + 8'd1;
    else
        rdaddress <= rdaddress;

​ 在上一節中模擬雙埠 RAM 時發現其輸出tx_data會延遲兩個系統時鐘週期。這是為了保證資料變化穩定之後才進行資料輸出,所以將驅動 Send_En 的訊號接兩級暫存器進行延遲兩拍。當 按鍵按下後啟動一次傳送,然後判斷上一位元組是否傳送結束,是則進行下一位元組傳送,否則不進行下一次傳送。

	//加入兩個暫存器"r0_send_done","r1_send_done",延遲兩拍
    reg r0_send_done,r1_send_done;

    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)begin
        r0_send_done <= 1'b0;
        r1_send_done <= 1'b0;
    end
    else begin
        r0_send_done <= (do_send && Tx_Done);
        r1_send_done <= r0_send_done;
    end

	//Send_En控制何時傳送資料
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        Send_En <= 1'b0;
    else if(r1_send_done)
        Send_En <= 1'b1;
else if(key_flag && !key_state)		//第一次按下按鍵時,Send_En為1
        Send_En <= 1'b1;
    else
        Send_En <= 1'b0;

​ 編譯無誤後,可以在 RTL viewer 中查到如圖 2 所示的頂層結構圖,可與實驗之初設計的系統結構圖進行對比。

image-20240408112502805

圖2——系統頂層 RTL viewer

​ 透過對比可以驗證本工程邏輯設計輸出的頂層 RTL 結構圖和系統結構圖的設想是一致的。

6、激勵建立及模擬測試

​ 為了進行模擬測試,接下來介紹模擬測試激勵檔案的編寫。這裡由於使用了按鍵消抖模組,因此須將之前建立的 key_modle.v 模擬模型加入到工程中。新建 UART_DPRAM_tb.v 文 件除了例化各模組,其激勵檔案可以直接複製串列埠傳送模組的測試激勵檔案 uart_byte_tx.v 產生激勵訊號 Rs232_Rx。再次進行分析和綜合直至沒有錯誤以及警告,並儲存到 testbench 資料夾下。

`timescale 1ns/1ns
`define clk_period 20

module UART_DPRAM_tb;

    reg Clk;
    reg Rst_n;
    wire Key_in;

    wire Rs232_Rx;
    wire Rs232_Tx;

    reg Send_En;
    reg [7:0]Data_Byte;
    wire [2:0]Baud_Set;
    wire Tx_Done;
    reg press;

    assign Baud_Set = 3'd0;

    UART_DPRAM UART_DPRAM(
        .Clk(Clk),
        .Rst_n(Rst_n),

        .Key_in(Key_in),

        .Rs232_Rx(Rs232_Rx),
        .Rs232_Tx(Rs232_Tx)
    );

    uart_byte_tx uart_byte_tx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Send_En(Send_En),
        .Data_Byte(Data_Byte),
        .Baud_Set(Baud_Set),

        .Rs232_Tx(Rs232_Rx),
        .Tx_Done(Tx_Done),
        .UART_state()
    );

    key_model key_model(
        .press(press),
        .key(Key_in)
    );

    initial Clk = 1'b1;
    always#(`clk_period/2) Clk = ~Clk;

    initial begin
        //傳送模組激勵
        Rst_n = 1'b0;
        press = 1'b0;
        Data_Byte = 8'b0;
        Send_En = 1'b0;

        #(`clk_period*20 + 1)   //讓復位訊號不與時鐘邊沿對其,更好觀察時序關係
        Rst_n = 1'b1;

        #(`clk_period*50)
        Data_Byte = 8'haa;  //第一次,傳送"8'haa"
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)  //當Tx_Done訊號出現上升沿時,進行接下來的語句

        #(`clk_period*5000)
        Data_Byte = 8'h55;  //第二次,傳送"8'h55"
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)  //當Tx_Done訊號出現上升沿時,進行接下來的語句

        #(`clk_period*5000)
        Data_Byte = 8'h33;  //第三次,傳送"8'h33"
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)  //當Tx_Done訊號出現上升沿時,進行接下來的語句

        #(`clk_period*5000)
        Data_Byte = 8'haf;  //第四次,傳送"8'haf"
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)  //當Tx_Done訊號出現上升沿時,進行接下來的語句

        #(`clk_period*5000)

        //按下一次按鍵
        press = 1'b1;
        #(`clk_period*3)
        press = 1'b0;

        #(`clk_period*2500000)

        $stop;

    end

endmodule

​ 這裡將按鍵的模擬模型加模擬指令碼後進行功能模擬就可以看到如圖 3 所示的波形檔案。每當寫入地址加一時資料均可以有效的寫入,按鍵按下後每當一次輸出結束後讀地址也進行加一,實現資料輸出。

image-20240408154612957

圖3——功能模擬波形檔案

​ 放大波形檔案的資料傳送部分,如圖 4 所示。可看出在第一次給出寫使能訊號後, 將資料 aa 寫入地址 0,寫入成功後寫入地址加一。tx_data 之所以會在沒有 Send_en 時資料會更新,是因為讀取地址在復位時地址初始值為 0,在成功寫入資料 aa 後,自然就會顯示更新,但是 Rs232_Tx 不會有資料更新。延遲兩個系統時鐘週期是因為 RAM IP 本身的特性。 可以放大後面資料寫入的過程,tx_data 的值並不會更新。

image-20240408154419781

圖4——資料接收及寫入部分波形

​ 放大波形檔案的傳送部分,如圖 5 和圖 6 所示。可看出每當 Send_en 有效後均 會進行一位元組資料的傳送。傳輸完激勵中的四個資料後即輸出 0,這是由於這裡將 RAM 定 為 256 寬度,只用的前四個,沒有用到的資料即全為 0。且可以看出 Send_en 與資料傳送嚴 格對齊,與設計預期相符。

image-20240408155213658

圖5——資料讀取及傳送部分波形

image-20240408155241739

圖6——資料讀取及傳送部分波形

​ 以上,模擬部分的驗證工作就順利完成。

7、板級驗證

​ 經過上述工作,所有程式碼都已經設計完畢,接下來介紹板級測試和驗證方法:

​ (1)按照 AC620/AC6102/AC609 開發板的引腳分配表分配正確的引腳, 本設計例項針 對各開發板的管腳分配如下表所示:

image-20240408185438625

(2)分配引腳後進行全編譯無誤後,下載進開發板,開啟串列埠助手依次輸入 11、22、aa、 dd、34、67。按下按鍵 S0 後即可看見資料來源源不斷的傳送到串列埠上位機軟體,再次按下即 停止。且可以計算得每一個迴圈的資料量自動補 0 到 256 個位元組,與設計的 RAM 儲存量相 符合,如圖 7 所示

image-20240408185505575

圖7——串列埠助手接收資料顯示

(3)按下 FPGA 復位申請併傳送新的資料 68、76、ff,可以看出,RAM 中已有的資料並 不會消除,只是會加入新的資料,且一個迴圈資料量也是自動補 0 到 256 位元組,如圖8所示。

image-20240408185553337

圖8——串列埠助手接收資料顯示

相關文章