I2C协议广泛用于短距板级低速通信,尤其是处理器与各类芯片诸如传感器的配置等功能。本人由于需要使用MT9V034完成图像采集,以及驱动ZedBoard板子上的ADV7511用于HDMI显示,需要用到I2C通信接口。可供选择的方案是使用PS端的I2C硬件接口、IP Catalog中的AXI I2C IP核,以及自己编写HDL。考虑到手头上的两个MT9V034模块地址相同、版权、开发时间等问题,最终选择在搜集到资料基础上自己改写。
言归正传,这里上MT9V034数据手册上的时序:
协议这里就不再详述,这里主要讲讲HDL的实现思路。可以看到,时序可以划分成4种操作:起始位start、停止位stop、写Byte和读Byte(包括应答信号),进一步划分可以到相应的位操作上。所以这里采用”Bit-Byte-Command”的设计思路,由低层次到高层次设计:先设计位操作级控制器(包括start-bit、restart-bit、stop-bit、write-bit、read-bit操作),然后设计字节操作级控制器,最后是命令级控制器,控制器借助FSM。
这里首先需要考虑的是输入和输出。输出不必说,输入应该是AXI总线,然后总线连接控制器。这里将输入端口设计成TX/RX-FIFO形式,主要考虑时钟同步——使用异步FIFO不必考虑总线时钟与模块时钟的数据同步,而寄存器表虽然控制容易且规范但是却无法避免此问题。下面简单给出了框架:
FIFO端口连接到AXI-Stream需要注意,单次发送包大小需要处理一下(或者在应用端处理一下),避免应用端写1Byte而硬件部分没有执行操作。
i2c_master_defines.vh
`define I2C_CMD_NOP 4'b0000
`define I2C_CMD_START 4'b0001
`define I2C_CMD_STOP 4'b0010
`define I2C_CMD_WRITE 4'b0100
`define I2C_CMD_READ 4'b1000
i2c_master_bit_ctrl.v
`timescale 1ns / 10ps
//
/////////////////////////////////////
// Bit controller section
/////////////////////////////////////
//
// Translate simple commands into SCL/SDA transitions
// Each command has 5 states, A/B/C/D/idle
//
// start: SCL ~~~~~~~~~~\____
// SDA ~~~~~~~~\______
// x | A | B | C | D | i
//
// repstart SCL ____/~~~~\___
// SDA __/~~~\______
// x | A | B | C | D | i
//
// stop SCL ____/~~~~~~~~
// SDA ==\____/~~~~~
// x | A | B | C | D | i
//
//- write SCL ____/~~~~\____
// SDA ==X=========X=
// x | A | B | C | D | i
//
//- read SCL ____/~~~~\____
// SDA XXXX=====XXXX
// x | A | B | C | D | i
//
// Timing: Normal mode Fast mode
///////////////////////////////////////////////////////////////////////
// Fscl 100KHz 400KHz
// Th_scl 4.0us 0.6us High period of SCL
// Tl_scl 4.7us 1.3us Low period of SCL
// Tsu:sta 4.7us 0.6us setup time for a repeated start condition
// Tsu:sto 4.0us 0.6us setup time for a stop conditon
// Tbuf 4.7us 1.3us Bus free time between a stop and start condition
//
// synopsys translate_off
//`include "timescale.v"
// synopsys translate_on
`include "i2c_master_defines.vh"
module i2c_master_bit_ctrl(
clk, rst,
clk_cnt, ena, cmd, cmd_ack, busy, al, din, dout,
scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen
);
//
// inputs & outputs
//
input clk;
input rst;
input ena; // core enable signal
input [15:0] clk_cnt; // clock prescale value
input [3:0] cmd;
output cmd_ack; // command complete acknowledge
reg cmd_ack;
output busy; // i2c bus busy
reg busy;
output al; // i2c bus arbitration lost
reg al;
input din;
output dout;
reg dout;
// I2C lines
input scl_i; // i2c clock line input
output scl_o; // i2c clock line output
output scl_oen; // i2c clock line output enable (active low)
reg scl_oen = 1'b1;
input sda_i; // i2c data line input
output sda_o; // i2c data line output
output sda_oen; // i2c data line output enable (active low)
reg sda_oen = 1'b1;
//
// variable declarations
//
reg sSCL, sSDA; // synchronized SCL and SDA inputs
reg dscl_oen; // delayed scl_oen
reg sda_chk; // check SDA output (Multi-master arbitration)
reg clk_en; // clock generation signals
wire slave_wait;
// reg [15:0] cnt = clk_cnt; // clock divider counter (simulation)
reg [15:0] cnt; // clock divider counter (synthesis)
//
// module body
//
// whenever the slave is not ready it can delay the cycle by pulling SCL low
// delay scl_oen
always @(posedge clk)
dscl_oen <= #1 scl_oen;
assign slave_wait = dscl_oen && !sSCL;
// generate clk enable signal
always @(posedge clk)
if (rst)
begin
cnt <= #1 16'h0;
clk_en <= #1 1'b1;
end
else if ( ~|cnt || ~ena)
if (~slave_wait)
begin
cnt <= #1 clk_cnt;
clk_en <= #1 1'b1;
end
else
begin
cnt <= #1 cnt;
clk_en <= #1 1'b0;
end
else
begin
cnt <= #1 cnt - 16'h1;
clk_en <= #1 1'b0;
end
// generate bus status controller
reg dSCL, dSDA;
reg sta_condition;
reg sto_condition;
// synchronize SCL and SDA inputs
// reduce metastability risc
always @(posedge clk)
if (rst)
begin
sSCL <= #1 1'b1;
sSDA <= #1 1'b1;
dSCL <= #1 1'b1;
dSDA <= #1 1'b1;
end
else
begin
sSCL <= #1 scl_i;
sSDA <= #1 sda_i;
dSCL <= #1 sSCL;
dSDA <= #1 sSDA;
end
// detect start condition => detect falling edge on SDA while SCL is high
// detect stop condition => detect rising edge on SDA while SCL is high
always @(posedge clk)
if (rst)
begin
sta_condition <= #1 1'b0;
sto_condition <= #1 1'b0;
end
else
begin
sta_condition <= #1 ~sSDA & dSDA & sSCL;
sto_condition <= #1 sSDA & ~dSDA & sSCL;
end
// generate i2c bus busy signal
always @(posedge clk)
if (rst)
busy <= #1 1'b0;
else
busy <= #1 (sta_condition | busy) & ~sto_condition;
// generate arbitration lost signal
// aribitration lost when:
// 1) master drives SDA high, but the i2c bus is low
// 2) stop detected while not requested
reg cmd_stop, dcmd_stop;
always @(posedge clk)
if (rst)
begin
cmd_stop <= #1 1'b0;
dcmd_stop <= #1 1'b0;
al <= #1 1'b0;
end
else
begin
cmd_stop <= #1 cmd == `I2C_CMD_STOP;
dcmd_stop <= #1 cmd_stop;
al <= #1 (sda_chk & ~sSDA & sda_oen) | (sto_condition & ~dcmd_stop);
end
// generate dout signal (store SDA on rising edge of SCL)
always @(posedge clk)
if(sSCL & ~dSCL)
dout <= #1 sSDA;
// generate statemachine
// nxt_state decoder
parameter [17:0] idle = 18'b0_0000_0000_0000_00000;
parameter [17:0] start_a = 18'b0_0000_0000_0000_00010;
parameter [17:0] start_b = 18'b0_0000_0000_0000_00100;
parameter [17:0] start_c = 18'b0_0000_0000_0000_01000;
parameter [17:0] start_d = 18'b0_0000_0000_0000_10000;
parameter [17:0] start_e = 18'b0_0000_0000_0001_00000;
parameter [17:0] stop_a = 18'b0_0000_0000_0010_00000;
parameter [17:0] stop_b = 18'b0_0000_0000_0100_00000;
parameter [17:0] stop_c = 18'b0_0000_0000_1000_00000;
parameter [17:0] stop_d = 18'b0_0000_0001_0000_00000;
parameter [17:0] rd_a = 18'b0_0000_0010_0000_00000;
parameter [17:0] rd_b = 18'b0_0000_0100_0000_00000;
parameter [17:0] rd_c = 18'b0_0000_1000_0000_00000;
parameter [17:0] rd_d = 18'b0_0001_0000_0000_00000;
parameter [17:0] wr_a = 18'b0_0010_0000_0000_00000;
parameter [17:0] wr_b = 18'b0_0100_0000_0000_00000;
parameter [17:0] wr_c = 18'b0_1000_0000_0000_00000;
parameter [17:0] wr_d = 18'b1_0000_0000_0000_00000;
reg [17:0] c_state; // synopsis enum_state
always @(posedge clk)
if (rst | al)
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b0;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end
else
begin
cmd_ack <= #1 1'b0; // default no command acknowledge + assert cmd_ack only 1clk cycle
if (clk_en)
case (c_state) // synopsis full_case parallel_case
// idle state
idle:
begin
case (cmd) // synopsis full_case parallel_case
`I2C_CMD_START:
c_state <= #1 start_a;
`I2C_CMD_STOP:
c_state <= #1 stop_a;
`I2C_CMD_WRITE:
c_state <= #1 wr_a;
`I2C_CMD_READ:
c_state <= #1 rd_a;
default:
c_state <= #1 idle;
endcase
scl_oen <= #1 scl_oen; // keep SCL in same state
sda_oen <= #1 sda_oen; // keep SDA in same state
sda_chk <= #1 1'b0; // don't check SDA output
end
// start
start_a:
begin
c_state <= #1 start_b;
scl_oen <= #1 scl_oen; // keep SCL in same state
sda_oen <= #1 1'b1; // set SDA high
sda_chk <= #1 1'b0; // don't check SDA output
end
start_b:
begin
c_state <= #1 start_c;
scl_oen <= #1 1'b1; // set SCL high
sda_oen <= #1 1'b1; // keep SDA high
sda_chk <= #1 1'b0; // don't check SDA output
end
start_c:
begin
c_state <= #1 start_d;
scl_oen <= #1 1'b1; // keep SCL high
sda_oen <= #1 1'b0; // set SDA low
sda_chk <= #1 1'b0; // don't check SDA output
end
start_d:
begin
c_state <= #1 start_e;
scl_oen <= #1 1'b1; // keep SCL high
sda_oen <= #1 1'b0; // keep SDA low
sda_chk <= #1 1'b0; // don't check SDA output
end
start_e:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b0; // set SCL low
sda_oen <= #1 1'b0; // keep SDA low
sda_chk <= #1 1'b0; // don't check SDA output
end
// stop
stop_a:
begin
c_state <= #1 stop_b;
scl_oen <= #1 1'b0; // keep SCL low
sda_oen <= #1 1'b0; // set SDA low
sda_chk <= #1 1'b0; // don't check SDA output
end
stop_b:
begin
c_state <= #1 stop_c;
scl_oen <= #1 1'b1; // set SCL high
sda_oen <= #1 1'b0; // keep SDA low
sda_chk <= #1 1'b0; // don't check SDA output
end
stop_c:
begin
c_state <= #1 stop_d;
scl_oen <= #1 1'b1; // keep SCL high
sda_oen <= #1 1'b0; // keep SDA low
sda_chk <= #1 1'b0; // don't check SDA output
end
stop_d:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b1; // keep SCL high
sda_oen <= #1 1'b1; // set SDA high
sda_chk <= #1 1'b0; // don't check SDA output
end
// read
rd_a:
begin
c_state <= #1 rd_b;
scl_oen <= #1 1'b0; // keep SCL low
sda_oen <= #1 1'b1; // tri-state SDA
sda_chk <= #1 1'b0; // don't check SDA output
end
rd_b:
begin
c_state <= #1 rd_c;
scl_oen <= #1 1'b1; // set SCL high
sda_oen <= #1 1'b1; // keep SDA tri-stated
sda_chk <= #1 1'b0; // don't check SDA output
end
rd_c:
begin
c_state <= #1 rd_d;
scl_oen <= #1 1'b1; // keep SCL high
sda_oen <= #1 1'b1; // keep SDA tri-stated
sda_chk <= #1 1'b0; // don't check SDA output
end
rd_d:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b0; // set SCL low
sda_oen <= #1 1'b1; // keep SDA tri-stated
sda_chk <= #1 1'b0; // don't check SDA output
end
// write
wr_a:
begin
c_state <= #1 wr_b;
scl_oen <= #1 1'b0; // keep SCL low
sda_oen <= #1 din; // set SDA
sda_chk <= #1 1'b0; // don't check SDA output (SCL low)
end
wr_b:
begin
c_state <= #1 wr_c;
scl_oen <= #1 1'b1; // set SCL high
sda_oen <= #1 din; // keep SDA
sda_chk <= #1 1'b1; // check SDA output
end
wr_c:
begin
c_state <= #1 wr_d;
scl_oen <= #1 1'b1; // keep SCL high
sda_oen <= #1 din;
sda_chk <= #1 1'b1; // check SDA output
end
wr_d:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b0; // set SCL low
sda_oen <= #1 din;
sda_chk <= #1 1'b0; // don't check SDA output (SCL low)
end
endcase
end
// assign scl and sda output (always gnd)
assign scl_o = 1'b0;
assign sda_o = 1'b0;
endmodule
i2c_master_byte_ctrl.v
`timescale 1ns / 10ps
`include "i2c_master_defines.vh"
module i2c_master_byte_ctrl (
clk, rst, ena, clk_cnt, start, stop, read, write, ack_in, din,
cmd_ack, ack_out, dout, i2c_busy, i2c_al, scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen );
//
// inputs & outputs
//
input clk; // master clock
input rst; // synchronous active high reset
input ena; // core enable signal
input [15:0] clk_cnt; // 4x SCL
// control inputs
input start;
input stop;
input read;
input write;
input ack_in;
input [7:0] din;
// status outputs
output cmd_ack;
reg cmd_ack;
output ack_out;
reg ack_out;
output i2c_busy;
output i2c_al;
output [7:0] dout;
// I2C signals
input scl_i;
output scl_o;
output scl_oen;
input sda_i;
output sda_o;
output sda_oen;
//
// Variable declarations
//
// statemachine
parameter [5:0] ST_IDLE = 6'b0_00001;
parameter [5:0] ST_START = 6'b0_00010;
parameter [5:0] ST_READ = 6'b0_00100;
parameter [5:0] ST_WRITE = 6'b0_01000;
parameter [5:0] ST_ACK = 6'b0_10000;
parameter [5:0] ST_STOP = 6'b1_00000;
// signals for bit_controller
reg [3:0] core_cmd;
reg core_txd;
wire core_ack, core_rxd;
// signals for shift register
reg [7:0] sr; //8bit shift register
reg shift, ld;
// signals for state machine
wire go;
reg [2:0] dcnt;
wire cnt_done;
//
// Module body
//
// hookup bit_controller
i2c_master_bit_ctrl bit_controller (
.clk ( clk ),
.rst ( rst ),
.ena ( ena ),
.clk_cnt ( clk_cnt ),
.cmd ( core_cmd ),
.cmd_ack ( core_ack ),
.busy ( i2c_busy ),
.al ( i2c_al ),
.din ( core_txd ),
.dout ( core_rxd ),
.scl_i ( scl_i ),
.scl_o ( scl_o ),
.scl_oen ( scl_oen ),
.sda_i ( sda_i ),
.sda_o ( sda_o ),
.sda_oen ( sda_oen )
);
// generate go-signal
assign go = (read | write | stop) & ~cmd_ack;
// assign dout output to shift-register
assign dout = sr;
// generate shift register
always @(posedge clk)
if (rst)
sr <= #1 8'h0;
else if (ld)
sr <= #1 din;
else if (shift)
sr <= #1 {sr[6:0], core_rxd};
// generate counter
always @(posedge clk)
if (rst)
dcnt <= #1 3'h0;
else if (ld)
dcnt <= #1 3'h7;
else if (shift)
dcnt <= #1 dcnt - 3'h1;
assign cnt_done = ~(|dcnt);
//
// state machine
//
reg [5:0] c_state; // synopsis enum_state
always @(posedge clk)
if (rst | i2c_al)
begin
core_cmd <= #1 `I2C_CMD_NOP;
core_txd <= #1 1'b0;
shift <= #1 1'b0;
ld <= #1 1'b0;
cmd_ack <= #1 1'b0;
c_state <= #1 ST_IDLE;
ack_out <= #1 1'b0;
end
else
begin
// initially reset all signals
core_txd <= #1 sr[7];
shift <= #1 1'b0;
ld <= #1 1'b0;
cmd_ack <= #1 1'b0;
case (c_state) // synopsis full_case parallel_case
ST_IDLE:
if (go)
begin
if (start)
begin
c_state <= #1 ST_START;
core_cmd <= #1 `I2C_CMD_START;
end
else if (read)
begin
c_state <= #1 ST_READ;
core_cmd <= #1 `I2C_CMD_READ;
end
else if (write)
begin
c_state <= #1 ST_WRITE;
core_cmd <= #1 `I2C_CMD_WRITE;
end
else // stop
begin
c_state <= #1 ST_STOP;
core_cmd <= #1 `I2C_CMD_STOP;
// generate command acknowledge signal
cmd_ack <= #1 1'b1;
end
ld <= #1 1'b1;
end
ST_START:
if (core_ack)
begin
if (read)
begin
c_state <= #1 ST_READ;
core_cmd <= #1 `I2C_CMD_READ;
end
else
begin
c_state <= #1 ST_WRITE;
core_cmd <= #1 `I2C_CMD_WRITE;
end
ld <= #1 1'b1;
end
ST_WRITE:
if (core_ack)
if (cnt_done)
begin
c_state <= #1 ST_ACK;
core_cmd <= #1 `I2C_CMD_READ;
end
else
begin
c_state <= #1 ST_WRITE; // stay in same state
core_cmd <= #1 `I2C_CMD_WRITE; // write next bit
shift <= #1 1'b1;
end
ST_READ:
if (core_ack)
begin
if (cnt_done)
begin
c_state <= #1 ST_ACK;
core_cmd <= #1 `I2C_CMD_WRITE;
end
else
begin
c_state <= #1 ST_READ; // stay in same state
core_cmd <= #1 `I2C_CMD_READ; // read next bit
end
shift <= #1 1'b1;
core_txd <= #1 ack_in;
end
ST_ACK:
if (core_ack)
begin
if (stop)
begin
c_state <= #1 ST_STOP;
core_cmd <= #1 `I2C_CMD_STOP;
end
else
begin
c_state <= #1 ST_IDLE;
core_cmd <= #1 `I2C_CMD_NOP;
end
// assign ack_out output to bit_controller_rxd (contains last received bit)
ack_out <= #1 core_rxd;
// generate command acknowledge signal
cmd_ack <= #1 1'b1;
core_txd <= #1 1'b1;
end
else
core_txd <= #1 ack_in;
ST_STOP:
if (core_ack)
begin
c_state <= #1 ST_IDLE;
core_cmd <= #1 `I2C_CMD_NOP;
end
endcase
end
endmodule
i2c_master_cmd_ctrl.v
`timescale 1ns / 10ps
module i2c_master_cmd_ctrl(
input clk,
input rst,
input ena,
//commands
output reg start,
output reg stop,
output reg write,
output reg read,
output reg ack_out,
//data
output reg [7:0] txd,
input wire [7:0] rxd,
//status
input wire cmd_ack,
input wire ack_in,
input wire i2c_busy,
input wire i2c_al,
//tx_fifo
output wire rd_en,
input wire empty,
input wire [7:0] dout,
//rx_fifo
output reg wr_en,
input wire full,
output reg [7:0] din,
//
output reg missed_ack
);
//parameter
localparam [10:0] ST_IDLE = 11'b000_0000_0001,
ST_CFG = 11'b000_0000_0010,
ST_TXDEVICE = 11'b000_0000_0100,
ST_TXDEVICEW = 11'b000_0000_1000,
ST_TXADDR = 11'b000_0001_0000,
ST_TXADDRW = 11'b000_0010_0000,
ST_TXDATA = 11'b000_0100_0000,
ST_TXDATAW = 11'b000_1000_0000,
ST_RXDATA = 11'b001_0000_0000,
ST_RXDATAW = 11'b010_0000_0000,
ST_STOP = 11'b100_0000_0000;
//variables
reg [10:0] CS,NS;
reg [1:0] channel;
reg [5:0] length;
reg [7:0] device_addr;
reg device_addr_resend;
//state_machine
//current state
always @(posedge clk) begin
if(rst) CS<= #1 ST_IDLE;
else if(ena) CS<= #1 NS;
end
//next state
always @(*) begin
case(CS)
ST_IDLE: NS<=(empty)? ST_IDLE:ST_CFG;
ST_CFG: NS<=ST_TXDEVICE;
ST_TXDEVICE:NS<=ST_TXDEVICEW;
ST_TXDEVICEW:NS<=(cmd_ack)? ((device_addr_resend)? ST_RXDATA:ST_TXADDR):ST_TXDEVICEW;
ST_TXADDR: NS<=ST_TXADDRW;
ST_TXADDRW: NS<=(cmd_ack)? ((device_addr[0])? ST_TXDEVICE:ST_TXDATA):ST_TXADDRW;
ST_TXDATA: NS<=ST_TXDATAW;
ST_TXDATAW: NS<=(cmd_ack)? ((length==6'd0)? ST_STOP:ST_TXDATA):ST_TXDATAW;
ST_RXDATA: NS<=ST_RXDATAW;
ST_RXDATAW: NS<=(cmd_ack)? ((length==6'd0)? ST_STOP:ST_RXDATA):ST_RXDATAW;
ST_STOP: NS<=ST_IDLE;
default: NS<=ST_IDLE;
endcase
end
//outputs
//start
always @(posedge clk) begin
if(rst) start<= #1 1'b0;
else if(ena) start<= #1 (CS==ST_TXDEVICE)? 1'b1:1'b0;
end
//stop
always @(posedge clk) begin
if(rst) stop<= #1 1'b0;
else if(ena) stop<= #1 (CS==ST_STOP)? 1'b1:1'b0;
end
//write
always @(posedge clk) begin
if(rst) write<= #1 1'b0;
else if(ena) write<= #1 ((CS==ST_TXDEVICE)||(CS==ST_TXADDR)||(CS==ST_TXDATA))? 1'b1:1'b0;
end
//read
always @(posedge clk) begin
if(rst) read<= #1 1'b0;
else if(ena) read<= #1 (CS==ST_RXDATA)? 1'b1:1'b0;
end
//ack_out
always @(posedge clk) begin
if(rst) ack_out<= #1 1'b0;
else if(ena) ack_out<= #1 ((CS==ST_RXDATAW)&&(length!=0))? 1'b0:1'b1;
end
//length
always @(posedge clk) begin
if(rst) length<= #1 6'd0;
else if(ena) begin
if(CS==ST_CFG) length<= #1 dout[7:2];
else if((CS==ST_TXDATA)||(CS==ST_RXDATA)) length<= #1 length-6'd1;
end
end
//channel
always @(posedge clk) begin
if(rst) channel<= #1 2'd0;
else if(ena) begin
if(CS==ST_CFG) channel<= #1 dout[1:0];
end
end
//device_addr
always @(posedge clk) begin
if(rst) device_addr<= #1 8'd0;
else if(ena) begin
if(CS==ST_STOP) device_addr<= #1 8'd0;
else if((CS==ST_TXDEVICE)&&(device_addr_resend==0))
device_addr<= #1 dout;
end
end
//device_addr_resend
always @(posedge clk) begin
if(rst) device_addr_resend<= #1 1'd0;
else if(ena) begin
if(CS==ST_STOP) device_addr_resend<= #1 1'd0;
else if((CS==ST_TXADDR)&&(device_addr[0]==1))
device_addr_resend<= #1 1'd1;
end
end
//missed_ack
always @(posedge clk) begin
if(rst) missed_ack<= #1 1'b0;
else if(ena) begin
if(((CS==ST_TXDEVICEW)||(CS==ST_TXADDRW)||(CS==ST_TXDATAW))&&cmd_ack)
missed_ack<= #1 ack_in;
else missed_ack<= #1 1'b0;
end
end
//fifo_tx
//rd_en
assign rd_en=((NS==ST_CFG)||(NS==ST_TXDEVICE)||(NS==ST_TXADDR)||
(NS==ST_TXDATA))? 1'b1:1'b0;
always @(posedge clk) begin
if(rst) txd<= #1 8'd0;
else if(ena) begin
if(CS==ST_TXDEVICE) txd<= #1 (device_addr_resend)? device_addr:(dout&8'hfe);
else if((CS==ST_TXADDR)||(CS==ST_TXDATA)) txd<= #1 dout;
end//txd<= #1 ((CS==ST_TXDEVICE)||(CS==ST_TXADDR)||(CS==ST_TXDATA))? dout:txd;
end
//fifo_rx
//wr_en
always @(posedge clk) begin
if(rst) wr_en<= #1 1'b0;
else if(ena) wr_en<= #1 ((CS==ST_RXDATAW) && cmd_ack && ~i2c_al)? 1'b1:1'b0;
end
always @(posedge clk) begin
if(rst) din<= #1 8'd0;
else if(ena) din<= #1 rxd;
end
endmodule
i2c_master.v
/*
* An i2c master controller implementation. 7-bit address 8-bit data, r/w.
*
* Copyright (c) 2015 Joel Fernandes @linuxinternals.org>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
`timescale 1ns / 10ps
module i2c_master(
input wire clk,
input wire rst,
input wire ena,
// Xillybus
input wire bus_clk,
input wire quiesce,
// Wires related to /dev/xillybus_read_8
input wire user_r_read_8_rden,
output wire user_r_read_8_empty,
output wire [7:0] user_r_read_8_data,
input wire user_r_read_8_open,
// Wires related to /dev/xillybus_write_8
input wire user_w_write_8_wren,
output wire user_w_write_8_full,
input wire [7:0] user_w_write_8_data,
input wire user_w_write_8_open,
// Wires of i2c
inout wire scl_pin,
inout wire sda_pin,
//output wire scl_oen,
//output wire sda_oen,
//missed_ack
output wire missed_ack
);
/* variables */
wire tx_fifo_rd_en,rx_fifo_wr_en;
wire tx_fifo_empty,rx_fifo_full;
wire [7:0] tx_fifo_dout,rx_fifo_din;
wire start,stop,read,write;
wire ack_out,ack_in;
wire cmd_ack;
wire [7:0] txd,rxd;
wire i2c_busy,i2c_al;
wire scl_o,scl_oen;
wire sda_o,sda_oen;
/* connection */
assign scl_pin = (scl_oen==1)? 1'bz:scl_o;
assign scl_i = scl_pin;
assign sda_pin = (sda_oen==1)? 1'bz:sda_o;
assign sda_i = sda_pin;
/* module initialization */
// hookup the tx_fifo block
i2c_master_fifo FIFO_TX (
.rst ( quiesce&~user_w_write_8_open), // input wire rst
.wr_clk ( bus_clk ), // input wire wr_clk
.rd_clk ( clk ), // input wire rd_clk
.din ( user_w_write_8_data), // input wire [7 : 0] din
.wr_en ( user_w_write_8_wren), // input wire wr_en
.rd_en ( tx_fifo_rd_en ), // input wire rd_en
.dout ( tx_fifo_dout ), // output wire [7 : 0] dout
.full ( user_w_write_8_full), // output wire full
.empty ( tx_fifo_empty ) // output wire empty
);
// hookup the rx_fifo block
i2c_master_fifo FIFO_RX (
.rst ( quiesce&~user_r_read_8_open), // input wire rst
.wr_clk ( clk ), // input wire wr_clk
.rd_clk ( bus_clk ), // input wire rd_clk
.din ( rx_fifo_din ), // input wire [7 : 0] din
.wr_en ( rx_fifo_wr_en ), // input wire wr_en
.rd_en ( user_r_read_8_rden ), // input wire rd_en
.dout ( user_r_read_8_data ), // output wire [7 : 0] dout
.full ( rx_fifo_full ), // output wire full
.empty ( user_r_read_8_empty) // output wire empty
);
// hookup the command controller block
i2c_master_cmd_ctrl DUT_CMD(
.clk ( clk ),
.rst ( rst ),
.ena ( ena ),
.start ( start ),
.stop ( stop ),
.write ( write ),
.read ( read ),
.ack_out ( ack_out ),
.txd ( txd ),
.rxd ( rxd ),
.cmd_ack ( cmd_ack ),
.ack_in ( ack_in ),
.i2c_busy ( i2c_busy ),
.i2c_al ( i2c_al ),
.rd_en ( tx_fifo_rd_en),
.empty ( tx_fifo_empty),
.dout ( tx_fifo_dout ),
.wr_en ( rx_fifo_wr_en),
.full ( rx_fifo_full ),
.din ( rx_fifo_din ),
.missed_ack(missed_ack )
);
// hookup byte controller block
i2c_master_byte_ctrl DUT_BYTE (
.clk ( clk ),
.rst ( rst ),
.ena ( ena ),
.clk_cnt ( 16'h0003 ),
.start ( start ),
.stop ( stop ),
.read ( read ),
.write ( write ),
.ack_in ( ack_out ),
.din ( txd ),
.cmd_ack ( cmd_ack ),
.ack_out ( ack_in ),
.dout ( rxd ),
.i2c_busy ( i2c_busy ),
.i2c_al ( i2c_al ),
.scl_i ( scl_i ),
.scl_o ( scl_o ),
.scl_oen ( scl_oen ),
.sda_i ( sda_i ),
.sda_o ( sda_o ),
.sda_oen ( sda_oen )
);
endmodule
这里用到了一个FIFO IP核,Vivado中调用并配置:
模块输入时钟为8MHz;
写操作方法按照如下顺序依次向FIFO_TX写入字节:
配置字节(1Byte)->从设备地址(1Byte)->寄存器地址(1Byte)->数据(nBytes);
读操作方法按照写操作的顺序写入FIFO_RX,省去最后的数据部分,逻辑完成操作后会把指定字节数据写入FIFO_RX中,用户可以从中读取期望的数据;
需要说明的是,以上配置字节的高6位为本次操作的数据长短,低2位用于切换i2c通道(reserved),从设备地址的第0位指示本次是读还是写操作。
篇幅限制,这里简单给出i2c_master_cmd_ctrl的状态转移图:
这里设计的功能是单主设备的读写操作,注意并不支持多主设备总线操作;验证时,写操作用功能仿真的方法很容易,写操作还要建立i2c_slave模型。为了简化,直接使用Chipscope的逻辑分析仪功能板上调试:
write测试:
read测试:
以上写测试时依次向FIFO_TX写入0x09-0x90-0x01-0x00-0xaa
从设备地址:0x90
存储地址:0x01
写入数据:0x00aa
然后读出向FIFO_RX写入0x09-0x91-0x00
从设备地址:0x90
存储地址:0x00
读出数据:0x1324
这符合MT9V034的默认数值:
我用的测试办法是将逻辑接入xillybus测试的,实际上也可以组织成AXI-stream总线,在SDK中裸机测试。
(1)本文工作的部分代码取自网站电子发烧友,这里将根据需求将原来的wishbone总线去掉并改写成FIFO的接口模式,便于连接其他总线
(2)工程通过了板上测试,具备master端的基本读写能力,操作简单,需要的可以借鉴一下