0%

day9-i2c_master_byte_ctrl 模擬

為了更好了解並除錯i2c_master_byte_ctrl這個模組,
我建立簡單的testbench檔案

`include "i2c_master_byte_ctrl.v"
`include "i2c_master_bit_ctrl.v"
`include "i2c_master_defines.v"

module _;

// i2c_master_byte_ctrl Parameters
parameter PERIOD = 10 ;

// i2c_master_byte_ctrl Inputs
reg clk = 0 ;
reg rst = 1 ;
reg nReset = 0 ;
reg ena = 1 ;
reg [15:0] clk_cnt = 5 ; //若50MHz, 應設為125, 以達到400KHz速度
reg start = 0 ;
reg stop = 0 ;
reg read = 0 ;
reg write = 0 ;
reg ack_in = 0 ;
reg [7:0] din = 0 ;
reg scl_i = 1 ;
reg sda_i = 1 ;

// i2c_master_byte_ctrl Outputs
wire cmd_ack ;
wire ack_out ;
wire i2c_busy ;
wire i2c_al ;
wire [7:0] dout ;
wire scl_o ;
wire scl_oen ;
wire sda_o ;
wire sda_oen ;


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

initial
begin
#(PERIOD*2) nReset = 1;
end

i2c_master_byte_ctrl __ (
.clk ( clk ),
... 省略
.sda_oen ( sda_oen )
);

initial begin
$dumpfile("test.vcd");
$dumpvars(0);
// 這邊放置模擬參數
end

initial begin
#6000 $finish;
end

endmodule

狀態跳轉測試

code

  • rst 依序放掉
  • 給Command Register 訊號 Start+Read
  • 收到 byte ack 後放掉
  • 在一段時間後停止模擬
initial begin
$dumpfile("test.vcd");
$dumpvars();
#5 nReset = 1;
#5 rst = 0;
#3 start = 1; read = 1; // 設定 cr
wait(cmd_ack) start = 0; read = 0; // 模擬自動 clear cr
end

initial begin
#4000 $finish;
end

waveform

先看起始部分:

  • clk: 系統頻率
  • 沒寫出來的scl_i, sda_i: 暫時設high以防影響.
  • scl_oen, sda_oen: bit ctrl 的輸出, 根據其state會有不同輸出.
  • c_state: (byte ctrl 的)
    • 因為 byte ctrl 使用系統clk, 很快就跳到 ST_IDLE, 並且指定start指令給 bit ctrl
    • 等待 bit ctrl 的 core_ack, 改成跳到 ST_START, 並且指定read指令給 bit ctrl
  • core_ack: bit ctrl 每完成一個 bit 指令, 就會回傳 core_ack(內部ack) 給 byte ctrl
  • clk_en: 為 bit ctrl 狀態機跳動的頻率, 經prescale value除頻, 為了方便觀察設為 5, 所以只跳動5次
  • bit controller.c_state: bit ctrl 的 狀態, 會在每一次 clk_en 之後改變

請特別注意此處的state, 除了IDLE, 即使已經在該state, 收到core_ack才做出動作

寫入測試

code

initial begin
$dumpfile("test.vcd");
$dumpvars();
assign scl_i = scl_oen; // 讓 scl_i 隨著 scl_oen 波動
sda_i = 1; // 假設 slave sda_i 為高阻抗
#5 nReset = 1; // rst 依序放掉, 第一個要放掉的 low active rst
#5 rst = 0; // 第二個放掉的 high active rst
#5 start = 1; write = 1; // 設定 CR
din = 8'b00111011; // din 設定為 讀取地址 0011101, W/R 為 1
wait( __.c_state == 8 )
sda_i = 0; // slave在 ack state 答覆 ack (sda_i = 0)
wait(cmd_ack | i2c_al)
start = 0; write = 0; din = 8'b0; sda_i = 1; // 模擬收到 byte ack 後p【放掉 CR
end

initial begin
#4000 $finish;
end

waveform

假設寫入為slave地址, 0011101, W/R 為 1 代表想要讀取該地址的資訊

clk:                系統頻率  
scl_i, sda_i: slave 的 scl, sda
scl_oen, sda_oen: bit ctrl 輸出, 在SCL為高時保持SDA值
core_ack: bit ctrl 每完成一個 bit 指令, 就會回傳 內部ack 給 byte ctrl
c_state: byte ctrl 的狀態, 使用系統clk
din: 地址 + W/R
ack_out: 傳遞到 top 的 ack 訊號

寫入7-bit addr, 在寫入1 bit W/R, 接著收ACK

Aribitration Lost

code

// 硬刻出scl_i跟sda_i
initial begin
#200
repeat(9) begin
#180 scl_i = ~scl_i;
#180 scl_i = ~scl_i;
end
end

initial begin
@(negedge scl_i) #100 sda_i = 0; // 必須在 scl low 才可以改變值
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
end
// 這部分雷同上一個實驗, 取消掉 scl_i, sda_i 賦值的部分
initial begin
$dumpfile("test.vcd");
$dumpvars();
//assign scl_i = scl_oen; // 讓 scl_i 隨著 scl_oen 波動
//sda_i = 1; // 假設 slave sda_i 為高阻抗
#5 nReset = 1; // rst 依序放掉, 第一個要放掉的 low active rst
#5 rst = 0; // 第二個放掉的 high active rst
#5 start = 1; write = 1; // 設定 CR
din = 8'b00111011; // din 設定為 讀取地址 0011101, W/R 為 1
wait( __.c_state == 8 )
sda_i = 0; // slave在 ack state 答覆 ack (sda_i = 0)
wait(cmd_ack | i2c_al)
start = 0; write = 0; din = 8'b0; sda_i = 1; // 模擬收到 byte ack 後p【放掉 CR
end

waveform

當aribitration Lost發生時, master本身進入 ST_IDLE, 下空指令 I2C_CMD_NOP

slave_wait

code

initial begin
#200 // 讓SCL與master的不同步
#200 scl_i = 0;
#600 scl_i = 1;
repeat(9) begin
#200 scl_i = ~scl_i;
#400 scl_i = ~scl_i;
end
end

initial begin
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
end

waveform

slave_wait 會讓 bit ctrl rst, 慢速 clock 重算.
直到 iSCL 為 high 才重新計算

scl_sync

code

initial begin
#200
#200 scl_i = 0;
repeat(9) begin
#180 scl_i = ~scl_i;
#100 scl_i = ~scl_i;
end
end

initial begin
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 0;
@(negedge scl_i) #100 sda_i = 1;
@(negedge scl_i) #100 sda_i = 1;
end

waveform

在oSCL HIGH時, 若發現 iSCL negedge 則讓 慢速clk 重算.