I2C控制器的Verilog建模之一

  前言:之前申请了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:

  1 `timescale 1 ns / 1 ps
  2 `define SIM
  3 `define SYS_CLK    50000000
  4 `define I2C_CLK    100000
  5 `define I2C_DIV    `SYS_CLK/`I2C_CLK
  6 `define ADV7180
  7 `define SCLK_CNT_WIDTH    9
  8 module i2c_controller(
  9                                 sys_clk,
 10                                 sys_rst_n,
 11                                 sys_wreq_i,
 12                                 reg_addr_i,
 13                                 sys_data_i,
 14                                 i2c_idle_o,
 15                                 i2c_ack_o,
 16                                 i2c_sclk,
 17                                 i2c_sdat
 18                                 );
 19 input sys_clk;
 20 input sys_rst_n;
 21 input sys_wreq_i;
 22 input [7:0] reg_addr_i;    //从机寄存器地址
 23 input [7:0] sys_data_i;    //待写的数据
 24 output i2c_idle_o;        //模块空闲
 25 output i2c_ack_o;    //非I2C的ACK,模块的ack
 26 output i2c_sclk;
 27 inout i2c_sdat;
 28 `ifdef ADV7180
 29     parameter DEVICE_READ = 8'h40;        //器件读操作地址
 30     parameter DEVICE_WRITE = 8'h41;        //器件写操作地址
 31 `endif
 32 
 33 `ifdef SIM
 34         parameter ST_WIDTH = 56;
 35         parameter IDLE = "IDLE...",
 36                         START1 = "START1.",
 37                         SET_SLAVE = "SET_SLA",
 38                         ACK1 = "ACK1...",
 39                         SET_REG = "SET_REG",
 40                         ACK2 = "ACK2...",
 41                         WR_DATA = "WR_DATA",
 42                         ACK3 = "ACK3...",
 43                         STOP = "STOP...";
 44 
 45 `else
 46         `define FSM    9
 47         parameter ST_WIDTH = 9;
 48         parameter IDLE = `FSM'b0_0000_0001,
 49                         START1 =  `FSM'b0_0000_0010,    //写操作一共有1个start,读操作一共2个start
 50                         SET_SLAVE =  `FSM'b0_0000_0100,
 51                         ACK1 =  `FSM'b0_0000_1000,
 52                         SET_REG =  `FSM'b0_0001_0000,
 53                         ACK2 =  `FSM'b0_0010_0000,
 54                         WR_DATA =  `FSM'b0_0100_0000,
 55                         ACK3 =  `FSM'b0_1000_0000,
 56                         STOP =  `FSM'b1_0000_0000;
 57 `endif
 58 
 59 //GENERATE SCLK_R
 60 reg [`SCLK_CNT_WIDTH-1:0] sclk_cnt = 0;
 61 always @ (posedge sys_clk) begin
 62 if(1'b0 == sys_rst_n) sclk_cnt <= 0;
 63 else if(sclk_cnt < `I2C_DIV-1) sclk_cnt <= sclk_cnt + 1'd1;
 64 else sclk_cnt <= 0;
 65 end
 66 
 67 `define SCLK_POS    (sclk_cnt == `SCLK_CNT_WIDTH'd499)
 68 `define SCLK_HIGH    (sclk_cnt == `SCLK_CNT_WIDTH'd124)
 69 `define SCLK_NEG    (sclk_cnt == `SCLK_CNT_WIDTH'd249)
 70 `define SCLK_LOW    (sclk_cnt == `SCLK_CNT_WIDTH'd374)
 71 
 72 assign i2c_sclk = (sclk_cnt <= `SCLK_CNT_WIDTH'd249)?1'b1:1'b0;
 73 
 74 //caputre the posedge of sys_wreq_i;
 75 reg sys_wreq_r0 = 0;
 76 always @ (posedge sys_clk) begin
 77 if(sys_rst_n == 1'b0)    sys_wreq_r0 <= 0;
 78 else sys_wreq_r0 <= sys_wreq_i;
 79 end
 80 wire do_wreq = sys_wreq_i & ~sys_wreq_r0;
 81 //generate the wr_start;
 82 reg wr_start = 0;
 83 always @ (posedge sys_clk) begin
 84 if(sys_rst_n == 1'b0)    wr_start <= 0;
 85 else if(i2c_ack_o == 1'b1) wr_start <= 0;
 86 else if(do_wreq)    wr_start <= 1;
 87 else wr_start <= wr_start;
 88 end
 89 //FSM
 90 reg [7:0] data2slave = 0;
 91 reg sdat_r = 1;
 92 reg link = 0;        //控制三态口读写方向,默认为读方向0,写时为1
 93 reg [3:0] bit_cnt = 4'd0;
 94 reg [ST_WIDTH-1:0] c_st = IDLE;
 95 reg [ST_WIDTH-1:0] n_st = IDLE;
 96 //FSM-1
 97 always @ (posedge sys_clk) begin
 98 if(1'b0 == sys_rst_n) c_st <= IDLE;
 99 else c_st <= n_st;
100 end
101 //fsm-2
102 //实际的状态转移中ack[2:0]比物理等待的ack少四分之一
103 always @ (*) begin
104     n_st = IDLE;
105     case(c_st)
106     IDLE:begin
107                 n_st = ((wr_start == 1'b1)&&(`SCLK_HIGH))?START1:IDLE;end
108     START1:begin
109                     n_st = (`SCLK_LOW)?SET_SLAVE:START1;end    //sclk为高电平中心时转移
110     SET_SLAVE:begin
111                             n_st = ((`SCLK_LOW)&&(bit_cnt == 4'd8))?ACK1:SET_SLAVE;end//数据在低电平是更新
112     ACK1:begin 
113                 n_st = (`SCLK_NEG)?SET_REG:ACK1;end//为保证下一步设置寄存器,提前1/4进入下一个状态
114     SET_REG:begin
115                         n_st = ((`SCLK_LOW)&&(bit_cnt == 4'd8))?ACK2:SET_REG;end//数据在低电平是更新
116     ACK2:begin
117                     n_st = (`SCLK_NEG)?WR_DATA:ACK2;end//为保证下一步设置寄存器,提前1/4进入下一个状态
118     WR_DATA:begin
119                         n_st = ((`SCLK_LOW)&&(bit_cnt == 4'd8))?ACK3:WR_DATA;end
120     ACK3:begin
121                     n_st = (`SCLK_NEG)?STOP:ACK3;end
122     STOP:begin
123                     n_st = (`SCLK_NEG)?IDLE:STOP;end
124     default:begin
125                     n_st = IDLE;end
126     endcase
127 end
128 //FSM-3
129 always @ (posedge sys_clk) begin
130 if(sys_rst_n == 1'b0) begin
131                                 link <= 1'd1;
132                                 data2slave <= 8'd0;
133                                 bit_cnt <= 4'd0;
134                                 sdat_r <= 1'd1;
135                                 end
136 else begin
137     case(c_st)
138     IDLE:begin
139                                 link <= 1'd1;
140                                 data2slave <= DEVICE_WRITE;
141                                 bit_cnt <= 4'd0;
142                                 sdat_r <= 1'd1;
143                                 end
144     START1:begin
145                                 link <= 1'd1;
146                                 bit_cnt <= 4'd1;
147                                 data2slave <= (`SCLK_LOW)?data2slave<<1:data2slave;
148                                 sdat_r <= (`SCLK_LOW)?data2slave[7]:1'd0;    //pull down,由于data2slave缓存一级的缘故,需要提前在START里输出第MSB位
149                                 end
150     SET_SLAVE:begin
151                             if(`SCLK_LOW) begin
152                                     link <= (bit_cnt == 4'd8)?1'b0:1'b1;    //释放数据总线
153                                     bit_cnt <= (bit_cnt == 4'd8)?4'd0:bit_cnt+1'd1;
154                                     data2slave <= {data2slave[6:0],1'd0};//左移一位
155                                     sdat_r <= (bit_cnt == 4'd8)?1'd1:data2slave[7];end
156                             else begin
157                                     link <= link;
158                                     bit_cnt <= bit_cnt;
159                                     data2slave <= data2slave;
160                                     sdat_r <= sdat_r;end
161                         end
162     ACK1:begin
163                                 link <= 1'd0;
164                                 data2slave <= (`SCLK_POS)?reg_addr_i:data2slave;    //读入待写的寄存器地址
165                                 bit_cnt <= 4'd0;
166                                 sdat_r <= 1'd1;
167                                 end
168     SET_REG:begin
169                             if(`SCLK_LOW) begin
170                                     link <= (bit_cnt == 4'd8)?1'b0:1'b1;    //释放数据总线
171                                     bit_cnt <= (bit_cnt == 4'd8)?4'd0:bit_cnt+1'd1;
172                                     data2slave <= {data2slave[6:0],1'd0};//左移一位
173                                     sdat_r <= (bit_cnt == 4'd8)?1'd1:data2slave[7];end
174                             else begin
175                                     link <= link;
176                                     bit_cnt <= bit_cnt;
177                                     data2slave <= data2slave;
178                                     sdat_r <= sdat_r;end
179                         end
180     ACK2:begin
181                                 link <= 1'd0;
182                                 data2slave <= (`SCLK_POS)?sys_data_i:data2slave;    //读入待写的寄存器地址
183                                 bit_cnt <= 4'd0;
184                                 sdat_r <= 1'd1;
185                                 end
186     WR_DATA:begin
187                             if(`SCLK_LOW) begin
188                                     link <= (bit_cnt == 4'd8)?1'b0:1'b1;    //释放数据总线
189                                     bit_cnt <= (bit_cnt == 4'd8)?4'd0:bit_cnt+1'd1;
190                                     data2slave <= {data2slave[6:0],1'd0};//左移一位
191                                     sdat_r <= data2slave[7];end
192                             else begin
193                                     link <= link;
194                                     bit_cnt <= bit_cnt;
195                                     data2slave <= data2slave;
196                                     sdat_r <= sdat_r;end
197                         end
198     ACK3:begin
199                     link <= 1'd0;
200                     sdat_r <= 1'd0;//预先拉低
201                     bit_cnt <= bit_cnt;
202                     data2slave <= data2slave;end
203     STOP:begin
204                     link <= (`SCLK_LOW)?1'b1:link;
205                     bit_cnt <= bit_cnt;
206                     data2slave <= data2slave;
207                     sdat_r <= (`SCLK_HIGH)?1'b1:sdat_r;end
208     default:begin
209                                 link <= 1'd1;
210                                 data2slave <= 8'd0;
211                                 bit_cnt <= 4'd0;
212                                 sdat_r <= 1'd1;
213                                 end
214     endcase
215     end
216 end
217 //assign
218 assign i2c_sdat = (link == 1'b1)?sdat_r:8'hzz;
219 assign i2c_idle_o = (c_st == IDLE)?1'b1:1'b0;
220 assign i2c_ack_o = ((c_st == STOP)&&(`SCLK_NEG))?1'b1:1'b0;
221 
222 
223 
224 endmodule

写操作源码2:

`timescale 1 ns / 1 ps
`define LUT_WIDTH    4
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 [7:0] sys_data_o;
output [7:0] reg_addr_o;
output config_done_o;

//generate wreq_o
reg sys_wreq_o = 0;
reg [`LUT_WIDTH-1:0] lut_index = 0;
reg [15:0] lut_data = 0;
always @ (posedge sys_clk) begin
if(1'b0 == sys_rst_n) begin
                                sys_wreq_o <= 1;
                                lut_index <= 0;end
else if((i2c_ack_i == 1'b1)&&(config_done_o == 1'b0)) begin
                                sys_wreq_o <= 1;
                                lut_index <= lut_index + 1'd1;end
else begin
        sys_wreq_o <= 0;
        lut_index <= lut_index;end
end
//assign
assign config_done_o = (lut_index == `LUT_WIDTH'd15)?1'b1:1'b0;
assign sys_data_o = lut_data[7:0];
assign reg_addr_o = lut_data[15:8];
//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:

 1 `timescale 1 ns / 1 ps
 2 module tb_i2c();
 3 reg sys_clk;
 4 reg sys_rst_n;
 5 initial begin
 6 sys_clk = 1;
 7 sys_rst_n = 0;
 8 #100 sys_rst_n = 1;
 9 end
10 
11 always begin
12 #10 sys_clk=~sys_clk;end
13 
14 wire i2c_sclk;
15 wire sys_wreq;
16 wire [7:0] reg_addr;
17 wire [7:0] sys_data;
18 wire i2c_idle;
19 wire i2c_ack;
20 wire i2c_sdat;
21 i2c_controller            u0(
22                                 .sys_clk( sys_clk ),
23                                 .sys_rst_n( sys_rst_n ),
24                                 .sys_wreq_i( sys_wreq ),
25                                 .reg_addr_i( reg_addr ),
26                                 .sys_data_i( sys_data ),
27                                 .i2c_idle_o( i2c_idle ),
28                                 .i2c_ack_o( i2c_ack ),
29                                 .i2c_sclk( i2c_sclk),
30                                 .i2c_sdat( i2c_sdat )
31                                 );
32 
33 wire config_done;
34 adv7180_config                u1(
35                                     .sys_clk( sys_clk ),
36                                     .sys_rst_n( sys_rst_n ),
37                                     .i2c_ack_i( i2c_ack ),
38                                     .sys_wreq_o( sys_wreq ),
39                                     .sys_data_o( sys_data ),
40                                     .reg_addr_o( reg_addr ),
41                                     .config_done_o( config_done )
42                                     );
43 
44 
45 
46 endmodule
原文地址:https://www.cnblogs.com/loadomain/p/3247517.html