fpga iic协议
协议本身就不做介绍了,只介绍一下编写思路,首先这个iic模块具有两个功能,对一个地址进行一个字节读,对一个地址进行一个字节写。由于iic的地址包含设备地址(7位)和寄存器地址(8或16位),因此模块对外提供参数来让用户选择采用哪种位宽的寄存器地址。即REGADDR_2BYTES=1时候采用16位,0时采用8位,而SCLK_FREQ则表示iic的时钟多少频率,而输入的i_clk是50mhz的时钟,这里写了sim是为了仿真用,可以删除
其中本模块核心的寄存器变量如上图,其中状态有如下这几个,除了IDLE,START,STOP,每个状态都会读写完一个字节后进入下一个状态
当start信号拉高,模块开始工作,此时bytecnt开始计数,这个变量和当前读写的字节有关,同时采用sclcnt开始计数,这个计数器是用来给iic做分频时钟用的,本设计采用4分频,其逻辑如下:检验sclcnt的最后两位,那么最后两位有4种数值在反复循环00,01,10,11,那么在11和01处进行SCL时钟的翻转来实现四分频,同时iic协议要求scl低电平时候变化数据,因此在00也就是图上绿色的那一时刻让bytecnt++,并根据当前bytecnt来更新sda信号线。对应代码如下:
bytecnt++:
设备地址信号更新:
寄存器地址信号更新:
要写的字节数据更新和要读的字节数据更新,同时三态线io_sda逻辑如下,后面会说:
从上面时序图可以看到还有一个scl_vld信号,这个是说明只有该信号高电平时候scl才能执行,是为了防止在IDLE,START,STOP这几个状态的时候scl开始变化而设立的,也就是说只有该信号高电平,数据传输才真正开始。
然后ack信号会在bytecnt=0的时候读取,并在bytecnt==0且sclcnt=11的时候进行状态机跳转
由于为了适应多个时钟域,因此额外定义了CLK_DIV信号让其作为四分频标志位,当其拉高时scl_cnt++
总体代码如下:
`timescale 1ns / 1ps
// 采用字节写,随机地址读
// 字节写: 起始位|器件地址|读ACK|寄存器地址|读ACK|数据|读ACK|停止位
// 随机地址读: 起始位|器件地址|读ACK|寄存器地址|读ACK|停止位|起始位|器件地址|读ACK|数据|写ACK|停止位
`define sim
module I2C_Master#(parameter SCLK_FREQ = 400_000, parameter [0:0] REGADDR_2BYTES = 0
)(input i_clk ,input i_rst ,input i_start ,input i_rw_flag ,input [6:0] i_dev_addr ,input [15:0] i_reg_addr ,input [7:0] i_wr_data , inout io_sda ,`ifdef simoutput o_rden ,`endifoutput reg o_scl ,output reg [7:0] o_rd_data ,output reg o_vld ,output reg o_err ,output o_rdy );localparam S_IDLE = 8'b00000001,S_START = 8'b00000010,S_DADDR = 8'b00000100,S_RADDR_16 = 8'b00001000,S_RADDR_8 = 8'b00010000,S_WR_DATA = 8'b00100000,S_RD_DATA = 8'b01000000,S_STOP = 8'b10000000;reg [7:0] r_cstate ;reg [7:0] r_nstate ;localparam CLK_DIV = 50_000_000 / SCLK_FREQ / 4;//时钟计数器reg [15:0] r_clk_cnt ;wire w_div_flag ;assign w_div_flag = r_clk_cnt >= CLK_DIV; //分频标志always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_clk_cnt <= 'd0;else if(r_cstate == S_IDLE)r_clk_cnt <= 'd0;else if(r_clk_cnt < CLK_DIV)r_clk_cnt <= r_clk_cnt + 1;elser_clk_cnt <= 'd0;endreg [6:0] r_dev_addr ;reg [7:0] r_reg_addr16;reg [7:0] r_reg_addr8 ;reg [7:0] r_wr_data ;reg r_rw_flag ;assign o_rdy = r_cstate == S_IDLE;always@(posedge i_clk or posedge i_rst) beginif(i_rst) beginr_dev_addr <= 'd0;r_reg_addr16<= 'd0;r_reg_addr8 <= 'd0;r_wr_data <= 'd0;r_rw_flag <= 'b0;endelse if(r_cstate == S_IDLE & i_start) beginr_dev_addr <= i_dev_addr ;r_reg_addr16<= i_reg_addr[15:8] ;r_reg_addr8 <= i_reg_addr[7:0] ;r_wr_data <= i_wr_data ; r_rw_flag <= i_rw_flag ;endelse beginr_dev_addr <= r_dev_addr ;r_reg_addr16<= r_reg_addr16 ;r_reg_addr8 <= r_reg_addr8 ;r_wr_data <= r_wr_data ;r_rw_flag <= r_rw_flag ;endend//时钟四分频计数器reg [15:0] r_scl_cnt ;reg r_scl_vld ; always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_scl_cnt <= 'd0;else if(r_cstate == S_IDLE)r_scl_cnt <= 'd0;else if(r_cstate == S_STOP & (&r_scl_cnt[1:0]) & w_div_flag)//当计数到2‘b11且状态为stop便清零r_scl_cnt <= 'd0;else if(w_div_flag)r_scl_cnt <= r_scl_cnt + 1;//四分频一次加一elser_scl_cnt <= r_scl_cnt;end//iic时钟只有在scl_vld拉高期间才能变化always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_scl_vld <= 'b0;else if(r_cstate == S_STOP & r_scl_cnt[1:0] >= 2)r_scl_vld <= 'b0;else if(r_cstate == S_START & r_scl_cnt[1:0] >= 2)r_scl_vld <= 'b1;elser_scl_vld <= r_scl_vld;endalways@(posedge i_clk or posedge i_rst) beginif(i_rst)o_scl <= 'b1;else if(r_scl_vld & r_scl_cnt[0] & r_clk_cnt == 0)//iic时钟o_scl <= ~o_scl;elseo_scl <= o_scl;endreg r_dummy_wr ;wire w_rw_bit ;assign w_rw_bit = r_dummy_wr ^ r_rw_flag;always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_dummy_wr <= 'b0;else if(r_cstate == S_IDLE & i_start)r_dummy_wr <= i_rw_flag;else if(r_cstate == S_STOP & (&r_scl_cnt[1:0]) & w_div_flag)r_dummy_wr <= 'b0;elser_dummy_wr <= r_dummy_wr;endreg [4:0] r_byte_cnt ;//根据该计数器去变化sdareg r_read_en ;`ifdef simassign o_rden = r_read_en;`endifalways@(posedge i_clk or posedge i_rst) beginif(i_rst)r_byte_cnt <= 'd0;else if(r_scl_vld & r_cstate != S_STOP)if(&r_scl_cnt[1:0] & w_div_flag)//当scl cnt后两位为2‘b11 同时分频标志拉高时,计数器++r_byte_cnt <= r_byte_cnt < 8 ? r_byte_cnt + 1 : 'd0;elser_byte_cnt <= r_byte_cnt;elser_byte_cnt <= 'd0;endalways@(*) beginif(i_rst)r_read_en = 'b0;else if(r_cstate == S_RD_DATA)r_read_en = |r_byte_cnt;else if(|(r_cstate & 8'b10000011))r_read_en = 'b0;elser_read_en <= !r_byte_cnt;endreg r_daddr_bit ;always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_daddr_bit <= 'b0;else if(r_cstate == S_DADDR)case(r_byte_cnt)1: r_daddr_bit <= r_dev_addr[6];2: r_daddr_bit <= r_dev_addr[5];3: r_daddr_bit <= r_dev_addr[4];4: r_daddr_bit <= r_dev_addr[3];5: r_daddr_bit <= r_dev_addr[2];6: r_daddr_bit <= r_dev_addr[1];7: r_daddr_bit <= r_dev_addr[0];8: r_daddr_bit <= w_rw_bit;default: r_daddr_bit <= 'b0;endcaseelser_daddr_bit <= 'b0;endreg r_raddr_bit ;always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_raddr_bit <= 'b0;else if(r_cstate == S_RADDR_16)case(r_byte_cnt)1: r_raddr_bit <= r_reg_addr16[7];2: r_raddr_bit <= r_reg_addr16[6];3: r_raddr_bit <= r_reg_addr16[5];4: r_raddr_bit <= r_reg_addr16[4];5: r_raddr_bit <= r_reg_addr16[3];6: r_raddr_bit <= r_reg_addr16[2];7: r_raddr_bit <= r_reg_addr16[1];8: r_raddr_bit <= r_reg_addr16[0];default: r_raddr_bit <= 'b0;endcaseelse if(r_cstate == S_RADDR_8)case(r_byte_cnt)1: r_raddr_bit <= r_reg_addr8[7];2: r_raddr_bit <= r_reg_addr8[6];3: r_raddr_bit <= r_reg_addr8[5];4: r_raddr_bit <= r_reg_addr8[4];5: r_raddr_bit <= r_reg_addr8[3];6: r_raddr_bit <= r_reg_addr8[2];7: r_raddr_bit <= r_reg_addr8[1];8: r_raddr_bit <= r_reg_addr8[0];default: r_raddr_bit <= 'b0;endcaseelser_raddr_bit <= 'b0;endreg r_wrdata_bit ;always@(posedge i_clk or posedge i_rst) beginif(i_rst)r_wrdata_bit <= 'b0;else if(r_cstate == S_WR_DATA)case(r_byte_cnt)1: r_wrdata_bit <= r_wr_data[7];2: r_wrdata_bit <= r_wr_data[6];3: r_wrdata_bit <= r_wr_data[5];4: r_wrdata_bit <= r_wr_data[4];5: r_wrdata_bit <= r_wr_data[3];6: r_wrdata_bit <= r_wr_data[2];7: r_wrdata_bit <= r_wr_data[1];8: r_wrdata_bit <= r_wr_data[0];default: r_wrdata_bit <= 'b0;endcaseelser_wrdata_bit <= 'b0;endassign io_sda = ~r_read_en ? |{~r_scl_vld, r_daddr_bit, r_raddr_bit, r_wrdata_bit} : 1'bz;always@(posedge i_clk or posedge i_rst) beginif(i_rst)o_rd_data <= 'd0;else if(r_cstate == S_RD_DATA && r_scl_cnt[1:0] == 2'b01 && w_div_flag)case(r_byte_cnt)1: o_rd_data[7] <= io_sda;2: o_rd_data[6] <= io_sda;3: o_rd_data[5] <= io_sda;4: o_rd_data[4] <= io_sda;5: o_rd_data[3] <= io_sda;6: o_rd_data[2] <= io_sda;7: o_rd_data[1] <= io_sda;8: o_rd_data[0] <= io_sda;default: o_rd_data <= o_rd_data;endcaseelseo_rd_data <= o_rd_data;end wire w_ack_flag ;assign w_ack_flag = |(r_cstate & 8'b00111100) & r_byte_cnt == 'd0 & r_scl_cnt[1:0] == 2'b01 & w_div_flag;always@(posedge i_clk or posedge i_rst) beginif(i_rst)o_err <= 'b0;else if(r_cstate == S_IDLE)o_err <= 'b0;else if(w_ack_flag)o_err <= io_sda | o_err;else o_err <= o_err;endreg [7:0] r_raddr_st ;always@(posedge i_clk) beginif(r_cstate == S_RADDR_16)r_raddr_st <= S_RADDR_8;elser_raddr_st <= REGADDR_2BYTES ? S_RADDR_16 : S_RADDR_8;endwire w_state_chg ;assign w_state_chg = (i_start & r_cstate == S_IDLE) | (&(r_scl_cnt[1:0]) && !r_byte_cnt && w_div_flag);always@(posedge i_clk or posedge i_rst) beginif(i_rst)o_vld <= 'b0;else if(r_cstate == S_STOP & r_nstate == S_IDLE)o_vld <= 'b1;elseo_vld <= 'b0;endalways@(posedge i_clk or posedge i_rst) beginif(i_rst)r_cstate <= S_IDLE;elser_cstate <= r_nstate;endalways@(*) beginif(i_rst)r_nstate = S_IDLE;elsecase(r_cstate)S_IDLE: r_nstate = w_state_chg ? S_START : S_IDLE;S_START: r_nstate = w_state_chg ? S_DADDR : S_START;S_DADDR: r_nstate = w_state_chg ? w_rw_bit ? S_RD_DATA : r_raddr_st : S_DADDR; S_RADDR_16: r_nstate = w_state_chg ? S_RADDR_8 : S_RADDR_16;S_RADDR_8: r_nstate = w_state_chg ? r_dummy_wr ? S_STOP : S_WR_DATA : S_RADDR_8;S_WR_DATA: r_nstate = w_state_chg ? S_STOP : S_WR_DATA;S_RD_DATA: r_nstate = w_state_chg ? S_STOP : S_RD_DATA;S_STOP: r_nstate = w_state_chg ? r_dummy_wr ? S_START : S_IDLE : S_STOP;default: r_nstate = S_IDLE;endcaseendendmodule
tb文件如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2025/05/22 11:15:31
// Design Name:
// Module Name: IIC_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////module IIC_tb();localparam REGADDR_2BYTES = 0;reg i_clk50m ;reg i_rst50m ;reg i_start ;reg i_rw_flag ;reg [6:0] i_dev_addr ;reg [15:0] i_reg_addr ;reg [7:0] i_wr_data ;wire o_rden ;wire io_sda ;wire o_scl ;wire [7:0] o_rd_data ;wire o_vld ;wire o_err ;wire o_rdy ;always#10 i_clk50m = ~i_clk50m;reg [15:0] r_random;always#20 r_random = $random()%16'hffff;assign io_sda = o_rden ? r_random[0] : 1'bz;initial begini_start = 0;i_clk50m = 1;i_rst50m = 1;i_rw_flag = 0;i_dev_addr = 7'b1010101;i_reg_addr = {8'h00, 8'h34};i_wr_data = 8'he3;#100i_rst50m = 0;#10i_start = 1;#30i_start = 0;@(posedge o_vld)#5000i_rw_flag = 1;i_dev_addr = 7'b1110001;i_reg_addr = {8'h00,8'hab};i_start = 1;#30i_start = 0;endI2C_Master#(.REGADDR_2BYTES(REGADDR_2BYTES))
I2C_Master_U (i_clk50m ,i_rst50m ,i_start ,i_rw_flag ,i_dev_addr ,i_reg_addr ,i_wr_data , io_sda ,o_rden ,o_scl ,o_rd_data ,o_vld ,o_err ,o_rdy
);endmodule
讲解视频:
https://www.bilibili.com/video/BV1HUhtzGEFS/?spm_id_from=333.1387.upload.video_card.click&vd_source=69fb997b62efa60ae1add8b53b6a5923