FPGA 基本與設計的一些要點


FPGA 資訊

FPGA 組成元素

1. 尋找表(LUT) -------
2. 多工器(MUX)       | 邏輯單元(CLB)
3. 正反器(FF) --------
5. 線路(Wires)
6. 輸入輸出(I/O)


組合邏輯電路

任一時刻的穩態輸出,僅僅與該時刻輸入變量的取值有關。意思是根據目前的 Input 產生對應的 Output,之間不受 Clock 影響。

循序邏輯電路

輸出會 Feedbake 回輸入,任一時刻的穩態輸出,與該時刻輸入變量還有前一刻輸入變量的取值有關。

HDL 資訊

Blocking & Non-Blocking

這是 Verilog 中兩種不同的給值的方式。Verilog 中的 Blocking 語句等同軟體語言一樣,是一行一行由上至下執行的,但 Non-blocking 則是同步執行的。

電路都使用 Blocking 的方式設計會造成電路串連太長,導致延遲太多時間。
電路都使用 Non-blocking 的方式設計會造成電路面積加大(成本提高),因為並行處理的輸出都要額外給予一個暫存器來儲存。

  • 基本原則:

    1. 組合邏輯 assign 採用 Blocking,且必須搭配 wire。
    2. 循序邏輯無 Clock 的 always 區塊採用 Blocking,且必須搭配 reg。
    3. 循序邏輯有 Clock 的 always 區塊採用 Non-blocking,且必須搭配 reg。
    4. 一個 always 區塊中不能同時使用 Blocking 與 Non-blocking。

時序資訊

Setup Time & Hold Time

Setup time:Clock 上升前,存進暫存器前需維持一段穩定的時間,才能保證存進暫存器的值沒有問題,這段需維持穩定的時間就稱為 Setup time,遇到 Setup time violation 的話,最簡單的方法就是根據 Violation 的資訊找出有問題的 Path,然後多加一層 Register 進去。

會遇到 Timing 問題的通常就像下圖,Combination 運算太過複雜,一個 Clock 做不完邏輯運算,所以拆成兩個 Clock 去運算,原本的邏輯運算需要 12 ns,如果 Run 在 100MHz (10 ns) Clock rate 的板子下就會有 Timing violation,所以找到問題的 Path,在中間多加一個 Register 進去。

上述有效解決 Timing 問題,但會影響 Performance,原本預計一個 Clock 就東西突然要變成兩個 Clock,效率突然降兩倍,為此嘗試用 Pipeline 解決 Timing violation。

假設 Combination circuit 運算是 a <= bcde;

一般

口口口口 | 口口口口 | 口口口口
 指令 1    指令 2     指令 3

如果單指令所需時間是 10 + 15 + 10 + 5 = 40 ns
總共執行時間為 40 * 3 = 120 ns

Run 在 1000 MHz (100 ns) Clock rate 的板子下有問題

Pipeline

口口口口    指令 1
  口口口口    指令 2
    口口口口    指令 3

單個 Clock cycle 所執行時間 max{10, 15, 10, 5} = 15 ns
共執行了 6 個 Clock cycle,執行時間為 15 * 6 = 90 ns

可以 Run 在 1000 MHz (100 ns) Clock rate 的板子下

Pipeline 能維持一個 Cycle 就算出結果,但相對要多花額外的資源去儲存前一筆的資料,用 Pipeline 除了加速以外,資源也要納入考量,若花了很多資源去加速一個不常用到的電路是挺浪費的。

Hold time:clock 上升後,暫存器的值需穩定一段時間,才能保證傳到下一層時的值是正確的,這段穩定的時間就稱為 Hold time.遇到 Hold time violation 時,可以加幾個 buffer 緩衝。

首先 CLK(A)觸發後,有兩條路徑會同時開賽跑,一條從 FF1-C 跑到 FF1-Q 再跑到 FF2-D 這條路徑(虛線)的時間假設為 T1,另一條直通到 FF2-C 假設為 T2,如果 T2 比 T1 快,那 FF2 的 D 資料就會因為還沒有穩定直接被採樣。

Clock Domain 資訊

Clock Domain

由某一頻率 Clock 所驅動的電路為一個 Clock Domain。

Metastability

有兩個 FF(FF-A、FF-B),分別由兩個不同的 CLK 所驅動(不一定誰的頻率快誰慢),當 DA 隨著 CLK A 產生一個 Cycle 訊號,CLK B 需要在一個 Cycle 內將訊號鎖進 FF,但由於兩個 Clk Frequency 不同,當 DA 變為 0 的瞬間,CLK B 的 Posiedge trigger 正要把 DA 的值存進 DFF-B,由於 DA 的訊號驟降讓 DFF-B 沒有足夠的準備時間來完成鎖值,此時就會發生 Metastable(亞穩態,或稱準穩態)。

沒有足夠的準備時間的意思是?

