694 lines
27 KiB
Systemverilog
694 lines
27 KiB
Systemverilog
`timescale 1ns / 1ps
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// Company: www.circuitden.com
|
|
// Engineer: Artin Isagholian
|
|
// artinisagholian@gmail.com
|
|
//
|
|
// Create Date: 01/20/2021 05:47:22 PM
|
|
// Design Name:
|
|
// Module Name: i2c_master
|
|
// Project Name:
|
|
// Target Devices:
|
|
// Tool Versions:
|
|
// Description:
|
|
//
|
|
// Dependencies:
|
|
//
|
|
// Revision:
|
|
// Revision 0.01 - File Created
|
|
// Additional Comments:
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
module i2c_master#(
|
|
parameter DATA_WIDTH = 8,
|
|
parameter REGISTER_WIDTH = 8,
|
|
parameter ADDRESS_WIDTH = 7
|
|
)(
|
|
input wire clock,
|
|
input wire reset_n,
|
|
input wire enable,
|
|
input wire read_write,
|
|
input wire [DATA_WIDTH-1:0] mosi_data,
|
|
input wire [REGISTER_WIDTH-1:0] register_address,
|
|
input wire [ADDRESS_WIDTH-1:0] device_address,
|
|
input wire [15:0] divider,
|
|
|
|
output reg [DATA_WIDTH-1:0] miso_data,
|
|
output reg busy,
|
|
|
|
inout external_serial_data,
|
|
inout external_serial_clock,
|
|
// debug if slave NACKs
|
|
output reg slave_nack // high at same time as busy deasserts if slave nacks at previous transaction. otherwise low
|
|
);
|
|
|
|
|
|
/*INSTANTATION TEMPLATE
|
|
i2c_master #(.DATA_WIDTH(8),.REGISTER_WIDTH(8),.ADDRESS_WIDTH(7))
|
|
i2c_master_inst(
|
|
.clock (),
|
|
.reset_n (),
|
|
.enable (),
|
|
.read_write (),
|
|
.mosi_data (),
|
|
.register_address (),
|
|
.device_address (),
|
|
.divider (),
|
|
|
|
.miso_data (),
|
|
.busy (),
|
|
|
|
.external_serial_data (),
|
|
.external_serial_clock (),
|
|
.slave_nack ()
|
|
);
|
|
*/
|
|
|
|
typedef enum
|
|
{
|
|
S_IDLE = 0,
|
|
S_START = 1,
|
|
S_WRITE_ADDR_W = 2,
|
|
S_CHECK_ACK = 3,
|
|
S_WRITE_REG_ADDR = 4,
|
|
S_RESTART = 5,
|
|
S_WRITE_ADDR_R = 6,
|
|
S_READ_REG = 7,
|
|
S_SEND_NACK = 8,
|
|
S_SEND_STOP = 9,
|
|
S_WRITE_REG_DATA = 10,
|
|
S_WRITE_REG_ADDR_MSB = 11,
|
|
S_WRITE_REG_DATA_MSB = 12,
|
|
S_READ_REG_MSB = 13,
|
|
S_SEND_ACK = 14
|
|
} state_type;
|
|
|
|
state_type state;
|
|
state_type _state;
|
|
state_type post_state;
|
|
state_type _post_state;
|
|
reg serial_clock;
|
|
logic _serial_clock;
|
|
reg [ADDRESS_WIDTH:0] saved_device_address;
|
|
logic [ADDRESS_WIDTH:0] _saved_device_address;
|
|
reg [REGISTER_WIDTH-1:0] saved_register_address;
|
|
logic [REGISTER_WIDTH-1:0] _saved_register_address;
|
|
reg [DATA_WIDTH-1:0] saved_mosi_data;
|
|
logic [DATA_WIDTH-1:0] _saved_mosi_data;
|
|
reg [1:0] process_counter;
|
|
logic [1:0] _process_counter;
|
|
reg [7:0] bit_counter;
|
|
logic [7:0] _bit_counter;
|
|
reg serial_data;
|
|
logic _serial_data;
|
|
reg post_serial_data;
|
|
logic _post_serial_data;
|
|
reg last_acknowledge;
|
|
logic _last_acknowledge;
|
|
logic _saved_read_write;
|
|
reg saved_read_write;
|
|
reg [15:0] divider_counter;
|
|
logic [15:0] _divider_counter;
|
|
reg divider_tick;
|
|
logic [DATA_WIDTH-1:0] _miso_data;
|
|
logic _busy;
|
|
logic serial_data_output_enable;
|
|
logic serial_clock_output_enable;
|
|
|
|
assign external_serial_clock = (serial_clock_output_enable) ? serial_clock : 1'bz;
|
|
assign external_serial_data = (serial_data_output_enable) ? serial_data : 1'bz;
|
|
|
|
// determine if slave NACK
|
|
reg last_acknowledge_q;
|
|
always_ff @(posedge clock) begin
|
|
if (!reset_n) begin
|
|
last_acknowledge_q <= 1'b0;
|
|
slave_nack <= 1'b0;
|
|
end
|
|
else begin
|
|
last_acknowledge_q <= last_acknowledge? 1'b1 : last_acknowledge_q; // if last_ack becomes high, store that high
|
|
if(!_busy && busy) begin // if busy is about to deassert next clk
|
|
last_acknowledge_q <= 1'b0; // reset to zero
|
|
slave_nack <= !last_acknowledge_q;// if last_acknowledge_q never becomes 1 then the slave NACKs
|
|
end
|
|
else if(_busy) begin
|
|
slave_nack <= 1'b0;
|
|
end
|
|
end
|
|
end
|
|
|
|
always_comb begin
|
|
_state = state;
|
|
_post_state = post_state;
|
|
_process_counter = process_counter;
|
|
_bit_counter = bit_counter;
|
|
_last_acknowledge = last_acknowledge;
|
|
_miso_data = miso_data;
|
|
_saved_read_write = saved_read_write;
|
|
_busy = busy;
|
|
_divider_counter = divider_counter;
|
|
_saved_register_address = saved_register_address;
|
|
_saved_device_address = saved_device_address;
|
|
_saved_mosi_data = saved_mosi_data;
|
|
_serial_data = serial_data;
|
|
_serial_clock = serial_clock;
|
|
_post_serial_data = post_serial_data;
|
|
|
|
if (divider_counter == divider) begin
|
|
_divider_counter = 0;
|
|
divider_tick = 1;
|
|
end
|
|
else begin
|
|
_divider_counter = divider_counter + 1;
|
|
divider_tick = 0;
|
|
end
|
|
|
|
if (state!=S_IDLE && state!=S_CHECK_ACK && state!=S_READ_REG && state!=S_READ_REG_MSB) begin
|
|
serial_data_output_enable = 1;
|
|
end
|
|
else begin
|
|
serial_data_output_enable = 0;
|
|
end
|
|
|
|
if (state!=S_IDLE && process_counter!=1 && process_counter!=2) begin
|
|
serial_clock_output_enable = 1;
|
|
end
|
|
else begin
|
|
serial_clock_output_enable = 0;
|
|
end
|
|
|
|
case (state)
|
|
S_IDLE: begin
|
|
_process_counter = 0;
|
|
_bit_counter = 0;
|
|
_last_acknowledge = 0;
|
|
_busy = 0;
|
|
_saved_read_write = read_write;
|
|
_saved_register_address = register_address;
|
|
_saved_device_address = {device_address,1'b0};
|
|
_saved_mosi_data = mosi_data;
|
|
_serial_data = 1;
|
|
_serial_clock = 1;
|
|
|
|
if (enable) begin
|
|
_state = S_START;
|
|
_post_state = S_WRITE_ADDR_W;
|
|
_busy = 1;
|
|
end
|
|
end
|
|
S_START: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
_serial_data = 0;
|
|
_process_counter = 2;
|
|
end
|
|
2: begin
|
|
_bit_counter = 8;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
_serial_clock = 0;
|
|
_process_counter = 0;
|
|
_state = post_state;
|
|
_serial_data = saved_device_address[ADDRESS_WIDTH];
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_WRITE_ADDR_W: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
_post_serial_data = saved_register_address[REGISTER_WIDTH-1];
|
|
|
|
if (REGISTER_WIDTH == 16) begin
|
|
_post_state = S_WRITE_REG_ADDR_MSB;
|
|
end
|
|
else begin
|
|
_post_state = S_WRITE_REG_ADDR;
|
|
end
|
|
|
|
_state = S_CHECK_ACK;
|
|
_bit_counter = 8;
|
|
end
|
|
else begin
|
|
_serial_data = saved_device_address[bit_counter-1];
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_CHECK_ACK: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
|
|
if (external_serial_data == 0) begin
|
|
_last_acknowledge = 1;
|
|
end
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (last_acknowledge == 1) begin
|
|
_last_acknowledge = 0;
|
|
_serial_data = post_serial_data;
|
|
_state = post_state;
|
|
end
|
|
else begin
|
|
_state = S_SEND_STOP;
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_WRITE_REG_ADDR_MSB: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
_post_state = S_WRITE_REG_ADDR;
|
|
_post_serial_data = saved_register_address[7];
|
|
_bit_counter = 8;
|
|
_serial_data = 0;
|
|
_state = S_CHECK_ACK;
|
|
end
|
|
else begin
|
|
_serial_data = saved_register_address[bit_counter+7];
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_WRITE_REG_ADDR: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
if (read_write == 0) begin
|
|
if (DATA_WIDTH == 16) begin
|
|
_post_state = S_WRITE_REG_DATA_MSB;
|
|
_post_serial_data = saved_mosi_data[DATA_WIDTH == 16? 15 : 7];
|
|
end
|
|
else begin
|
|
_post_state = S_WRITE_REG_DATA;
|
|
_post_serial_data = saved_mosi_data[7];
|
|
end
|
|
end
|
|
else begin
|
|
_post_state = S_RESTART;
|
|
_post_serial_data = 1;
|
|
end
|
|
_bit_counter = 8;
|
|
_serial_data = 0;
|
|
_state = S_CHECK_ACK;
|
|
end
|
|
else begin
|
|
_serial_data = saved_register_address[bit_counter-1];
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_WRITE_REG_DATA_MSB: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
_state = S_CHECK_ACK;
|
|
_post_state = S_WRITE_REG_DATA;
|
|
_post_serial_data = saved_mosi_data[7];
|
|
_bit_counter = 8;
|
|
_serial_data = 0;
|
|
end
|
|
else begin
|
|
_serial_data = saved_mosi_data[bit_counter+7];
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_WRITE_REG_DATA: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
_state = S_CHECK_ACK;
|
|
_post_state = S_SEND_STOP;
|
|
_post_serial_data = 0;
|
|
_bit_counter = 8;
|
|
_serial_data = 0;
|
|
end
|
|
else begin
|
|
_serial_data = saved_mosi_data[bit_counter-1];
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_RESTART: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
_process_counter = 2;
|
|
_serial_clock = 1;
|
|
end
|
|
2: begin
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
_state = S_START;
|
|
_post_state = S_WRITE_ADDR_R;
|
|
_saved_device_address[0] = 1;
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_WRITE_ADDR_R: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
if (DATA_WIDTH == 16) begin
|
|
_post_state = S_READ_REG_MSB;
|
|
_post_serial_data = 0;
|
|
end
|
|
else begin
|
|
_post_state = S_READ_REG;
|
|
_post_serial_data = 0;
|
|
end
|
|
_state = S_CHECK_ACK;
|
|
_bit_counter = 8;
|
|
end
|
|
else begin
|
|
_serial_data = saved_device_address[bit_counter-1];
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_READ_REG_MSB: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
//sample data on this rising edge of scl
|
|
_miso_data[bit_counter+7] = external_serial_data;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
_post_state = S_READ_REG;
|
|
_state = S_SEND_ACK;
|
|
_bit_counter = 8;
|
|
_serial_data = 0;
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_READ_REG: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_serial_clock = 0;
|
|
//sample data on this rising edge of scl
|
|
_miso_data[bit_counter-1] = external_serial_data;
|
|
_bit_counter = bit_counter - 1;
|
|
_process_counter = 3;
|
|
end
|
|
3: begin
|
|
if (bit_counter == 0) begin
|
|
_state = S_SEND_NACK;
|
|
_serial_data = 0;
|
|
end
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_SEND_NACK: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_serial_data = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_process_counter = 3;
|
|
_serial_clock = 0;
|
|
end
|
|
3: begin
|
|
_state = S_SEND_STOP;
|
|
_process_counter = 0;
|
|
_serial_data = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_SEND_ACK: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
_serial_data = 0;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_process_counter = 3;
|
|
_serial_clock = 0;
|
|
end
|
|
3: begin
|
|
_state = post_state;
|
|
_process_counter = 0;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
S_SEND_STOP: begin
|
|
if (divider_tick) begin
|
|
case (process_counter)
|
|
0: begin
|
|
_serial_clock = 1;
|
|
_process_counter = 1;
|
|
end
|
|
1: begin
|
|
//check for clock stretching
|
|
if (external_serial_clock == 1) begin
|
|
_last_acknowledge = 0;
|
|
_process_counter = 2;
|
|
end
|
|
end
|
|
2: begin
|
|
_process_counter = 3;
|
|
_serial_data = 1;
|
|
end
|
|
3: begin
|
|
_state = S_IDLE;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
endcase
|
|
end
|
|
|
|
always_ff @(posedge clock) begin
|
|
if (!reset_n) begin
|
|
state <= S_IDLE;
|
|
post_state <= S_IDLE;
|
|
process_counter <= 0;
|
|
bit_counter <= 0;
|
|
last_acknowledge <= 0;
|
|
miso_data <= 0;
|
|
saved_read_write <= 0;
|
|
divider_counter <= 0;
|
|
saved_device_address <= 0;
|
|
saved_register_address <= 0;
|
|
saved_mosi_data <= 0;
|
|
serial_clock <= 0;
|
|
serial_data <= 0;
|
|
saved_mosi_data <= 0;
|
|
post_serial_data <= 0;
|
|
busy <= 0;
|
|
end
|
|
else begin
|
|
state <= _state;
|
|
post_state <= _post_state;
|
|
process_counter <= _process_counter;
|
|
bit_counter <= _bit_counter;
|
|
last_acknowledge <= _last_acknowledge;
|
|
miso_data <= _miso_data;
|
|
saved_read_write <= _saved_read_write;
|
|
divider_counter <= _divider_counter;
|
|
saved_device_address <= _saved_device_address;
|
|
saved_register_address <= _saved_register_address;
|
|
saved_mosi_data <= _saved_mosi_data;
|
|
serial_clock <= _serial_clock;
|
|
serial_data <= _serial_data;
|
|
post_serial_data <= _post_serial_data;
|
|
busy <= _busy;
|
|
end
|
|
end
|
|
|
|
endmodule |