今天,我們要來完成整個 I2C 的最後一個部份了!
先來看看這個 I2C Master write 模塊該有哪些輸入輸出腳吧:
輸入:
(如果是 write 模式,那前 8 bit 傳 address,而後 8 bit 傳 data_i)
輸出:
(如果是 read 模式,那前 8 bit 傳 address,而後面都傳 "1" (高阻抗),因為要接收 data 所以斷開,由 slave 去給信號)
輸入+輸出:
module usage_I2C_write_R_W(
en,
clk_sys,
rst_n,
addr,
data_i,
SCLK,
SDA,
data_o
);
/*------ports declaration------*/
input clk_sys;
input rst_n;
input en;
input [7:0] addr;
input [7:0] data_i;
inout SDA;
output SCLK;
output [15:0] data_o;
reg [15:0] data_o;
wire SCLK;
再來,因為我打算收資料也做個狀態機,先來定義三個狀態:
/*------parameter------*/
parameter IDLE = 2'd0;
parameter SHIFT = 2'd1;
parameter STOP = 2'd2;
宣告變數:
引用上次寫好的模組
/*------module I2C_write instantiate------*/
I2C_write_R_W U0(
.clk_sys(clk_sys),
.rst_n(rst_n),
.en(en),
.data_R({addr, 1'b1, 8'hff, 1'b0, 8'hff, 1'b1}),//27bit
.data_W({addr, 1'b1, data_i, 1'b1}),//18bit
.ACK(ACK),
.ACK1(),
.ACK2(),
.ACK3(),
.rstACK(rstACK),
.SCLK(SCLK),
.SDA(SDA),
.ldnACK1(ldnACK1),
.ldnACK2(ldnACK2),
.ldnACK3(ldnACK3),
.R_W(addr[0]),
.tick_I2C_neg(tick_I2C_neg)
);
狀態邏輯:
/*------fstate state------*/
always@(posedge clk_sys or negedge rst_n)begin
if(!rst_n)fstate <= IDLE;
else begin
case(fstate)
IDLE:begin
if(ldnACK1&&(addr[0])&&tick_I2C_neg)fstate <= SHIFT;
else fstate <= IDLE;
end
SHIFT:begin
if(count==4'd15&&tick_I2C_neg)fstate <= STOP;
else fstate <= SHIFT;
end
STOP:begin
fstate <= IDLE;
end
default:begin
fstate <= IDLE;
end
endcase
end
end
輸出邏輯:
/*------fstate output------*/
always@(posedge clk_sys or negedge rst_n)begin
if(!rst_n)begin
count <= 4'd0;
data_temp <= 16'd0;
data_o <= 16'd0;
end
else begin
case(fstate)
IDLE:begin
count <= 4'd0;
data_temp <= data_temp;
end
SHIFT:begin
if(!ldnACK2&&tick_I2C_neg)begin
count <= count + 4'd1;
data_temp <= {data_temp[14:0], SDA};
end
else begin
count <= count;
data_temp <= data_temp;
end
end
STOP:begin
count <= 4'd0;
data_o <= data_temp;
end
default:begin
count <= 4'dx;
data_temp <= 16'hxx;
data_o <= 16'hxx;
end
endcase
end
end
endmodule
這樣就結束了我們的 I2C 整個模塊
再來是 TestBench 的部分~~~~
`timescale 1ns / 10ps
module i2c_tb();
localparam durTime = 5*1000;
reg sysClk, rst_n, tick_tx, sda_i;
reg [7:0]addr_i, data_i;
wire [15:0]data_o;
wire SCL;
wire SDA;
integer i;
assign SDA = (sda_i==1'b1)?1'bz:1'b0;
pullup(SDA);
usage_I2C_write_R_W UUT(
.en(tick_tx),
.clk_sys(sysClk),
.rst_n(rst_n),
.addr(addr_i),
.data_i(data_i),
.SCLK(SCL),
.SDA(SDA),
.data_o(data_o)
);
always@(negedge SCL, negedge rst_n)begin
if(!rst_n)sda_i <= 1'b1;
else if(i==8||i==17||i==27||i==28||i==36||i==35||i==43)#2000 sda_i <= 1'b0;//<27 for write
else#2000 sda_i <= 1'b1;
end
always@(posedge SCL, negedge rst_n)begin
if(!rst_n)i <= 0;
else i <= i + 1;
end
always #10 sysClk = ~sysClk;
initial begin
sysClk = 0;
rst_n = 1;
tick_tx = 0;
sda_i = 1'b1;
addr_i = 8'hA6;
data_i = 8'h81;
#50 rst_n = 0;
#50 rst_n = 1;
#10 tick_tx = 1;
#10 tick_tx = 0;
repeat(50) #durTime;
addr_i = 8'hA7;
#10 tick_tx = 1;
#10 tick_tx = 0;
repeat(100) #durTime;
$stop;
end
endmodule
值得注意的是 Verilog 本身就有 "pullup" 語法可以模擬上拉電阻的效果
Wave
可以看到上半部的部分:
前半部是 write 模式,先傳送 addr_i 的 10100110(最後一 bit 是 "0" ,所以是 write)再來是 ACK,再接著 data_i 的 10000001,最後也是 1 bit 的 ACK。
後半部是 read 模式,先傳送 addr_i 的 10100111(最後一 bit 是 "1" ,所以是 read ),再來是 ACK,接著的 16 bit 是由 tb 發送的 01111110 和 11111101,而最後再搭配一個 master 端的 non-ACK 完成通訊。
而由此波形也可以看見 SDA 確實有在 SCLK 為 LOW 時才改變值。
註:虛線是因為 pullup 的關係,所以 HIGH 才變成虛線
而這裡可以看到 tick_I2C_neg(紅色那條)位於 SDA 的正中間,以此來取樣 SDA 信號可以確保值的正確性~~~~
來看看 State Machine Viewer 是否如預期
狀態確實都有照著我們的想法去跑呢~
那麼 I2C 的教學也到這邊結束嘍~~~~