正反器由許多 CMOS(互補式金屬氧化物半導體)所組成,CMOS 要存值就需要足夠的"充電"時間(Setup time)。

當 bdat1 處於 Metastable 時,後面如果分接到三個 Not gate,不能保證每個 Not gate 的輸出會是高電位還是低電位,因為輸入處於 Metastable,輸入的電流不穩定,有的 Gate 認為接收到的是高電位,有的卻認為接收到低電位,當 bdat1 接到多個 Combination circuit 時,電路的功能就可能會出錯,造成系統故障。

那怎麼解決?bdat1 後面不要接上任何組合電路,再接一級由 bclk 驅動的 FF,這就是 Two Flip-Flop Synchronizer。處理 Metastable 的最基本的步驟就是遵守 Two Flip-Flop 方法。

CDC 設計

提高平均故障間隔(MTBF)從而降低亞穩態發生的概率,處理方法可以分為兩大類:單 Bit 訊號 CDC 處理、多 Bit 訊號 CDC 處理。多 Bit 訊號的傳遞不光只有亞穩態這一個問題,還可能會因為多個訊號之間由於工藝、PCB 佈局等因素導致的訊號傳輸延時(Skew),從而導致訊號被漏採或者錯採。反正就是要想辦法"同步"。

常見的同步方法有:

  1. m-FF based Synchronizers.
  2. MUX based Synchronizers.
  3. Handshake Synchronizer.
  4. Toggle Synchronizers.

m-FF based synchronizers 使用條件:

  1. source clock domain is slower than destination clock domain.
  2. clock domain crossing (CDC) is on a control signal (either single bit or multibit).

概念與圖示如上所示。

MUX based synchronizers 使用條件:

  1. The source clock domain is slower than destination clock domain.
  2. Clock domain crossing (CDC) is on a data bus.
  3. Data is stable for at least m+1 clock cycles.

MUX Synchronizers 要求被同步的資料,跟隨一個 Enable Singal。

想要將 Data bus 從 CLKA 轉到 CLKB,通過 Data bus 的 valid 訊號,即為 Data enable A;將 Data enable A 使用 Two Flip-Flop Synchronizer 跨到 CLKB,也就是 Data enable B ,並且使用 Data enable B 當作最右邊 DFF 的 Flip-Flop Enable(在圖中使用 MUX 示意)。由於 Data enable A 的時序等同於 Data bus,跨到 Data enable B 時也就保證了 data bus 穿過 DFF 的訊號已經穩定,即可拿來鎖入最後一級 DFF,最後一級 DFF 的 Q 即是 Data bus 存在於 CLKB Domain 的穩定信號。

Handshake Synchronizer 使用條件:

  • Extension of mux based synchronization with an additional acknowledgment line.

Toggle synchronizers 使用條件:

  1. source clock domain is faster than destination clock domain.
  2. Clock domain crossing (CDC)is on a single bit.

Asynchronous Reset Synchronous Release

同步 Reset (Synchronous Reset):當 Reset 訊號為 Active 的時候,Register 在下一個Clk Pulse 到來之後被重設,Clk Pulse 到來之前 Register 保持其之前的值。

always @ (posedge clk) begin
    if ( !reset_n )
        q2 <= 1'b0;
    else
        q2 <= ...;
end

非同步 Reset (Asynchronous Reset):當 Reset 訊號為 Active 的時候,Register 立刻被重設,與 Clk Pulse 到來與否沒有關係。

always @ ( posedge clk or negedge reset_n ) begin
    if ( !reset_n )
        q2 <= 1'b0;
    else
        q2 <= ...;
end

非同步重設,同步釋放(Synchronized Asynchronous Reset):STA 時要保證訊號的傳輸滿足 Setup Time & Hold Time,避免 Metastability Sample。還有 Reset 訊號時到來時 Reset 和 Release 也必須滿足 Setup Time & Hold Time。一般採用非同步重設,同步釋放,如圖所示(只適用於沒有 PLL 的系統)。在 reset_n 到來的時候不受 clock 的同步,而是在 reset_n 釋放的時候受到 clock 的同步。

module sync_async_reset ( clock, reset_n, rst_n );

    input clock, reset_n;
    output rst_n;
    reg rst_nr1, rst_nr2;

    always @ ( posedge clk or negedge reset_n ) begin
        if ( !reset_n ) begin
            rst_nr1 <= 1'b0; // Asynchronous Reset
            rst_nr2 <= 1'b0;
        end
        else begin
            rst_nr1 <= 1'b1; // Synchronous Release
            rst_nr2 <= rst_nr1;
        end
    end

    assign rst_n = rst_nr2;

endmodule

非同步 FIFO

