Verilog IIC驱动| FPGA驱动

发布于:2025-03-07 ⋅ 阅读:(20) ⋅ 点赞:(0)

module iic(
    input                               clk                        ,
    input                               rst                       ,
    input              [   4:0]         cmd                        ,
    input                               cmd_vld                    ,
    input              [   7:0]         wr_data                    ,
    output             [   7:0]         rd_data                    ,
    output                              rd_data_vld                ,

    output reg                          rev_ack                    ,
    output                              done                       ,
    output reg                          scl                        ,
    inout                               sda                         

);

/*
模拟过程
1. cnt_num
2. 二阶状态机
3. scl的生成

cmd和cmd_vld是一起过来的!!!
*/

`define     START_BIT   5'b00001
`define     WRITE_BIT   5'b00010
`define     READ_BIT    5'b00100
`define     STOP_BIT    5'b01000
`define     ACK_BIT     5'b10000
`define     ACK         0
`define     NO_ACK      1

localparam STATUS_IDLE      = 7'b000_0001;
localparam STATUS_START     = 7'b000_0010;
localparam STATUS_WR_DATA   = 7'b000_0100;
localparam STATUS_RD_DATA   = 7'b000_1000;
localparam STATUS_T_ACK     = 7'b001_0000;
localparam STATUS_R_ACK     = 7'b010_0000;
localparam STATUS_STOP      = 7'b100_0000;
localparam T = 100_000;
localparam SCL_MAX = 50_000_000/T; // 一个完整的SCL周期

/*
localparam和define放在最前面
*/


reg	[3:0]cnt_num	   	;
reg [6:0]cstate;
reg [6:0]nstate;
reg [7:0]wr_data_r;
reg sda_out;


parameter   SCL_LOW_HALF  = (SCL_MAX * 1 / 4) - 1,
            SCL_HIGH_HALF = (SCL_MAX * 3 / 4) - 1;  
reg [25:0]cnt_bit;
// 下面是关键的 状态转换的检测信号






reg OE;
reg[8*20-1:0]state_name;
reg [7:0]rd_data_r;
reg [4:0]num;
wire end_cnt_num;

/*下面是输入输出信号*/
assign rd_data = rd_data_r; //读也从寄存器开始输出

assign sda = OE?sda_out:1'bz;
wire sda_in = sda;


reg [4:0]cmd_r;


wire IF_IDLE_START       = (cstate == STATUS_IDLE       )    && cmd_vld      && (cmd & `START_BIT)    ;
wire IF_START_WR_DATA    = (cstate == STATUS_START      )    && end_cnt_num  && (cmd_r & `WRITE_BIT)    ;
wire IF_WR_DATA_R_ACK    = (cstate == STATUS_WR_DATA    )    && end_cnt_num                             ;
wire IF_R_ACK_IDLE       = (cstate == STATUS_R_ACK      )    && end_cnt_num  && !(cmd_r & `STOP_BIT)    ; // 可以接收到响应后停止(比如说在读数据之前)
wire IF_IDLE_WR_DATA     = (cstate == STATUS_IDLE       )    && cmd_vld      && (cmd & `WRITE_BIT)    ;
wire IF_R_ACK_STOP       = (cstate == STATUS_R_ACK      )    && end_cnt_num  && (cmd_r & `STOP_BIT)     ;
wire IF_STOP_IDLE        = (cstate == STATUS_STOP       )    && end_cnt_num                             ;
wire IF_IDLE_RD_DATA     = (cstate == STATUS_IDLE       )    && cmd_vld      && (cmd & `READ_BIT)     ;
wire IF_RD_DATA_T_ACK    = (cstate == STATUS_RD_DATA    )    && end_cnt_num                             ;
wire IF_T_ACK_IDLE       = (cstate == STATUS_T_ACK      )    && end_cnt_num  && !(cmd_r & `STOP_BIT)    ;
wire IF_T_ACK_STOP       = (cstate == STATUS_T_ACK      )    && end_cnt_num  && (cmd_r & `STOP_BIT)     ;

/*
cmd_vld对应cmd   				是由外界的cmd触发的
end_cnt_num对应cmd_r			是由自动触发引起的
*/


assign add_cnt_bit = cstate != STATUS_IDLE;
assign end_cnt_bit = add_cnt_bit && cnt_bit == SCL_MAX - 1'd1;

assign add_cnt_num = end_cnt_bit;
assign end_cnt_num = add_cnt_num && cnt_num == num - 1;

assign done = IF_R_ACK_IDLE || IF_STOP_IDLE || IF_T_ACK_IDLE;//都是回到IDLE  用来对应eeprom的cnt_byte
assign rd_data_vld = IF_T_ACK_STOP || IF_T_ACK_IDLE;// 快发送ACK的时候

/*
    cnt_bit的目的是组成完整的scl时钟周期
*/ 
always @(posedge clk or negedge rst)
	if (!rst) begin
        cnt_bit <= 26'd0;
    end else if(add_cnt_bit)begin
        if(end_cnt_bit)
            cnt_bit <= 26'd0;
        else
            cnt_bit <= cnt_bit + 26'd1;
    end 

//IIC_SCL
always @(posedge clk or negedge rst) begin
	if (!rst) begin
		scl <= 1'b1;
    end else if(cnt_bit == (SCL_MAX - 1'd1)>>1 || IF_STOP_IDLE)begin
        scl <= 1'b1;
    end else if(end_cnt_bit)begin
        scl <= 1'b0;//start信号  默认是1 过了第一个end_cnt_bit才把scl拉低  妙啊
    end
end


// 能用寄存器   都用寄存器


always @(posedge clk or negedge rst) 
	if (!rst) begin
        OE = 1'b1;
    end else if(IF_IDLE_START || IF_START_WR_DATA || IF_IDLE_WR_DATA || IF_RD_DATA_T_ACK || IF_T_ACK_STOP || IF_R_ACK_STOP)begin// !!!添加了IF_T_ACK_STOP
        OE = 1'b1;
    end else if(IF_WR_DATA_R_ACK || IF_IDLE_RD_DATA || IF_STOP_IDLE)begin
        OE = 1'b0;
    end

 always @(posedge clk or negedge rst) begin
	if (!rst) begin
		sda_out <= 1;
		end
	else begin
		case (cstate)
			STATUS_IDLE    :sda_out <= 1;
			STATUS_START   :begin
                if(cnt_bit == SCL_LOW_HALF)
                    sda_out <= 1;
                else if(cnt_bit == SCL_HIGH_HALF)
                    sda_out <= 0;
				end
			STATUS_WR_DATA :begin
                if(cnt_bit == SCL_LOW_HALF)
                    sda_out <= wr_data_r[7-cnt_num];
				end
			STATUS_T_ACK   :begin//
                if(cnt_bit == SCL_LOW_HALF)
                    if(cmd_r & `ACK_BIT)
                        sda_out <= `NO_ACK;
                    else
                        sda_out <= `ACK;
				end
			STATUS_STOP    :begin
                if(cnt_bit == SCL_LOW_HALF)
                    sda_out <= 0;
                else if(cnt_bit == SCL_HIGH_HALF)
                    sda_out <= 1;
				end
			default: sda_out <= 'b1;
		endcase
	end
