1. 程式人生 > 其它 >03-FPGA設計套路(1)——移位暫存器

03-FPGA設計套路(1)——移位暫存器

技術標籤:FPGA教程fpga

移位暫存器

在點亮LED中,有如下程式碼:

always @ (posedge sys_clk or negedge hard_rst_n) begin
    if (~hard_rst_n) begin
        r_sys_rst_8ff   <= 8'hFF;
        r_sys_rst       <= 1'b1;
    end else begin
        r_sys_rst_8ff   <= {r_sys_rst_8ff[6:0],~s_pll_lock};
        r_sys_rst       <= |r_sys_rst_8ff;
    end
end

此程式碼中主要用了移位暫存器。
向左或向右的移位暫存器,在FPGA設計中有3種常用用途:

  1. 連續打拍
  2. 標識節拍
  3. 拉長訊號

連續打拍

假設我們需要把一個8bit訊號i_a打4拍生成o_a輸出,在FPGA中有以下幾種設計方法:

always @ (posedge clk) begin
    r_a_d1  <= i_a;
    r_a_d2  <= r_a_d1;
    r_a_d3  <= r_a_d2;
    r_a_d4  <= r_a_d3;
end
assign o_a = r_a_d4;

always @ (posedge clk) begin
    r_a_4ff <= {r_a_4ff[23:0],i_a};
end
assign o_a = r_a_4ff[31:24];

always @ (posedge clk) begin
    r_a_4ff <= {i_a,r_a_4ff[31:8]};
end
assign o_a = r_a_4ff[7:0];

在需要打拍的級數較大時,使用移位暫存器可以縮減程式碼量。比如將一個2bit訊號打20拍,顯然使用移位暫存器會更簡便。

標識節拍

假設我們有一個序列輸入的8bit訊號i_a,將提供給3個通道的資料順序放在i_a上。即i_a輸入的資料為:ch0的資料->ch1的資料->ch2的資料->ch0的資料。
例如i_a輸入1,2,3,4,5,那麼1是ch0通道的資料,2是ch1通道的資料,3是ch2通道的資料,4是ch0通道的資料,以此類推。

需要將資料分離出來輸出給3個通道,程式碼如下:

always @ (posedge clk) begin
    if (rst) begin
        r_ch_flag   <= 3'b001;
        r_a         <= 8'd0;
        r_ch0_valid <= 1'b0;
        r_ch1_valid <= 1'b0;
        r_ch2_valid <= 1'b0;
    end else begin
        if (i_a_valid) begin
            r_ch_flag   <= {r_ch_flag[1:0],r_ch_flag[2]};
            r_a         <= i_a;
        end
        r_ch0_valid <= i_a_valid & r_ch_flag[0];
        r_ch1_valid <= i_a_valid & r_ch_flag[1];
        r_ch2_valid <= i_a_valid & r_ch_flag[2];
    end
end
assign o_ch0_data   = r_a;
assign o_ch0_valid  = r_ch0_valid;
assign o_ch1_data   = r_a;
assign o_ch1_valid  = r_ch1_valid;
assign o_ch2_data   = r_a;
assign o_ch2_valid  = r_ch2_valid;

在上述程式碼中,復位時,r_ch_flag[0]為1,表示當前輸入的資料是ch0的資料。當接收到有效的a後(i_a_valid有效),通過移位切換成r_ch_flag[1]為1,表示當前輸入的資料是ch1的資料。於是,通過對flag迴圈移位,實現了對3個通道資料的標識。

在FPGA中,資料通常會和有效標誌成對出現,即data伴隨valid。
在上述程式碼中,i_a伴隨i_a_valid。當i_a打一拍生成r_a時,r_a的有效訊號應該為r_a_valid。但在這裡為了直接將r_a分配給3個通道,所以ch0通道的i_a的有效訊號是i_a_valid & r_ch_flag[0],那麼ch0通道的r_a的有效訊號就是上述程式碼中的r_ch0_valid。

再例如,我們需要將i_a延遲4拍輸出,那麼程式碼如下:

always @ (posedge clk) begin
    //CHN: i_a打4拍
    r_a_d1  <= i_a;
    r_a_d2  <= r_a_d1;
    r_a_d3  <= r_a_d2;
    r_a_d4  <= r_a_d3;
    //CHN: 生成伴隨資料的valid訊號
    r_a_valid_4ff   <= {r_a_valid_4ff[2:0],i_a_valid};
end
assign o_a      = r_a_d4;
assign o_valid  = r_a_valid_4ff[3];

在上述程式碼中,r_a_valid_4ff[0]標識r_a_d1是否有效,r_a_valid_4ff[1]標識r_a_d2是否有效,以此類推。

另一種標識節拍的常見程式碼是將序列協議解碼。比如我們有一個序列協議:{包頭0,包頭1,包頭2,資料}。序列輸入的資料包含3個包頭,然後是資料。3個包頭的內容分別如下:

  • 包頭0:資料型別,比如資料是引數,還是資料。
  • 包頭1:資料地址,比如資料需要寫入的地址。
  • 包頭2:資料長度,需要注意協議是使用size(由1起始)還是length(由0起始)。
    那麼解包的參考程式碼如下:
//input FF
always @ (posedge clk) begin
    if (rst) begin
        r_a_d1      <= 'd0;     //CHN: 使用'd或'h,將位寬預設,工具會自動補全
        r_a_valid   <= 'd0;
    end else begin
        r_a_d1      <= i_a;         //CHN: 輸入資料通常都需要先打1拍,可以優化時序
        r_a_valid   <= i_a_valid;
    end
end

//CHN: 序列資料解包
always @ (posedge clk) begin
    if (rst) begin
        r_hd_flag       <= 3'd1;    //CHN: 移位暫存器構成的包頭標識,復位後當前輸入為包頭0
        r_data_type     <= 'd0;
        r_data_addr     <= 'd0;
        r_data_len      <= 'd0;     //CHN: 假設協議中使用的是length
        r_data          <= 'd0;
        r_data_valid    <= 'd0;
    end else begin
        if (s_data_ilast) begin
            r_hd_flag   <= 3'd1;            //CHN: 輸入最後1個數據後,接下來當前輸入為包頭0
        end else if (i_a_valid) begin
            r_hd_flag   <= (r_hd_flag << 1);    //CHN: 每次輸入有效,都將包頭標誌左移
        end
        if (i_a_valid & r_hd_flag[0]) begin //CHN: 獲取資料型別
            r_data_type <= i_a;
        end
        if (i_a_valid & r_hd_flag[1]) begin //CHN: 獲取資料地址
            r_data_addr <= i_a;
        end
        if (i_a_valid & r_hd_flag[2]) begin //CHN: 獲取資料長度
            r_data_len  <= i_a;
        end else if (i_a_valid & ~|r_hd_flag) begin
            r_data_len  <= r_data_len - 1;  //CHN: 每收到1個數據,資料長度減1
        end
        if (i_a_valid & ~|r_hd_flag) begin  //CHN: 3個包頭標識都是0,說明是資料
            r_data      <= i_a;
        end
        r_data_valid    <= i_a_valid & ~|r_hd_flag;
    end
end
//CHN: len是由0開始計數,當輸入資料有效並且len為0,說明這是最後一個數據
assign s_data_ilast = i_a_valid & ~|r_data_len;

思考上述程式碼的設計思路。

拉長訊號

拉長訊號就是使用移位暫存器把想要拉長的單拍訊號打拍,然後將移位暫存器按位或。
比如點亮led程式碼中的把~s_pll_lock打8拍,然後|r_sys_rst_8ff就是一個8拍長度的未鎖定標誌。