一種 Data Cache,沒有外部讀寫 address,使用起來簡單,缺點是只能順序寫入與讀出資料,其資料地址由內部讀寫指標自動加 1 完成,不能像一般儲存器可以由 address 決定讀取或寫入某個指定的地址。同步 FIFO 是指讀寫為同一個 Clk。在 Clk Pulse 來臨時同時發生讀寫操作。非同步 FIFO 是指讀寫 Clk 不一致,讀寫 Clk 是互相獨立的。

非同步 FIFO 主要由五部分組成:寫控制端、讀控制端、FIFO Memory 和兩個時脈同步端。寫控制端用於判斷是否可以寫入資料、讀控制端用於判斷是否可以讀取資料、FIFO Memory 用於儲存資料、兩個時脈同步端用於將讀寫 Clock 進行同步處理。

簡化圖:

判斷能否寫入或讀取資料關鍵在於:

  • Write 時,Write enable 有效且 FIFO 不是滿的。
  • Read 時,Read enable 有效且 FIFO 不是空的。

Empty 可以理解為 rd_ptr 追上 wr_ptr,Full 可以理解為 wr_ptr 再次追上 rd_ptr。在非同步 FIFO 中,讀寫是在不同的 Clock 下進行的,因此在比較 rd_ptr 跟 wr_ptr 之前,應該先同步 Clock。而在同步之前,應該先將二進制 Address 轉換為 Gray code。

假設內圈為讀,外圈為寫,Empty 是 rd/wr_ptr 指向同一個地址,就像這樣 ↑ 。此時,rd/wr_ptr 完全相同,就以 0010 為例,0010 的 Gray code 為 0011,可以看出對於 Empty,無論是二進制還是 Gray code 所有 Bit 都相同

走了一圈之後,1010 的 Gray code 是 1111, 0010 的 Gray code 是 0011,對比兩個 Gray code 可以發現,此時高兩位相反,低兩位相同,這便是 Gray code 下寫滿的判斷條件。

因此可以總結出判斷 Empty 跟 Full 可以透過 Gray code:

  • Empty 時,rd_ptr 跟 wr_ptr 的 Gray code 一模一樣。
  • Full 時,rd_ptr 跟 wr_ptr 的 Gray code 差 2 個 Bits(高兩位相反,低兩位相同)。
// Full
always @ ( posedge wr_clk or negedge wr_rstn ) begin
    if ( !wr_rstn )
            fifo_full <= 0; // Write reset
    else if ( (wr_ptr[$clog2(DEPTH)] != rd_ptr[$clog2(DEPTH)])
            && (wr_ptr[$clog2(DEPTH) - 1] != rd_ptr[$clog2(DEPTH) - 1])
            && (wr_ptr[$clog2(DEPTH) - 2 : 0] == rd_ptr[$clog2(DEPTH) - 2 : 0]) )
            fifo_full <= 1; 
        else
            fifo_full <= 0;
    end

// Empty
always @ (posedge rd_clk or negedge rd_rstn) begin
    if ( !rd_rstn )
        fifo_empty <= 0; // Read reset
    else if ( wr_ptr[$clog2(DEPTH) : 0] == rd_ptr[$clog2(DEPTH) : 0] )
        fifo_empty <= 1;
    else
        fifo_empty <= 0;
end

非同步 FIFO 架構圖:

FSM

Moore:出只取決於當前狀態的狀態機。
Mealy:輸出不光取決於當前狀態,還與輸入有關係的狀態機。

Inrerface

UART 非同步串列通訊埠

要開始資料傳輸,Transmitting UART 會將傳輸線從"高電壓拉到低電壓"並保持 1 個 Clock cycle。當 Receiving UART 檢測到高到低電壓躍遷時,便開始以串列傳輸速率對應的頻率讀取 Data Frame 中的 Bit。

透過 Parity Bit,Receiving UART 判斷傳輸期間是否有資料發生改變。電磁輻射、不一致的串列傳輸速率或長距離資料傳輸都可能改變資料位元。

為了表示資料封包結束,Transmitting UART 將資料傳輸線從低電壓驅動到高電壓並保持 1 到 2 Bits 時間。

SPI 高速同步串列介面(全雙工)

SPI Slave 晶片一般只支援一種工作模式,所以通常 Master 必需遷就 Slave 把工作模式設成和 Slave 一致,才能正常運作(二個內部都是 Shift Register 所以 MOSI 和 MISO 的時序需求一樣)。

  1. 判別 SCLK 的極性(Polarity),極性指 SPI 不工作時, SCLK 停留在高電位還是低電位。CPOL = 0 是 SCLK 在不工作時停留在低電位, CPOL = 1 則是停留在高電位.
  2. Slave 晶片是在 SCLK 的 Negative Edge Trigger 栓鎖資料, 還是在 SCLK 的 Positive Edge Trigger。要讓對方栓鎖資料,我們就必需把資料 Hold 住,保持穩定,所以 Slave 晶片在 SCLK 的 Negative 栓鎖資料, 相對的 Master 就必需要在之前的 Positive 就送出資料,反之亦然。
  3. CPHA 的定義並不是依 Positive/Negative Edge Trigger。CPHA 設定的是栓鎖資料的時機是在 /SS 訊號下降之後,SCLK 的奇數次變化(CPHA = 0),還是偶數次變化(CPHA = 1)。
  4. 所以 CPHA 配合 CPOL(SCLK 的極性)設定,組合起來一共有 4 種工作模式。

