FPGA学习笔记——简单的IIC读写EEPROM

目录

一、任务

二、需求分析

三、Visio图

四、具体分析

五、代码

六、实现现象


一、任务

1、PC端通过串口发送数据至FPGA,然后将数据写进EEPROM,每写1次,地址加1。
2、按下按键进行一次随机读操作,随机读的地址从地址0开始,依次读;读出来的数据通过串口发送至PC端显示,显示内容为"读数据:xx"。


二、需求分析

首先,肯定需要串口的发送和接收模块

然后,还需要EEPROM的读写控制模块

最后,还需要按键消抖模块。
了解了IIC通信协议(FPGA学习笔记------IIC协议简介-CSDN博客),我们可以知道,在随机读和字节写的时候都要发送一个带起始信号的写(在随机读方向位也是"0")和 一个普通的写(Word Address) **。**其余的就是:带起始信号的读,普通的读,带停止信号的读和带停止信号的写。

当然,这是一种想法,另外一种想法就是分成:起始信号、写、读、停止信号,分别组合在一起,在rw_ctrl模块中传相应的命令去执行相应的读写操作(在代码中有体现)。cmd参数定义:
cmd[0]:起始
信号,cmd[1]:写
,cmd[2]:读
,cmd[3]:停止信号。

我这里使用的IIC的时钟频率是200kHz(在100kHz~400kHz之间),这里****不用写分频,直接MAX_CNT_SCL= 50MHz**/200kHzMAX_CNT_SCL>>1直接分完低电平和高电平。**


三、Visio图

Visio图


四、具体分析

首先,串口发送和接收模块( FPGA学习笔记------串口RS232回环实验_verilog 串口回环-CSDN博客之前的文章里面已经讲过了,这里就不分析这个,
然后,EEPROM的读写控制模块 可以分成
rw_ctrl(读写控制模块)模块和iic_intf(IIC接口模块),这样可以将读和写控制分开写,这样写我觉得思路更清晰一点,更好的描述这个两个状态。

rw_ctrl模块:这个模块的状态分为

IDLE
WRITE
READ
DONE

为什么分成这几个状态:

首先,在进行写操作的时候,不能进行读操作,所以将读写操作状态分开来,

写操作: 当上位机发送1byte数据过来,就从IDLE进入WRITE状态,将命令发给iic_intf模块和1byte写入EEPROM里面,

写完到DONE状态,

最后到IDLE状态,等待下一次触发。

读操作: 当按键按下,就从IDLE进入READ状态,从EEPROM里面读出1byte数据,发给上位机显示

发完到DONE状态,

最后到IDLE状态,等待下一次触发。

iic_intf模块:这个模块的状态分为

IDLE
START
WR_DATA
RD_DATA
R_ACK
S_ACK
STOP

为什么分成这几个状态:

首先 ,根据rw_ctrl模块传过来cmd 对相应的位进行判断,先判断cmd[0] ,如果cmd[0]==1 ,则跳到START 状态,如果cmd[0]==0 ,则判断cmd[1]和cmd[2] ,哪个为1,则跳到相应的状态,在等待字节写完或读完,再判断cmd[3] ,是否跳到STOP状态。

以上就是每个模块的思路。(有可能说的不是很清楚,可以看看代码,应该就能理解了)


五、代码

sys_top.v

cpp 复制代码
module sys_top( 
    input               sys_clk  ,
    input               sys_rst_n,
    input               uart_rxd ,
    output              uart_txd ,
    input               key_in   ,
    output              iic_scl  ,
    inout               iic_sda  ,

    output              rtc_nrst
);

wire    [7:0]   rx_data    ;
wire            rx_data_vld;
wire            ready      ;      
wire    [7:0]   tx_data    ;    
wire            tx_data_vld;
wire            key_down   ;
wire            sda_in     ;
wire            sda_out    ;
wire            sda_out_en ;

assign iic_sda = sda_out_en ? sda_out : 1'bz;
assign sda_in = iic_sda;

assign rtc_nrst = 1'b0;

uart_rx #( .CLOCK_FRQ(50_000_000),
           .BAUD(115200),
           .DATA_LENTH(8)     ,
           .CHECKBIT_SELECT(0),
           .CHECK_TYPE(0)   //偶校验:0  奇校验:1
)uart_rx_inst( 
.    clk      (sys_clk    ),
.    rst_n    (sys_rst_n  ),
.    uart_rxd (uart_rxd   ),
.    dout     (rx_data    ),
.    dout_vld (rx_data_vld)
);

