| ////////////////////////////////////////////////////////////////////////////// |
| // SPDX-FileCopyrightText: 2021 , Richard Herveille |
| // |
| // 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> |
| // |
| ///////////////////////////////////////////////////////////////////// |
| //// //// |
| //// WISHBONE rev.B2 compliant synthesizable I2C Slave model //// |
| //// //// |
| //// //// |
| //// Authors: Richard Herveille (richard@asics.ws) www.asics.ws //// |
| //// John Sheahan (jrsheahan@optushome.com.au) //// |
| //// //// |
| //// Downloaded from: http://www.opencores.org/projects/i2c/ //// |
| //// //// |
| ///////////////////////////////////////////////////////////////////// |
| //// //// |
| //// Copyright (C) 2001,2002 Richard Herveille //// |
| //// richard@asics.ws //// |
| //// //// |
| //// 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 SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY //// |
| //// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED //// |
| //// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS //// |
| //// FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR //// |
| //// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, //// |
| //// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES //// |
| //// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE //// |
| //// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR //// |
| //// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF //// |
| //// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT //// |
| //// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT //// |
| //// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE //// |
| //// POSSIBILITY OF SUCH DAMAGE. //// |
| //// //// |
| ///////////////////////////////////////////////////////////////////// |
| |
| // CVS Log |
| // |
| // $Id: i2c_slave_model.v,v 1.7 2006-09-04 09:08:51 rherveille Exp $ |
| // |
| // $Date: 2006-09-04 09:08:51 $ |
| // $Revision: 1.7 $ |
| // $Author: rherveille $ |
| // $Locker: $ |
| // $State: Exp $ |
| // |
| // Change History: |
| // $Log: not supported by cvs2svn $ |
| // Revision 1.6 2005/02/28 11:33:48 rherveille |
| // Fixed Tsu:sta timing check. |
| // Added Thd:sta timing check. |
| // |
| // Revision 1.5 2003/12/05 11:05:19 rherveille |
| // Fixed slave address MSB='1' bug |
| // |
| // Revision 1.4 2003/09/11 08:25:37 rherveille |
| // Fixed a bug in the timing section. Changed 'tst_scl' into 'tst_sto'. |
| // |
| // Revision 1.3 2002/10/30 18:11:06 rherveille |
| // Added timing tests to i2c_model. |
| // Updated testbench. |
| // |
| // Revision 1.2 2002/03/17 10:26:38 rherveille |
| // Fixed some race conditions in the i2c-slave model. |
| // Added debug information. |
| // Added headers. |
| // |
| |
| |
| module i2c_slave_model (scl, sda); |
| |
| // |
| // parameters |
| // |
| parameter I2C_ADR = 7'b001_0000; |
| |
| // |
| // input && outpus |
| // |
| input scl; |
| inout sda; |
| |
| // |
| // Variable declaration |
| // |
| wire debug = 1'b1; |
| |
| reg [7:0] mem [255:0]; // initiate memory |
| reg [7:0] mem_adr; // memory address |
| reg [7:0] mem_do; // memory data output |
| |
| reg sta, d_sta; |
| reg sto, d_sto; |
| |
| reg [7:0] sr; // 8bit shift register |
| reg rw; // read/write direction |
| |
| wire my_adr; // my address called ?? |
| wire i2c_reset; // i2c-statemachine reset |
| reg [2:0] bit_cnt; // 3bit downcounter |
| wire acc_done; // 8bits transfered |
| reg ld; // load downcounter |
| |
| reg sda_o; // sda-drive level |
| wire sda_dly; // delayed version of sda |
| |
| // statemachine declaration |
| parameter idle = 3'b000; |
| parameter slave_ack = 3'b001; |
| parameter get_mem_adr = 3'b010; |
| parameter gma_ack = 3'b011; |
| parameter data = 3'b100; |
| parameter data_ack = 3'b101; |
| |
| reg [2:0] state; // synopsys enum_state |
| |
| // |
| // module body |
| // |
| |
| initial |
| begin |
| sda_o = 1'b1; |
| state = idle; |
| end |
| |
| // generate shift register |
| always @(posedge scl) |
| sr <= #1 {sr[6:0],sda}; |
| |
| //detect my_address |
| assign my_adr = (sr[7:1] == I2C_ADR); |
| // FIXME: This should not be a generic assign, but rather |
| // qualified on address transfer phase and probably reset by stop |
| |
| //generate bit-counter |
| always @(posedge scl) |
| if(ld) |
| bit_cnt <= #1 3'b111; |
| else |
| bit_cnt <= #1 bit_cnt - 3'h1; |
| |
| //generate access done signal |
| assign acc_done = !(|bit_cnt); |
| |
| // generate delayed version of sda |
| // this model assumes a hold time for sda after the falling edge of scl. |
| // According to the Phillips i2c spec, there s/b a 0 ns hold time for sda |
| // with regards to scl. If the data changes coincident with the clock, the |
| // acknowledge is missed |
| // Fix by Michael Sosnoski |
| assign #1 sda_dly = sda; |
| |
| |
| //detect start condition |
| always @(negedge sda) |
| if(scl) |
| begin |
| sta <= #1 1'b1; |
| d_sta <= #1 1'b0; |
| sto <= #1 1'b0; |
| |
| if(debug) |
| $display("DEBUG i2c_slave; start condition detected at %t", $time); |
| end |
| else |
| sta <= #1 1'b0; |
| |
| always @(posedge scl) |
| d_sta <= #1 sta; |
| |
| // detect stop condition |
| always @(posedge sda) |
| if(scl) |
| begin |
| sta <= #1 1'b0; |
| sto <= #1 1'b1; |
| |
| if(debug) |
| $display("DEBUG i2c_slave; stop condition detected at %t", $time); |
| end |
| else |
| sto <= #1 1'b0; |
| |
| //generate i2c_reset signal |
| assign i2c_reset = sta || sto; |
| |
| // generate statemachine |
| always @(negedge scl or posedge sto) |
| if (sto || (sta && !d_sta) ) |
| begin |
| state <= #1 idle; // reset statemachine |
| |
| sda_o <= #1 1'b1; |
| ld <= #1 1'b1; |
| end |
| else |
| begin |
| // initial settings |
| sda_o <= #1 1'b1; |
| ld <= #1 1'b0; |
| |
| case(state) // synopsys full_case parallel_case |
| idle: // idle state |
| if (acc_done && my_adr) |
| begin |
| state <= #1 slave_ack; |
| rw <= #1 sr[0]; |
| sda_o <= #1 1'b0; // generate i2c_ack |
| |
| #2; |
| if(debug && rw) |
| $display("DEBUG i2c_slave; command byte received (read) at %t", $time); |
| if(debug && !rw) |
| $display("DEBUG i2c_slave; command byte received (write) at %t", $time); |
| |
| if(rw) |
| begin |
| mem_do <= #1 mem[mem_adr]; |
| |
| if(debug) |
| begin |
| #2 $display("DEBUG i2c_slave; data block read %x from address %x (1)", mem_do, mem_adr); |
| #2 $display("DEBUG i2c_slave; memcheck [%x]=%x", mem_adr, mem[mem_adr]); |
| end |
| end |
| end |
| |
| slave_ack: |
| begin |
| if(rw) |
| begin |
| state <= #1 data; |
| sda_o <= #1 mem_do[7]; |
| end |
| else |
| state <= #1 get_mem_adr; |
| |
| ld <= #1 1'b1; |
| end |
| |
| get_mem_adr: // wait for memory address |
| if(acc_done) |
| begin |
| state <= #1 gma_ack; |
| mem_adr <= #1 sr; // store memory address |
| sda_o <= #1 !(sr <= 255); // generate i2c_ack, for valid address |
| |
| if(debug) |
| #1 $display("DEBUG i2c_slave; address received. adr=%x, ack=%b", sr, sda_o); |
| end |
| |
| gma_ack: |
| begin |
| state <= #1 data; |
| ld <= #1 1'b1; |
| end |
| |
| data: // receive or drive data |
| begin |
| if(rw) |
| sda_o <= #1 mem_do[7]; |
| |
| if(acc_done) |
| begin |
| state <= #1 data_ack; |
| mem_adr <= #2 mem_adr + 8'h1; |
| sda_o <= #1 (rw && (mem_adr <= 255) ); // send ack on write, receive ack on read |
| |
| if(rw) |
| begin |
| #3 mem_do <= mem[mem_adr]; |
| |
| if(debug) |
| #5 $display("DEBUG i2c_slave; data block read %x from address %x (2)", mem_do, mem_adr); |
| end |
| |
| if(!rw) |
| begin |
| mem[ mem_adr ] <= #1 sr; // store data in memory |
| |
| if(debug) |
| #2 $display("DEBUG i2c_slave; data block write %x to address %x", sr, mem_adr); |
| end |
| end |
| end |
| |
| data_ack: |
| begin |
| ld <= #1 1'b1; |
| |
| if(rw) |
| if(sr[0]) // read operation && master send NACK |
| begin |
| state <= #1 idle; |
| sda_o <= #1 1'b1; |
| end |
| else |
| begin |
| state <= #1 data; |
| sda_o <= #1 mem_do[7]; |
| end |
| else |
| begin |
| state <= #1 data; |
| sda_o <= #1 1'b1; |
| end |
| end |
| |
| endcase |
| end |
| |
| // read data from memory |
| always @(posedge scl) |
| if(!acc_done && rw) |
| mem_do <= #1 {mem_do[6:0], 1'b1}; // insert 1'b1 for host ack generation |
| |
| // generate tri-states |
| assign sda = sda_o ? 1'bz : 1'b0; |
| |
| |
| // |
| // Timing checks |
| // |
| |
| wire tst_sto = sto; |
| wire tst_sta = sta; |
| |
| specify |
| specparam normal_scl_low = 4700, |
| normal_scl_high = 4000, |
| normal_tsu_sta = 4700, |
| normal_thd_sta = 4000, |
| normal_tsu_sto = 4000, |
| normal_tbuf = 4700, |
| |
| fast_scl_low = 1300, |
| fast_scl_high = 600, |
| fast_tsu_sta = 1300, |
| fast_thd_sta = 600, |
| fast_tsu_sto = 600, |
| fast_tbuf = 1300; |
| |
| $width(negedge scl, normal_scl_low); // scl low time |
| $width(posedge scl, normal_scl_high); // scl high time |
| |
| $setup(posedge scl, negedge sda &&& scl, normal_tsu_sta); // setup start |
| $setup(negedge sda &&& scl, negedge scl, normal_thd_sta); // hold start |
| $setup(posedge scl, posedge sda &&& scl, normal_tsu_sto); // setup stop |
| |
| $setup(posedge tst_sta, posedge tst_sto, normal_tbuf); // stop to start time |
| endspecify |
| |
| endmodule |
| |
| |