SPI Master 介接多個 Slave 的二種接法:

使用 SPI,最麻煩一件事是確認周邊晶片的工作模式。

I²C(半雙工)

I²C Bus 協定在傳輸的內容上,除了 Start(啟始)和 Stop(結束)二個訊號之外,所有的訊號傳輸固定 8 bits(1 Byte)為一組,MSB(Most Significant Bit)先送出。發送端在每組(8 bits)訊號送出後,需讀取接收端所回應的一個 ACK bit 或者 NACK bit(看 Spec or Datasheet 決定)。發送端不一定是 Master。例如: 讀取資料時,發送端為 Slave。

其他

Clock Jitter 與 Clock Skew 區別

簡言之,Skew 通常是 Clock 相位上的不確定,而 Jitter 是指 Clock 頻率上的不確定。Clock 到達不同 Register 所經歷路徑的驅動和負載的不同,Clk Pulse 的位置有所差異,是 Skew。

而由於振盪器本身穩定性,電源以及溫度變化等原因造成了 Clock 頻率的變化,就是 Jitter。

Blocking 及 Non-blocking 實例

Design

timescale 1ns / 1ps

module block_and_nonblock (
    input clk,
    input reset,
    input [7:0] in,
    output reg [7:0] x,
    output reg [7:0] y,
    output reg [7:0] z
    );
    always @ (posedge clk) begin
        if (reset) begin
            x = 0;
            y = 0;
            z = 0;
        end
        else begin
            // Non-blocking Assignment
            x <= in + 1; 
            y <= x + 1;
            z <= y + 1;
            // Blocking Assignment
            x = in + 1; 
            y = x + 1;
            z = y + 1;
        end
    end

endmodule

Testbench

module block_and_nonblock_tb();
    // input
    reg clk;
    reg reset;
    reg [7:0] in;
    // output
    wire [7:0] x;
    wire [7:0] y;
    wire [7:0] z;

    block_and_nonblock uut (
        .clk(clk),
        .reset(reset),
        .in(in),
        .x(x),
        .y(y),
        .z(z)
    );
    initial begin
        clk = 0;
        reset = 0;
        in = 0;
        #10;
        reset = 1;
        #10;
        reset = 0;
        #10;
        in = 2;
        #10;
        in = 4;
        #10;
        in = 6;
        #30
        in = 8;
    end

    always #5 clk = ~clk;

endmodule

Non-Blocking Simulation

Blocking Simulation

Mealy Machines 及 Moore Machines 實例

以自動販賣機為例的功能描述如下:

飲料單價 20 元,該販賣機只能接受 5 元、10 元的硬幣。考慮找零和出貨。投幣和出貨過程都是一次一次的進行,不會出現一次性投入多幣或一次性出貨多瓶飲料的現象。每一輪販賣機接受投幣、出貨、找零完成後,才能進入到新的自動販賣狀態。

該販賣機的工作狀態轉移圖如下所示,包含了輸入、輸出訊號狀態。其中,Coin = 5 代表投入了 5 元硬幣,Coin = 10 代表投入了 10 元硬幣,Sell = 1 代表是否售出,Change 則是找零。

Melay Design

timescale 1ns / 1ps

