| /* |
| * i2c_master.v |
| * |
| * vim: ts=4 sw=4 |
| * |
| * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com> |
| * SPDX-License-Identifier: CERN-OHL-P-2.0 |
| */ |
| |
| `default_nettype none |
| |
| module i2c_master #( |
| parameter integer DW = 3 |
| )( |
| // IOs |
| output reg scl_oe, |
| output reg sda_oe, |
| input wire sda_i, |
| |
| // Control |
| input wire [7:0] data_in, |
| input wire ack_in, |
| input wire [1:0] cmd, |
| input wire stb, |
| |
| output wire [7:0] data_out, |
| output wire ack_out, |
| |
| output wire ready, |
| |
| // Clock / Reset |
| input wire clk, |
| input wire rst |
| ); |
| |
| // Commands |
| localparam [2:0] CMD_START = 3'b00; |
| localparam [2:0] CMD_STOP = 3'b01; |
| localparam [2:0] CMD_WRITE = 3'b10; |
| localparam [2:0] CMD_READ = 3'b11; |
| |
| // FSM states |
| localparam |
| ST_IDLE = 0, |
| ST_LOWER_SCL = 1, |
| ST_LOW_CYCLE = 2, |
| ST_RISE_SCL = 3, |
| ST_HIGH_CYCLE = 4; |
| |
| |
| // Signals |
| // ------- |
| |
| reg [2:0] state; |
| reg [2:0] state_nxt; |
| |
| reg [1:0] cmd_cur; |
| |
| reg [DW:0] cyc_cnt; |
| wire cyc_now; |
| |
| reg [3:0] bit_cnt; |
| wire bit_last; |
| |
| reg [8:0] data_reg; |
| |
| |
| // State Machine |
| // ------------- |
| |
| always @(posedge clk) |
| if (rst) |
| state <= ST_IDLE; |
| else |
| state <= state_nxt; |
| |
| always @(*) |
| begin |
| // Default is to stay put |
| state_nxt = state; |
| |
| // Act depending on current state |
| case (state) |
| ST_IDLE: |
| if (stb) |
| state_nxt = ST_LOW_CYCLE; |
| |
| ST_LOW_CYCLE: |
| if (cyc_now) |
| state_nxt = ST_RISE_SCL; |
| |
| ST_RISE_SCL: |
| if (cyc_now) |
| state_nxt = ST_HIGH_CYCLE; |
| |
| ST_HIGH_CYCLE: |
| if (cyc_now) |
| state_nxt = (cmd_cur == 2'b01) ? ST_IDLE : ST_LOWER_SCL; |
| |
| ST_LOWER_SCL: |
| if (cyc_now) |
| state_nxt = bit_last ? ST_IDLE : ST_LOW_CYCLE; |
| endcase |
| end |
| |
| // Misc control |
| // ------------ |
| |
| always @(posedge clk) |
| if (stb) |
| cmd_cur <= cmd; |
| |
| |
| // Baud Rate generator |
| // ------------------- |
| |
| always @(posedge clk) |
| if (state == ST_IDLE) |
| cyc_cnt <= 0; |
| else |
| cyc_cnt <= cyc_cnt[DW] ? 0 : (cyc_cnt + 1); |
| |
| assign cyc_now = cyc_cnt[DW]; |
| |
| |
| // Bit count |
| // --------- |
| |
| always @(posedge clk) |
| if ((state == ST_LOWER_SCL) && cyc_now) |
| bit_cnt <= bit_cnt + 1; |
| else if (stb) |
| case (cmd) |
| 2'b00: bit_cnt <= 4'h8; // START |
| 2'b01: bit_cnt <= 4'h8; // STOP |
| 2'b10: bit_cnt <= 4'h0; // Write |
| 2'b11: bit_cnt <= 4'h0; // Read |
| default: bit_cnt <= 4'hx; |
| endcase |
| |
| assign bit_last = bit_cnt[3]; |
| |
| |
| // Data register |
| // ------------- |
| |
| always @(posedge clk) |
| if ((state == ST_HIGH_CYCLE) && cyc_now) |
| data_reg <= { data_reg[7:0], sda_i }; |
| else if (stb) |
| // Only handle Write / Read. START & STOP is handled in IO mux |
| data_reg <= cmd[0] ? { 8'b11111111, ack_in } : { data_in, 1'b1 }; |
| |
| |
| // IO |
| // -- |
| |
| always @(posedge clk) |
| if (rst) |
| scl_oe <= 1'b0; |
| else if (cyc_now) begin |
| if (state == ST_LOWER_SCL) |
| scl_oe <= 1'b1; |
| else if (state == ST_RISE_SCL) |
| scl_oe <= 1'b0; |
| end |
| |
| always @(posedge clk) |
| if (rst) |
| sda_oe <= 1'b0; |
| else if (cyc_now) begin |
| if (~cmd_cur[1]) begin |
| if (state == ST_LOW_CYCLE) |
| sda_oe <= cmd_cur[0]; |
| else if (state == ST_HIGH_CYCLE) |
| sda_oe <= ~cmd_cur[0]; |
| end else begin |
| if (state == ST_LOW_CYCLE) |
| sda_oe <= ~data_reg[8]; |
| end |
| end |
| |
| |
| // User IF |
| // ------- |
| |
| assign data_out = data_reg[8:1]; |
| assign ack_out = data_reg[0]; |
| |
| assign ready = (state == ST_IDLE); |
| |
| endmodule |