I2C控制器的Verilog建模之一

时间:2023-12-09 13:32:19

  前言:之前申请了ADI公司的一款ADV7181CBSTZ的视频解码芯片,正好原装DE2板子安的是同系列的ADV7181BBSTZ。虽然都是ADV7181的宗出,但是寄存器配置等等还是有些诧异,引脚也不兼容。而且ADI已经停产了ADV7181B,现在主推C系列的。关于7181不同系列配置和操作问题可以在http://ez.analog.com社区,ADI公司的工程师都会直接帮你回答,我搜索了一部分Q&A,里面的问题基本都能得到不错解决。ps.其实从工程师解答的方式,对比一下自己遇到问题首先该怎么思考解决方法,这一点是可以学到很多知识。

  设计思路:废话多说了,I2C总线协议主要用来给模拟和数字芯片配置内能寄存器的主要方式,因为用ADI的芯片经历比较多,他们的芯片主要是I2C和SPI配置。I2C总线标准细节请参照《i2c总线协议(中文版pdf)》。以下设计的I2C模型肯定没有协议里的功能那么完备,这里主要是考虑到I2C仅仅用做配置寄存器这一点点的通信量,不像早前E2PROM还能涉及到I2C块页读写操作,状态机可以简单到用一个计数器就可以控制完成,没有那么多条件转移。

  所以类似这种应用场合下,I2C、SPI协议可以简单到和UART一样。那么就涉及到状态的颗粒程度了,参考了一篇opencores上发布LGPL开源的I2C控制器,其功能复杂程度不是一般人能写出来的,该控制器将手册读写SDA和SCLK的上升时间、保持时间参数化,细化了一个比特的电平变化。网络上还有一种思路是颗粒化成一比特四个状态(上升沿、高电平、下降沿、低电平)。我选择了后者,设计模块功能够用就行。具体写操作代码比较简单最后附出。

  难点和技巧:

  (a)SDA三态口设计。由于I2C总线里SDA数据线是双向线,多个I2C设备的SDA是想“与”的逻辑,因此在不是拥有控制权的时候应该释放且高阻输出。以前用51单片机配置SA7113时用C写的I2C里面是没有办法输出高阻的,因此在等待从机ACK时候,主机在这个时刻是置SDA为1的。提这一点避免让一些人误会设置高祖就是置1。多建议inout的使用尽量用在最顶层模块,避免综合出错,或者在STP2使用时候出错。我见过Terasic的sdram控制器就是这么做的。

  (b)读操作。注意读操作需要两个START信号。否则读寄存器收不到ACK信号,然后误以为写操作有问题。

  (c)关于ACK和NACK。这里站在主机角度,每次读写都应该等待ACK,这一点肯定是没错的。其实NACK是主机在结束最后一个比特之后结束信号之间由主机产生NACK。关于这一点的解释,这里贴出ADI工程师的回答:”In Read Mode, the highest subaddress register contents continue to be output until the master device issues a no-acknowledge. This indicates the end of a read. A no-acknowledge condition is where the SDA line is not pulled low on the ninth pulse.I think this is what you are referring to.  A master NACK during the read back byte stops the read back.  In absolute I2C standards probably not but for this part it's ok since you can only access one register at a time and the subaddress register does not auto-increment so doing another byte read will just return the same byte you just read.  A stop essentially kills the transaction anyways.  This is an older device.“

  (d)个人习惯。在每个操作结束后产生一个脉冲宽度的模块ack信号(非I2C的ACK),这样做可以将该信号反馈激励该模块,提高模块使用效率。对于读写请求的处理,个人喜欢采信号的边沿变化,因为对于一些前个模块给的是脉冲信号就不一定可以保持到完整激励状态机转移时候的电平宽度。另外:配合这模块ack和读写请求就能完美提高模块效率。

  写操作源码1:

 `timescale  ns /  ps
`define SIM
`define SYS_CLK
`define I2C_CLK
`define I2C_DIV `SYS_CLK/`I2C_CLK
`define ADV7180
`define SCLK_CNT_WIDTH
module i2c_controller(
sys_clk,
sys_rst_n,
sys_wreq_i,
reg_addr_i,
sys_data_i,
i2c_idle_o,
i2c_ack_o,
i2c_sclk,
i2c_sdat
);
input sys_clk;
input sys_rst_n;
input sys_wreq_i;
input [:] reg_addr_i; //从机寄存器地址
input [:] sys_data_i; //待写的数据
output i2c_idle_o; //模块空闲
output i2c_ack_o; //非I2C的ACK,模块的ack
output i2c_sclk;
inout i2c_sdat;
`ifdef ADV7180
parameter DEVICE_READ = 'h40; //器件读操作地址
parameter DEVICE_WRITE = 'h41; //器件写操作地址
`endif `ifdef SIM
parameter ST_WIDTH = ;
parameter IDLE = "IDLE...",
START1 = "START1.",
SET_SLAVE = "SET_SLA",
ACK1 = "ACK1...",
SET_REG = "SET_REG",
ACK2 = "ACK2...",
WR_DATA = "WR_DATA",
ACK3 = "ACK3...",
STOP = "STOP..."; `else
`define FSM
parameter ST_WIDTH = ;
parameter IDLE = `FSM'b0_0000_0001,
START1 = `FSM'b0_0000_0010, //写操作一共有1个start,读操作一共2个start
SET_SLAVE = `FSM'b0_0000_0100,
ACK1 = `FSM'b0_0000_1000,
SET_REG = `FSM'b0_0001_0000,
ACK2 = `FSM'b0_0010_0000,
WR_DATA = `FSM'b0_0100_0000,
ACK3 = `FSM'b0_1000_0000,
STOP = `FSM'b1_0000_0000;
`endif //GENERATE SCLK_R
reg [`SCLK_CNT_WIDTH-:] sclk_cnt = ;
always @ (posedge sys_clk) begin
if('b0 == sys_rst_n) sclk_cnt <= 0;
else if(sclk_cnt < `I2C_DIV-) sclk_cnt <= sclk_cnt + 'd1;
else sclk_cnt <= ;
end `define SCLK_POS (sclk_cnt == `SCLK_CNT_WIDTH'd499)
`define SCLK_HIGH (sclk_cnt == `SCLK_CNT_WIDTH'd124)
`define SCLK_NEG (sclk_cnt == `SCLK_CNT_WIDTH'd249)
`define SCLK_LOW (sclk_cnt == `SCLK_CNT_WIDTH'd374) assign i2c_sclk = (sclk_cnt <= `SCLK_CNT_WIDTH'd249)?1'b1:'b0; //caputre the posedge of sys_wreq_i;
reg sys_wreq_r0 = ;
always @ (posedge sys_clk) begin
if(sys_rst_n == 'b0) sys_wreq_r0 <= 0;
else sys_wreq_r0 <= sys_wreq_i;
end
wire do_wreq = sys_wreq_i & ~sys_wreq_r0;
//generate the wr_start;
reg wr_start = ;
always @ (posedge sys_clk) begin
if(sys_rst_n == 'b0) wr_start <= 0;
else if(i2c_ack_o == 'b1) wr_start <= 0;
else if(do_wreq) wr_start <= ;
else wr_start <= wr_start;
end
//FSM
reg [:] data2slave = ;
reg sdat_r = ;
reg link = ; //控制三态口读写方向,默认为读方向0,写时为1
reg [:] bit_cnt = 'd0;
reg [ST_WIDTH-:] c_st = IDLE;
reg [ST_WIDTH-:] n_st = IDLE;
//FSM-1
always @ (posedge sys_clk) begin
if('b0 == sys_rst_n) c_st <= IDLE;
else c_st <= n_st;
end
//fsm-2
//实际的状态转移中ack[2:0]比物理等待的ack少四分之一
always @ (*) begin
n_st = IDLE;
case(c_st)
IDLE:begin
n_st = ((wr_start == 'b1)&&(`SCLK_HIGH))?START1:IDLE;end
START1:begin
n_st = (`SCLK_LOW)?SET_SLAVE:START1;end //sclk为高电平中心时转移
SET_SLAVE:begin
n_st = ((`SCLK_LOW)&&(bit_cnt == 'd8))?ACK1:SET_SLAVE;end//数据在低电平是更新
ACK1:begin
n_st = (`SCLK_NEG)?SET_REG:ACK1;end//为保证下一步设置寄存器,提前1/4进入下一个状态
SET_REG:begin
n_st = ((`SCLK_LOW)&&(bit_cnt == 'd8))?ACK2:SET_REG;end//数据在低电平是更新
ACK2:begin
n_st = (`SCLK_NEG)?WR_DATA:ACK2;end//为保证下一步设置寄存器,提前1/4进入下一个状态
WR_DATA:begin
n_st = ((`SCLK_LOW)&&(bit_cnt == 'd8))?ACK3:WR_DATA;end
ACK3:begin
n_st = (`SCLK_NEG)?STOP:ACK3;end
STOP:begin
n_st = (`SCLK_NEG)?IDLE:STOP;end
default:begin
n_st = IDLE;end
endcase
end
//FSM-3
always @ (posedge sys_clk) begin
if(sys_rst_n == 'b0) begin
link <= 'd1;
data2slave <= 'd0;
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
else begin
case(c_st)
IDLE:begin
link <= 'd1;
data2slave <= DEVICE_WRITE;
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
START1:begin
link <= 'd1;
bit_cnt <= 'd1;
data2slave <= (`SCLK_LOW)?data2slave<<:data2slave;
sdat_r <= (`SCLK_LOW)?data2slave[]:'d0; //pull down,由于data2slave缓存一级的缘故,需要提前在START里输出第MSB位
end
SET_SLAVE:begin
if(`SCLK_LOW) begin
link <= (bit_cnt == 'd8)?1'b0:'b1; //释放数据总线
bit_cnt <= (bit_cnt == 'd8)?4'd0:bit_cnt+'d1;
data2slave <= {data2slave[:],'d0};//左移一位
sdat_r <= (bit_cnt == 'd8)?1'd1:data2slave[];end
else begin
link <= link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= sdat_r;end
end
ACK1:begin
link <= 'd0;
data2slave <= (`SCLK_POS)?reg_addr_i:data2slave; //读入待写的寄存器地址
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
SET_REG:begin
if(`SCLK_LOW) begin
link <= (bit_cnt == 'd8)?1'b0:'b1; //释放数据总线
bit_cnt <= (bit_cnt == 'd8)?4'd0:bit_cnt+'d1;
data2slave <= {data2slave[:],'d0};//左移一位
sdat_r <= (bit_cnt == 'd8)?1'd1:data2slave[];end
else begin
link <= link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= sdat_r;end
end
ACK2:begin
link <= 'd0;
data2slave <= (`SCLK_POS)?sys_data_i:data2slave; //读入待写的寄存器地址
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
WR_DATA:begin
if(`SCLK_LOW) begin
link <= (bit_cnt == 'd8)?1'b0:'b1; //释放数据总线
bit_cnt <= (bit_cnt == 'd8)?4'd0:bit_cnt+'d1;
data2slave <= {data2slave[:],'d0};//左移一位
sdat_r <= data2slave[];end
else begin
link <= link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= sdat_r;end
end
ACK3:begin
link <= 'd0;
sdat_r <= 'd0;//预先拉低
bit_cnt <= bit_cnt;
data2slave <= data2slave;end
STOP:begin
link <= (`SCLK_LOW)?'b1:link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= (`SCLK_HIGH)?'b1:sdat_r;end
default:begin
link <= 'd1;
data2slave <= 'd0;
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
endcase
end
end
//assign
assign i2c_sdat = (link == 'b1)?sdat_r:8'hzz;
assign i2c_idle_o = (c_st == IDLE)?'b1:1'b0;
assign i2c_ack_o = ((c_st == STOP)&&(`SCLK_NEG))?'b1:1'b0; endmodule

写操作源码2:

`timescale  ns /  ps
`define LUT_WIDTH
module adv7180_config(
sys_clk,
sys_rst_n,
i2c_ack_i,
sys_wreq_o,
sys_data_o,
reg_addr_o,
config_done_o
);
input sys_clk;
input sys_rst_n;
input i2c_ack_i;
output sys_wreq_o;
output [:] sys_data_o;
output [:] reg_addr_o;
output config_done_o; //generate wreq_o
reg sys_wreq_o = ;
reg [`LUT_WIDTH-:] lut_index = ;
reg [:] lut_data = ;
always @ (posedge sys_clk) begin
if('b0 == sys_rst_n) begin
sys_wreq_o <= ;
lut_index <= ;end
else if((i2c_ack_i == 'b1)&&(config_done_o == 1'b0)) begin
sys_wreq_o <= ;
lut_index <= lut_index + 'd1;end
else begin
sys_wreq_o <= ;
lut_index <= lut_index;end
end
//assign
assign config_done_o = (lut_index == `LUT_WIDTH'd15)?1'b1:'b0;
assign sys_data_o = lut_data[:];
assign reg_addr_o = lut_data[:];
//lut
always @ (*) begin
case(lut_index)
`LUT_WIDTH'd0:lut_data <= 16'h2330;
`LUT_WIDTH'd1:lut_data <= 16'h4161;
`LUT_WIDTH'd2:lut_data <= 16'hf22a;
`LUT_WIDTH'd3:lut_data <= 16'ha344;
`LUT_WIDTH'd4:lut_data <= 16'h4353;
`LUT_WIDTH'd5:lut_data <= 16'h1325;
`LUT_WIDTH'd6:lut_data <= 16'h6546;
`LUT_WIDTH'd7:lut_data <= 16'h7657;
`LUT_WIDTH'd8:lut_data <= 16'h8565;
`LUT_WIDTH'd9:lut_data <= 16'h9357;
`LUT_WIDTH'd10:lut_data <= 16'h1450;
`LUT_WIDTH'd11:lut_data <= 16'h1311;
`LUT_WIDTH'd12:lut_data <= 16'h1542;
`LUT_WIDTH'd13:lut_data <= 16'h1133;
`LUT_WIDTH'd14:lut_data <= 16'h1134;
`LUT_WIDTH'd15:lut_data <= 16'h1965;
endcase
end endmodule

仿真写操作源码3:

 `timescale  ns /  ps
module tb_i2c();
reg sys_clk;
reg sys_rst_n;
initial begin
sys_clk = ;
sys_rst_n = ;
# sys_rst_n = ;
end always begin
# sys_clk=~sys_clk;end wire i2c_sclk;
wire sys_wreq;
wire [:] reg_addr;
wire [:] sys_data;
wire i2c_idle;
wire i2c_ack;
wire i2c_sdat;
i2c_controller u0(
.sys_clk( sys_clk ),
.sys_rst_n( sys_rst_n ),
.sys_wreq_i( sys_wreq ),
.reg_addr_i( reg_addr ),
.sys_data_i( sys_data ),
.i2c_idle_o( i2c_idle ),
.i2c_ack_o( i2c_ack ),
.i2c_sclk( i2c_sclk),
.i2c_sdat( i2c_sdat )
); wire config_done;
adv7180_config u1(
.sys_clk( sys_clk ),
.sys_rst_n( sys_rst_n ),
.i2c_ack_i( i2c_ack ),
.sys_wreq_o( sys_wreq ),
.sys_data_o( sys_data ),
.reg_addr_o( reg_addr ),
.config_done_o( config_done )
); endmodule