/* * i2c.v * * Copyright (C) 2018, 2019 Mind Chasers Inc. * * 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. * * function: I2C slave controller for communicating with internal controller * */ `timescale 1ns /10ps module i2c( input rstn, input clk, // external I2C I/O input scl_i, // serial clock output scl_o, // serial clock out output scl_oe, // serial clock output enable input sda_i, // serial data in output reg sda_o, // serial data out output reg sda_oe, // serial data output enable // shared memory and controller data output output [7:0] mem_do, // dedicated memory interface output [10:0] mem_ad, output mem_ce, output reg mem_we, input [7:0] mem_di, // dedicated controller write interface output reg cont_we, output cont_done, // Controller->FIFO input interface input tx_fifo_empty, // use for control and pass as msbit of data input [6:0] fifo_di, // data from FIFO to transmit on I2C output reg fifo_re ); localparam CONT_SEL = 7'h10, DPRAM_SEL = 7'h20, SCI_SEL = 7'h30; // slave-related signals reg scl_high, scl_low; reg [4:0] bit_cnt; // cnt the bits received reg start; // falling SDA while SCL is high reg stop; // rising SDA while SCL is high reg run; // assert while running and counting bits reg rwn; // follows address reg [6:0] dev_ad; // 7-bit device address reg [7:0] addr; // address reg [7:0] d; // data received from external i2c controller during an I2C write wire target_sel, dpram_sel, cont_sel; reg scl_i_m1, scl_i_m2, sda_i_m1; // delayed versions of the inputs reg ack; wire [7:0] i_di; // internal data in muxed from external sources (e.g., DPRAM and controller) assign i_di = dpram_sel ? mem_di : { tx_fifo_empty, fifo_di }; // master-related signals //reg halt; // assert if we lose arbitration, detect a zero in place of a one assign scl_oe = 1'b0; // since it's open drain, never drive the outputs high assign scl_o = 1'b0; // slave interface /* debounce and capture the asynch inputs delayed signals, by default, they're 1 since they're open drain */ always @(posedge clk or negedge rstn) begin if (!rstn) begin scl_i_m1 <= 1'b1; scl_i_m2 <= 1'b1; sda_i_m1 <= 1'b1; end else begin scl_i_m1 <= scl_i; scl_i_m2 <= scl_i_m1; sda_i_m1 <= sda_i; end end /* create a one shot when scl is first stable high use this to register inputs */ always@(posedge clk or negedge rstn) begin if (!rstn) scl_high <= 1'b0; else if (scl_i && scl_i_m1 && ~scl_i_m2) scl_high <= 1'b1; else scl_high <= 1'b0; end /* create a one shot when scl is first stable low use this to register outputs */ always@(posedge clk or negedge rstn) begin if (!rstn) scl_low <= 1'b0; else if ( !scl_i && !scl_i_m1 && scl_i_m2) scl_low <= 1'b1; else scl_low <= 1'b0; end /* one shot start/restart bit, sda 1 to 0 anytime while scl is high */ always @(posedge clk or negedge rstn) begin if (!rstn) start <= 1'b0; else if ( scl_i == 1'b1 && scl_i_m1 == 1'b1 && sda_i == 1'b0 && sda_i_m1 == 1'b1 ) start <= 1'b1; else start <= 1'b0; end /* one shot stop bit, sda 0 to 1 anytime while scl is high */ assign cont_done = stop & ~rwn; always @(posedge clk or negedge rstn) begin if (!rstn ) stop <= 1'b0; else if ( scl_i == 1'b1 && scl_i_m1 == 1'b1 && sda_i == 1'b1 && sda_i_m1 == 1'b0 ) stop <= 1'b1; else stop <= 1'b0; end /* This I2C block runs between start and stop while run == 1 */ always @(posedge clk or negedge rstn) begin if (!rstn) run <= 1'b0; else if ( start ) run <= 1'b1; else if ( stop ) run <= 1'b0; end /* bit_cnt indicates the state of the i2c transfer: start/stop act as synchronous resets otherwise, bit_cnt can change when scl is low 31 (1F): reserved for first cycle after start / idle state 0:6: dev_ad 7: rwn, ACK is clocked out 8: 9:16: if rwn == 0 addr, else d, ACK is clocked out on 16 17: ( restart to 9 if read ) 18:25: write data, ack write data is clocked out on 25 26: ( restart to 18 for write data ) */ always @(posedge clk or negedge rstn) begin if ( !rstn ) bit_cnt <= 5'h1f; else if ( start || stop ) bit_cnt <= 5'h1f; else if ( run && scl_low ) begin if ( rwn && bit_cnt == 5'd17 ) bit_cnt <= 5'd9; else if ( bit_cnt == 5'd26 ) bit_cnt <= 5'd18; else bit_cnt <= bit_cnt + 1; end end /* shift device address (dev_ad) into the module from the i2c bus */ always @(posedge clk or negedge rstn) begin if ( !rstn ) dev_ad <= 7'h0; else if ( stop ) dev_ad <= 7'h0; else if ( run && scl_high && bit_cnt <= 5'h6 ) dev_ad <= { dev_ad[5:0], sda_i }; end /* shift I2C memory addr into the module from the i2c bus during first cycle auto increment on subsequent byte reads during same I2C cycle */ always @(posedge clk or negedge rstn) begin if (!rstn) addr <= 8'h00; else if ( run && scl_high) begin if ( !rwn && bit_cnt >= 5'd9 && bit_cnt <= 5'd16 ) addr <= { addr[6:0], sda_i }; else if ( (rwn && bit_cnt == 5'd17) || (!rwn && bit_cnt == 5'd26) ) addr <= addr + 1; end end /* * shift write data (d) into the module from the i2c bus. */ always @(posedge clk or negedge rstn) begin if (!rstn) d <= 8'ha5; else if ( run && scl_high && !rwn && !start && bit_cnt >= 5'd18 && bit_cnt < 5'd26 ) d <= { d[6:0], sda_i }; end assign dpram_sel = ( dev_ad == DPRAM_SEL ) ? 1 : 0; assign cont_sel = ( dev_ad == CONT_SEL ) ? 1 : 0; assign target_sel = cont_sel | dpram_sel; /* register ack during I2C reads when bit_cnt == 17 */ always @(posedge clk or negedge rstn) begin if ( !rstn ) ack <= 1'b0; else if ( run && scl_high && rwn && bit_cnt == 5'd17 ) ack <= ~sda_i; end /* register rwn bit */ always @(posedge clk or negedge rstn) begin if ( !rstn ) rwn <= 1'b1; else if ( stop ) rwn <= 1'b1; else if ( run && scl_high && bit_cnt == 5'd7 ) rwn <= sda_i; end /* sda_oe logic, note that the bit_cnt will be changing simultaneously, so it's one behind */ always @(posedge clk or negedge rstn) begin if ( !rstn ) begin sda_oe <= 1'b0; sda_o <= 1'b1; end else if ( stop ) begin sda_oe <= 1'b0; sda_o <= 1'b1; end else if ( scl_low ) begin // ack the first byte written by master if it's addressed to our I2C slave if ( target_sel && bit_cnt == 5'd7 ) begin sda_oe <= 1'b1; sda_o <= 1'b0; end // ack write address else if ( target_sel && rwn == 1'b0 && bit_cnt == 5'd16 ) begin sda_oe <= 1'b1; sda_o <= 1'b0; end // ack write data else if ( target_sel && rwn == 1'b0 && bit_cnt == 5'd25 ) begin sda_oe <= 1'b1; sda_o <= 1'b0; end // drive read data else if ( target_sel && rwn == 1'b1 && bit_cnt >= 5'd8 && bit_cnt <=5'd15 ) // xmt data for read cycle begin sda_oe <= 1'b1; sda_o <= i_di[7-bit_cnt[2:0]]; end // drive data for first bit of burst read cycle, multi-byte if we get an ack // if no ack, then the burst read or single read is done else if ( target_sel && rwn == 1'b1 && ack && bit_cnt == 5'd17 ) begin sda_oe <= 1'b1; sda_o <= i_di[7]; end else begin sda_oe <= 1'b0; sda_o <= 1'b1; // don't care end end end /* DPRAM Control */ assign mem_ad = {3'b0, addr}; assign mem_ce = 1'b1; /* driver: mem_do shared between both cont and dpram interfaces drive addr for first byte since this is the controller cmd */ assign mem_do = ( bit_cnt <= 5'd17 ) ? addr : d; // mem_we bit always @(posedge clk or negedge rstn) begin if ( !rstn ) mem_we <= 1'b0; else if ( run && scl_high && dpram_sel && !rwn && bit_cnt == 5'd26 ) mem_we <= 1'b1; else mem_we <= 1'b0; end // cont_we bit is asserted at the end of both command and data bytes always @(posedge clk or negedge rstn) begin if ( !rstn ) cont_we <= 1'b0; else if ( run && scl_high && cont_sel && !rwn && (bit_cnt == 5'd26 || bit_cnt == 5'd17) ) cont_we <= 1'b1; else cont_we <= 1'b0; end /* * drive: fifo_re * Stop asserting during a burst if we don't get an ACK (use sda_i for real-time ACK) */ always @(posedge clk or negedge rstn) begin if ( !rstn ) fifo_re <= 1'b0; else if ( run && scl_high && cont_sel && rwn && !tx_fifo_empty && ( bit_cnt == 5'd8 || ( bit_cnt == 5'd17 && !sda_i ) ) ) fifo_re <= 1'b1; else fifo_re <= 1'b0; end endmodule