• 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
  • 相关阅读:
    DDL、DML、TCL
    SQL简介
    Java基础--常用API--IO流相关API
    Java基础--多线程
    Java基础--常用API--集合类相关API
    Java基础--常用API--日期相关API
    Java基础--常用API--字符串相关API
    Java基础--正则表达式的规则
    Java基础--常用API--java.lang.Object
    26、springboot——整合JPA
  • 原文地址:https://www.cnblogs.com/loadomain/p/3247517.html
Copyright © 2020-2023  润新知