summaryrefslogtreecommitdiffhomepage
path: root/source/i2c.v
diff options
context:
space:
mode:
authormindchasers <privateisland@mindchasers.com>2019-05-01 18:16:45 -0400
committermindchasers <privateisland@mindchasers.com>2019-05-01 18:16:45 -0400
commit5723ec1a34181f1cfef9b8e870ab2e9a0362487c (patch)
tree0dc958d5b1931934ded5d0302b2b5fedab081d2c /source/i2c.v
initial commit, all basic functions work on Darsena V02
Diffstat (limited to 'source/i2c.v')
-rw-r--r--source/i2c.v391
1 files changed, 391 insertions, 0 deletions
diff --git a/source/i2c.v b/source/i2c.v
new file mode 100644
index 0000000..aca6452
--- /dev/null
+++ b/source/i2c.v
@@ -0,0 +1,391 @@
+/*
+ * 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