From 5723ec1a34181f1cfef9b8e870ab2e9a0362487c Mon Sep 17 00:00:00 2001 From: mindchasers Date: Wed, 1 May 2019 18:16:45 -0400 Subject: initial commit, all basic functions work on Darsena V02 --- source/i2c.v | 391 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 source/i2c.v (limited to 'source/i2c.v') 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 -- cgit v1.2.3-8-gadcc