eeprom_ctrl u_eeprom_ctrl(
    .clk         (sys_clk     ),
    .rst_n       (sys_rst_n   ),
    .rx_data     (rx_data     ),
    .rx_data_vld (rx_data_vld ),
    .ready       (ready       ),
    .tx_data     (tx_data     ),
    .tx_data_vld (tx_data_vld ),
    .key_down    (key_down    ),
    .scl         (iic_scl     ),
    .sda_in      (sda_in      ),
    .sda_out     (sda_out     ),
    .sda_out_en  (sda_out_en  )
);

key_filter u_key_filter(
    .clk      (sys_clk  ),
    .rst_n    (sys_rst_n),
    .key_in   (key_in   ),
    .key_down (key_down )
);

uart_tx #(  .CLOCK_FRQ(50_000_000),
                .BAUD(115200),
                .DATA_LENTH(8)     ,
                .CHECKBIT_SELECT(0),
                .CHECK_TYPE(0)   //偶校验:0  奇校验:1
    )uart_tx_inst( 
.    clk      (sys_clk    ),
.    rst_n    (sys_rst_n  ),
.    tx_din   (tx_data    ),
.    tx_enable(tx_data_vld),
.    uart_txd (uart_txd   ),
.    ready    (ready      ) //忙闲指示信号
);
    
    
endmodule

uart_tx

cpp 复制代码
module uart_tx #(parameter CLOCK_FRQ  = 50_000_000,
                           BAUD       = 9600      ,
                           DATA_LENTH = 8         ,
                           CHECKBIT_SELECT = 0    ,
                           CHECK_TYPE = 0           //偶校验:0  奇校验:1
)( 
    input                           clk      ,
    input                           rst_n    ,
    input       [DATA_LENTH-1:0]    tx_din   ,
    input                           tx_enable,
    output  reg                     uart_txd ,
    output  reg                     ready     //忙闲指示信号
);
//---------<参数定义>------------------------------------------------
    //状态机参数定义
    localparam  IDLE  = 5'b00001,
                START = 5'b00010,
                DATA  = 5'b00100,
                CHECK = 5'b01000,
                STOP  = 5'b10000;
    localparam  BUAD_MAX = CLOCK_FRQ/BAUD;

//---------<内部信号定义>--------------------------------------------
    reg     [4:0]   state_c     ;
    reg     [4:0]   state_n     ;

    reg		[15:0]	cnt_baud	;//波特率计数器
    wire			add_cnt_baud;
    wire			end_cnt_baud;
    reg		[2:0]	cnt_bit	    ;//数据传输的比特计数器
    wire			add_cnt_bit ;
    wire			end_cnt_bit ;

    reg     [7:0]   tx_din_r    ;

    //状态转移条件定义
    wire            idle2start  ;
    wire            start2data  ;
    wire            data2check  ;
    wire            data2stop   ;
    wire            check2stop  ;
    wire            stop2idle   ;

//*******************************************************************
//--tx_din_r
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            tx_din_r <= 'd0;
        end 
        else if(tx_enable)begin 
            tx_din_r <= tx_din;
        end 
    end