// module
module  mealy_vending_machine (
    input           clk ,
    input           rst_n ,
    input [1:0]     coin ,  
    output [1:0]    change ,
    output          sell   
    );

    // define machine state
    `define idle = 3'd0 ;
    `define get05 = 3'd1 ;
    `define get10 = 3'd2 ;
    `define get15 = 3'd3 ;

    // state variable    
    reg [2:0] st_next ;
    reg [2:0] st_curr ; 

    // present state FF's
    always @ ( posedge clk or negedge rst_n ) begin
        if ( ! rst_n ) st_cur <= 'b0 ;
        else st_cur <= st_next ;
    end
    // state switch
    always @ ( * ) begin
        st_next = st_cur ;
        case ( st_cur )
            idle :
                case ( coin )
                    2'b01 :    st_next = get05 ;
                    2'b10 :    st_next = get10 ;
                    default :  st_next = idle ;
                endcase
            get05 :
                case ( coin )
                    2'b01 :    st_next = get10 ;
                    2'b10 :    st_next = get15 ;
                    default :  st_next = get05 ;
                endcase

            get10 :
                case ( coin )
                    2'b01 :    st_next = get15 ;
                    2'b10 :    st_next = idle ;
                    default :  st_next = get10 ;
                endcase
            get15 :
                case ( coin )
                    2'b01 :    st_next = idle ;
                    2'b10 :    st_next = idle ;
                    default :  st_next = get15 ;
                endcase
            default :    st_next = idle ;
        endcase
    end
    // output
    reg [ 1 : 0 ] change_r ;
    reg sell_r ;
    always @ ( posedge clk or negedge rst_n ) begin
        if ( ! rst_n ) begin
            change_r  <= 2'b0 ;
            sell_r    <= 1'b0 ;
        end
        else if ( ( st_cur == GET15 && coin == 2'd1 ) ||
                  ( st_cur == GET10 && coin == 2'd2 ) ) begin
            change_r  <= 2'b0 ;
            sell_r    <= 1'b1 ;
        end
        else if ( st_cur == GET15 && coin == 2'h2 ) begin
            change_r  <= 2'b1 ;
            sell_r    <= 1'b1 ;
        end
        else begin
            change_r  <= 2'b0 ;
            sell_r    <= 1'b0 ;
        end
    end
    assign sell   = sell_r ;
    assign change = change_r ;

endmodule

Moore Design

module  moore_vending_machine (
    input           clk ,
    input           rst_n ,
    input [1:0]     coin ,   
    output [1:0]    change ,
    output          sell   
    );

    //machine state decode
    parameter            IDLE   = 3'd0 ;
    parameter            GET05  = 3'd1 ;
    parameter            GET10  = 3'd2 ;
    parameter            GET15  = 3'd3 ;

    // new state for moore state-machine
    parameter            GET20  = 3'd4 ;
    parameter            GET25  = 3'd5 ;

    //machine variable
    reg [2:0]            st_next ;
    reg [2:0]            st_cur ;
    // (1) state transfer
    always @ (posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur <= 'b0 ;
        end
        else begin
            st_cur <= st_next ;
        end
    end
    // (2) state switch, using block assignment for combination-logic
    always @ (*) begin
        case(st_cur)
            IDLE:
                case (coin)
                    2'b01:     st_next = GET05 ;
                    2'b10:     st_next = GET10 ;
                    default:   st_next = IDLE ;
                endcase
            GET05:
                case (coin)
                    2'b01:     st_next = GET10 ;
                    2'b10:     st_next = GET15 ;
                    default:   st_next = GET05 ;
                endcase

            GET10:
                case (coin)
                    2'b01:     st_next = GET15 ;
                    2'b10:     st_next = GET20 ;
                    default:   st_next = GET10 ;
                endcase
            GET15:
                case (coin)
                    2'b01:     st_next = GET20 ;
                    2'b10:     st_next = GET25 ;
                    default:   st_next = GET15 ;
                endcase
            GET20:         st_next = IDLE ;
            GET25:         st_next = IDLE ;
            default:       st_next = IDLE ;
        endcase 
    end
   // (3) output logic,
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
        else if (st_cur == GET20 ) begin
            sell_r         <= 1'b1 ;
        end
        else if (st_cur == GET25) begin
            change_r       <= 2'b1 ;
            sell_r         <= 1'b1 ;
        end
        else begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
    end
    assign       sell    = sell_r ;
    assign       change  = change_r ;

endmodule

Testbench

module state_machine_tb ();
    reg          clk;
    reg          rst_n ;
    reg [1:0]    coin ;
    wire [1:0]   change ;
    wire         sell ;

    always #10 clk = ~clk;

    //motivation generating
    reg [9:0]    buy_oper ; //store state of the buy operation
    initial begin
        buy_oper  = 'h0 ;
        coin      = 2'h0 ;
        rstn      = 1'b0 ;
        #8 rstn   = 1'b1 ;
        @(negedge clk) ;

        //case(1) 0.5 -> 0.5 -> 0.5 -> 0.5
        #16 ;
        buy_oper  = 10'b00_0101_0101 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end
        //case(2) 1 -> 0.5 -> 1, taking change
        #16 ;
        buy_oper  = 10'b00_0010_0110 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(3) 0.5 -> 1 -> 0.5
        #16 ;
        buy_oper  = 10'b00_0001_1001 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(4) 0.5 -> 0.5 -> 0.5 -> 1, taking change
        #16 ;
        buy_oper  = 10'b00_1001_0101 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end
    end

   //(1) mealy state with 3-stage
    vending_machine_p3    u_mealy_p3     (
        .clk              (clk),
        .rstn             (rstn),
        .coin             (coin),
        .change           (change),
        .sell             (sell)
        );

   //simulation finish
   always begin
      #100;
      if ($time >= 10000)  $finish ;
   end

endmodule // test

Clock Comain Crossing (CDC) 相關設計與 Interface

CDC《跨時鐘域處理》三大方法

  1. 打兩拍

    處理 CDC 的資料有單 Bit 和多 Bit 之分,而打兩拍的方式常見於處理單 Bit 資料的 CDC 問題。

  2. Asynchronous Dual-Port RAM

    處理多 Bit 資料的 CDC,一般採用 Asynchronous Dual-Port RAM,假設有一個訊號採集平台,ADC 提供 60MHz,而 FPGA 內部需要使用 100MHz 的 Clock 來處理 ADC 採集到的資料(多 Bit)。先利用 ADC 提供的 60MHz Clock 將 ADC 輸出的 Data 寫入 Asynchronous Dual-Port RAM,然後使用 100MHz 的 Clock 從 RAM 中讀出。

  3. Gray Code

    相鄰的兩個數間只有一個 Bit 是不一樣的,如果先將 RAM 的 Write Addr 轉為 Gray Code,然後再將 Write Addr 的 Gray Code 進行打兩拍,之後在 RAM 的 Read Clock Domain 將 Gray Code 恢復成 10 進制。

UART(全雙工)跟 RS232 差異

  • RS232 的 Vpp(峰值電壓)較高,有 6 V - 30 V;UART 則是較低的 3.3 V 或 5 V
  • RS232 為負邏輯,UART 為正邏輯,因此兩者波形是反相的

SPI 四種工作模式

SCLK (Serial Clock) 不同,具體工作由 CPOL (Clock Polarity) 和 CPHA (Clock Phase) 決定:
CPOL = 0,有效狀態是 SCLK 處於高電位時
CPOL = 1,有效狀態是 SCLK 處於低電位時
CPHA = 0,表示 Data 取樣是在第 1 個 Edge,發送在第 2 個 Edge
CPHA = 1,表示 Data 取樣是在第 2 個 Edge,發送在第 1 個 Edge

因此 CPOL = 1,CPHA = 0:平常 SCLK 處於高電位,取樣是在第 1 個 Edge,也就是 SCLK 由高電位到低電位的跳變,所以資料取樣是在 Negative Edge,送資料是在 Positive Edge 時 Trigger。

I²C 再談

實體層圖

ACK / NACK

每一個 8-bit(I²C 以 1-byte 為單位傳送 data)的資料傳輸結束後,會跟著一個 acknowledge bit(第 9 個 bit)。這個 acknowledge bit 固定由接收方產生,有兩種用法:

當 master 是傳送方(Transmitter)、slave 是接收方(Receiver),也就是說這個傳輸是 master 寫入資料到 slave 時,這個 acknowledge bit 是用來讓 slave 告訴 master「收到!了解!正確!」
當 master 是接收方、slave 是傳送方,也就是說這個傳輸是 master 從 slave 讀取資料時,這個 acknowledge bit 是用來讓 master 告訴 slave「我還要接著讀,請繼續準備下一筆資料」或者是「夠了,我讀完了」。

傳送方會在 ACK 的 SCL 時脈釋放 SDA 線(SDA 為 high),如果接收方正常收到 data 的話,會在第 9 個 bit 將 SDA 拉 low,也就是回應 ACK。

如果接收方有任何狀況,或是 bus 上面根本就沒有那個 address 的 slave 裝置,到了第 9 個 SCL 週期時,當傳送方釋放SDA後,就沒有人去驅動 SDA,這時 SDA 就會因為上拉電阻(Rp)的關係維持在 high,如此一來,傳送方就知道出問題了,不會繼續接下來的傳輸,而要進行錯誤處理,這種情況稱之為「non-acknowledge」,或簡稱為「NACK」。

產生 NACK 的五大原因:

在 multiple data read 傳輸中,當 Master 要告知 Slave 停止傳輸,則會在最後一個 data byte 收下後,改發 NACK 以告訴 Slave 裝置 data 讀取已結束。
沒有裝置去回應ACK(根本沒有接任何裝置或是發出的 address 不屬於 bus 上任一個裝置所有)。
接收的裝置因為某種原因無暇進行指令的接收。
傳輸過程中,接收裝置收到了他無法理解的資料。
傳輸過程中,接收裝置的資料 Buffer 滿了,未處理前,再也收不下來。

Paper 1 - Verilog Coding Styles for Improved Simulation Efficiency

整篇的重點就是在論 "哪一種的 Coding Style 比較有效率"。

  1. Case Statements Vs. Large If/Else-If Structures

    • 重點:兩種能達到一樣功能的敘述,哪一種寫法比較好。
    • 測試項目:8-to-1 Multiplexer。
    • 實驗結果:8-to-1 MUX 用 Case Statements 在 CPU Simulation 比 If Else 快 6%,Memory Usage 則差不多。
    • 總結:當比較項目多的時候應該使用 Case Statements,但這篇 Paper 並未表明,到幾個 Item 才叫多。
  2. Begin-End Statements

    • 重點:在 Code 中加上額外的 Begin End 會不會對效能造成影響。
    • 測試項目:D Flip-Flop with Asynchronous Reset。
    • 實驗結果:只要增加額外 3 個 Begin End 敘述就會造成效能下降 6%、多使用了 6% 的記憶體。
    • 總結:在某些非必要加 Begin End 的地方直接省略(?)
  3. `define Vs. Parameters

    • 重點:哪一種方法定義變數會比較有效率
    • `define 常用於定義常數可以跨模組、跨文件。
    • parameter 常用於模組間參數傳遞,Module 內有效的定義,可用於參數傳遞。
    • defparam:當一個模組引用另外一個模組時,High Level 模組可以改變 Low Level 模組用 parameter 定義的參數值。
    • 測試項目:Models with nine parameter delays and nine `define-macro delays。
    • 實驗結果:如下圖所示。

    • 總結:parameter 和 defparam 記憶體佔用比 `define 大約多 25% - 35%。

  4. Grouping of Assignments Within Always Blocks

    • 重點:把 Assignments 拆成很多的 Always Blocks,還是把相同 Assignments 組在比較少的 Always Blocks 中比較有效。
    • 測試項目:如下程式碼所示。
      ```verilog=
      `ifdef GROUP4 // Group of four always blocks
        always @ ( posedge clk ) begin
            b <= a;
        end
        always @ ( posedge clk ) begin
            c <= b;
        end
        always @ ( posedge clk ) begin
            d <= c;
        end
        always @ ( posedge clk ) begin
            e <= d;
        end
      `else // Four assignments grouped into a single always block
        always @ ( posedge clk ) begin
            b <= a;
            c <= b;
            d <= c;
            e <= d;
        end
      `endif
      
      ```
    • 實驗結果:如下圖所示。

    • 總結:把相同的 Assignments 寫在一個 Always Blocks。

  5. Port Connections

    • 重點:傳資料的時候,通過 Module Ports 還是 Hierarchical Reference 比較好。
    • 測試項目:兩個 FF,一個用 Hierarchical Reference,另一個用 Module Ports
      ```verilog=
      `ifdef NOPORTS
        module PortModels; // Hierarchical Reference
        reg [15:0] q;
        reg [15:0] d;
        reg clk, rstN;
        always @ ( posedge clk or negedge rstN )
            if ( rstN == 0 ) q <= 0;
            else q <= d;
        endmodule
      `else
        module PortModels ( q, d, clk, rstN ); // Module Ports
        output [15:0] q;
        input [15:0] d;
        input clk, rstN;
        reg [15:0] q;
        always @ ( posedge clk or negedge rstN )
            if ( rstN == 0 ) q <= 0;
            else q <= d;
        endmodule
      `endif
      
      ```
    • 實驗結果:如下圖所示。

    • 總結:記憶體使用率差不多,但 Port 在 CPU 效能上差 46%

  6. `timescale Efficiencies

    • 重點:using a higher precision `timescale 更高的時間精度會不會影響效能。
    • 測試項目:`ifdef Times 1 ns、100 ps、10 ps、1 ps
    • 實驗結果:如下圖所示。

    • 總結:精度越高,越吃效能

  7. Displaying $time Values

    • 重點:向 STDOUT(標準輸出)顯示不同的 $time(系統函數,給出當前模擬時間)值會影響模擬效能嗎。
    • 測試項目:如下。
    ```verilog=
    `ifdef NoDisplay // display with no time values
    $display ("Negedge Clk");

    `ifdef OneDisplay // display with one time value
    $display ("Negedge Clk at %d", fClkTime);

    `ifdef TwoDisplay // display with two time values
    $display ("Posedge Clk at %d - Negedge Clk at %d", rClkTime, fClkTime);

    `ifdef TwoDisplay0 // display with two time values
    $display ("Posedge Clk at %0d - Negedge Clk at %0d", rClkTime, fClkTime);

    `ifdef TwoFormat0 // display with two time values
    $display ("Posedge Clk at %0t - Negedge Clk at %0t", rClkTime, fClkTime);

    `ifdef TwoRtimeFormat0 // display with two time values
    $display ("Posedge Clk at %0t - Negedge Clk at %0t", rRealTime,fRealTime);

    * 實驗結果:如下圖所示。

    ![](https://i.imgur.com/HgQwpwS.png)

    * 總結:結果表明,不必要地顯示 $time 值在模擬時間上的成本非常高

8. Clock Oscillators

    * 重點:使用 Always Block、Forever Loop 還是 Verilog Gate Primitives 實現 Clock Oscillator 比較有效。

    * 測試項目:
// always
always #(`cycle/2) clk = ~clk;

// forever
forever #(`cycle/2) clk = ~clk;

// gate
initial begin
    start = 0; #(`cycle/2) start = 1;
end
nand #(`cycle/2) (clk, clk, start);

// dumpfile
$dumpfile(dump.ut); $vtDumpvars;

```

* 實驗結果:如下圖所示。

![](https://i.imgur.com/LX9Owi3.png)

* 總結:不要用 Gate,看他多浪費。

Paper 2 - Passive Device Verilog Models For Board And System-Level Digital Simulation

這篇詳細介紹了一種雙向電阻器,包括範例和基準。還介紹了一種可配置的電源模型。

PS. Passive Device(被動元件)是「一種不會產生電力,但會儲存或釋放電力的電子元件」。

作者需要不同的電阻模型用於串聯並聯和上拉電阻配置,在他們使用 PLD Device 模擬的時候發現了些問題,於是作者的目標是創造一個高效且通用的 Model。

Q:作者遇到了什麼問題?

  1. 不想用不同的 Schematic Symbol 來表示 Pull-up Resistors 和 Termination Resistors。
  2. 反對指定方向的 Schematic Resistor Symbols。

A:所以作者設定了?

  1. 一個 Schematic symbol 適用所有 resistor 配置。
  2. 雙向符號和模型。
  3. Intelligent default setting and If possible, faster default setting option。

Paper 3 - The Fundamentals of Efficient Synthesizable Finite State Machine Design using NC-Verilog and BuildGates

簡言之,介紹 & 手把手教你設計一個 "好" 的 FSM。

架構圖:

FSM 狀態編碼常見有二進制 & Onehot 編碼

Binary-encoded FSM 只需要 2^n 取上限個 FF,Onehot FSM 一個狀態就需要一個 FF。對於一個有 9 - 16 個狀態的 FSM 來說,Binary-encoded 只需要 4 個 FF,Onehot 則需要 9 - 16 個。

Onehot 優勢在於,實現 Onehot 的組合邏輯通常比 Binary-encoded 的 Size 還要小,因為效能常跟組合邏輯大小有關,於是 Onehot 通常比 Binary-encoded 還要快。

怎麼樣是一個好的 FSM Coding Style?

  • Compact
  • Easy to code and understand
  • Facilitate debugging
  • Yield efficient synthesis results

... 總之,該篇研究了幾種不同的 FSM 設計。

(1) 兩個 Always Block Style (One of the best Style)

1 個 Always Block 用循序狀態暫存器,另 1 個用於下一狀態和組合邏輯輸出。

Coding Note:

  • *** 用 Parameters 來定義,而非 `define,
  • 在 Parameter assignments 之後宣告 current_state & next_state
  • Sequential always block 用 Non-blocking (<=)
  • Combinational always block 用 Blocking (=)
  • Combinational always block 引用所有的輸入,like : always @ (*)
  • Combinational always block 需要 default next state assignment
  • Default output assignments 要在 case statement 之前設定
  • Transition arc 都有一個 if 和 else if 或 else,Transition arc 的數量應該與 if-else 一樣
  • *** 所有的 Next assignments 應被放在同一列
  • *** Output 不是在 Always block 被 Assignment 的話,對每個狀態只 Assignment 一次

談談 Paper 5.2 The unfounded fear of transitions to erroneous states,作者表示即使 FSM 現在處於已知狀態,我們的設計也可能會等待一個永遠不會到達的訊號,因為 FSM 更改了狀態而不重置設計的其餘部分,至少,FSM 應該轉換到一個 Error state,讓這個錯誤狀態與設計的其餘部分通訊,在下一個狀態轉換時 Reset。

談談 Paper 5.3 Default,常用的 Default next_state 有三種類型:(1) next 設置為所有 X's,(2) next 設置為預定的恢復狀態,例如 Idle,或 (3) next 僅設為 State FF 的值。如果沒有明確說明所有狀態轉換,進行模擬時 FSM 的輸出變得未知。

(2) 一個 Always Block Style (Avoid This Style!)

最常見的 FSM Coding Style 之一,比 Two Always Block 更冗長、更混亂且更容易出錯。

Coding Note:

  • Parameters 定義跟 Two Style 一樣
  • 只有一個 Always Block,用 Non-blocking assignments
  • ...

(3) Onehot FSM Coding Style (Good Style)

可以用 Inverse case statement 編碼,每個 Case 項都是一個計算結果為 True 或 False 的表達式。注意 Case 語句現在如何與 Onehot state bit 進行 1-bit 比較。

(4) Registered FSM Outputs (Good Style)

好處在於可以確保輸出沒有 glitch-free,FSM outputs 多加第三個 Always sequential block,Output assignments 寫在 Case statement 中

總結:不要用 One always。







你可能感興趣的文章

後端與前端的差異

後端與前端的差異

用 TypeScript 輕鬆學 Design pattern - Command Pattern

用 TypeScript 輕鬆學 Design pattern - Command Pattern

NVIDIA 實習心得

NVIDIA 實習心得






留言討論