单字节读写I2C的verilog实现

本设计用verilog实现了一个简单的I2C协议,实现功能为往固定地址先写入一个字节,然后再读出该字节。

涉及到的EEPROM为Atmel家的AT24C04,4Kbit存储空间,8位位宽,需要9位宽的地址,其他细节参见规格书doc0180。

AT24C04支持5种读写模式:字节写,页写,当前地址读,随机读,顺序读。在当前地址读或顺序读时序的最后不发送NAK,则可以继续下一个地址的数据,直到主控给出一个NAK。

另外需要注意一点是,AT24C04需要至多5ms的写时间,即在写操作的结束时序到下一个操作的开始时许之间需要间隔5ms,以保证器件对写入数据的固化。

简化起见,本设计使用了一个1s的从开始写到开始读的时间间隔。

接口程序

`timescale 1ns/1ps

// AT24C04接口程序,只支持字节写入和随机读取
// 本接口接收指定地坿的单字节写入和单字节读取
module i2c_intf #(
    parameter SYS_FREQ = 200_000_000,
    parameter SCL_FREQ = 100_000
    )(
    input wire clk, nrst,
    // 写信号
    input wire wrreq,
    input wire [8:0] waddr, 
    input wire [7:0] wdata,
    input wire rdreq,
    // 读信号
    input wire [8:0] raddr,
    output reg [7:0] rdata,
    output reg vld,
    // 忙线指示信号
    output reg rdy,
    // i2c接口信号
    output reg scl,
    inout sda
    );
    
    reg sda_out;
    // 用于观察信号
    wire sda_in;
    
    assign sda = (sda_out == 0) ? 1'b0 : 1'bz;
    assign sda_in = sda;
    // I2C的SCL周期
    localparam SCL_T = SYS_FREQ / SCL_FREQ;

    // 由于AT24C04为4K容量,数据长度8位,需要9bit地址,最高位存于器件地址中
    localparam DADDR_6 = 6'b101000;
    
    reg [7:0] device_addr;
    
    // SCL计数
    reg [15:0] cnt_scl;
    wire add_cnt_scl;
    wire end_cnt_scl;
    // bit计数
    reg [3:0] cnt_bit;
    wire add_cnt_bit;
    wire end_cnt_bit;
    // cnt计数状态内执行顺序
    reg [3:0] cnt_step;
    wire add_cnt_step;
    wire end_cnt_step;
    // 变量对应不同状态需要执行的步骤数
    reg [3:0] bit_num, step_num;
        
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            device_addr <= 0;
        else if(state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && cnt_step == 2 - 1)
            device_addr <= {DADDR_6, waddr[8], 1'b0};
        else if(state_c == S_RD_RANDOM && cnt_step == 5 - 1)
            device_addr <= {DADDR_6, raddr[8], 1'b1};
    end

    // 状态划分为:空闲,写字节,随机写
    localparam S_IDLE         = 6'b000_001;
    localparam S_WR_BYTE     = 6'b000_010;
    localparam S_RD_RANDOM    = 6'b000_100;
    
    reg [5:0] state_c, state_n;
    
    wire idle2wr_byte;
    wire idle2rd_random;
    wire wr_byte2idle;
    wire rd_random2idle;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            state_c <= S_IDLE;
        else
            state_c <= state_n;
    end
    
    always @* begin
        case (state_c)
            S_IDLE: begin
                    if(idle2wr_byte)
                        state_n = S_WR_BYTE;
                    else if(idle2rd_random)
                        state_n = S_RD_RANDOM;
                    else
                        state_n = state_c;
                end
            S_WR_BYTE: begin
                    if(wr_byte2idle)
                        state_n = S_IDLE;
                    else
                        state_n = state_c;
                end
            S_RD_RANDOM: begin
                    if(rd_random2idle)
                        state_n = S_IDLE;
                    else
                        state_n = state_c;
                end
            default: state_n = state_c;
        endcase
    end
    
    assign idle2wr_byte        = state_c == S_IDLE     && wrreq;
    assign idle2rd_random    = state_c == S_IDLE        && rdreq;
    assign wr_byte2idle        = state_c == S_WR_BYTE    && end_cnt_step;
    assign rd_random2idle    = state_c == S_RD_RANDOM && end_cnt_step;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt_scl <= 0;
        else if(add_cnt_scl) begin
            if(end_cnt_scl)
                cnt_scl <= 0;
            else
                cnt_scl <= cnt_scl + 1'b1;
        end
    end
    assign add_cnt_scl = state_c != S_IDLE;
    assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_T - 1;
        
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt_bit <= 0;
        else if(add_cnt_bit) begin
            if(end_cnt_bit)
                cnt_bit <= 0;
            else
                cnt_bit <= cnt_bit + 1'b1;
        end
    end
    assign add_cnt_bit = end_cnt_scl;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1;
    // 状态内以开始,读,写或结束为一个步骤
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt_step <= 0;
        else if(add_cnt_step) begin
            if(end_cnt_step)
                cnt_step <= 0;
            else
                cnt_step <= cnt_step + 1'b1;
        end
    end
    assign add_cnt_step = end_cnt_bit;
    assign end_cnt_step = add_cnt_step && cnt_step == step_num - 1;

    // 写单个字节分为以下步骤:开始,写器件地址,写存储地址,写数据,结束
    // 读单个字节分为以下步骤:开始,写器件地址,写存储地址,开始时序,写器件地址,读数据,结束
    always @* begin
        if(state_c == S_IDLE) begin
            step_num = 0;
            bit_num = 0;
        end
        else if(state_c == S_WR_BYTE) begin
            step_num = 5;
            if(cnt_step == 1 - 1 || cnt_step == step_num - 1)
                bit_num = 1;
            else
                bit_num = 9;
        end
        else if(state_c == S_RD_RANDOM) begin
            step_num = 7;
            if(cnt_step == 1 - 1 || cnt_step == 4 - 1 || cnt_step == step_num - 1)
                bit_num = 1;
            else
                bit_num = 9;
        end
        else begin
          step_num = 0;
          bit_num = 0;
        end
    end
    
    // scl信号前半个周期为低,后半个周期为高,且第1个半个周期保持高电平
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            scl <= 1;
        else if(add_cnt_scl && cnt_scl == SCL_T / 2 - 1)
            scl <= 1;
        else if(end_cnt_scl && !end_cnt_step)
            scl <= 0;
    end
    
    // 重点,SDA信号
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            sda_out <= 1;
        else begin
            // 开始时序
            if((cnt_step == 1 - 1 || state_c == S_RD_RANDOM && cnt_step == 4 - 1) && cnt_scl == SCL_T * 3 / 4 - 1)
                sda_out <= 0;
            // 结束时序,且在结束时钟周期需要需要事先将SDA拉低
            else if(cnt_step == step_num - 1 && cnt_scl == SCL_T / 4 - 1)
                sda_out <= 0;
            else if(cnt_step == step_num - 1 && cnt_scl == SCL_T * 3 / 4 - 1)
                sda_out <= 1;
            // 在应答周期将SDA拉高
            else if(cnt_bit == 9 - 1 && cnt_scl == SCL_T / 4 - 1)
                sda_out <= 1;
            // 器件地址
            else if((state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && (cnt_step == 2 - 1 || cnt_step == 5 - 1)) && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= device_addr[7 - cnt_bit];
            // 写地址
            else if(state_c == S_WR_BYTE && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= waddr[7 - cnt_bit];
            // 写数据
            else if(state_c == S_WR_BYTE && cnt_step == 4 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= wdata[7 - cnt_bit];
            // 读地址
            else if(state_c == S_RD_RANDOM && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1)
                sda_out <= raddr[7 - cnt_bit];
        end
    end

    // 读数据代码
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            rdata <= 0;
        else if(state_c == S_RD_RANDOM && cnt_step == 6 - 1 && cnt_scl == SCL_T / 4 * 3 - 1 && cnt_bit != 9 - 1)
            rdata[7 - cnt_bit] <= sda;
    end
    
    // 读数据有效指示信号
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            vld <= 0;
        else if(state_c == S_RD_RANDOM && end_cnt_step)
            vld <= 1;
        else
            vld <= 0;
    end
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            rdy <= 1;
        else if(state_c == S_IDLE)
            rdy <= 1;
        else
            rdy <= 0;
    end
    
    // ILA例化代码
    ila_read ila_read_u (
    .clk    (clk    ), // input wire clk
    .probe0 (vld    ), // input wire [7:0] probe0
    .probe1 (scl    ),
    .probe2 (sda_out),
    .probe3 (cnt_step),
    .probe4 (cnt_bit),
    .probe5 (rdata  ),
    .probe6 (wrreq  ),
    .probe7 (rdreq  ),
    .probe8 (sda_in )
    ); 
endmodule

顶层测试例程

`timescale 1ns / 1ps

