赞
踩
verilog 实现IIC协议
算是一个简单的IP核,本来是挂在AXI总线上,可以通过microblaze对其进行配置。最近在弄ADV7511,用到IIC来配置它.直接上代码:
module iic_drive(
input clk,
input reset_n,
// 与控制器通信信号 input [31:0] slv_reg0, input [31:0] slv_reg1, input [31:0] slv_reg2, input [31:0] slv_reg3, input [31:0] slv_reg4, output reg [ 7:0] iic_rddb, output iic_busy, // 外部信号 output iic_scl, input iic_sda_in, output sda_dir, output sda_r ); // SCL 分频系数 // 产生IIC时钟 100M/20K = 5000 parameter SCL_SUM = 13'd5000; // 仿真时 //parameter SCL_SUM = 13'd64; //*************************************************************************************** // iic读写状态机 reg [12:0] scl_cnt; always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) scl_cnt <= 13'd0; else if(scl_cnt < (SCL_SUM - 1)) scl_cnt <= scl_cnt + 1; else scl_cnt <= 13'd0; end // 不同的时钟阶段 assign iic_scl = (scl_cnt <= (SCL_SUM >> 1)); // 初始为高电平 wire scl_hs = (scl_cnt == 13'd1); // scl high start wire scl_hc = (scl_cnt == ((SCL_SUM >> 1)-(SCL_SUM >> 2))); // scl high center wire scl_ls = (scl_cnt == (SCL_SUM >> 1)); // scl low start wire scl_lc = (scl_cnt == ((SCL_SUM >> 1)+(SCL_SUM >> 2))); // scl low center //IIC状态机控制信号 reg iicwr_req; // IIC写请求信号,高电平有效 reg iicrd_req; // IIC读请求信号,高电平有效 reg [2:0] bcnt; wire [7:0] slave_addr_w = slv_reg0[7:0]; // slave地址,写数据 wire [7:0] slave_addr_r = slv_reg0[7:0]+1; // slave地址,读数据 wire [7:0] reg_addr = slv_reg1[7:0]; // reg地址 wire [7:0] iic_wrdb = slv_reg2[7:0]; // 待发送的数据 wire iic_wr_en = slv_reg3[0]; // 写使能 wire iic_rd_en = slv_reg4[0]; // 读使能 //**************************************************************************** // 读写使能上升沿信号 reg iic_wr_en_r0,iic_wr_en_r1; reg iic_rd_en_r0,iic_rd_en_r1; always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) begin iic_wr_en_r0 <= 1'b0; iic_wr_en_r1 <= 1'b0; end else begin iic_wr_en_r0 <= iic_wr_en; iic_wr_en_r1 <= iic_wr_en_r0; end end wire iic_wr_en_pos = (~iic_wr_en_r1 && iic_wr_en_r0); // 写使能上升沿 always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) begin iic_rd_en_r0 <= 1'b0; iic_rd_en_r1 <= 1'b0; end else begin iic_rd_en_r0 <= iic_rd_en; iic_rd_en_r1 <= iic_rd_en_r0; end end wire iic_rd_en_pos = (~iic_rd_en_r1 && iic_rd_en_r0); // 读使能上升沿 //**************************************************************************** // IIC状态 parameter IDLE = 4'd0, START0 = 4'd1, WRSADDR0 = 4'd2, ACK0 = 4'd3, WRRADDR = 4'd4, ACK1 = 4'd5, WRDATA = 4'd6, ACK2 = 4'd7, STOP = 4'd8, START1 = 4'd9, WRSADDR1 = 4'd10, ACK3 = 4'd11, RDDATA = 4'd12, NOACK = 4'd13; // 状态跳转 reg [3:0] c_state; reg [3:0] n_state; always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) c_state <= IDLE; else c_state <= n_state; end // 组合逻辑触发 always @ (*) begin case(c_state) IDLE: // 初始化 begin if(((iicwr_req == 1'b1)||(iicrd_req == 1'b1))&&(scl_hc == 1'b1)) n_state = START0; else n_state = IDLE; end START0: // 起始 begin if(scl_lc == 1'b1) n_state = WRSADDR0; else n_state = START0; end WRSADDR0: // 写slave地址 begin if((scl_lc == 1'b1)&&(bcnt == 3'd0)) n_state = ACK0; else n_state = WRSADDR0; end ACK0: // 接收应答 begin if(scl_lc == 1'b1) n_state = WRRADDR; else n_state = ACK0; end WRRADDR: // 写寄存器地址 begin if((scl_lc == 1'b1)&&(bcnt == 3'd0)) n_state = ACK1; else n_state = WRRADDR; end ACK1: // 接收应答 begin if(scl_lc == 1'b1) begin if(iicwr_req == 1'b1) n_state = WRDATA; else if(iicrd_req == 1'b1) n_state = START1; else n_state = IDLE; end else n_state = ACK1; end //************** // 写数据 WRDATA: begin if((scl_lc == 1'b1)&&(bcnt == 3'd0)) n_state = ACK2; else n_state = WRDATA; end ACK2: // 接收应答 begin if(scl_lc == 1'b1) n_state = STOP; else n_state = ACK2; end //************** // 读数据过程 START1: begin if(scl_lc == 1'b1) n_state = WRSADDR1; else n_state = START1; end WRSADDR1: begin if((scl_lc == 1'b1)&&(bcnt == 3'd0)) n_state = ACK3; else n_state = WRSADDR1; end ACK3: // 接收应答 begin if(scl_lc == 1'b1) n_state = RDDATA; else n_state = ACK3; end RDDATA: begin if((scl_lc == 1'b1)&&(bcnt == 3'd0)) n_state = NOACK; else n_state = RDDATA; end NOACK: begin if(scl_lc == 1'b1) n_state = STOP; else n_state = NOACK; end //************** STOP: begin if(scl_lc == 1'b1) n_state = IDLE; else n_state = STOP; end default: n_state = IDLE; endcase end // 计数器控制 always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) bcnt <= 3'd0; else begin case (n_state) WRSADDR0,WRRADDR,WRDATA,WRSADDR1,RDDATA: begin if(scl_lc == 1'b1) bcnt <= bcnt + 1; end default: bcnt <= 3'd0; endcase end end reg sda_dir; // 控制数据方向,高电平为数据输出 reg sda_r; // 输出数据 // 控制数据输出 always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) begin sda_dir <= 1'b1; sda_r <= 1'b1; end else begin case (n_state) IDLE,NOACK: begin sda_dir <= 1'b1; sda_r <= 1'b1; end START0: begin sda_dir <= 1'b1; sda_r <= 1'b0; // 进入开始状态后,数据拉低 end START1: begin sda_dir <= 1'b1; if(scl_lc == 1'b1) sda_r <= 1'b1; else if(scl_hc == 1'b1) sda_r <= 1'b0; end WRSADDR0: begin sda_dir <= 1'b1; if(scl_lc == 1'b1) sda_r <= slave_addr_w[7-bcnt]; end WRSADDR1: begin sda_dir <= 1'b1; if(scl_lc == 1'b1) sda_r <= slave_addr_r[7-bcnt]; end ACK0,ACK1,ACK2,ACK3: sda_dir <= 1'b0; // 接收总线数据 WRRADDR: begin sda_dir <= 1'b1; if(scl_lc == 1'b1) sda_r <= reg_addr[7-bcnt]; end WRDATA: begin sda_dir <= 1'b1; if(scl_lc == 1'b1) sda_r <= iic_wrdb[7-bcnt]; end RDDATA: begin sda_dir <= 1'b0; if(scl_lc == 1'b1) iic_rddb[7-bcnt] <= iic_sda_in; end STOP: begin sda_dir <= 1'b1; if(scl_lc == 1'b1) sda_r <= 1'b0; else if(scl_hc == 1'b1) sda_r <= 1'b1; end endcase end end
// assign iic_sda = sda_dir ? sda_r : 1’bz;
wire iic_ack = (c_state == STOP) && scl_hc; //IIC操作响应,高电平有效
// 确定读写过程标志 always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) iicwr_req <= 1'b0; else begin if(iic_wr_en_pos == 1'b1) iicwr_req <= 1'b1; else if(iic_ack == 1'b1) // IIC过程结束 iicwr_req <= 1'b0; end end always @ (posedge clk or negedge reset_n) begin if(reset_n == 1'b0) iicrd_req <= 1'b0; else begin if(iic_rd_en_pos == 1'b1) iicrd_req <= 1'b1; else if(iic_ack == 1'b1) // IIC过程结束 iicrd_req <= 1'b0; end end // 当有请求时一直忙,完成过程后忙结束 assign iic_busy = (iicwr_req || iicrd_req);
endmodule
/*
// testbench
module iic_drive_tb();
reg clk;
reg reset_n;
// 与控制器通信信号 reg [31:0] slv_reg0; reg [31:0] slv_reg1; reg [31:0] slv_reg2; reg [31:0] slv_reg3; reg [31:0] slv_reg4; wire [ 7:0] iic_rddb; wire iic_busy; // 外部信号 wire iic_scl; wire iic_sda; //*************************************************** // 例化顶层文件 iic_drive iic_drive_inst0( .clk ( clk ), .reset_n ( reset_n ), .slv_reg0 ( slv_reg0 ), .slv_reg1 ( slv_reg1 ), .slv_reg2 ( slv_reg2 ), .slv_reg3 ( slv_reg3 ), .slv_reg4 ( slv_reg4 ), .iic_rddb ( iic_rddb ), .iic_busy ( iic_busy), .iic_scl ( iic_scl ), .iic_sda ( iic_sda ) ); //*************************************************** // testcase // 时钟 always begin #5 clk = ~clk; end // 初始化 initial begin clk = 0; reset_n = 0; slv_reg0 = 0; slv_reg1 = 0; slv_reg2 = 0; slv_reg3 = 0; slv_reg4 = 0; #100; reset_n = 1; #1000; slv_reg0 = 32'hf0; slv_reg1 = 32'h20; slv_reg2 = 32'h33; slv_reg3 = 32'h0; slv_reg4 = 32'h0; #1000; slv_reg3 = 32'h1; #6000000; slv_reg0 = 32'h60; slv_reg1 = 32'h70; slv_reg2 = 32'h88; slv_reg3 = 32'h0; slv_reg4 = 32'h0; #1000; slv_reg4 = 32'h1; #10000; end
endmodule
*/
下面还有简单的testbench,主要配置数据和读写使能,然后根据忙信号来判断是否IIC过程完成。。
最后在最顶层中用IOBUF生成三态,vivado中一定要在最顶层中。。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。