Verilog реализует контроллер I2C (идея Xiaomi Ge) ---- подробный анализ

i2c_bit_shift

Структурная схема модуля следующая:
вставьте сюда описание изображения
Входные и выходные сигналы:
вставьте сюда описание изображения
Общая идея такова:
посредством комбинации входных команд осуществляется передача 8-байтовых данных. определены 6 команд,

WR Запрос данных на запись (6'b000_001)
СТА Запрос стартового бита (6'b000_010)
РД Запрос на чтение данных (6'b000_100)
СТО Запрос стопового бита (6'b001_000)
ПОДТВЕРЖДЕНИЕ Запрос бита ответа (6'b010_000)
НАК Нет ответа на запрос (6'b100_000)

Принята идея применения машины последовательности в конечном автомате.Каждое состояние разделено на четыре шага для завершения передачи одного бита данных и контролируется счетчиком. Определенное состояние выглядит следующим образом (горячее кодирование):

состояние описание
ПРАЗДНЫЙ состояние ожидания
GEN_STA генерировать стартовый сигнал
WR_DATA Запись статуса данных
РД_ДАТА Чтение статуса данных
ПРОВЕРИТЬ_ACK Обнаружить ответный сигнал
GEN_ACK генерировать ответный сигнал
ГНЕ_СТО генерировать стоп-сигнал

Переходы конечного автомата выглядят следующим образом:
вставьте сюда описание изображения

Код выглядит следующим образом, с очень четкими комментариями:

module i2c_bit_shift (
    input   wire            clk         ,
    input   wire            rst_n       ,
    input   wire    [5:0]   cmd         ,       //控制总线实现各种传输的各种命令组合
    input   wire            go          ,       //整个模块的启动信号
    input   wire    [7:0]   Tx_Data     ,       //总线要发送的8位数据

    output  reg             i2c_sclk    ,       //i2c时钟总线
    inout   wire            i2c_sdat    ,       //i2c数据总线
    output  reg             Trans_Done  ,       //传输完成信号
    output  reg     [7:0]   Rx_Data     ,       //总线要接收的8位数据
    output  reg             ack_o               //从机时候应答标志
);                                          

    localparam      IDLE        =       7'b0000_001,     //空闲状态
                    GEN_STA     =       7'b0000_010,     //产生起始信号
                    WR_DATA     =       7'b0000_100,     //写数据状态
                    RD_DATA     =       7'b0001_000,     //读数据状态
                    CHECK_ACK   =       7'b0010_000,     //检测应答信号
                    GEN_ACK     =       7'b0100_000,     //产生应答信号
                    GNE_STO     =       7'b1000_000;     //产生停止信号

    localparam      WR          =       6'b000_001,      //写数据请求
                    STA         =       6'b000_010,      //起始位请求
                    RD          =       6'b000_100,      //读数据请求
                    STO         =       6'b001_000,      //停止位请求 
                    ACK         =       6'b010_000,      //应答位请求
                    NACK        =       6'b100_000;      //无应答请求
    

    parameter       SYS_CLOCK   =       50_000_000;      //系统采用50MHz时钟
    parameter       SCL_CLOCK   =       400_000   ;      //SCL总线时钟采用400khz
    localparam      SCL_CNT_M   =       SYS_CLOCK / SCL_CLOCK / 4 - 1;  //产生时钟SCL计数器最大值

    
    reg                 en_div_cnt      ;               //计数器使能信号
    reg                 i2c_sda_oe      ;               //三态门使能信号,1:输出,0:输入
    reg     [6:0]       state           ;               //状态机的状态变量
    reg     [5:0]       cnt             ;               //计数器
    reg                 i2c_sdat_o      ;               //输出的i2c_sdat_o
    reg     [19:0]      div_cnt         ;               //分频信号

    wire    sclk_plus = (div_cnt == SCL_CNT_M);         //dic_cnt计数到最大值产生一个高脉冲

    //reset为低时,若干信号的初始化
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            en_div_cnt <= 1'b0;
            i2c_sda_oe <= 1'b1;
            state <= IDLE;
            cnt <= 0;
            i2c_sdat_o <= 1'b0;
            i2c_sclk <= 1'b1;
            Rx_Data <= 8'd0;
        end
    end
    //一段式状态机
    always @(posedge clk or negedge rst_n) begin
        case(state)
            IDLE:   begin
                        Trans_Done <= 1'b0;
                        i2c_sda_oe <= 1'b1;
                        if(go) begin                            //整个模块的启动信号go
                            en_div_cnt <= 1'b1;                 //使能div_cnt计数器,跳转的状态都需要这个计数器
                            if(cmd & STA )                      //起始命令
                                state <= GEN_STA;               //跳转到起始状态
                            else if(cmd & WR)                   //写命令
                                state <= WR_DATA;               //跳转到写数据状态
                            else if(cmd & RD)                   //读命令
                                state <= RD_DATA;               //跳转到读数据状态
                            else 
                                state <= IDLE;                  //都不满足则跳转到IDLE状态
                        end
                        else begin
                            en_div_cnt <= 1'b0;                 //模块未启动
                            state <= IDLE;                      //跳转到IDLE状态,且不使能div_cnt计数器
                        end
                    end
            GEN_STA:begin                                       //i2c_sclk为高电平时,i2c_sda从高电平变成低电平产生起始位
                        if(sclk_plus) begin                     //只有在sclk_plus产生高脉冲的时候,div_cnt计数到最大值
                            if(cnt == 3)                        //cnt计数到3,归0
                                cnt <= 0;
                            else 
                                cnt <= cnt + 1'b1;              //否则计数器自增1
                            case(cnt)                                                   //分四步
                                0:begin i2c_sdat_o <= 1'b1; i2c_sda_oe <= 1'b1; end     //cnt为0时,i2c_sdat_o拉高,三态门输出
                                1:begin i2c_sclk <= 1'b1; end                           //cnt为1时,拉高i2c_sclk
                                2:begin i2c_sdat_o <= 0; i2c_sclk <= 1; end             //cnt为2时,继续拉高i2c_sclk,拉低i2c_sdat_o
                                3:begin i2c_sclk <= 0; end                              //cnt为3时,拉低i2c_sclk
                                default: begin i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end
                            endcase

                            if(cnt == 3) begin
                                if(cmd & WR)
                                    state <= WR_DATA;           //跳转到写数据状态
                                else if(cmd & RD)
                                    state <= RD_DATA;           //跳转到读数据状态
                            end
                        end
                    end
            WR_DATA:begin                                       //写数据时,只有在sclk为低电平的时候,sda才可以变化
                        if(sclk_plus) begin                     //8位数据,每位数据分4步,故cnt计数器计数到31
                            if(cnt == 31)
                                cnt <= 0;
                            else 
                                cnt <= cnt + 1'b1;
                            case(cnt)
                                0,4,8,12,16,20,24,28:                           
                                    begin           
                                        i2c_sdat_o <= Tx_Data[7-cnt[4:2]];      //在GNE_STA状态时,i2c_sclk拉低了,所以此时把Tx_data一位传给i2s_sdat_o
                                        i2c_sda_oe <= 1'b1;                     //三态门输出使能
                                    end
                                1,5,9,13,17,21,25,29:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk posedge 拉高i2c_sclk
                                    end
                                2,6,10,14,18,22,26,30:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk keep high 继续拉高i2c_sclk
                                    end
                                3,7,11,15,19,23,27,31:
                                    begin
                                        i2c_sclk <= 1'b0;                       //sclk negedge 拉低i2c_sclk,以便在下次传输Tx_Data的一位
                                    end
                                default: begin i2c_sdat_o <= 1; i2c_sclk <= 1; end
                            endcase

                            if(cnt == 31) begin
                                state <= CHECK_ACK;                             //Tx_Data的8位写完之后,跳转到检查应答位状态
                            end
                            
                        end
                    end
            CHECK_ACK:begin
                        if(sclk_plus) begin
                            if(cnt == 3)
                                cnt <= 0;
                            else
                                cnt <= cnt + 1;
                            
                            case(cnt)                                                           //应答位的检查依然分四步完成,使用计数器cnt来控制
                                0:  begin i2c_sda_oe <= 1'b0; i2c_sclk <= 1'b0; end             //拉低i2c_sclk信号,并且设置三态门为输入
                                1:  begin i2c_sclk <= 1; end                                    //拉高i2c_sclk
                                2:  begin ack_o <= i2c_sdat; i2c_sclk <= 1; end                 //拉高i2c_sclk,并且把i2c_sdat赋值给ack_o,
                                3:  begin i2c_sclk <= 0; end                                    //拉低i2c_sclk
                                default:    begin   i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end   //上面连续两次拉高i2c_sclk是为了占空比为50%的i2c_sclk时钟
                            endcase

                            if(cnt == 3) begin
                                if(cmd & STO)
                                    state <= GNE_STO;                                           //跳转到产生停止位状态
                                else begin
                                    state <= IDLE;
                                    Trans_Done <= 1'b1;                                         //一次传输完成
                                end     
                            end
                        end

                      end
            GNE_STO :   begin                                                                   //在i2c_sclk为高电平,i2c_sda从低到高则为停止信号
                            if(sclk_plus) begin                                                 //与产生起始信号类似
                                if(cnt == 3)
                                    cnt <= 0;
                                else
                                    cnt <= cnt + 1'b1;
                                
                                case(cnt)
                                    0:  begin i2c_sdat_o <= 1'b0; i2c_sda_oe <= 1'b1; end       //三态门输出, i2c_sda拉低
                                    1:  begin i2c_sclk <= 1'b1; end                             //拉高i2c_sclk
                                    2:  begin i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end         //拉高i2c_sda,i2c_sclk
                                    3:  begin i2c_sclk <= 1'b1; end
                                    default: begin i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end
                                endcase

                                if(cnt == 3) begin
                                    Trans_Done <= 1'b1;                                         //传输完成
                                    state <= IDLE;  
                                end
                            end
                        end
            
            RD_DATA:    begin                                                                   //取数据
                            if(sclk_plus) begin
                                if(cnt == 31)
                                    cnt <= 0;
                                else
                                    cnt <= cnt + 1'b1;                                          //8位,每位分4步完成
                                
                                case(cnt)
                                0,4,8,12,16,20,24,28:
                                    begin
                                        i2c_sda_oe <= 1'b0;
                                        i2c_sclk <= 0;                          //set data,拉低i2c_sclk,三态门设置为输入
                                    end
                                1,5,9,13,17,21,25,29:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk posedge, 拉高i2c_scclk
                                    end
                                2,6,10,14,18,22,26,30:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk keep high 利用移位的方法来存储,技巧需掌握
                                        Rx_Data <= {
    
    Rx_Data[6:0], i2c_sdat};
                                    end
                                3,7,11,15,19,23,27,31:                          //sclk negedge
                                    begin
                                        i2c_sclk <= 1'b0;
                                    end
                                default: begin i2c_sdat_o <= 1; i2c_sclk <= 1; end
                                endcase

                                if(cnt == 31) begin
                                    state <= GEN_ACK;
                                end
                            end
                        end
            GEN_ACK:    begin                                                   //产生应答信号
                            if(sclk_plus) begin                                 //分四步完成
                                if(cnt == 3)
                                    cnt <= 0;
                                else
                                    cnt <= cnt + 1'b1;
                                
                                case(cnt)
                                    0:  begin   
                                            i2c_sda_oe <= 1'b1;                 //三态门输出
                                            i2c_sclk <= 1'b0;                   //拉低i2c_sclk
                                            if(cmd & ACK)                       //产生ACK,输出0
                                                i2c_sdat_o <= 1'b0;
                                            else if(cmd & NACK)                 //产生NOACK,输出1
                                                i2c_sdat_o <= 1'b1;
                                        end
                                    1:  begin i2c_sclk <= 1'b1; end
                                    2:  begin i2c_sclk <= 1'b1; end
                                    3:  begin i2c_sclk <= 1'b0; end
                                    default:    begin i2c_sdat_o <= 1; i2c_sclk <= 1; end
                                    
                                endcase

                                if(cnt == 3) begin
                                    if(cmd & STO)
                                        state <= GNE_STO;                       //跳转到产生停止位状态
                                    else begin
                                        state <= IDLE;
                                        Trans_Done <= 1'b1;                     //传输完成
                                    end
                                end
                            end
                        end
            default:    begin 
                            i2c_sclk <= 1'b1;
                            Trans_Done <= 1'b0;
                            i2c_sdat_o <= 1'b0;
                            i2c_sda_oe <= 1'b1;
                            cnt <= 0;
                        end
        endcase
    end 
    
    //在en_div_cnt使能,div_cnt开始奇数,计数大最大值循环计数
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            div_cnt <= 20'd0;
        else if(en_div_cnt) begin
            if(div_cnt < SCL_CNT_M)
                div_cnt <= div_cnt + 1'b1;
            else
                div_cnt <= 0;
        end
        else
            div_cnt <= 20'd0;
    end

    assign i2c_sdat = !i2c_sdat_o && i2c_sda_oe ? 1'b0 : 1'bz;              //三态门设置

endmodule

Тестовый файл tb выглядит следующим образом: запишите 1, 2, 3, 4, 5 в 0x0A, 0x0B, 0x0C, 0x0D, 0X0F соответственно. Затем прочитайте их еще раз.

`timescale 1ns/1ns
module i2c_bit_shift_tb;

	reg Clk;
	reg Rst_n;
	
	reg [5:0]Cmd;
	reg Go;
	wire [7:0]Rx_DATA;
	reg [7:0]Tx_DATA;
	wire  Trans_Done;
	wire  ack_o;
	wire i2c_sclk;
	wire i2c_sdat;
	
	pullup PUP (i2c_sdat);
	
	localparam 
		WR =  6'b000001,	// 写请求
		STA = 6'b000010,	//起始位请求
		RD =  6'b000100,	//读请求
		STO = 6'b001000,	//停止位请求
		ACK = 6'b010000,	//应答位请求
		NACK = 6'b100000;	//无应答请求

/*
	i2c_bit_shift i2c_bit_shift(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.Cmd(Cmd),
		.Go(Go),
		.Rx_DATA(Rx_DATA),
		.Tx_DATA(Tx_DATA),
		.Trans_Done(Trans_Done),
		.ack_o(ack_o),
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
*/
	i2c_bit_shift i2c_bit_shift(
		.clk(Clk),
		.rst_n(Rst_n),
		.cmd(Cmd),
		.go(Go),
		.Rx_Data(Rx_DATA),
		.Tx_Data(Tx_DATA),
		.Trans_Done(Trans_Done),
		.ack_o(ack_o),
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);

	M24LC04B M24LC04B(
		.A0(0), 
		.A1(0), 
		.A2(0), 
		.WP(0), 
		.SDA(i2c_sdat), 
		.SCL(i2c_sclk), 
		.RESET(~Rst_n)
	);
	
	initial Clk = 1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 0;
		Cmd = 0;
		Go = 0;
		Tx_DATA = 0;
		#2001;
		Rst_n = 1;
		#2000;
		
		write_one_byte(8'hA0,8'h0A,8'hd1);
		//#20000;
		write_one_byte(8'hA0,8'h0B,8'hd2);
		//#20000;
		write_one_byte(8'hA0,8'h0C,8'hd3);
		//#20000;
		write_one_byte(8'hA0,8'h0D,8'hd4);
		//#20000;
		write_one_byte(8'hA0,8'h0F,8'hd5);
		//#20000;
		
		read_one_byte(8'hA0,8'h0A);
		//#20000;
		read_one_byte(8'hA0,8'h0B);
		//#20000;
		read_one_byte(8'hA0,8'h0C);
		//#20000;
		read_one_byte(8'hA0,8'h0D);
		//#20000;
		read_one_byte(8'hA0,8'h0F);
		//#20000;
		$stop;	
	end
	
	task write_one_byte;
		input [7:0]device_id;						//器件地址
		input [7:0]mem_address; 					//寄存器地址
		input [7:0]data;							//需要写的数据
		begin
			Cmd = WR | STA;							//写入产生起始位和写数据命令
			Go = 1;									//启动整个模块
			Tx_DATA = device_id;					//把器件地址赋值给Tx_Data
			#20;		
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次传输数据完成
			#20;
			//#20000;
			
			Cmd = WR;								//传入写数据命令
			Go = 1;									//启动整个模块
			Tx_DATA = mem_address;					//把寄存器地址赋值给Tx_Data
			#20;
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次数据传输完成
			//#20000;
			#20
		
			Cmd = WR | STO;							//传入写数据和产生停止位的命令
			Go = 1;									//启动整个模块
			Tx_DATA = data;							//把需要写入的数据赋值给Tx_Data
			#20;
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次传输数据完成
			#20000;	
		end
	endtask
	
	task read_one_byte;
		input [7:0]device_id;						//传入器件地址
		input [7:0]mem_address;						//传入寄存器地址
		begin
			Cmd = WR | STA;							//传入写数据和产生开始位的命令
			Go = 1;									//启动整个模块
			Tx_DATA = device_id;					//把器件地址赋值给Tx_Data
			#20;
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次传输数据完成
			#20000;
		
			Cmd = WR | STO;
			Go = 1;
			Tx_DATA = mem_address;
			#20;
			Go = 0;
			#200;
			@(posedge Trans_Done);
			//#20000;
			#20;
			
			Cmd = WR | STA;
			Go = 1;
			Tx_DATA = device_id | 8'd1;
			#20;
			Go = 0;
			#200;
			@(posedge Trans_Done);
			//#20000;
			#20;
			
			Cmd = RD | NACK | STO;
			Go = 1;
			#20;
			Go = 0;
			#200;
			@(posedge Trans_Done);
			//#20000;
			#20;			
		end
	endtask
	
	

endmodule

Форма сигнала моделирования выглядит следующим образом, что показывает, что данные были успешно считаны:
вставьте сюда описание изображения

Модуль управления I2C

Поскольку модуль i2c_bit_shift может отправлять только один байт данных, при одновременной записи ввода или чтении вывода необходимо записать несколько байтов. Следовательно, для записи ввода и ввода можно выделить несколько задач записи байтов соответственно, обе из которых начинаются с состояния IDLE. Но эти задачи последовательные и должны дождаться завершения предыдущей задачи.В этот момент нам пригодится наш сигнал Trans_done. Следовательно, его можно спроектировать с использованием конечного автомата.
Для одной записи:
вставьте сюда описание изображения
Для одного чтения:
вставьте сюда описание изображения
следующие состояния (закодированные в одном горячем коде):

ПРАЗДНЫЙ состояние ожидания
WR_REG написать статус
WAIT_WR_DONE Ожидание статуса завершения записи
WR_REG_DONE Написать статус завершения
РД_РЕГ статус чтения
WAIT_RD_DONE Дождитесь статуса завершения чтения
RD_REG_DONE прочитать статус завершения

Схема перехода состояний:
вставьте сюда описание изображения

Код модуля управления следующий (в коде есть очень подробные комментарии), поэтому дальше объяснять не буду.


module i2c_ctrl(
    input   wire            clk         ,               //时钟信号
    input   wire            rst_n       ,               //复位信号
    input   wire            wrreg_req   ,               //写请求信号
    input   wire            rdreg_req   ,               //读请求信号
    input   wire    [15:0]  addr        ,               //16位地址输入       
    input   wire            addr_mode   ,               //输入地址的模式,0:8位的地址 1:16位的地址
    input   wire    [7:0]   wrdata      ,               //总线发送的8位数据
    output  reg     [7:0]   rddata      ,               //总线接收的8位数据
    input   wire    [7:0]   device_id   ,               //i2c器件的ID
    output  reg             RW_Done     ,               //读/写完成的标志
    output  reg             ack         ,               //从机是否应答标志
    output  wire            i2c_sclk    ,               //i2c时钟总线
    inout   wire            i2c_sdat  
);


localparam  IDLE        =       7'b0000_001,            //空闲状态
            WR_REG      =       7'b0000_010,            //写状态
            WAIT_WR_DONE=       7'b0000_100,            //等待写完成状态
            WR_REG_DONE =       7'b0001_000,            //写完成状态
            RD_REG      =       7'b0010_000,            //读状态
            WAIT_RD_DONE=       7'b0100_000,            //等待读完成状态
            RD_REG_DONE =       7'b1000_000;            //读完成状态

localparam  WR          =       6'b000_001,             //写数据请求
            STA         =       6'b000_010,             //起始位请求
            RD          =       6'b000_100,             //读数据请求
            STO         =       6'b001_000,             //停止位请求 
            ACK         =       6'b010_000,             //应答位请求
            NACK        =       6'b100_000;             //无应答请求

                                       

reg     [5:0]   cmd         ;
reg     [7:0]   Tx_Data     ;
reg             go          ;
reg     [6:0]   state       ;
reg     [7:0]   cnt         ;
//reg     [15:0]  reg_addr    ;

wire            Trans_Done  ;
wire            ack_o       ;
wire    [7:0]   Rx_Data     ;
wire    [15:0]  reg_addr    ;

i2c_bit_shift i2c_bit_shift (
    .clk        (clk) ,
    .rst_n      (rst_n) ,
    .cmd        (cmd) ,  
    .go         (go) ,  
    .Tx_Data    (Tx_Data) ,  

    .i2c_sclk   (i2c_sclk) ,  
    .i2c_sdat   (i2c_sdat) ,  
    .Trans_Done (Trans_Done) ,  
    .Rx_Data    (Rx_Data) ,  
    .ack_o      (ack_o)    
);  
assign reg_addr = (addr_mode) ? addr : {
    
    addr[7:0], addr[15:8]};
    
    always @(posedge clk or negedge rst_n)
        if(!rst_n) begin
            cmd <= 6'd0;
            Tx_Data <= 8'd0;
            go <= 1'b0;
            rddata <= 8'd0;
            state <= IDLE;
            ack <= 0;
        end
        else begin
            case(state)
                RD_REG: begin                                                       //读寄存器状态包括读数据的四个task
                            state <= WAIT_RD_DONE;                                  //跳转等待读完成状态
                            case(cnt)
                                0:  write_byte(WR | STA, device_id) ;               //产生起始位并写入器件地址
                                1:  write_byte(WR, reg_addr[15:8])  ;               //写入寄存器地址的高8位
                                2:  write_byte(WR, reg_addr[7:0]);                  //写入寄存器地址的低8位
                                3:  write_byte(WR | STA, device_id | 8'd1);         //产生起始位并写入器件地址(最后一位是1,表示读)
                                4:  read_byte(RD | NACK | STO);                     //读数据并且产生NACK,STOP信号
                                default:;
                            endcase
                        end
                WAIT_RD_DONE:begin
                            go <= 1'b0;                                             //拉低go信号
                            if(Trans_Done) begin                                    //等待此次数据传输完成
                                if(cnt <= 3)
                                    ack <= ack | ack_o;
                                case(cnt)
                                    0:  begin cnt <= 1; state <= RD_REG; end        //cnt为0时,cnt加1,跳转读寄存器状态
                                    1:  begin
                                            state <= RD_REG;
                                            if(addr_mode)                           //根据地址模式
                                                cnt <= 2;                           //addr_more为1:16位地址
                                            else
                                                cnt <= 3;                           //addr_mode位0,8位地址
                                        end
                                    2:  begin
                                            cnt <= 3;
                                            state <= RD_REG;
                                        end
                                    3:  begin
                                            cnt <= 4;
                                            state <= RD_REG;
                                        end
                                    4:  state <= RD_REG_DONE;                       //cnt为4时,跳转到读完成状态                   
                                    default:    state <= IDLE;
                                endcase
                            end
                        end
                RD_REG_DONE:begin
                            RW_Done <= 1'b1;                                        //拉高读写完成信号
                            rddata <= Rx_Data;                                      //把读出的数据Rx_Data赋值给rddata
                            state <= IDLE;                                          //跳转到IDLE状态
                        end

                WR_REG: begin
                            state <= WAIT_WR_DONE;                                  //跳转到等待写完成状态
                            case(cnt)
                                0:  write_byte(WR | STA, device_id);                //产生起始位并写入器件地址
                                1:  write_byte(WR, reg_addr[15:8]);                 //写入寄存器地址的高8位
                                2:  write_byte(WR, reg_addr[7:0]);                  //写入寄存器地址的低8位
                                3:  write_byte(WR | STO, wrdata);                   //写入数据并且产生停止位
                                default:;
                            endcase
                        end

                WAIT_WR_DONE:begin
                                go <= 1'b0;                                         //拉低go信号
                                if(Trans_Done) begin                                //等待此次传输数据完成
                                    ack <= ack | ack_o;
                                    case(cnt)
                                        0:  begin cnt <= 1; state <= WR_REG; end    //cnt为0时,跳转写寄存器状态
                                        1:  begin
                                                state <= WR_REG;                    
                                                if(addr_mode)                       //不同的地址模式
                                                    cnt <= 2;                       //决定时写入16位还是8位
                                                else
                                                    cnt <= 3;
                                            end
                                        2:  begin
                                                cnt <= 3;
                                                state <= WR_REG;
                                            end
                                        3:  state <= WR_REG_DONE;                   //cnt为3时,跳转写完成状态
                                        default: state <= IDLE;
                                    endcase
                                end
                            end

                WR_REG_DONE:begin
                                RW_Done <= 1'b1;                                    //拉高读写完成信号
                                state <= IDLE;                                      //跳转到IDLE状态
                            end
                IDLE:       begin
                                cnt <= 0;
                                ack <= 0;
                                RW_Done <= 1'b0;
                                if(wrreg_req)                           //写请求则进入写寄存器状态
                                    state <= WR_REG;
                                else if(rdreg_req)                      //读请求则进入读寄存器状态
                                    state <= RD_REG;
                                else
                                    state <= IDLE;
                            end
                default:    state <= IDLE;
            endcase
end     


//读一个字节的任务
task read_byte;
    input   [5:0]   ctrl_cmd        ;           //传入命令
    begin                       
        cmd <= ctrl_cmd             ;           //将传入的命令赋值给cmd
        go <= 1'b1                  ;           //启动整个模块
    end
endtask

//写一个字节的任务
task write_byte;
    input   [5:0]   ctrl_cmd        ;           //传入命令
    input   [7:0]   wr_byte_data    ;           //传入要写入的8位字节数据
    begin
        cmd <= ctrl_cmd             ;           //将传入的命令赋值给cmd
        Tx_Data <= wr_byte_data     ;           //将传入的8位字节数据赋值给Tx_Data
        go <= 1'b1                  ;           //启动整个模块
    end    
endtask

endmodule



Имитированный файл TB:

`timescale 1ns/1ns
module i2c_control_tb;

	reg Clk;
	reg Rst_n;
	
	reg wrreg_req;
	reg rdreg_req;
	reg [15:0]addr;
	reg addr_mode;
	reg [7:0]wrdata;
	wire [7:0]rddata;
	reg [7:0]device_id;
	wire RW_Done;
	
	wire  ack;
	wire i2c_sclk;
	wire i2c_sdat;
	
	pullup PUP (i2c_sdat);
	
	i2c_ctrl i2c_control(
		.clk(Clk),
		.rst_n(Rst_n),
		.wrreg_req(wrreg_req),
		.rdreg_req(rdreg_req),
		.addr(addr),
		.addr_mode(addr_mode),
		.wrdata(wrdata),
		.rddata(rddata),
		.device_id(device_id),
		.RW_Done(RW_Done),
		.ack(ack),		
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
	/*
	i2c_control i2c_control(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.wrreg_req(wrreg_req),
		.rdreg_req(rdreg_req),
		.addr(addr),
		.addr_mode(addr_mode),
		.wrdata(wrdata),
		.rddata(rddata),
		.device_id(device_id),
		.RW_Done(RW_Done),
		.ack(ack),		
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
	*/


	M24LC64 M24LC64(
		.A0(0), 
		.A1(0), 
		.A2(0), 
		.WP(0), 
		.SDA(i2c_sdat), 
		.SCL(i2c_sclk), 
		.RESET(~Rst_n)
	);
	
	initial Clk = 1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 0;
		rdreg_req = 0;
		wrreg_req = 0;
		#2001;
		Rst_n = 1;
		#2000;
		
		write_one_byte(8'hA0,8'h0A,8'hd1);
		//#20000;
		write_one_byte(8'hA0,8'h0B,8'hd2);
		//#20000;
		write_one_byte(8'hA0,8'h0C,8'hd3);
		//#20000;
		write_one_byte(8'hA0,8'h0D,8'hd4);
		//#20000;
		write_one_byte(8'hA0,8'h0F,8'hd5);
		//#20000;
		
		read_one_byte(8'hA0,8'h0A);
		//#20000;
		read_one_byte(8'hA0,8'h0B);
		//#20000;
		read_one_byte(8'hA0,8'h0C);
		//#20000;
		read_one_byte(8'hA0,8'h0D);
		//#20000;
		read_one_byte(8'hA0,8'h0F);
		//#20000;
		$stop;	
	end
	
	task write_one_byte;
		input [7:0]id;
		input [7:0]mem_address; 
		input [7:0]data;
		begin
			addr = {
    
    8'd0,mem_address};
			device_id = id;
			addr_mode = 1;
			wrdata = data;
			wrreg_req = 1;
			#20;
			wrreg_req = 0;
			@(posedge RW_Done);
			#20000;	
		end
	endtask
	
	task read_one_byte;
		input [7:0]id;
		input [7:0]mem_address;
		begin
			addr = {
    
    8'd0,mem_address};
			device_id = id;
			addr_mode = 1;
			rdreg_req = 1;
			#20;
			rdreg_req = 0;
			@(posedge RW_Done);
			#20000;			
		end
	endtask
	
	

endmodule

Из диаграммы формы сигнала моделирования видно, что данные были успешно прочитаны и записаны.

вставьте сюда описание изображения

Supongo que te gusta

Origin blog.csdn.net/weixin_45614076/article/details/126545998
Recomendado
Clasificación