module i2c_example(
    input wire clk_p, clk_n, nrst,
    input wire key_in,
    output wire scl,
    inout sda    
    );
    
    parameter SYS_FREQ = 200_000_000;
    parameter SCL_FREQ = 100_000;
    
    // 差分时钟信号转为单端信号
    IBUFGDS #(
        .DIFF_TERM("FALSE"),
        .IBUF_LOW_PWR("TRUE"),
        .IOSTANDARD("DEFAULT")
    )  IBUFGDS_inst(
        .O(clk),
        .I(clk_p),
        .IB(clk_n)
    );
    
    reg [31:0] cnt;
    wire add_cnt;
    wire end_cnt;
    reg flag;
    wire [7:0] data;
    wire vld;
    wire wrreq;
    
    // 加入按键模块用于调试
    wire key_out;
    reg key_out_ff;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_out_ff <= 0;
        else
            key_out_ff <= key_out;
    end
    
    assign wrreq = key_out == 0 && key_out_ff == 1;
    
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            flag <= 0;
        else if(wrreq)
            flag <= 1;
        else if(end_cnt)
            flag <= 0;
    end
           
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;
        else if(add_cnt) begin
            if(end_cnt)
                cnt <= 0;
            else
                cnt <= cnt + 1'b1;
        end
    end
    assign add_cnt = flag;
    assign end_cnt = add_cnt && cnt == SYS_FREQ - 1;
    
    i2c_intf #(SYS_FREQ, SCL_FREQ) i2c_intf_u(
        .clk     (clk    ), 
        .nrst    (nrst   ),
        .wrreq   (wrreq  ),
        .waddr   (9'h03  ), 
        .wdata   (8'h55  ),
        
        .rdreq   (end_cnt),
        .raddr   (9'h03  ),
        .rdata   (data   ),
        .vld     (vld    ),
        
        .rdy     (       ),
        
        .scl     (scl    ),
        .sda     (sda    )
        );
    
    debounce debounce_u(
        .clk    (clk    ), 
        .nrst   (nrst   ),
        .key_in (key_in ),
        .key_out(key_out)
    );               
endmodule
原文地址:https://www.cnblogs.com/qingkai/p/7743488.html