├── .gitignore ├── .travis.yml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Manifest.py ├── README.md ├── requirements.txt ├── sim ├── clock_tb │ └── Manifest.py ├── fast_core_tb │ └── Manifest.py ├── master_tb │ └── Manifest.py ├── slow_core_tb │ └── Manifest.py └── vsim.do ├── src ├── Manifest.py ├── clock.sv ├── i2c_core.sv └── i2c_master.sv └── test ├── Manifest.py ├── clock_tb.sv ├── core_tb.sv ├── fast_core_tb.sv ├── master_tb.sv └── slow_core_tb.sv /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .venv 3 | *.pin 4 | *.pof 5 | *.sof 6 | *.qpf 7 | *.qsf 8 | *.sid 9 | *.map.* 10 | *.sta.* 11 | *.fit.* 12 | bitstream.tcl 13 | bitstream 14 | incremental_db 15 | *.hdb 16 | *.cdb 17 | *.ddb 18 | *.idb 19 | *.rdb 20 | *.logdb 21 | *.qmsg 22 | *.hsd 23 | *.ammdb 24 | db 25 | *.rpt 26 | *.sld 27 | *.jdi 28 | *.done 29 | *.pow.* 30 | Makefile 31 | project 32 | project.tcl 33 | files.tcl 34 | *.vcd 35 | *.vvp 36 | run.command 37 | modelsim.ini 38 | transcript 39 | work/ 40 | *.wlf 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: python 3 | os: linux 4 | addons: 5 | apt: 6 | update: false 7 | packages: 8 | - lib32z1 9 | - lib32stdc++6 10 | - libexpat1:i386 11 | - libc6:i386 12 | - libsm6:i386 13 | - libncurses5:i386 14 | - libx11-6:i386 15 | - zlib1g:i386 16 | - libxext6:i386 17 | - libxft2:i386 18 | 19 | install: 20 | - pip install -r requirements.txt 21 | - stat /home/travis/intelFPGA/19.1/modelsim_ase || (curl 'http://download.altera.com/akdlm/software/acdsinst/19.1std/670/ib_installers/ModelSimSetup-19.1.0.670-linux.run' -o ModelSimSetup.run && chmod +x ModelSimSetup.run && travis_wait 30 ./ModelSimSetup.run --mode unattended --accept_eula 1 && sed -i 's/linux_rh60/linux/g' /home/travis/intelFPGA/19.1/modelsim_ase/vco ) 22 | script: 23 | - export PATH=$PATH:/home/travis/intelFPGA/19.1/modelsim_ase/bin 24 | - cd ./sim/master_tb/ && hdlmake fetch && hdlmake && make 25 | - cd - 26 | - cd ./sim/slow_core_tb/ && hdlmake fetch && hdlmake && make 27 | - cd - 28 | - cd ./sim/fast_core_tb/ && hdlmake fetch && hdlmake && make 29 | - cd - 30 | - cd ./sim/clock_tb/ && hdlmake fetch && hdlmake && make 31 | 32 | cache: 33 | directories: 34 | - /home/travis/intelFPGA/ 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Sameer Puri 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sameer Puri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Manifest.py: -------------------------------------------------------------------------------- 1 | modules = { 2 | "local": "./src/" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # i2c 2 | 3 | [![Build Status](https://travis-ci.com/hdl-util/i2c.svg?branch=master)](https://travis-ci.com/hdl-util/i2c) 4 | 5 | SystemVerilog code for [I2C](https://en.wikipedia.org/wiki/I%C2%B2C) master/slave on an [FPGA](https://simple.wikipedia.org/wiki/Field-programmable_gate_array). 6 | 7 | ## Usage 8 | 9 | 10 | 1. Take files from `src/` and add them to your own project. If you use [hdlmake](https://hdlmake.readthedocs.io/en/master/), you can add this repository itself as a remote module. 11 | 1. Other helpful modules are also available in this GitHub organization. 12 | 1. Consult the usage example in [i2c-demo](https://github.com/hdl-util/i2c-demo) for code that runs a demo over HDMI. 13 | 1. Read through the parameters in `i2c_master.sv`/`i2c_slave.sv` and tailor any instantiations to your situation. 14 | 1. Please create an issue if you run into a problem or have any questions. 15 | 16 | ### To-do List 17 | 18 | - Master 19 | - [x] SCL 20 | - [x] Clock stretching 21 | - [x] Clock synchronization (multi-master) 22 | - [ ] Handle early counter reset 23 | - [x] Stuck LOW line detection (bus clear via HW reset or Power-On Reset) 24 | - [x] Release line when bus is free / in use by another master 25 | - [x] Conformity to stop/repeated start setup & hold times 26 | - [x] SDA 27 | - [x] Transmit 28 | - [x] Receive 29 | - [x] Arbitration (multi-master) (untested) 30 | - [x] Basic Implementation 31 | - [x] Detect other masters triggering start before this master 32 | - [ ] Hotloading (not from i2c spec) 33 | - [ ] Self 34 | - compensating for jitter of wires connecting/disconnecting... (Schmitt enough?) 35 | - listen for WAIT_TIME_END to see if the clock is driven LOW 36 | - if no: bus is free 37 | - if yes: keep listening until a STOP or START 38 | - [x] Other masters (untested) 39 | - [x] erroneous starts detected w/ start_err 40 | - [x] Port map 41 | - Slave 42 | - [ ] SCL 43 | - [ ] SDA 44 | - Speeds 45 | - [x] Standard-mode 46 | - [x] Fast-mode 47 | - [x] Fast-mode Plus 48 | - [ ] High-speed mode 49 | - [ ] Ultra Fast-mode 50 | - [ ] MIPI I3C 51 | 52 | 53 | ## Reference Documents 54 | 55 | These documents are not hosted here! They are available on Library Genesis and at other locations. 56 | 57 | - [I2C Specification](https://www.nxp.com/docs/en/user-guide/UM10204.pdf) 58 | - [Understanding the I2C Bus](http://www.ti.com/lit/an/slva704/slva704.pdf) 59 | - [MIPI I3C Specification](https://b-ok.cc/book/3710131/fc48ef) 60 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | hdlmake==3.3 2 | six==1.14.0 3 | -------------------------------------------------------------------------------- /sim/clock_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "clock_tb" 4 | 5 | sim_post_cmd = "vsim -novopt -do ../vsim.do -c clock_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/fast_core_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "fast_core_tb" 4 | 5 | sim_post_cmd = "vsim -novopt -do ../vsim.do -c fast_core_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/master_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "master_tb" 4 | 5 | sim_post_cmd = "vsim -novopt -do ../vsim.do -c master_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/slow_core_tb/Manifest.py: -------------------------------------------------------------------------------- 1 | action = "simulation" 2 | sim_tool = "modelsim" 3 | sim_top = "slow_core_tb" 4 | 5 | sim_post_cmd = "vsim -novopt -do ../vsim.do -c slow_core_tb" 6 | 7 | modules = { 8 | "local" : [ "../../test/" ], 9 | } 10 | -------------------------------------------------------------------------------- /sim/vsim.do: -------------------------------------------------------------------------------- 1 | onfinish stop 2 | run -all 3 | if { [runStatus -full] == "break simulation_stop {\$finish}" } { 4 | echo Build succeeded 5 | quit -f -code 0 6 | } else { 7 | echo Build failed with status [runStatus -full] 8 | quit -f -code 1 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "clock.sv", 3 | "i2c_core.sv", 4 | "i2c_master.sv" 5 | ] 6 | -------------------------------------------------------------------------------- /src/clock.sv: -------------------------------------------------------------------------------- 1 | module clock #( 2 | parameter int COUNTER_WIDTH, 3 | parameter bit [COUNTER_WIDTH-1:0] COUNTER_END, 4 | parameter bit [COUNTER_WIDTH-1:0] COUNTER_HIGH, 5 | parameter bit [COUNTER_WIDTH-1:0] COUNTER_RISE, 6 | parameter bit MULTI_MASTER, 7 | parameter bit CLOCK_STRETCHING, 8 | parameter int WAIT_WIDTH, 9 | parameter bit [WAIT_WIDTH-1:0] WAIT_END, 10 | parameter bit PUSH_PULL = 0 11 | )( 12 | inout wire scl, 13 | input logic clk_in, 14 | input logic release_line, 15 | output logic bus_clear, 16 | output logic [COUNTER_WIDTH-1:0] counter = COUNTER_HIGH 17 | ); 18 | 19 | logic scl_internal = 1'b1; 20 | assign scl = scl_internal ? (PUSH_PULL ? 1'b1 : 1'bz) : 1'b0; 21 | 22 | logic last_scl = 1'b1; 23 | always @(posedge clk_in) 24 | `ifdef MODEL_TECH 25 | last_scl <= scl === 1'bz; 26 | `else 27 | last_scl <= scl; 28 | `endif 29 | 30 | logic [WAIT_WIDTH-1:0] wait_counter = WAIT_WIDTH'(0); 31 | 32 | assign bus_clear = wait_counter == WAIT_END; 33 | 34 | always @(posedge clk_in) 35 | begin 36 | if (release_line) 37 | begin 38 | counter <= counter < COUNTER_HIGH ? COUNTER_HIGH : counter; 39 | wait_counter <= WAIT_WIDTH'(0); 40 | scl_internal <= 1'b1; 41 | end 42 | // See Figure 7, counter reset. SCL becomes LOW prematurely. 43 | // Detects a falling edge during a counter-sized interval 44 | else if (last_scl && !scl && (counter == COUNTER_WIDTH'(0) || counter >= COUNTER_HIGH + COUNTER_RISE) && MULTI_MASTER) 45 | begin 46 | counter <= COUNTER_WIDTH'(1); 47 | wait_counter <= WAIT_WIDTH'(0); 48 | scl_internal <= 1'b0; 49 | end 50 | // See Figure 7, wait state. SCL is being held LOW by another device after SCL should have risen. 51 | else if (!scl && (counter == COUNTER_HIGH + COUNTER_RISE) && !PUSH_PULL && (CLOCK_STRETCHING || MULTI_MASTER)) 52 | begin 53 | counter <= COUNTER_HIGH + COUNTER_RISE; 54 | if (wait_counter < WAIT_END) // Saturates to indicate bus clear condition 55 | wait_counter <= wait_counter + 1'd1; 56 | scl_internal <= 1'b1; 57 | end 58 | else if (counter >= COUNTER_HIGH) 59 | begin 60 | // See Figure 7, counting HIGH period 61 | counter <= counter == COUNTER_END ? COUNTER_WIDTH'(0) : counter + 1'd1; 62 | wait_counter <= WAIT_WIDTH'(0); 63 | scl_internal <= 1'b1; 64 | end 65 | else // LOW period counting 66 | begin 67 | counter <= counter + 1'd1; 68 | wait_counter <= WAIT_WIDTH'(0); 69 | scl_internal <= 1'b0; 70 | end 71 | end 72 | 73 | endmodule 74 | -------------------------------------------------------------------------------- /src/i2c_core.sv: -------------------------------------------------------------------------------- 1 | module i2c_core #( 2 | // 50 MHz is commonly available in many FPGAs. Must be at least 4 times the target scl rate. 3 | parameter int INPUT_CLK_RATE, 4 | // Targeted i2c bus frequency. Actual frequency depends on the slowest device. 5 | parameter int TARGET_SCL_RATE, 6 | 7 | // Is a slave on the bus capable of clock stretching? 8 | // If unsure, it's safer to assume yes. 9 | parameter bit CLOCK_STRETCHING, 10 | 11 | // Are there multiple masters? 12 | // If unsure, it's safer to assume yes, but more efficient to assume no. 13 | parameter bit MULTI_MASTER, 14 | 15 | // Detecting a stuck state depends on knowing how slow the slowest device is. 16 | parameter int SLOWEST_DEVICE_RATE, 17 | 18 | // "For a single master application, the master’s SCL output can be a push-pull driver design if there are no devices on the bus which would stretch the clock." 19 | // When using a push-pull driver, driving SCL HIGH while another device is driving it LOW will create a short circuit, damaging your FPGA. 20 | // If you enable this, you must be certain that it will not happen. 21 | // By doing so, you acknowledge and accept the risks involved. 22 | parameter bit FORCE_PUSH_PULL 23 | ) ( 24 | inout wire scl, 25 | input logic clk_in, // an arbitrary clock, used to derive the scl clock 26 | output logic bus_clear, 27 | 28 | inout wire sda, 29 | 30 | // When starting a single-byte transfer, only transfer_start should be true 31 | // When starting a multi-byte transfer, both transfer_start and transfer_continues should be true 32 | // When doing a repeated start after this upcoming transaction, only transfer_start should be true 33 | input logic transfer_start, // whether to begin a new transfer asap (repeated START, START) 34 | input logic transfer_continues, // whether the transfer contains another transaction AFTER this transaction. 35 | input logic mode, // 0 = transmit, 1 = receive 36 | input logic [7:0] data_tx, 37 | 38 | output logic transfer_ready, // ready for a new transfer (bus is free) 39 | output logic interrupt = 1'b0, // A transaction has completed or an error occurred. 40 | output logic transaction_complete, // ready for a new transaction 41 | output logic nack, // When a transaction is complete, whether ACK/NACK was received/sent for the last transaction (0 = ACK, 1 = NACK). 42 | output logic [7:0] data_rx, 43 | 44 | // The below errors matter ONLY IF there are multiple masters on the bus 45 | output logic start_err = 1'd0, // Another master illegally issued a START condition while the bus was busy 46 | output logic arbitration_err = 1'b0 // Another master won the transaction due to arbitration, (or issued a START condition, when the user of this master wanted to) 47 | ); 48 | 49 | // Derives the desired i2c mode from target rate. 50 | localparam int MODE = $unsigned(TARGET_SCL_RATE) <= 100000 ? 0 : $unsigned(TARGET_SCL_RATE) <= 400000 ? 1 : $unsigned(TARGET_SCL_RATE) <= 1000000 ? 2 : -1; 51 | 52 | localparam int COUNTER_WIDTH = $clog2(($unsigned(INPUT_CLK_RATE) - 1) / $unsigned(TARGET_SCL_RATE)); 53 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_END = COUNTER_WIDTH'(($unsigned(INPUT_CLK_RATE) - 1) / $unsigned(TARGET_SCL_RATE)); 54 | // Conforms to Table 10 tLOW, tHIGH for SCL clock. 55 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_HIGH = COUNTER_WIDTH'(MODE == 0 ? ( (COUNTER_WIDTH + 1)'(COUNTER_END) + 1) / 2 : (( (COUNTER_WIDTH + 2)'(COUNTER_END) + 1) * 2) / 3); 56 | // Conforms to Table 10 tr (rise time) for SCL clock. 57 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_RISE = COUNTER_WIDTH'(($unsigned(INPUT_CLK_RATE) - 1) / 1.0E9 * $unsigned(MODE == 0 ? 1000 : MODE == 1 ? 300 : MODE == 2 ? 120 : 0) + 1); 58 | 59 | // Bus clear event counter 60 | localparam int WAIT_WIDTH = $clog2(($unsigned(INPUT_CLK_RATE) - 1) / $unsigned(SLOWEST_DEVICE_RATE)); 61 | localparam bit [WAIT_WIDTH-1:0] WAIT_END = WAIT_WIDTH'(($unsigned(INPUT_CLK_RATE) - 1) / $unsigned(SLOWEST_DEVICE_RATE)); 62 | 63 | logic [COUNTER_WIDTH-1:0] counter; 64 | // stick counter used to meet timing requirements 65 | logic [COUNTER_WIDTH-1:0] countdown = COUNTER_WIDTH'(0); 66 | 67 | logic [3:0] transaction_progress = 4'd0; 68 | 69 | logic release_line; 70 | assign release_line = (transaction_progress == 4'd0 && counter == COUNTER_HIGH) || countdown > 0; 71 | 72 | clock #( 73 | .COUNTER_WIDTH(COUNTER_WIDTH), 74 | .COUNTER_END(COUNTER_END), 75 | .COUNTER_HIGH(COUNTER_HIGH), 76 | .COUNTER_RISE(COUNTER_RISE), 77 | .MULTI_MASTER(MULTI_MASTER), 78 | .CLOCK_STRETCHING(CLOCK_STRETCHING), 79 | .WAIT_WIDTH(WAIT_WIDTH), 80 | .WAIT_END(WAIT_END), 81 | .PUSH_PULL(!CLOCK_STRETCHING && !MULTI_MASTER && FORCE_PUSH_PULL) 82 | ) clock (.scl(scl), .clk_in(clk_in), .release_line(release_line), .bus_clear(bus_clear), .counter(counter)); 83 | 84 | logic sda_internal = 1'b1; 85 | assign sda = sda_internal ? 1'bz : 1'b0; 86 | 87 | // Conforms to Table 10 minimum setup/hold/bus free times. 88 | localparam real TLOW_MIN = MODE == 0 ? 4.7 : MODE == 1 ? 1.3 : MODE == 2 ? 0.5 : 0; // in microseconds 89 | localparam real THIGH_MIN = MODE == 0 ? 4.0 : MODE == 1 ? 0.6 : MODE == 2 ? 0.26 : 0; // in microseconds 90 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_SETUP_REPEATED_START = COUNTER_WIDTH'($unsigned(INPUT_CLK_RATE) / 1.0E6 * TLOW_MIN); 91 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_BUS_FREE = COUNTER_SETUP_REPEATED_START; 92 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_HOLD_REPEATED_START = COUNTER_WIDTH'($unsigned(INPUT_CLK_RATE) / 1.0E6 * THIGH_MIN); 93 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_SETUP_STOP = COUNTER_HOLD_REPEATED_START; 94 | 95 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_TRANSMIT = COUNTER_WIDTH'(COUNTER_HIGH / 2); 96 | localparam bit [COUNTER_WIDTH-1:0] COUNTER_RECEIVE = COUNTER_WIDTH'(COUNTER_HIGH + COUNTER_RISE); 97 | 98 | logic latched_mode; 99 | logic [7:0] latched_data; 100 | logic latched_transfer_continues; 101 | 102 | assign data_rx = latched_data; 103 | 104 | // assume bus is free 105 | logic busy = 1'b0; 106 | assign transfer_ready = counter == COUNTER_HIGH && !busy && countdown == 0; 107 | 108 | // See Section 3.1.4: START and STOP conditions 109 | logic last_sda = 1'b1; 110 | always_ff @(posedge clk_in) 111 | `ifdef MODEL_TECH 112 | last_sda <= sda === 1'bz; 113 | `else 114 | last_sda <= sda; 115 | `endif 116 | 117 | logic start_by_a_master; 118 | logic stop_by_a_master; 119 | `ifdef MODEL_TECH 120 | assign start_by_a_master = last_sda === 1'bz && sda === 1'b0 && scl === 1'bz; 121 | assign stop_by_a_master = last_sda === 1'b0 && sda === 1'bz && scl === 1'bz; 122 | `else 123 | assign start_by_a_master = last_sda && !sda && scl; 124 | assign stop_by_a_master = !last_sda && sda && scl; 125 | `endif 126 | 127 | // transmitter notes whether ACK/NACK was received 128 | // receiver notes whether ACK/NACK was sent 129 | // treats a start by another master as as an ACK 130 | `ifdef MODEL_TECH 131 | assign nack = sda === 1'bz; 132 | `else 133 | assign nack = sda; 134 | `endif 135 | 136 | always_ff @(posedge clk_in) 137 | begin 138 | start_err = MULTI_MASTER && start_by_a_master && !(transaction_progress == 4'd0 || (transaction_progress == 4'd11 && transfer_start && counter == COUNTER_RECEIVE)); 139 | 140 | // transmitter listens for loss of arbitration 141 | arbitration_err = MULTI_MASTER && (counter == COUNTER_RECEIVE && transaction_progress >= 4'd2 && transaction_progress < 4'd10 && !latched_mode && sda != latched_data[4'd9 - transaction_progress] && !start_err); 142 | 143 | transaction_complete = counter == COUNTER_RECEIVE - 2 && transaction_progress == (COUNTER_RECEIVE - 2 == COUNTER_TRANSMIT ? 4'd9 : 4'd10) && !start_err && !arbitration_err; 144 | 145 | interrupt = start_err || arbitration_err || transaction_complete; 146 | 147 | // See Note 4 in Section 3.1.10 148 | if (start_err || arbitration_err) 149 | begin 150 | sda_internal <= 1'b1; // release line 151 | transaction_progress <= 4'd0; 152 | countdown <= COUNTER_WIDTH'(0); 153 | busy <= 1'b1; 154 | end 155 | // Keep current state to meet setup/hold constraints in Table 10. 156 | else if (countdown != COUNTER_WIDTH'(0)) 157 | begin 158 | countdown <= countdown - 1'b1; 159 | end 160 | else if (transaction_progress == 4'd0 && !(transfer_start && counter == COUNTER_HIGH) && MULTI_MASTER) 161 | begin 162 | busy <= busy ? !stop_by_a_master : start_by_a_master; 163 | end 164 | else if (counter == COUNTER_HIGH) 165 | begin 166 | if ((transaction_progress == 4'd0 || transaction_progress == 4'd11) && transfer_start) 167 | begin 168 | if (transaction_progress == 4'd0) 169 | transaction_progress <= 4'd1; 170 | 171 | latched_mode <= mode; 172 | // if (!mode) // Mode doesn't matter, save some logic cells 173 | latched_data <= data_tx; 174 | latched_transfer_continues <= transfer_continues; 175 | end 176 | if (transaction_progress == 4'd11) // Setup time padding for repeated start, stop 177 | begin 178 | if (transfer_start && COUNTER_SETUP_REPEATED_START > COUNTER_RECEIVE - COUNTER_HIGH) 179 | countdown <= COUNTER_SETUP_REPEATED_START - (COUNTER_RECEIVE - COUNTER_HIGH); 180 | else if (COUNTER_SETUP_STOP > COUNTER_RECEIVE - COUNTER_HIGH) 181 | countdown <= COUNTER_SETUP_STOP - (COUNTER_RECEIVE - COUNTER_HIGH); 182 | end 183 | end 184 | // "The data on the SDA line must be stable during the HIGH period of the clock." 185 | else if (counter == COUNTER_RECEIVE) 186 | begin 187 | if (transaction_progress == 4'd0) 188 | sda_internal <= 1'b1; 189 | // START or repeated START condition 190 | else if ((transaction_progress == 4'd1 || transaction_progress == 4'd11) && transfer_start) 191 | begin 192 | transaction_progress <= 4'd1; 193 | sda_internal <= 1'b0; 194 | if (transaction_progress == 4'd11 && COUNTER_HOLD_REPEATED_START > (COUNTER_END - COUNTER_RECEIVE)) // Hold time padding 195 | countdown <= COUNTER_HOLD_REPEATED_START - (COUNTER_END - COUNTER_RECEIVE); 196 | busy <= 1'b1; 197 | end 198 | // See Section 3.1.5. Shift in data. 199 | else if (transaction_progress >= 4'd2 && transaction_progress < 4'd10 && latched_mode) 200 | begin 201 | `ifdef MODEL_TECH 202 | latched_data[4'd9 - transaction_progress] <= sda === 1'bz; 203 | `else 204 | latched_data[4'd9 - transaction_progress] <= sda; 205 | `endif 206 | sda_internal <= 1'b1; // Should help reduce slave rise time 207 | end 208 | // See Section 3.1.6. Transmitter got an acknowledge bit or receiver sent it. 209 | // transfer continues immediately in the next LOW, latch now 210 | // refuses to continue the transfer if transmitter got a NACK (TODO: there should be no transfer_start here, it could be set but it doesn't make sense to set it) 211 | else if (transaction_progress == 4'd10 && latched_transfer_continues && (mode || !nack)) 212 | begin 213 | transaction_progress <= 4'd1; 214 | latched_mode <= mode; 215 | // if (!mode) // Mode doesn't matter, save some logic cells 216 | latched_data <= data_tx; 217 | latched_transfer_continues <= transfer_continues; 218 | end 219 | // STOP condition 220 | else if (transaction_progress == 4'd11 && !transfer_start) 221 | begin 222 | sda_internal <= 1'b1; 223 | transaction_progress <= 4'd0; 224 | if (COUNTER_BUS_FREE > COUNTER_END - COUNTER_RECEIVE) 225 | countdown <= COUNTER_BUS_FREE - (COUNTER_END - COUNTER_RECEIVE); 226 | busy <= 1'b0; 227 | end 228 | end 229 | // "The HIGH or LOW state of the data line can only change when the clock signal on the SCL line is LOW" 230 | else if (counter == COUNTER_TRANSMIT && transaction_progress != 4'd0) 231 | begin 232 | transaction_progress <= transaction_progress + 4'd1; 233 | // See Section 3.1.5. Shift out data. 234 | if (transaction_progress < 4'd9) 235 | begin 236 | if (!latched_mode) 237 | sda_internal <= latched_data[4'd8 - transaction_progress]; 238 | else 239 | sda_internal <= 1'b1; // release line for RX 240 | end 241 | // See Section 3.1.6. Expecting an acknowledge bit transfer in the next HIGH. 242 | else if (transaction_progress == 4'd9) 243 | sda_internal <= latched_mode ? !transfer_continues : 1'b1; // receiver sends ACK / NACK, transmitter releases line 244 | // See Section 3.1.4 245 | else if (transaction_progress == 4'd10) 246 | sda_internal <= transfer_start; // prepare for repeated START condition or STOP condition 247 | end 248 | end 249 | 250 | endmodule 251 | -------------------------------------------------------------------------------- /src/i2c_master.sv: -------------------------------------------------------------------------------- 1 | module i2c_master #( 2 | // 50 MHz is commonly available in many FPGAs. Must be at least 4 times the target scl rate. 3 | parameter int INPUT_CLK_RATE = 50000000, 4 | // Targeted i2c bus frequency. Actual frequency depends on the slowest device. 5 | parameter int TARGET_SCL_RATE = 100000, 6 | 7 | // Is a slave on the bus capable of clock stretching? 8 | // If unsure, it's safer to assume yes. 9 | parameter bit CLOCK_STRETCHING = 1, 10 | 11 | // Are there multiple masters? 12 | // If unsure, it's safer to assume yes, but more efficient to assume no. 13 | parameter bit MULTI_MASTER = 0, 14 | 15 | // Detecting a stuck state depends on knowing how slow the slowest device is. 16 | parameter int SLOWEST_DEVICE_RATE = 100, 17 | 18 | // "For a single master application, the master’s SCL output can be a push-pull driver design if there are no devices on the bus which would stretch the clock." 19 | // When using a push-pull driver, driving SCL HIGH while another device is driving it LOW will create a short circuit, damaging your FPGA. 20 | // If you enable this, you must be certain that it will not happen. 21 | // By doing so, you acknowledge and accept the risks involved. 22 | parameter bit FORCE_PUSH_PULL = 0 23 | ) ( 24 | inout wire scl, 25 | input logic clk_in, // an arbitrary clock, used to derive the scl clock 26 | output logic bus_clear, 27 | 28 | inout wire sda, 29 | 30 | // 7-bit device address followed by 1 bit mode (1 = read, 0 = write) 31 | input logic [7:0] address, 32 | 33 | // When starting a single-byte transfer, only transfer_start should be true 34 | // When starting a multi-byte transfer, both transfer_start and transfer_continues should be true 35 | // When doing a repeated start after this upcoming transaction, only transfer_start should be true 36 | input logic transfer_start, // whether to begin a new transfer asap (repeated START, START) 37 | input logic transfer_continues, // whether the transfer contains another transaction AFTER this transaction. 38 | input logic [7:0] data_tx, 39 | 40 | output logic transfer_ready, // ready for a new transfer (bus is free) 41 | 42 | output logic interrupt, // A transaction has completed or an error occurred. 43 | output logic transaction_complete, // ready for a new transaction 44 | output logic nack, // When a transaction is complete, whether ACK/NACK was received/sent for the last transaction (0 = ACK, 1 = NACK). 45 | output logic [7:0] data_rx, 46 | 47 | output logic address_err, // Address was not acknowledged by a slave 48 | 49 | // The below errors matter ONLY IF there are multiple masters on the bus 50 | output logic start_err, // Another master illegally issued a START condition while the bus was busy 51 | output logic arbitration_err // Another master won the transaction due to arbitration, (or issued a START condition, when the user of this master wanted to) 52 | ); 53 | 54 | logic internal_interrupt; 55 | logic internal_transaction_complete; 56 | 57 | // 0 = address transfer 58 | // 1 = regular transfer 59 | logic state = 1'b0; 60 | 61 | logic just_interrupted = 1'd0; 62 | always @(posedge clk_in) just_interrupted <= interrupt; 63 | 64 | // Transition to 1 after address sent, transition back to 0 after transfer completes 65 | logic instantaneous_state; 66 | assign instantaneous_state = state ? !(transfer_ready || (transfer_start && just_interrupted)) : (internal_interrupt && internal_transaction_complete); 67 | 68 | // Transfer must continue after sending I2C address 69 | logic internal_transfer_continues; 70 | assign internal_transfer_continues = instantaneous_state ? transfer_continues : 1'b1; 71 | 72 | 73 | assign interrupt = internal_interrupt && (state == 1'd1 || address_err); 74 | 75 | // Don't send a complete until after the I2C address is sent 76 | assign transaction_complete = state ? internal_transaction_complete : 1'b0; 77 | 78 | // TX address, either TX/RX the rest by user command 79 | logic mode; 80 | assign mode = instantaneous_state ? address[0] : 1'b0; 81 | 82 | // Transmit address, then user data 83 | logic [7:0] internal_data_tx; 84 | assign internal_data_tx = instantaneous_state ? data_tx : address; 85 | 86 | // First transmit transaction completes but got a NACK 87 | assign address_err = state ? 1'b0 : internal_transaction_complete && nack; 88 | 89 | always_ff @(posedge clk_in) state <= instantaneous_state; 90 | 91 | i2c_core #( 92 | .INPUT_CLK_RATE(INPUT_CLK_RATE), 93 | .TARGET_SCL_RATE(TARGET_SCL_RATE), 94 | .CLOCK_STRETCHING(CLOCK_STRETCHING), 95 | .MULTI_MASTER(MULTI_MASTER), 96 | .SLOWEST_DEVICE_RATE(SLOWEST_DEVICE_RATE), 97 | .FORCE_PUSH_PULL(FORCE_PUSH_PULL) 98 | ) core ( 99 | .scl(scl), 100 | .clk_in(clk_in), 101 | .bus_clear(bus_clear), 102 | .sda(sda), 103 | .transfer_start(transfer_start), 104 | .transfer_continues(internal_transfer_continues), 105 | .mode(mode), 106 | .data_tx(internal_data_tx), 107 | .transfer_ready(transfer_ready), 108 | .interrupt(internal_interrupt), 109 | .transaction_complete(internal_transaction_complete), 110 | .nack(nack), 111 | .data_rx(data_rx), 112 | .start_err(start_err), 113 | .arbitration_err(arbitration_err) 114 | ); 115 | 116 | endmodule 117 | -------------------------------------------------------------------------------- /test/Manifest.py: -------------------------------------------------------------------------------- 1 | files = [ 2 | "clock_tb.sv", 3 | "core_tb.sv", 4 | "fast_core_tb.sv", 5 | "slow_core_tb.sv", 6 | "master_tb.sv" 7 | ] 8 | 9 | modules = { 10 | "local" : [ "../src/" ], 11 | } 12 | -------------------------------------------------------------------------------- /test/clock_tb.sv: -------------------------------------------------------------------------------- 1 | module clock_tb(); 2 | 3 | 4 | 5 | localparam INPUT_CLK_RATE = $unsigned(400000); 6 | localparam TARGET_SCL_RATE = $unsigned(100000); 7 | localparam SLOWEST_DEVICE_RATE = $unsigned(10000); 8 | 9 | logic scl_in = 1'bz; // Initially, no other master present 10 | 11 | logic inoutmode = 1'b0; 12 | wire scl; 13 | assign scl = inoutmode ? scl_in : 1'bz; 14 | 15 | logic clk_in = 1'b0; 16 | always #2 clk_in = ~clk_in; 17 | logic bus_clear; 18 | 19 | localparam MODE = $unsigned(TARGET_SCL_RATE) <= 100000 ? 0 : $unsigned(TARGET_SCL_RATE) <= 400000 ? 1 : $unsigned(TARGET_SCL_RATE) <= 1000000 ? 2 : -1; 20 | localparam COUNTER_WIDTH = $clog2($unsigned(INPUT_CLK_RATE) / $unsigned(TARGET_SCL_RATE)); 21 | localparam COUNTER_END = COUNTER_WIDTH'($unsigned(INPUT_CLK_RATE) / $unsigned(TARGET_SCL_RATE) - 1); 22 | // Conforms to Table 10 tLOW, tHIGH for SCL clock. 23 | localparam COUNTER_HIGH = COUNTER_WIDTH'(MODE == 0 ? ( (COUNTER_WIDTH + 1)'(COUNTER_END) + 1) / 2 : (( (COUNTER_WIDTH + 2)'(COUNTER_END) + 1) * 2) / 3); 24 | // Conforms to Table 10 tr (rise time) for SCL clock. 25 | localparam COUNTER_RISE = COUNTER_WIDTH'($ceil($unsigned(INPUT_CLK_RATE) / 1.0E9 * $unsigned(MODE == 0 ? 1000 : MODE == 1 ? 300 : MODE == 2 ? 120 : 0))); 26 | 27 | // Bus clear event counter 28 | localparam WAIT_WIDTH = $clog2(2 * $unsigned(INPUT_CLK_RATE) / $unsigned(SLOWEST_DEVICE_RATE)); 29 | localparam WAIT_END = WAIT_WIDTH'(2 * $unsigned(INPUT_CLK_RATE) / $unsigned(SLOWEST_DEVICE_RATE) - 1); 30 | logic [COUNTER_WIDTH-1:0] counter; 31 | clock #(.COUNTER_WIDTH(COUNTER_WIDTH), .COUNTER_END(COUNTER_END), .COUNTER_HIGH(COUNTER_HIGH), .COUNTER_RISE(1), .MULTI_MASTER(1), .CLOCK_STRETCHING(1), .WAIT_WIDTH(WAIT_WIDTH), .WAIT_END(WAIT_END)) clock(.scl(scl), .clk_in(clk_in), .release_line(1'b0), .bus_clear(bus_clear), .counter(counter)); 32 | 33 | logic [COUNTER_WIDTH-1:0] last_counter = COUNTER_HIGH; 34 | always @(posedge clk_in) 35 | begin 36 | last_counter <= counter; 37 | if (last_counter < COUNTER_HIGH) 38 | assert (scl === 1'b0) else $fatal(1, "High when counter hasn't risen: %b", scl); 39 | else if (!inoutmode) 40 | begin 41 | assert (scl === 1'bz) else $fatal(1, "Low when counter has risen: %b", scl); 42 | end 43 | end 44 | 45 | initial 46 | begin 47 | assert(COUNTER_WIDTH == 2) else $fatal(1, "Counter width should be 3 but was %d", COUNTER_WIDTH); 48 | assert(COUNTER_END == 3) else $fatal(1, "Counter end should be 4 but was %d", COUNTER_END); 49 | #100ns; 50 | $display("Testing bus clear"); 51 | wait (!scl && !clk_in && counter == COUNTER_HIGH); 52 | scl_in <= 1'b0; 53 | inoutmode <= 1'b1; 54 | #310ps; 55 | assert (!clock.bus_clear) else $fatal(1, "Bus clear asserted early"); 56 | #50ps; 57 | assert (clock.bus_clear) else $fatal(1, "Bus clear not asserted when SCL line stuck"); 58 | scl_in <= 1'bz; 59 | #6ps; 60 | assert (!clock.bus_clear) else $fatal(1, "Bus clear asserted after SCL line released"); 61 | inoutmode <= 1'b0; 62 | 63 | #1ns; 64 | $display("Testing reset"); 65 | wait (scl === 1'bz && !clk_in && counter == 0); 66 | scl_in <= 1'b0; 67 | inoutmode <= 1'b1; 68 | wait (clk_in); 69 | wait (!clk_in); 70 | assert (counter == 1) else $fatal(1, "Counter did not reset after early drive to low"); 71 | scl_in <= 1'bz; 72 | inoutmode <= 1'b0; 73 | 74 | $finish; 75 | end 76 | 77 | endmodule 78 | -------------------------------------------------------------------------------- /test/core_tb.sv: -------------------------------------------------------------------------------- 1 | module core_tb #( 2 | parameter INPUT_CLK_RATE, 3 | parameter TARGET_SCL_RATE = 100000, 4 | parameter SLOWEST_DEVICE_RATE = 10000 5 | ) (); 6 | logic sda_in = 1'bz; 7 | logic inoutmode = 1'b0; 8 | wire scl; 9 | wire sda; 10 | assign scl = 1'bz; 11 | assign sda = inoutmode ? sda_in : 1'bz; 12 | 13 | 14 | logic clk_in = 1'b0; 15 | always #2 clk_in = ~clk_in; 16 | logic bus_clear; 17 | logic mode = 1'b0; 18 | logic transfer_start = 1'b0; 19 | logic transfer_continues = 1'b0; 20 | logic transfer_ready; 21 | logic interrupt; 22 | logic transaction_complete; 23 | logic nack; 24 | logic start_err; 25 | logic arbitration_err; 26 | logic [7:0] data_tx = 8'd0; 27 | logic [7:0] data_rx; 28 | 29 | i2c_core #( 30 | .INPUT_CLK_RATE(INPUT_CLK_RATE), 31 | .TARGET_SCL_RATE(TARGET_SCL_RATE), 32 | .CLOCK_STRETCHING(0), 33 | .MULTI_MASTER(0), 34 | .SLOWEST_DEVICE_RATE(SLOWEST_DEVICE_RATE), 35 | .FORCE_PUSH_PULL(0) 36 | ) master ( 37 | .scl(scl), 38 | .clk_in(clk_in), 39 | .bus_clear(bus_clear), 40 | .sda(sda), 41 | .mode(mode), 42 | .transfer_start(transfer_start), 43 | .transfer_continues(transfer_continues), 44 | .transfer_ready(transfer_ready), 45 | .interrupt(interrupt), 46 | .transaction_complete(transaction_complete), 47 | .nack(nack), 48 | .start_err(start_err), 49 | .arbitration_err(arbitration_err), 50 | .data_tx(data_tx), 51 | .data_rx(data_rx) 52 | ); 53 | 54 | integer i, j; 55 | 56 | logic [7:0] TEST1 = 8'b10110100; 57 | logic [63:0] TEST2 = 64'hFEEDFACECAFEBEEF; 58 | logic [63:0] TEST3 = 64'hFAC3B00CBAAAAAAD; 59 | 60 | initial 61 | begin 62 | $display("Parameters: COUNTER_END %d, COUNTER_HIGH %d, COUNTER_RECEIVE %d, COUNTER_TRANSMIT %d", master.COUNTER_END, master.COUNTER_HIGH, master.COUNTER_RECEIVE, master.COUNTER_TRANSMIT); 63 | wait (!clk_in && transfer_ready); 64 | $display("Beginning transmission ending with NACK"); 65 | mode <= 1'b0; 66 | transfer_start <= 1'b1; 67 | transfer_continues <= 1'b0; 68 | data_tx <= TEST1; 69 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) && !clk_in); 70 | assert (master.busy) else $fatal(1, "Master should be busy"); 71 | for (i = 0; i < 8; i++) 72 | begin 73 | wait (master.counter == master.COUNTER_TRANSMIT + 1 && !clk_in); 74 | assert (master.transaction_progress == 4'(i + 2)) else $fatal(1, "Unexpected TX progress: %d should be ", master.transaction_progress, i + 2); 75 | assert (master.busy) else $fatal(1, "Master should be busy in %d", 3'(i)); 76 | assert (master.sda_internal === TEST1[7 - i]) else $fatal(1, "Loop %d TX progress %d expected %b but was %b", i, master.transaction_progress, TEST1[7 - i], master.sda_internal); 77 | wait (master.counter == master.COUNTER_TRANSMIT && !clk_in); 78 | end 79 | wait (interrupt && !clk_in); 80 | assert (transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 81 | assert (nack) else $fatal(1, "Slave sent NACK, master should've noted it"); 82 | transfer_start <= 1'b0; 83 | transfer_continues <= 1'b0; 84 | 85 | wait (!clk_in && transfer_ready); 86 | $display("Beginning transmission ending with ACK"); 87 | mode <= 1'b0; 88 | transfer_start <= 1'b1; 89 | transfer_continues <= 1'b0; 90 | data_tx <= TEST1; 91 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) && !clk_in); 92 | assert (master.busy) else $fatal(1, "Master should be busy"); 93 | for (i = 0; i < 8; i++) 94 | begin 95 | wait (master.counter == master.COUNTER_TRANSMIT + 1 && !clk_in); 96 | assert (master.transaction_progress == 4'(i + 2)) else $fatal(1, "Unexpected TX progress: %d should be ", master.transaction_progress, i + 2); 97 | assert (master.busy) else $fatal(1, "Master should be busy in %d", 3'(i)); 98 | assert (master.sda_internal === TEST1[7 - i]) else $fatal(1, "Loop %d TX progress %d expected %b but was %b", i, master.transaction_progress, TEST1[7 - i], master.sda_internal); 99 | wait (master.counter == master.COUNTER_TRANSMIT && !clk_in); 100 | end 101 | inoutmode <= 1'b1; 102 | sda_in <= 1'b0; 103 | wait (interrupt && !clk_in); 104 | inoutmode <= 1'b0; 105 | assert (transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 106 | assert (!nack) else $fatal(1, "Slave sent ACK, master should've noted it"); 107 | 108 | 109 | $display("Beginning repeated start reception ending with NACK"); 110 | mode <= 1'b1; 111 | transfer_start <= 1'b1; 112 | transfer_continues <= 1'b0; 113 | data_tx <= 8'd0; 114 | inoutmode <= 1'b1; 115 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) && !clk_in); 116 | wait (master.counter == (master.COUNTER_RECEIVE) % (master.COUNTER_END + 1) && !clk_in); 117 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) && !clk_in); 118 | assert (master.busy) else $fatal(1, "Master should be busy"); 119 | for (i = 0; i < 8; i++) 120 | begin 121 | wait (master.counter == master.COUNTER_TRANSMIT && !clk_in); 122 | wait (clk_in); 123 | sda_in <= TEST1[7 - i] ? 1'bz : 1'b0; 124 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) && !clk_in); 125 | assert (master.transaction_progress == 4'(i + 2)) else $fatal(1, "Unexpected TX progress: %d should be ", master.transaction_progress, i + 2); 126 | assert (master.latched_data[7 - i] == TEST1[7 - i]) else $fatal(1, "Loop %d RX progress %d expected %b but was %b", i, master.transaction_progress, TEST1[7 - i], master.latched_data[7 - i]); 127 | end 128 | inoutmode <= 1'b0; 129 | wait (master.counter == master.COUNTER_TRANSMIT + 1 && !clk_in); 130 | wait (interrupt && !clk_in); 131 | assert (transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 132 | assert (nack) else $fatal(1, "Master should've sent NACK"); 133 | assert (data_rx == TEST1) else $fatal(1, "Expected %b, was %b", TEST1, data_rx); 134 | transfer_start <= 1'b0; 135 | transfer_continues <= 1'b0; 136 | 137 | 138 | 139 | wait (transfer_ready && !clk_in); 140 | $display("\nBeginning bulk transmission"); 141 | mode <= 1'b0; 142 | transfer_start <= 1'b1; 143 | transfer_continues <= 1'b1; 144 | data_tx <= TEST2[7:0]; 145 | for (j = 0; j < 8; j++) 146 | begin 147 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) + (j == 0 ? 0 : 1) && !clk_in); 148 | $display("Byte %d (%h)", j, TEST2[7:0]); 149 | assert (master.busy) else $fatal(1, "Master should be busy"); 150 | wait (master.counter == master.COUNTER_TRANSMIT && !clk_in); 151 | assert (master.latched_data == TEST2[7:0]) else $fatal(1, "Master didn't latch current byte expected %h but was %h", TEST2[7:0], master.latched_data); 152 | inoutmode <= 1'b0; 153 | for (i = 0; i < 8; i++) 154 | begin 155 | wait (master.counter == master.COUNTER_TRANSMIT + 1 && !clk_in); 156 | assert (master.transaction_progress == 4'(i + 2)) else $fatal(1, "Unexpected TX progress: %d should be ", master.transaction_progress, i + 2); 157 | assert (master.busy) else $fatal(1, "Master should be busy"); 158 | assert (master.sda_internal == TEST2[7 - i]) else $fatal(1, "Loop %d TX progress %d expected %b but was %b", 3'(i), master.transaction_progress, TEST2[7 - i], master.sda_internal); 159 | wait (master.counter == master.COUNTER_TRANSMIT && !clk_in); 160 | end 161 | inoutmode <= 1'b1; 162 | sda_in <= j == 7 ? 1'bz : 1'b0; // NACK or ACK 163 | wait (interrupt && !clk_in); 164 | assert (transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 165 | assert (j == 7 ? nack : !nack) else $fatal(1, "Unexpected ACK/NACK for %d", j); 166 | transfer_start <= 1'b0; 167 | transfer_continues <= 1'(j + 1 != 7); 168 | if (j != 7) 169 | begin 170 | TEST2 <= {8'd0, TEST2[63:8]}; 171 | data_tx <= TEST2[15:8]; 172 | end 173 | end 174 | 175 | wait (transfer_ready && !clk_in); 176 | $display("\nBeginning bulk reception"); 177 | mode <= 1'b1; 178 | transfer_start <= 1'b1; 179 | transfer_continues <= 1'b1; 180 | for (j = 0; j < 8; j++) 181 | begin 182 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) + (j == 0 ? 0 : 1) && !clk_in); 183 | $display("Byte %d (%h)", j, TEST3[7:0]); 184 | assert (master.busy) else $fatal(1, "Master should be busy"); 185 | inoutmode <= 1'b1; 186 | for (i = 0; i < 8; i++) 187 | begin 188 | wait (master.counter == master.COUNTER_TRANSMIT && clk_in); 189 | sda_in <= TEST3[7 - i] ? 1'bz : 1'b0; 190 | wait (master.counter == (master.COUNTER_RECEIVE + 1) % (master.COUNTER_END + 1) && !clk_in); 191 | assert (master.latched_data[7 - i] == TEST3[7 - i]) else $fatal(1, "Loop %d RX progress %d expected %b but was %b", i, master.transaction_progress, TEST3[7 - i], master.latched_data[7 - i]); 192 | end 193 | inoutmode <= 1'b0; 194 | wait (master.counter == master.COUNTER_TRANSMIT + 1 && !clk_in); 195 | wait (interrupt && !clk_in); 196 | assert (transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 197 | assert (j == 7 ? nack : !nack) else $fatal(1, "Master sent unexpected ACK/NACK for %d", j); 198 | assert (data_rx == TEST3[7:0]) else $fatal(1, "Data did not reach data_rx"); 199 | transfer_start <= 1'b0; 200 | transfer_continues <= 1'(j + 1 != 7); 201 | if (j != 7) 202 | TEST3 <= {8'd0, TEST3[63:8]}; 203 | end 204 | 205 | wait(transfer_ready && !clk_in); 206 | 207 | $finish; 208 | end 209 | 210 | endmodule 211 | -------------------------------------------------------------------------------- /test/fast_core_tb.sv: -------------------------------------------------------------------------------- 1 | module fast_core_tb(); 2 | 3 | localparam INPUT_CLK_RATE = 48000000; 4 | core_tb #(.INPUT_CLK_RATE(INPUT_CLK_RATE)) core_tb(); 5 | 6 | endmodule 7 | -------------------------------------------------------------------------------- /test/master_tb.sv: -------------------------------------------------------------------------------- 1 | module master_tb #( 2 | parameter INPUT_CLK_RATE = 50000000, 3 | parameter TARGET_SCL_RATE = 100000, 4 | parameter SLOWEST_DEVICE_RATE = 10000 5 | ) (); 6 | logic sda_in = 1'bz; 7 | logic inoutmode = 1'b0; 8 | wire scl; 9 | wire sda; 10 | assign scl = 1'bz; 11 | assign sda = inoutmode ? sda_in : 1'bz; 12 | 13 | 14 | logic clk_in = 1'b0; 15 | always #2 clk_in = ~clk_in; 16 | logic bus_clear; 17 | logic mode = 1'b0; 18 | logic transfer_start = 1'b0; 19 | logic transfer_continues = 1'b0; 20 | logic transfer_ready; 21 | logic interrupt; 22 | logic transaction_complete; 23 | logic nack; 24 | logic start_err; 25 | logic arbitration_err; 26 | logic [7:0] data_tx = 8'd0; 27 | logic [7:0] data_rx; 28 | 29 | 30 | logic [7:0] address; 31 | 32 | i2c_master #( 33 | .INPUT_CLK_RATE(INPUT_CLK_RATE), 34 | .TARGET_SCL_RATE(TARGET_SCL_RATE), 35 | .CLOCK_STRETCHING(0), 36 | .MULTI_MASTER(0), 37 | .SLOWEST_DEVICE_RATE(SLOWEST_DEVICE_RATE), 38 | .FORCE_PUSH_PULL(0) 39 | ) master ( 40 | .scl(scl), 41 | .clk_in(clk_in), 42 | .bus_clear(bus_clear), 43 | .sda(sda), 44 | .address(address), 45 | .transfer_start(transfer_start), 46 | .transfer_continues(transfer_continues), 47 | .transfer_ready(transfer_ready), 48 | .interrupt(interrupt), 49 | .transaction_complete(transaction_complete), 50 | .nack(nack), 51 | .start_err(start_err), 52 | .arbitration_err(arbitration_err), 53 | .data_tx(data_tx), 54 | .data_rx(data_rx) 55 | ); 56 | 57 | integer i, j, k; 58 | logic [63:0] TEST2_CONST = {56'hFEEDFACECAFEBE, 8'h54}; 59 | logic [63:0] TEST2 = TEST2_CONST; 60 | 61 | logic [63:0] TEST3_CONST = {56'hFAC3B00CBAAAAA, 8'h21}; 62 | logic [63:0] TEST3 = TEST3_CONST; 63 | 64 | initial 65 | begin 66 | wait (transfer_ready && !clk_in); 67 | 68 | for (k = 0; k < 4; k++) 69 | begin 70 | $display("Beginning bulk transmission"); 71 | transfer_start <= 1'b1; 72 | transfer_continues <= 1'b1; 73 | address <= TEST2_CONST[7:0]; 74 | if (k != 0) wait (master.core.transaction_progress == 4'd11); 75 | 76 | for (j = 0; j < 8; j++) 77 | begin 78 | wait (master.core.counter == (master.core.COUNTER_RECEIVE + 1) % (master.core.COUNTER_END + 1) + (j == 0 ? 0 : 1) && !clk_in); 79 | $display("Byte %d (%h)", j, TEST2[7:0]); 80 | assert (master.core.busy) else $fatal(1, "Master should be busy"); 81 | wait (master.core.counter == master.core.COUNTER_TRANSMIT && !clk_in); 82 | assert (master.core.latched_data == TEST2[7:0]) else $fatal(1, "Master didn't latch current byte expected %h but was %h", TEST2[7:0], master.core.latched_data); 83 | inoutmode <= 1'b0; 84 | for (i = 0; i < 8; i++) 85 | begin 86 | wait (master.core.counter == master.core.COUNTER_TRANSMIT + 1 && !clk_in); 87 | assert (master.core.transaction_progress == 4'(i + 2)) else $fatal(1, "Unexpected TX progress: %d should be ", master.core.transaction_progress, i + 2); 88 | assert (master.core.busy) else $fatal(1, "Master should be busy"); 89 | assert (master.core.sda_internal == TEST2[7 - i]) else $fatal(1, "Loop %d TX progress %d expected %b but was %b", 3'(i), master.core.transaction_progress, TEST2[7 - i], master.core.sda_internal); 90 | wait (master.core.counter == master.core.COUNTER_TRANSMIT && !clk_in); 91 | end 92 | inoutmode <= 1'b1; 93 | sda_in <= j == 7 ? 1'bz : 1'b0; // NACK or ACK 94 | wait ((j == 0 ? master.internal_interrupt : interrupt) && !clk_in); 95 | assert (j == 0 ? master.internal_transaction_complete : transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 96 | assert (j == 7 ? nack : !nack) else $fatal(1, "Unexpected ACK/NACK for %d", j); 97 | transfer_start <= 1'b0; 98 | transfer_continues <= 1'(j + 1 != 7); 99 | if (j != 7) 100 | begin 101 | TEST2 <= {8'd0, TEST2[63:8]}; 102 | data_tx <= TEST2[15:8]; 103 | end 104 | end 105 | 106 | TEST2 <= TEST2_CONST; 107 | end 108 | 109 | wait (transfer_ready && !clk_in); 110 | 111 | $display("\nBeginning bulk reception"); 112 | transfer_start <= 1'b1; 113 | transfer_continues <= 1'b1; 114 | address <= TEST3[7:0]; 115 | for (j = 0; j < 8; j++) 116 | begin 117 | wait (master.core.counter == (master.core.COUNTER_RECEIVE + 1) % (master.core.COUNTER_END + 1) + (j == 0 ? 0 : 1) && !clk_in); 118 | $display("Byte %d (%h)", j, TEST3[7:0]); 119 | assert (master.core.busy) else $fatal(1, "Master should be busy"); 120 | if (j == 0) 121 | begin 122 | wait (master.core.counter == master.core.COUNTER_TRANSMIT && !clk_in); 123 | assert (master.core.latched_data == TEST3[7:0]) else $fatal(1, "Master didn't latch address expected %h but was %h", TEST3[7:0], master.core.latched_data); 124 | inoutmode <= 1'b0; 125 | end 126 | else 127 | begin 128 | inoutmode <= 1'b1; 129 | end 130 | for (i = 0; i < 8; i++) 131 | begin 132 | if (j == 0) 133 | begin 134 | wait (master.core.counter == master.core.COUNTER_TRANSMIT + 1 && !clk_in); 135 | assert (master.core.transaction_progress == 4'(i + 2)) else $fatal(1, "Unexpected TX progress: %d should be ", master.core.transaction_progress, i + 2); 136 | assert (master.core.busy) else $fatal(1, "Master should be busy"); 137 | assert (master.core.sda_internal == TEST3[7 - i]) else $fatal(1, "Loop %d TX progress %d expected %b but was %b", 3'(i), master.core.transaction_progress, TEST3[7 - i], master.core.sda_internal); 138 | wait (master.core.counter == master.core.COUNTER_TRANSMIT && !clk_in); 139 | end 140 | else 141 | begin 142 | wait (master.core.counter == master.core.COUNTER_TRANSMIT && clk_in); 143 | sda_in <= TEST3[7 - i] ? 1'bz : 1'b0; 144 | wait (master.core.counter == (master.core.COUNTER_RECEIVE + 1) % (master.core.COUNTER_END + 1) && !clk_in); 145 | assert (master.core.latched_data[7 - i] == TEST3[7 - i]) else $fatal(1, "Loop %d RX progress %d expected %b but was %b", i, master.core.transaction_progress, TEST3[7 - i], master.core.latched_data[7 - i]); 146 | end 147 | end 148 | inoutmode <= j == 0 ? 1'b1 : 1'b0; 149 | if (j == 0) 150 | begin 151 | sda_in <= 1'b0; 152 | wait (master.internal_interrupt && !clk_in); 153 | assert (master.internal_transaction_complete) else $fatal(1, "Address transmit did not complete successfully"); 154 | end 155 | else 156 | begin 157 | wait (master.core.counter == master.core.COUNTER_TRANSMIT + 1 && !clk_in); 158 | wait (interrupt && !clk_in); 159 | assert (transaction_complete) else $fatal(1, "Transaction did not complete successfully"); 160 | assert (j == 7 ? nack : !nack) else $fatal(1, "Master sent unexpected ACK/NACK for %d", j); 161 | assert (data_rx == TEST3[7:0]) else $fatal(1, "Data did not reach data_rx"); 162 | end 163 | transfer_start <= 1'b0; 164 | transfer_continues <= 1'(j + 1 != 7); 165 | if (j != 7) 166 | TEST3 <= {8'd0, TEST3[63:8]}; 167 | end 168 | 169 | $finish; 170 | end 171 | 172 | endmodule 173 | -------------------------------------------------------------------------------- /test/slow_core_tb.sv: -------------------------------------------------------------------------------- 1 | module slow_core_tb(); 2 | 3 | localparam INPUT_CLK_RATE = 400000; 4 | core_tb #(.INPUT_CLK_RATE(INPUT_CLK_RATE)) core_tb(); 5 | 6 | endmodule 7 | --------------------------------------------------------------------------------