0%

day7-i2c-master-bit-ctrl-模擬

為了更好了解並除錯i2c-master-bit-ctrl這個模組,
我建立簡單的testbench檔案

簡略設計如下, 名稱保留與原port一樣的名稱.

`include "i2c_master_bit_ctrl.v"
`include "i2c_master_defines.v"
module tb_i2c_master_bit_ctrl;

// i2c_master_bit_ctrl Parameters
parameter PERIOD = 10 ;

// i2c_master_bit_ctrl Inputs
reg clk = 0 ;
reg rst = 1 ;
reg nReset = 0 ;
reg ena = 1 ;
reg [15:0] clk_cnt = 125 ; // 假設i2c 400KHz
reg [ 3:0] cmd = 0 ;
reg din = 0 ;
reg scl_i = 1 ; // default pull high
reg sda_i = 1 ; // default pull high

// i2c_master_bit_ctrl Outputs
wire cmd_ack ;
wire busy ;
wire al ;
wire dout ;
wire scl_o ;
wire scl_oen ;
wire sda_o ;
wire sda_oen ;

// clk simulation
initial
begin
forever #(PERIOD/2) clk=~clk;
end

// reset simulation
initial
begin
#(PERIOD) nReset = 1;
#(PERIOD*2) rst = 0;
end

// call module
i2c_master_bit_ctrl u_i2c_master_bit_ctrl(
.clk ( clk ),
...
.sda_oen ( sda_oen )
);

// test signal
initial begin
$dumpfile("testbench.vcd");
$dumpvars(0);
// 模擬訊號會撰寫在此
end

initial begin
#100 $finish;
end

endmodule

接下來依據每一個章節分別模擬訊號,

PART0 slave_wait, scl_sync, 除頻

slave_wait (Clock Stretching)

  • 目的: 符合 (scl_oen & ~dscl_oen & ~sSCL) | (slave_wait & ~sSCL);
  • 方法: 選用I2C_CMD_STOP作為cmd輸入給模組,因為會有scl_oen上升的狀況
    #0 cmd = `I2C_CMD_STOP;
    #1200 scl_i = 0;
    #1800 scl_i = 1;
    假設系統50MHz:
  • T0: 下 I2C_CMD_STOP 指令
  • T1: scl_i 被降為0, 代表slave clock stretching
  • T2: 因為 I2C_CMD_STOP 指令, FSM開始動作產生STOP波形
  • T3: 因為 (scl_oen & ~dscl_oen & ~sSCL)條件成立slave_wait 拉高, master發現有clock stretching, 停止cnt, FSM不再跳動
  • T4: slave離開忙碌, 恢復SLC
  • T5: master在數完 filter_cnt 後, 發現 sSLC恢復了, 解除 slave_wait

scl_sync (Clock Synchronization)

  • 目的: 符合dSCL & ~sSCL & scl_oen;條件
  • 方法: 選用I2C_CMD_STOP做為測試用,因為會有scl_oen=1的狀況,
    #0 cmd = `I2C_CMD_STOP;
    #1200 scl_i = 0;
    #1800 scl_i = 1;
  • T0: 下 I2C_CMD_STOP 指令
  • T1: 在這個master動作時, 有其他的master把scl_i拉低
  • T2: 經過filter_cnt後, 這個masterˋ終於發現有問題,趕緊synchronize

除頻

  • 目的: 符合(rst || ~|cnt || !ena || scl_sync)除頻重置的條件
  • 方法: 逐一釋放rst, en, 以及製造 scl_sync
    #0 cmd = `I2C_CMD_STOP;
    #0 ena = 0;
    #400 ena = 1;
    #2200 scl_i = 0;
  • T0: 下 I2C_CMD_STOP 指令
  • T1: 放掉nReset
  • T2: 放掉rst
  • T3: 打開ena
  • T4: 製造scl_sync

可以看到除頻功能正常運作, 除了在T2~T3間有誤判scl_sync, 其餘工作皆正常.

PART1 訊號過濾

捕捉 cSCL, cSDA 訊號

過濾 fSCL, fSDA 訊號

產生過濾完成之sSCL, sSDA, 延遲 dSCL, dSDA