end


always@(*)
    case(cstate)
        STATUS_IDLE    :   state_name  = "IDLE   ";
        STATUS_START   :   state_name  = "START  ";
        STATUS_WR_DATA :   state_name  = "WR_DATA";
        STATUS_RD_DATA :   state_name  = "RD_DATA";
        STATUS_T_ACK   :   state_name  = "T_ACK  ";
        STATUS_R_ACK   :   state_name  = "R_ACK  ";
        STATUS_STOP    :   state_name  = "STOP   ";
        default 	   :   state_name  = "IDLE   ";
    endcase



always @(posedge clk or negedge rst) begin
	if (!rst) begin
		rev_ack <= 0;
		rd_data_r <= 8'b0;
	end else case (cstate)
        STATUS_RD_DATA:
            if(cnt_bit == SCL_HIGH_HALF)
	            rd_data_r[7 - cnt_num] <= sda;
        STATUS_R_ACK:
            if(cnt_bit == SCL_HIGH_HALF)
                rev_ack <= sda_in;    
        default:;
    endcase
end

/*
在scl的1/4发送数据,在3/4处接受数据
*/













always @(*) begin
	case (cstate)
		STATUS_IDLE    : num = 1;
		STATUS_START   : num = 1;
		STATUS_WR_DATA : num = 8;
		STATUS_RD_DATA : num = 8;
		STATUS_R_ACK   : num = 1;
		STATUS_T_ACK   : num = 1;
		STATUS_STOP    : num = 1;
		default : num = 1;
	endcase
end



// 把输入的东西先存放在寄存器里面  确保在状态机运行期间,这些信号的值保持稳定
always @(posedge clk or negedge rst) begin
	if (!rst) begin
		wr_data_r <= 'd0;
		cmd_r <= 'd0;
		end
	else if (cmd_vld) begin
		wr_data_r <= wr_data;
		cmd_r <= cmd;
		end
end




always@(posedge clk or negedge rst)
    if(!rst)begin
        cstate <= STATUS_IDLE;
    end else begin
        cstate <= nstate;
    end

always @(*) begin
	case(cstate)
		STATUS_IDLE    : begin
			if (IF_IDLE_START) begin
				nstate = STATUS_START;
				end
			else if (IF_IDLE_WR_DATA) begin
				nstate = STATUS_WR_DATA;
				end
			else if (IF_IDLE_RD_DATA) begin
				nstate = STATUS_RD_DATA;
				end
			else begin
				nstate = cstate;    //这个要注意
				end
			end 
		STATUS_START   : begin
			if (IF_START_WR_DATA) begin
				nstate = STATUS_WR_DATA;
				end
			else begin
				nstate = cstate;
				end
			end 
		STATUS_WR_DATA : begin
			if (IF_WR_DATA_R_ACK) begin
				nstate = STATUS_R_ACK;
				end
			else begin
				nstate = cstate;
				end
			end 
		STATUS_RD_DATA : begin
			if (IF_RD_DATA_T_ACK) begin
				nstate = STATUS_T_ACK;
				end
			else begin
				nstate = cstate;
				end
			end 
		STATUS_R_ACK   : begin
			if (IF_R_ACK_STOP) begin
				nstate = STATUS_STOP;
				end
			else if (IF_R_ACK_IDLE) begin
				nstate = STATUS_IDLE;
				end
			else begin
				nstate = cstate;
				end
			end 
		STATUS_T_ACK   : begin
			if (IF_T_ACK_STOP) begin
				nstate = STATUS_STOP;
				end
			else if (IF_T_ACK_IDLE) begin
				nstate = STATUS_IDLE;
				end
			else begin
				nstate = cstate;
				end
			end 
		STATUS_STOP    : begin
			if (IF_STOP_IDLE) begin
				nstate = STATUS_IDLE;
				end
			else begin
				nstate = cstate;
				end
			end 
		default : nstate = cstate;
	endcase
end




always @(posedge clk or negedge rst)begin 
	if(!rst)begin
		cnt_num <= 'd0;
		end 
	else if(add_cnt_num)begin 
		if(end_cnt_num)begin 
			cnt_num <= 'd0;
			end
		else begin 
			cnt_num <= cnt_num + 1'd1;
		end 
	end
end 










endmodule