//*******************************************************************
//--状态机
//*******************************************************************
    //第一段:同步时序描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
        
    //第二段:组合逻辑判断状态转移条件,描述状态转移规律
    always @(*) begin
        case(state_c)
            IDLE  : begin
                if(idle2start)
                    state_n = START; 
                else 
                    state_n = state_c;
            end
            START : begin
                if(start2data)
                    state_n = DATA;
                else 
                    state_n = state_c;
            end
            DATA  : begin
                if(data2check)
                    state_n = CHECK;
                else if(data2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;
            end
            CHECK : begin
                if(check2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;
            end
            STOP  : begin
                if(stop2idle)
                    state_n = IDLE; 
                else 
                    state_n = state_c;
            end
            default : state_n = IDLE;
        endcase
    end
    
    assign  idle2start = (state_c == IDLE ) && tx_enable; 
    assign  start2data = (state_c == START) && end_cnt_baud;
    assign  data2check = (state_c == DATA ) && end_cnt_bit && CHECKBIT_SELECT;
    assign  data2stop  = (state_c == DATA ) && end_cnt_bit && !CHECKBIT_SELECT;
    assign  check2stop = (state_c == CHECK) && end_cnt_baud;
    assign  stop2idle  = (state_c == STOP ) && end_cnt_baud;
    
//*******************************************************************
//--cnt_baud
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_baud <= 'd0;
        end 
        else if(add_cnt_baud)begin 
            if(end_cnt_baud)begin 
                cnt_baud <= 'd0;
            end
            else begin 
                cnt_baud <= cnt_baud + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_baud = state_c != IDLE;
    assign end_cnt_baud = add_cnt_baud && cnt_baud == ((state_c == STOP) ? ((BUAD_MAX>>1)+(BUAD_MAX>>2)) : (BUAD_MAX - 1));

//*******************************************************************
//--cnt_bit
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_bit <= 'd0;
        end 
        else if(add_cnt_bit)begin 
            if(end_cnt_bit)begin 
                cnt_bit <= 'd0;
            end
            else begin 
                cnt_bit <= cnt_bit + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_bit = state_c == DATA && end_cnt_baud;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == DATA_LENTH - 1;
    
//*******************************************************************
//--uart_txd
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            uart_txd <= 1'b1;
        end 
        else begin 
            case (state_c)
                IDLE  : uart_txd <= 1'b1;
                START : uart_txd <= 1'b0;
                DATA  : uart_txd <= tx_din_r[cnt_bit];//并转串,LSB
                // CHECK : uart_txd <= ^tx_din_r + CHECK_TYPE;
                CHECK : uart_txd <= CHECK_TYPE ? (^tx_din_r + 1'b1) : (^tx_din_r); 
                STOP  : uart_txd <= 1'b1;
                default: uart_txd <= 1'b1;
            endcase
        end 
    end

//*******************************************************************
//--ready
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            ready <= 'd0;
        end 
        else begin 
            ready <= state_n == IDLE;
        end 
    end
    


endmodule

uart_rx

cpp 复制代码
module uart_rx #(parameter CLOCK_FRQ  = 50_000_000,
                           BAUD       = 9600      ,
                           DATA_LENTH = 8         ,
                           CHECKBIT_SELECT = 0    ,
                           CHECK_TYPE = 0           //偶校验:0  奇校验:1
)( 
    input               clk      ,
    input               rst_n    ,
    input               uart_rxd ,
    output  reg [7:0]   dout     ,
    output              dout_vld 
);
//---------<参数定义>------------------------------------------------
    //状态机参数定义
    localparam  IDLE  = 5'b00001,
                START = 5'b00010,
                DATA  = 5'b00100,
                CHECK = 5'b01000,
                STOP  = 5'b10000;
    localparam  BUAD_MAX = CLOCK_FRQ/BAUD;
    
//---------<内部信号定义>--------------------------------------------
    reg     [4:0]   state_c     ;
    reg     [4:0]   state_n     ;
    reg		[15:0]	cnt_baud	;//波特率计数器
    wire			add_cnt_baud;
    wire			end_cnt_baud;
    reg		[2:0]	cnt_bit	   ;//接收bit数量的计数器
    wire			add_cnt_bit;
    wire			end_cnt_bit;

    reg     [2:0]   uart_rxd_r  ;
    wire            rxd_n_edge  ;

    //状态转移条件定义
    wire            idle2start  ;
    wire            start2data  ;
    wire            data2check  ;
    wire            data2stop   ;
    wire            check2stop  ;
    wire            stop2idle   ;

//*******************************************************************
//--打三拍(前两拍同步,后一拍获得边沿)
//*******************************************************************
    //uart_rxd_r
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            uart_rxd_r <= 3'b111;
        end 
        else begin 
            uart_rxd_r <= {uart_rxd_r[1:0],uart_rxd};
        end 
    end
    
    //rxd_n_edge
    assign  rxd_n_edge = ~uart_rxd_r[1] & uart_rxd_r[2];

//*******************************************************************
//--状态机
//*******************************************************************
    //第一段:同步时序描述状态转移
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
        
    //第二段:组合逻辑判断状态转移条件,描述状态转移规律
    always @(*) begin
        case(state_c)
            IDLE  : begin
                if(idle2start)
                    state_n = START; 
                else 
                    state_n = state_c;
            end
            START : begin
                if(start2data)
                    state_n = DATA;
                else 
                    state_n = state_c;
            end
            DATA  : begin
                if(data2check)
                    state_n = CHECK;
                else if(data2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;
            end
            CHECK : begin
                if(check2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;
            end
            STOP  : begin
                if(stop2idle)
                    state_n = IDLE; 
                else 
                    state_n = state_c;
            end
            default : state_n = IDLE;
        endcase
    end
    
    assign  idle2start = (state_c == IDLE ) && rxd_n_edge;
    assign  start2data = (state_c == START) && end_cnt_baud;
    assign  data2check = (state_c == DATA ) && end_cnt_bit && CHECKBIT_SELECT;
    assign  data2stop  = (state_c == DATA ) && end_cnt_bit && !CHECKBIT_SELECT;
    assign  check2stop = (state_c == CHECK) && end_cnt_baud;
    assign  stop2idle  = (state_c == STOP ) && end_cnt_baud;
    
//*******************************************************************
//--cnt_baud
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_baud <= 'd0;
        end 
        else if(add_cnt_baud)begin 
            if(end_cnt_baud)begin 
                cnt_baud <= 'd0;
            end
            else begin 
                cnt_baud <= cnt_baud + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_baud = state_c != IDLE;
    assign end_cnt_baud = add_cnt_baud && cnt_baud == ((state_c == STOP) ? ((BUAD_MAX>>1)+(BUAD_MAX>>2)) : (BUAD_MAX - 1));

//*******************************************************************
//--cnt_bit
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
       if(!rst_n)begin
            cnt_bit <= 'd0;
        end 
        else if(add_cnt_bit)begin 
            if(end_cnt_bit)begin 
                cnt_bit <= 'd0;
            end
            else begin 
                cnt_bit <= cnt_bit + 1'b1;
            end 
        end
    end 
    
    assign add_cnt_bit = state_c == DATA && end_cnt_baud;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == DATA_LENTH - 1;
    
//*******************************************************************
//--dout
//*******************************************************************
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            dout <= 'd0;
        end 
        else if(state_c == DATA && cnt_baud == (BUAD_MAX>>1) - 1)begin 
            dout[cnt_bit] <= uart_rxd_r[2];
            // dout <= {uart_rxd_r[2],dout[7:1]};
        end 
    end

//*******************************************************************
//--dout_vld
//*******************************************************************
    assign  dout_vld = stop2idle;

endmodule

param.v

cpp 复制代码
//IIC命令参数定义
`define CMD_START   4'b0001
`define CMD_WRITE   4'b0010
`define CMD_READ    4'b0100
`define CMD_STOP    4'b1000

`define WR_ID   8'ha0
`define RD_ID   8'ha1

eeprom.v

cpp 复制代码
module eeprom_ctrl( 
    input               clk        ,
    input               rst_n      ,
    //uart_rx
    input       [7:0]   rx_data    ,
    input               rx_data_vld,
    //uart_tx
    input               ready      ,
    output      [7:0]   tx_data    ,
    output              tx_data_vld,
    //key_filter
    input               key_down   ,
    //iic slave
    output              scl        ,
    input               sda_in     ,
    output              sda_out    ,
    output              sda_out_en
);

wire            start_en  ;
wire    [3:0]   cmd       ;
wire    [7:0]   wr_data   ;
wire    [7:0]   rd_data   ;
wire            trans_done;

rw_ctrl u_rw_ctrl( 
.clk        (clk        ),
.rst_n      (rst_n      ),

.rx_data    (rx_data    ),
.rx_data_vld(rx_data_vld),

.ready      (ready      ),
.tx_data    (tx_data    ),
.tx_data_vld(tx_data_vld),

.key_down   (key_down   ),

.start_en   (start_en   ),
.cmd        (cmd        ),
.wr_data    (wr_data    ),
.rd_data    (rd_data    ),
.trans_done (trans_done )
);
    
    
iic_intf u_iic_intf( 
.clk       (clk        ),
.rst_n     (rst_n      ),

.start_en  (start_en   ),//读/写使能信号
.cmd       (cmd        ),//命令信号 cmd[0]:起始 cmd[1]:写 cmd[2]:读 cmd[3]:停止信号
.wr_data   (wr_data    ),//写入的数据(ID、地址、数据)
.rd_data   (rd_data    ),//读出的数据
.trans_done(trans_done ),//一帧(字节)数据传输完成
.slave_ack (),//从机应答信号

.scl       (scl       ),
.sda_in    (sda_in    ),
.sda_out   (sda_out   ),
.sda_out_en(sda_out_en)
);
    
    
endmodule

rw_ctrl.v

cpp 复制代码
`include "./param.v"

module rw_ctrl#(parameter WR_LENTH = 3,RD_LENTH = 4)( 
    input               clk      ,
    input               rst_n    ,
    //uart_rx
    input       [7:0]   rx_data  ,
    input               rx_data_vld,
    //uart_tx
    input               ready      ,
    output  reg [7:0]   tx_data    ,
    output              tx_data_vld,
    //key_filter
    input               key_down   ,
    //iic_intf
    output  reg         start_en   ,
    output  reg [3:0]   cmd        ,
    output  reg [7:0]   wr_data    ,
    input       [7:0]   rd_data    ,
    input               trans_done 
);

localparam  IDLE  = 4'h1,
            WRITE = 4'h2,
            READ  = 4'h4,
            DONE  = 4'h8;

reg     [3:0]   state_c     ;
reg     [3:0]   state_n     ;
reg		[7:0]	cnt_byte	;//读/写操作传输字节数量计数器
wire			add_cnt_byte;
wire			end_cnt_byte;
reg     [7:0]   wr_addr     ;
reg     [7:0]   rd_addr     ;
reg             tx_flag     ;
reg		[3:0]	cnt_tx	    ;
wire			add_cnt_tx  ;
wire			end_cnt_tx  ;

wire            idle2write;
wire            idle2read ;
wire            write2done;
wire            read2done ;

//---------<State Machine>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        state_c <= IDLE;
    end 
    else begin 
        state_c <= state_n;
    end 
end
    
always @(*) begin
    case(state_c)
        IDLE  : state_n = idle2write ? WRITE : (idle2read ? READ : IDLE);
        WRITE : state_n = write2done ? DONE : WRITE;
        READ  : state_n = read2done ? DONE : READ;
        DONE  : state_n = IDLE;
        default : state_n = IDLE;
    endcase
end

assign idle2write = (state_c == IDLE) && rx_data_vld;
assign idle2read  = (state_c == IDLE) && key_down;
assign write2done = (state_c == WRITE) && end_cnt_byte;
assign read2done  = (state_c == READ) && end_cnt_byte;
    
//---------<cnt_byte>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_byte <= 'd0;
    end 
    else if(add_cnt_byte)begin 
        if(end_cnt_byte)begin 
            cnt_byte <= 'd0;
        end
        else begin 
            cnt_byte <= cnt_byte + 1'b1;
        end 
    end
end 

assign add_cnt_byte = trans_done && (state_c == WRITE || state_c == READ);
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c == WRITE) ? (WR_LENTH-1) : (RD_LENTH-1));//优先级

//---------<wr_addr>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        wr_addr <= 'd0;
    end 
    else if(write2done)begin 
        wr_addr <= wr_addr + 1'b1;
    end 
end

//---------<rd_addr>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rd_addr <= 'd0;
    end 
    else if(read2done)begin 
        rd_addr <= rd_addr + 1'b1;
    end 
end

//---------<start_en cmd wr_data>------------------------------------------------- 
always @(*)begin 
   if(state_c == WRITE)begin 
        case (cnt_byte)
            0 : begin start_en <= 1'b1;cmd <= `CMD_START | `CMD_WRITE;wr_data <= `WR_ID; end
            1 : begin start_en <= 1'b1;cmd <= `CMD_WRITE             ;wr_data <= wr_addr; end
            2 : begin start_en <= 1'b1;cmd <= `CMD_STOP  | `CMD_WRITE;wr_data <= rx_data; end
            default: begin start_en <= 1'b0;cmd <= 4'd0;wr_data <= 8'd0; end
        endcase
    end 
    else if(state_c == READ)begin
        case (cnt_byte)
            0 : begin start_en <= 1'b1;cmd <= `CMD_START | `CMD_WRITE;wr_data <= `WR_ID; end
            1 : begin start_en <= 1'b1;cmd <= `CMD_WRITE             ;wr_data <= rd_addr; end
            2 : begin start_en <= 1'b1;cmd <= `CMD_START | `CMD_WRITE;wr_data <= `RD_ID; end
            3 : begin start_en <= 1'b1;cmd <= `CMD_STOP  | `CMD_READ ;wr_data <= 8'h00; end
            default: begin start_en <= 1'b0;cmd <= 4'd0;wr_data <= 8'd0; end
        endcase
    end
    else begin 
        start_en <= 1'b0;cmd <= 4'd0;wr_data <= 8'd0;
    end 
end
    
//---------<串口显示>------------------------------------------------- 
//读数据:xx-->B6 C1 CA FD BE DD A3 BA x x

//tx_flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_flag <= 'd0;
    end 
    else if(read2done)begin 
        tx_flag <= 1'b1;
    end 
    else if(end_cnt_tx)begin 
        tx_flag <= 1'b0; 
    end 
end

//cnt_tx
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_tx <= 'd0;
    end 
    else if(add_cnt_tx)begin 
        if(end_cnt_tx)begin 
            cnt_tx <= 'd0;
        end
        else begin 
            cnt_tx <= cnt_tx + 1'b1;
        end 
    end
end 

assign add_cnt_tx = tx_flag && ready;
assign end_cnt_tx = add_cnt_tx && cnt_tx == 10 - 1;

//tx_data
always @(*)begin 
   case (cnt_tx)
    0 : tx_data = 8'hB6;
    1 : tx_data = 8'hC1;
    2 : tx_data = 8'hCA;
    3 : tx_data = 8'hFD;
    4 : tx_data = 8'hBE; 
    5 : tx_data = 8'hDD;
    6 : tx_data = 8'hA3;
    7 : tx_data = 8'hBA;
    8 : tx_data = (rd_data[7:4]<8'd10) ? (rd_data[7:4] + "0") : (rd_data[7:4] + "A" - 8'd10);
    9 : tx_data = (rd_data[3:0]<8'd10) ? (rd_data[3:0] + "0") : (rd_data[3:0] + "A" - 8'd10);
    default: tx_data = 0;
   endcase 
end

//tx_data_vld
assign tx_data_vld = tx_flag && ready;

endmodule

iic_intf.v

cpp 复制代码
module iic_intf #(parameter SYS_CLOCK = 50_000_000,//系统时钟频率
                            IIC_CLOCK = 200_000    //IIC传输速率
)( 
    input               clk       ,
    input               rst_n     ,
    //rw_ctrl
    input               start_en  ,//读/写使能信号
    input       [3:0]   cmd       ,//命令信号 cmd[0]:起始 cmd[1]:写 cmd[2]:读 cmd[3]:停止信号
    input       [7:0]   wr_data   ,//写入的数据(ID、地址、数据)
    output  reg [7:0]   rd_data   ,//读出的数据
    output              trans_done,//一帧(字节)数据传输完成
    // output  reg         slave_ack ,//从机应答信号
    //iic slave
    output  reg         scl       ,
    input               sda_in    ,
    output  reg         sda_out   ,
    output  reg         sda_out_en
);

localparam  IDLE    = 7'b000_0001,
            START   = 7'b000_0010,
            WR_DATA = 7'b000_0100,
            RD_DATA = 7'b000_1000,
            R_ACK   = 7'b001_0000,
            S_ACK   = 7'b010_0000,
            STOP    = 7'b100_0000;

localparam  CMD_START = 4'b0001,
            CMD_WRITE = 4'b0010,
            CMD_READ  = 4'b0100,
            CMD_STOP  = 4'b1000;

localparam  MAX_CNT_SCL = SYS_CLOCK/IIC_CLOCK,
            CHANGE_TIME = (MAX_CNT_SCL>>2)   ,//四分之一串行时钟周期
            SAMPLE_TIME =  (MAX_CNT_SCL>>1) +  (MAX_CNT_SCL>>2);//四分之三串行时钟周期
    
reg     [6:0]   state_c;
reg     [6:0]   state_n;
reg		[9:0]	cnt_scl	   ;//串行时钟计数器
wire			add_cnt_scl;
wire			end_cnt_scl;
reg		[2:0]	cnt_bit	   ;//读写数据比特计数器
wire			add_cnt_bit;
wire			end_cnt_bit;

wire            idle2start   ;
wire            idle2wr_data ;
wire            idle2rd_data ;
wire            start2wr_data;
wire            start2rd_data;
wire            wr_data2r_ack;
wire            r_ack2idle   ;
wire            r_ack2stop   ;
wire            rd_data2s_ack;
wire            s_ack2idle   ;
wire            s_ack2stop   ;
wire            stop2idle    ;

//---------<State Machine>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        state_c <= IDLE;
    end 
    else begin 
        state_c <= state_n;
    end 
end

always @(*) begin
    case(state_c)
        IDLE    : begin
            if(idle2start)  //带起始信号的传输优先级高于其它传输
                state_n = START;
            else if(idle2wr_data)
                state_n = WR_DATA;
            else if(idle2rd_data)
                state_n = RD_DATA;
            else 
                state_n = state_c;
        end
        START   : begin
            if(start2wr_data)
                state_n = WR_DATA;
            else if(start2rd_data)
                state_n = RD_DATA;
            else 
                state_n = state_c;
        end
        WR_DATA : begin
            if(wr_data2r_ack)
                state_n = R_ACK;
            else 
                state_n = state_c;
        end
        RD_DATA : begin
            if(rd_data2s_ack)
                state_n = S_ACK;
            else 
                state_n = state_c;
        end
        R_ACK   : begin
            if(r_ack2idle)
                state_n = IDLE;
            else if(r_ack2stop)
                state_n = STOP;
            else 
                state_n = state_c;
        end
        S_ACK   : begin
            if(s_ack2idle)
                state_n = IDLE;
            else if(s_ack2stop)
                state_n = STOP;
            else 
                state_n = state_c;
        end
        STOP    : begin
            if(stop2idle)
                state_n = IDLE;
            else 
                state_n = state_c;
        end
        default : state_n = IDLE;
    endcase
end

assign idle2start    = (state_c == IDLE) && start_en && cmd[0];//(cmd & CMD_START)
assign idle2wr_data  = (state_c == IDLE) && start_en && cmd[1];
assign idle2rd_data  = (state_c == IDLE) && start_en && cmd[2];
assign start2wr_data = (state_c == START) && end_cnt_scl && cmd[1];
assign start2rd_data = (state_c == START) && end_cnt_scl && cmd[2];
assign wr_data2r_ack = (state_c == WR_DATA) && end_cnt_bit;
assign r_ack2idle    = (state_c == R_ACK) && end_cnt_scl && cmd[3] == 1'b0;
assign r_ack2stop    = (state_c == R_ACK) && end_cnt_scl && cmd[3] == 1'b1;
assign rd_data2s_ack = (state_c == RD_DATA) && end_cnt_bit;
assign s_ack2idle    = (state_c == S_ACK) && end_cnt_scl && cmd[3] == 1'b0;
assign s_ack2stop    = (state_c == S_ACK) && end_cnt_scl && cmd[3] == 1'b1;
assign stop2idle     = (state_c == STOP) && end_cnt_scl;

//---------<cnt_scl>-------------------------------------------------   
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_scl <= 'd0;
    end 
    else if(add_cnt_scl)begin 
        if(end_cnt_scl)begin 
            cnt_scl <= 'd0;
        end
        else begin 
            cnt_scl <= cnt_scl + 1'b1;
        end 
    end
end 

assign add_cnt_scl = state_c != IDLE;
assign end_cnt_scl = add_cnt_scl && cnt_scl == MAX_CNT_SCL - 1;

//---------<cnt_bit>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 'd0;
    end 
    else if(add_cnt_bit)begin 
        if(end_cnt_bit)begin 
            cnt_bit <= 'd0;
        end
        else begin 
            cnt_bit <= cnt_bit + 1'b1;
        end 
    end
end 

assign add_cnt_bit = (state_c == WR_DATA || state_c == RD_DATA) && end_cnt_scl;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 8 - 1;

//---------<scl>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        scl <= 1'b1;
    end 
    else if(state_c != IDLE)begin 
        if(cnt_scl < (MAX_CNT_SCL>>1))
            scl <= 1'b0;
        else 
            scl <= 1'b1;
    end 
    else begin 
        scl <= 1'b1;
    end 
end

//---------<sda_out  sda_out_en rd_data>------------------------------------
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sda_out <= 1'b1;
        sda_out_en <= 1'b0;
        rd_data <= 8'd0;
        // slave_ack <= 1'b0;
    end 
    else begin 
        case (state_c)
            IDLE    : begin
                sda_out <= 1'b1;
                sda_out_en <= 1'b0;
            end
            START   : begin
                sda_out_en <= 1'b1;
                if(cnt_scl < SAMPLE_TIME)
                    sda_out <= 1'b1;
                else 
                    sda_out <= 1'b0;
            end
            WR_DATA : begin
                sda_out_en <= 1'b1;
                if(cnt_scl == CHANGE_TIME - 1) //并转串 MSB
                    sda_out <= wr_data[7-cnt_bit];
                else 
                    sda_out <= sda_out;
            end
            RD_DATA : begin
                sda_out <= 1'b0;
                sda_out_en <= 1'b0;
                if(cnt_scl == SAMPLE_TIME - 1)
                    rd_data[7-cnt_bit] <= sda_in;
                else 
                    rd_data <= rd_data;
            end
            R_ACK   : begin
                sda_out <= 1'b0;
                sda_out_en <= 1'b0;
                // // slave_ack <= (cnt_scl == SAMPLE_TIME - 1) ? sda_in : slave_ack;
                // if(cnt_scl == SAMPLE_TIME - 1)
                //     slave_ack <= sda_in;
                // else 
                //     slave_ack <= slave_ack;
            end
            S_ACK   : begin
                sda_out_en <= 1'b1;
                // sda_out <= (cnt_scl == CHANGE_TIME && cmd[3]) ? 1'b1 : 1'b0;
                if(cnt_scl == CHANGE_TIME)
                    sda_out <=  cmd[3] ? 1'b1 : 1'b0;
                else 
                    sda_out <= sda_out;
            end
            STOP    : begin
                sda_out_en <= 1'b1;
                if(cnt_scl == CHANGE_TIME)
                    sda_out <=  1'b0;
                else if(cnt_scl == SAMPLE_TIME)
                    sda_out <= 1'b1;
            end 
            default: begin
                sda_out_en <= 1'b0;
                sda_out <= 1'b0;
                rd_data <= rd_data;
            end
        endcase
    end 
end

//---------<trans_done>------------------------------------------------- 
assign trans_done = r_ack2idle | s_ack2idle | stop2idle;

endmodule

六、实现现象

以下是发送一个字节,并读取的出来的现象。


以上就是简单的IIC读写EEPROM实现。(如果有错误,还请大家指出来,谢谢!有什么不懂的都可以在评论区问,看到就会回复。)