把這三個功能放在一張圖解釋

  • 目的: 觀察訊號採樣(過濾)的方法
  • 方法: 僅以SDA為例,讓SDA訊號變化
    #1000 sda_i = 1;
    #1001 sda_i = 0;
  • T0: sda_i變為1, cSDA變化00 -> 01 -> 11
  • T1: cSDA已經是 11, 但fSDA頻率為1MHz比較慢, 所以要慢慢變
  • T2: fSDA變化為011, 因為符合&fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0])條件, 輸出sSDA為1
  • T3: sda_i變為0, cSDA變化11 -> 10 -> 00
  • T4: cSDA已經是 00, 但fSDA頻率為1MHz比較慢, 所以要慢慢變

可將這兩個訊號, 五個位元看作dff串,
其中若把cSDA當作T時間, 則cSDA[1]剛好會是T-20ns的訊號
而fSDA則是擷取 T-1020ns │ T-2020ns │ T-3020ns 這三個訊號的其中兩個
至於為什麼用這個時間點我無法解釋.

             cSDA[0]      cSDA[1]     fSDA[0]     fSDA[1]     fSDA[2]
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│ DFF │ │ DFF │ │ DFF │ │ DFF │ │ DFF │
│ │ │ │ │ │ │ │ │ │
sda_i──────►│D Q├───►│D Q├──►│D Q├──►│D Q├──►│D Q│
│ │ │ │ │ │ │ │ │ │
clk ────┬─►│CLK │ ┌─►│CLK │ ┌►│CLK │ ┌►│CLK │ ┌►│CLK │
│ │ RST │ │ │ RST │ │ │ RST │ │ │ RST │ │ │ RST │
│ └───────┘ │ └───────┘ │ └───────┘ │ └───────┘ │ └───────┘
│ T │ T-20ns │ T-1020ns │ T-2020ns │ T-3020ns
└────────────┘ │ │ │
filter_cnt │ │ │
──────────────────────────────┴───────────┴───────────┘

若以上無法理解可參考Opencores上的i2c controller core代码解析

至於dSCL, dSDA 僅只是過一個 DFF 作為delay訊號而已.

Part2 訊號判斷

開始結束 START, STOP

忙碌訊號 Busy

這兩個訊號也可以合併一起講

  • 目的: 當外部輸入START, STOP, master要能偵測到
  • 方法: 試著給外部 scl_i, sda_i
#1000 sda_i = 1; scl_i = 1;
#1001 sda_i = 0; scl_i = 1;
#1002 sda_i = 0; scl_i = 1;
#1003 sda_i = 0; scl_i = 1;
#1004 sda_i = 1; scl_i = 1;
  • T0: scl_i=1sda_i訊號拉低, 模擬的START訊號
  • T1: 經過filter_cnt時間後, 內部sSDA真正拉低, sta_condition條件滿足拉高一個clk,
    busy開始
  • T2: scl_i=1sda_i訊號拉高, 模擬的STOP訊號
  • T3: 經過filter_cnt時間後, 內部sSDA真正拉高, sto_condition條件滿足拉高一個clk,
    busy結束

爭輸贏Arbitration Lost

  • 目的: 在wb_c狀態下, 讓仲裁輸掉
  • 方法: 選用I2C_CMD_WRITE, 在wr_c 時sda_i設為0 (Din為0)
    #0 cmd = `I2C_CMD_WRITE; 
    #0 din = 1;
    #4000 sda_i = 0;
    #1500 sda_i = 1;
  • T0: 收到cmd I2C_CMD_WRITE, 下一個state wr_a
  • T1: 執行wr_a, 開始拉低SCL, 給SDA Din, 下一個state wr_b
  • T2: 執行wr_b, 拉高SCL, 給SDA Din, 下一個state wr_c
  • T3: 執行wr_c, 拉高SCL, 給SDA Din, 開啟 sda_chk, 馬上發現別人在這個bit是low, 輸了Arbitration 讓byte-ctrl以及top知道.
  • T4: 無法執行wr_d, 被強制跳回IDLE狀態, SCL, SDA維持.

PART3 訊號輸出

dout

FSM

這兩個部份就不多做sim了
有興趣的人再去下I2C_CMD_START, I2C_CMD_STOP, I2C_CMD_WRITE, I2C_CMD_READ四個command, 如同前面的cmd下法