blob: 35c214657b24784c76bd5324a0d946fa8b423b25 [file] [log] [blame]
//////////////////////////////////////////////////////////////////////////////
// SPDX-FileCopyrightText: 2021 , Dinesh Annayya
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileContributor: Created by Dinesh Annayya <dinesha@opencores.org>
//
//////////////////////////////////////////////////////////////////////
//// ////
//// OMS8051 I2C Master bit-controller Module ////
//// WISHBONE rev.B2 compliant I2C Master bit-controller ////
//// ////
//// This file is part of the OMS 8051 cores project ////
//// http://www.opencores.org/cores/oms8051mini/ ////
//// ////
//// Description ////
//// OMS 8051 definitions. ////
//// ////
//// To Do: ////
//// nothing ////
//// ////
//// Author(s): ////
//// -Richard Herveille , richard@asics.ws, www.asics.ws ////
//// -Dinesh Annayya, dinesha@opencores.org ////
//// ////
//// Revision : Jan 6, 2017 ////
//// ////
//////////////////////////////////////////////////////////////////////
// v0.0 - Dinesh A, 6th Jan 2017
// 1. Initail version picked from
// http://www.opencores.org/projects/i2c/
// v0.1 - Dinesh A, 19th Jan 2017
// 1. Lint warning clean up
//////////////////////////////////////////////////////////////////////
//// ////
//// Copyright (C) 2000 Authors and OPENCORES.ORG ////
//// ////
//// This source file may be used and distributed without ////
//// restriction provided that this copyright statement is not ////
//// removed from the file and that any derivative work contains ////
//// the original copyright notice and the associated disclaimer. ////
//// ////
//// This source file is free software; you can redistribute it ////
//// and/or modify it under the terms of the GNU Lesser General ////
//// Public License as published by the Free Software Foundation; ////
//// either version 2.1 of the License, or (at your option) any ////
//// later version. ////
//// ////
//// This source is distributed in the hope that it will be ////
//// useful, but WITHOUT ANY WARRANTY; without even the implied ////
//// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ////
//// PURPOSE. See the GNU Lesser General Public License for more ////
//// details. ////
//// ////
//// You should have received a copy of the GNU Lesser General ////
//// Public License along with this source; if not, download it ////
//// from http://www.opencores.org/lgpl.shtml ////
//// ////
//////////////////////////////////////////////////////////////////////
/////////////////////////////////////
// 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
//
`include "i2cm_defines.v"
module i2cm_bit_ctrl (
input clk, // system clock
input sresetn, // synchronous active low reset
input aresetn, // asynchronous active low reset
input ena, // core enable signal
input [15:0] clk_cnt, // clock prescale value
input [ 3:0] cmd, // command (from byte controller)
output reg cmd_ack, // command complete acknowledge
output reg busy, // i2c bus busy
output reg al, // i2c bus arbitration lost
input din,
output reg dout,
input scl_i, // i2c clock line input
output scl_o, // i2c clock line output
output reg scl_oen, // i2c clock line output enable (active low)
input sda_i, // i2c data line input
output sda_o, // i2c data line output
output reg sda_oen // i2c data line output enable (active low)
);
//
// variable declarations
//
reg [ 1:0] cSCL, cSDA; // capture SCL and SDA
reg [ 2:0] fSCL, fSDA; // SCL and SDA filter inputs
reg sSCL, sSDA; // filtered and synchronized SCL and SDA inputs
reg dSCL, dSDA; // delayed versions of sSCL and sSDA
reg dscl_oen; // delayed scl_oen
reg sda_chk; // check SDA output (Multi-master arbitration)
reg clk_en; // clock generation signals
reg slave_wait; // slave inserts wait states
reg [15:0] cnt; // clock divider counter (synthesis)
reg [13:0] filter_cnt; // clock divider for filter
// state machine variable
reg [17:0] c_state; // synopsys enum_state
//
// 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 <= scl_oen;
// slave_wait is asserted when master wants to drive SCL high, but the slave pulls it low
// slave_wait remains asserted until the slave releases SCL
always @(posedge clk or negedge aresetn)
if (!aresetn) slave_wait <= 1'b0;
else slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) | (slave_wait & ~sSCL);
// master drives SCL high, but another master pulls it low
// master start counting down its low cycle now (clock synchronization)
wire scl_sync = dSCL & ~sSCL & scl_oen;
// generate clk enable signal
always @(posedge clk or negedge aresetn)
if (~aresetn)
begin
cnt <= 16'h0;
clk_en <= 1'b1;
end
else if (!sresetn || ~|cnt || !ena || scl_sync)
begin
cnt <= clk_cnt;
clk_en <= 1'b1;
end
else if (slave_wait)
begin
cnt <= cnt;
clk_en <= 1'b0;
end
else
begin
cnt <= cnt - 16'h1;
clk_en <= 1'b0;
end
// generate bus status controller
// capture SDA and SCL
// reduce metastability risk
always @(posedge clk or negedge aresetn)
if (!aresetn)
begin
cSCL <= 2'b00;
cSDA <= 2'b00;
end
else if (!sresetn)
begin
cSCL <= 2'b00;
cSDA <= 2'b00;
end
else
begin
cSCL <= {cSCL[0],scl_i};
cSDA <= {cSDA[0],sda_i};
end
// filter SCL and SDA signals; (attempt to) remove glitches
always @(posedge clk or negedge aresetn)
if (!aresetn ) filter_cnt <= 14'h0;
else if (!sresetn || !ena ) filter_cnt <= 14'h0;
else if (~|filter_cnt) filter_cnt <= clk_cnt >> 2; //16x I2C bus frequency
else filter_cnt <= filter_cnt -1;
always @(posedge clk or negedge aresetn)
if (!aresetn)
begin
fSCL <= 3'b111;
fSDA <= 3'b111;
end
else if (!sresetn)
begin
fSCL <= 3'b111;
fSDA <= 3'b111;
end
else if (~|filter_cnt)
begin
fSCL <= {fSCL[1:0],cSCL[1]};
fSDA <= {fSDA[1:0],cSDA[1]};
end
// generate filtered SCL and SDA signals
always @(posedge clk or negedge aresetn)
if (~aresetn)
begin
sSCL <= 1'b1;
sSDA <= 1'b1;
dSCL <= 1'b1;
dSDA <= 1'b1;
end
else if (!sresetn)
begin
sSCL <= 1'b1;
sSDA <= 1'b1;
dSCL <= 1'b1;
dSDA <= 1'b1;
end
else
begin
sSCL <= &fSCL[2:1] | &fSCL[1:0] | (fSCL[2] & fSCL[0]);
sSDA <= &fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0]);
dSCL <= sSCL;
dSDA <= 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
reg sta_condition;
reg sto_condition;
always @(posedge clk or negedge aresetn)
if (~aresetn)
begin
sta_condition <= 1'b0;
sto_condition <= 1'b0;
end
else if (!sresetn)
begin
sta_condition <= 1'b0;
sto_condition <= 1'b0;
end
else
begin
sta_condition <= ~sSDA & dSDA & sSCL;
sto_condition <= sSDA & ~dSDA & sSCL;
end
// generate i2c bus busy signal
always @(posedge clk or negedge aresetn)
if (!aresetn) busy <= 1'b0;
else if (!sresetn ) busy <= 1'b0;
else busy <= (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;
always @(posedge clk or negedge aresetn)
if (~aresetn)
cmd_stop <= 1'b0;
else if (!sresetn)
cmd_stop <= 1'b0;
else if (clk_en)
cmd_stop <= cmd == `I2C_CMD_STOP;
always @(posedge clk or negedge aresetn)
if (~aresetn)
al <= 1'b0;
else if (!sresetn)
al <= 1'b0;
else
al <= (sda_chk & ~sSDA & sda_oen) | (|c_state & sto_condition & ~cmd_stop);
// generate dout signal (store SDA on rising edge of SCL)
always @(posedge clk)
if (sSCL & ~dSCL) dout <= sSDA;
// generate statemachine
// nxt_state decoder
parameter [17:0] idle = 18'b0_0000_0000_0000_0000;
parameter [17:0] start_a = 18'b0_0000_0000_0000_0001;
parameter [17:0] start_b = 18'b0_0000_0000_0000_0010;
parameter [17:0] start_c = 18'b0_0000_0000_0000_0100;
parameter [17:0] start_d = 18'b0_0000_0000_0000_1000;
parameter [17:0] start_e = 18'b0_0000_0000_0001_0000;
parameter [17:0] stop_a = 18'b0_0000_0000_0010_0000;
parameter [17:0] stop_b = 18'b0_0000_0000_0100_0000;
parameter [17:0] stop_c = 18'b0_0000_0000_1000_0000;
parameter [17:0] stop_d = 18'b0_0000_0001_0000_0000;
parameter [17:0] rd_a = 18'b0_0000_0010_0000_0000;
parameter [17:0] rd_b = 18'b0_0000_0100_0000_0000;
parameter [17:0] rd_c = 18'b0_0000_1000_0000_0000;
parameter [17:0] rd_d = 18'b0_0001_0000_0000_0000;
parameter [17:0] wr_a = 18'b0_0010_0000_0000_0000;
parameter [17:0] wr_b = 18'b0_0100_0000_0000_0000;
parameter [17:0] wr_c = 18'b0_1000_0000_0000_0000;
parameter [17:0] wr_d = 18'b1_0000_0000_0000_0000;
always @(posedge clk or negedge aresetn)
if (!aresetn)
begin
c_state <= idle;
cmd_ack <= 1'b0;
scl_oen <= 1'b1;
sda_oen <= 1'b1;
sda_chk <= 1'b0;
end
else if (!sresetn | al)
begin
c_state <= idle;
cmd_ack <= 1'b0;
scl_oen <= 1'b1;
sda_oen <= 1'b1;
sda_chk <= 1'b0;
end
else
begin
cmd_ack <= 1'b0; // default no command acknowledge + assert cmd_ack only 1clk cycle
if (clk_en)
case (c_state) // synopsys full_case parallel_case
// idle state
idle:
begin
case (cmd) // synopsys full_case parallel_case
`I2C_CMD_START: c_state <= start_a;
`I2C_CMD_STOP: c_state <= stop_a;
`I2C_CMD_WRITE: c_state <= wr_a;
`I2C_CMD_READ: c_state <= rd_a;
default: c_state <= idle;
endcase
scl_oen <= scl_oen; // keep SCL in same state
sda_oen <= sda_oen; // keep SDA in same state
sda_chk <= 1'b0; // don't check SDA output
end
// start
start_a:
begin
c_state <= start_b;
scl_oen <= scl_oen; // keep SCL in same state
sda_oen <= 1'b1; // set SDA high
sda_chk <= 1'b0; // don't check SDA output
end
start_b:
begin
c_state <= start_c;
scl_oen <= 1'b1; // set SCL high
sda_oen <= 1'b1; // keep SDA high
sda_chk <= 1'b0; // don't check SDA output
end
start_c:
begin
c_state <= start_d;
scl_oen <= 1'b1; // keep SCL high
sda_oen <= 1'b0; // set SDA low
sda_chk <= 1'b0; // don't check SDA output
end
start_d:
begin
c_state <= start_e;
scl_oen <= 1'b1; // keep SCL high
sda_oen <= 1'b0; // keep SDA low
sda_chk <= 1'b0; // don't check SDA output
end
start_e:
begin
c_state <= idle;
cmd_ack <= 1'b1;
scl_oen <= 1'b0; // set SCL low
sda_oen <= 1'b0; // keep SDA low
sda_chk <= 1'b0; // don't check SDA output
end
// stop
stop_a:
begin
c_state <= stop_b;
scl_oen <= 1'b0; // keep SCL low
sda_oen <= 1'b0; // set SDA low
sda_chk <= 1'b0; // don't check SDA output
end
stop_b:
begin
c_state <= stop_c;
scl_oen <= 1'b1; // set SCL high
sda_oen <= 1'b0; // keep SDA low
sda_chk <= 1'b0; // don't check SDA output
end
stop_c:
begin
c_state <= stop_d;
scl_oen <= 1'b1; // keep SCL high
sda_oen <= 1'b0; // keep SDA low
sda_chk <= 1'b0; // don't check SDA output
end
stop_d:
begin
c_state <= idle;
cmd_ack <= 1'b1;
scl_oen <= 1'b1; // keep SCL high
sda_oen <= 1'b1; // set SDA high
sda_chk <= 1'b0; // don't check SDA output
end
// read
rd_a:
begin
c_state <= rd_b;
scl_oen <= 1'b0; // keep SCL low
sda_oen <= 1'b1; // tri-state SDA
sda_chk <= 1'b0; // don't check SDA output
end
rd_b:
begin
c_state <= rd_c;
scl_oen <= 1'b1; // set SCL high
sda_oen <= 1'b1; // keep SDA tri-stated
sda_chk <= 1'b0; // don't check SDA output
end
rd_c:
begin
c_state <= rd_d;
scl_oen <= 1'b1; // keep SCL high
sda_oen <= 1'b1; // keep SDA tri-stated
sda_chk <= 1'b0; // don't check SDA output
end
rd_d:
begin
c_state <= idle;
cmd_ack <= 1'b1;
scl_oen <= 1'b0; // set SCL low
sda_oen <= 1'b1; // keep SDA tri-stated
sda_chk <= 1'b0; // don't check SDA output
end
// write
wr_a:
begin
c_state <= wr_b;
scl_oen <= 1'b0; // keep SCL low
sda_oen <= din; // set SDA
sda_chk <= 1'b0; // don't check SDA output (SCL low)
end
wr_b:
begin
c_state <= wr_c;
scl_oen <= 1'b1; // set SCL high
sda_oen <= din; // keep SDA
sda_chk <= 1'b0; // don't check SDA output yet
// allow some time for SDA and SCL to settle
end
wr_c:
begin
c_state <= wr_d;
scl_oen <= 1'b1; // keep SCL high
sda_oen <= din;
sda_chk <= 1'b1; // check SDA output
end
wr_d:
begin
c_state <= idle;
cmd_ack <= 1'b1;
scl_oen <= 1'b0; // set SCL low
sda_oen <= din;
sda_chk <= 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