From 889a5a5395872eeb3740d5d3281b56c7afa47a6f Mon Sep 17 00:00:00 2001 From: bnewbold Date: Fri, 20 Jan 2012 19:14:58 -0500 Subject: initial import from NeTV archive This repository is simply a mirror of the file fpga/hdmi_overlay_0xD_src.tgz downloaded from http://www.kosagi.com/netv_hardware/ on Jan 19th, 2011. It seems to contain all the verilog and scripts required to build FPGA firmware for the NeTV HDMI device from Chumby/Sutajio Ko-Usagi; see http://www.kosagi.com/blog/ for more information. Licensing is vague; see ip/license.txt --- common/DRAM16XN.v | 51 +++ common/boxtiming.v | 136 ++++++++ common/i2c_slave.v | 864 +++++++++++++++++++++++++++++++++++++++++++++ common/i2c_slave_tb.v | 375 ++++++++++++++++++++ common/i2c_snoop.v | 625 +++++++++++++++++++++++++++++++++ common/i2c_snoop_edid.v | 603 ++++++++++++++++++++++++++++++++ common/i2c_snoop_tb.v | 418 ++++++++++++++++++++++ common/i2c_squash_edid.v | 892 +++++++++++++++++++++++++++++++++++++++++++++++ common/i2c_squash_tb.v | 445 +++++++++++++++++++++++ common/pwm.v | 83 +++++ common/timing_detector.v | 390 +++++++++++++++++++++ 11 files changed, 4882 insertions(+) create mode 100755 common/DRAM16XN.v create mode 100755 common/boxtiming.v create mode 100755 common/i2c_slave.v create mode 100755 common/i2c_slave_tb.v create mode 100755 common/i2c_snoop.v create mode 100755 common/i2c_snoop_edid.v create mode 100755 common/i2c_snoop_tb.v create mode 100755 common/i2c_squash_edid.v create mode 100755 common/i2c_squash_tb.v create mode 100755 common/pwm.v create mode 100755 common/timing_detector.v (limited to 'common') diff --git a/common/DRAM16XN.v b/common/DRAM16XN.v new file mode 100755 index 0000000..656ec8f --- /dev/null +++ b/common/DRAM16XN.v @@ -0,0 +1,51 @@ +// +// Module: DRAM16XN +// +// Description: Distributed SelectRAM example +// Dual Port 16 x N-bit +// +// Device: Spartan-3 Family +//--------------------------------------------------------------------------------------- + +module DRAM16XN #(parameter data_width = 20) + ( + DATA_IN, + ADDRESS, + ADDRESS_DP, + WRITE_EN, + CLK, + O_DATA_OUT, + O_DATA_OUT_DP); + +input [data_width-1:0]DATA_IN; +input [3:0] ADDRESS; +input [3:0] ADDRESS_DP; +input WRITE_EN; +input CLK; + +output [data_width-1:0]O_DATA_OUT_DP; +output [data_width-1:0]O_DATA_OUT; + +genvar i; +generate + for(i = 0 ; i < data_width ; i = i + 1) begin : dram16s + RAM16X1D i_RAM16X1D_U( + .D(DATA_IN[i]), //insert input signal + .WE(WRITE_EN), //insert Write Enable signal + .WCLK(CLK), //insert Write Clock signal + .A0(ADDRESS[0]), //insert Address 0 signal port SPO + .A1(ADDRESS[1]), //insert Address 1 signal port SPO + .A2(ADDRESS[2]), //insert Address 2 signal port SPO + .A3(ADDRESS[3]), //insert Address 3 signal port SPO + .DPRA0(ADDRESS_DP[0]), //insert Address 0 signal dual port DPO + .DPRA1(ADDRESS_DP[1]), //insert Address 1 signal dual port DPO + .DPRA2(ADDRESS_DP[2]), //insert Address 2 signal dual port DPO + .DPRA3(ADDRESS_DP[3]), //insert Address 3 signal dual port DPO + .SPO(O_DATA_OUT[i]), //insert output signal SPO + .DPO(O_DATA_OUT_DP[i]) //insert output signal DPO + ); + end +endgenerate + +endmodule + diff --git a/common/boxtiming.v b/common/boxtiming.v new file mode 100755 index 0000000..c5d2995 --- /dev/null +++ b/common/boxtiming.v @@ -0,0 +1,136 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +`timescale 1 ns / 1ps + +module boxtiming ( + input wire pclk, + input wire rstin, + input wire vsync, + input wire hsync, + input wire sync_pol, // 0 means active 0, 1 means active 1 + input wire de, + input wire cv, + input wire [11:0] hpos, + input wire [11:0] hsize, + input wire [11:0] vpos, + input wire [11:0] vsize, + output reg box_active + ); + + reg [11:0] hcount; + reg [11:0] vcount; + + reg hsync_v; // active when high + reg hsync_v2; + reg vsync_v; + reg vsync_v2; + reg de_d; + + reg active; + + wire hsync_rising; + wire vsync_rising; + wire de_rising; + wire de_falling; + + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + hsync_v <= 0; + vsync_v <= 0; + + hsync_v2 <= 0; + vsync_v2 <= 0; + + de_d <= 0; + end else begin + de_d <= de; + + if( cv ) begin + hsync_v <= hsync ^ !sync_pol; + vsync_v <= vsync ^ !sync_pol; + end else begin + hsync_v <= hsync_v; + vsync_v <= vsync_v; + end + + hsync_v2 <= hsync_v; // just a delayed version + vsync_v2 <= vsync_v; + end // else: !if( rstin ) + end // always @ (posedge pclk or posedge rstin) + assign hsync_rising = hsync_v & !hsync_v2; + assign vsync_rising = vsync_v & !vsync_v2; + assign de_rising = de & !de_d; + assign de_falling = !de & de_d; + + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + hcount <= 0; + end else begin + if( de_rising ) begin + hcount <= 12'b0000_0000_0000; + end else begin + if( de ) begin + hcount <= hcount + 12'b0000_0000_0001; + end else begin + hcount <= hcount; + end + end + end // else: !if( rstin ) + end // always @ (posedge pclk or posedge rstin) + + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + vcount <= 0; + end else begin + if( vsync_rising ) begin + vcount <= 12'b0000_0000_0000; + end else begin + if( de_falling ) begin // this may be a bug but I think it's worked around elsewhere + vcount <= vcount + 12'b0000_0000_0001; + end else begin + vcount <= vcount; + end + end + end // else: !if( rstin ) + end // always @ (posedge pclk or posedge rstin) + + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + active <= 0; + end else begin + if( (hcount >= hpos) && (hcount < (hpos + hsize)) && + (vcount >= vpos) && (vcount < (vpos + vsize)) ) begin + active <= 1'b1; + end else begin + active <= 1'b0; + end + end + + box_active <= active; + end // always @ (posedge pclk or posedge rstin) + +endmodule + \ No newline at end of file diff --git a/common/i2c_slave.v b/common/i2c_slave.v new file mode 100755 index 0000000..d8672f7 --- /dev/null +++ b/common/i2c_slave.v @@ -0,0 +1,864 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +// A simple slave implementation. Oversampled for robustness. +// The slave is extended into the snoop & surpress version for the DDC bus; +// this is just a starting point for basic testing and also simple comms +// with the CPU. +// +// i2c slave module requires the top level module to implement the IOBs +// This is just to keep the tri-state easy to implemen across the hierarchy +// +// The code required on the top level is: +// IBUF IBUF_sda (.I(SDA), .O(SDA_int)); +// IOBUF #(.DRIVE(12), .SLEW("SLOW")) IOBUF_sda (.IO(SDA), .I(1'b0), .T(!SDA_pd)); +// +/////////// +`timescale 1 ns / 1 ps + +module i2c_slave ( + // external host interface + input wire SCL, // the SCL pin state + output wire SCL_pd, // signals to IOB to pull the SCL bus low + input wire SDA, + output reg SDA_pd, + output wire SDA_pu, // for overriding SDA...in the future. + + input wire clk, // internal FPGA clock + input wire reset, // internal FPGA reset + // i2c configuration + input wire [7:0] i2c_device_addr, + + // internal slave interface + input wire [7:0] reg_addr, + input wire wr_stb, + input wire [7:0] reg_data_in, + output wire [7:0] reg_0, + output wire [7:0] reg_1, + output wire [7:0] reg_2, + output wire [7:0] reg_3, + output wire [7:0] reg_4, + output wire [7:0] reg_5, + output wire [7:0] reg_6, + output wire [7:0] reg_7, + output wire [7:0] reg_8, + output wire [7:0] reg_9, + output wire [7:0] reg_a, + output wire [7:0] reg_b, + output wire [7:0] reg_c, + output wire [7:0] reg_d, + output wire [7:0] reg_e, + output wire [7:0] reg_f, + + input wire [7:0] reg_18, // note this is input now, not output + + output wire [7:0] reg_19, + output wire [7:0] reg_1a, + output wire [7:0] reg_1b, + output wire [7:0] reg_1c, + output wire [7:0] reg_1d, + output wire [7:0] reg_1e, + output wire [7:0] reg_1f, + + input wire [7:0] reg_10, + + output wire [7:0] reg_11, + output wire [7:0] reg_12, + + output wire [7:0] reg_13, + output wire [7:0] reg_14, + output wire [7:0] reg_15, + output wire [7:0] reg_16, + output wire [7:0] reg_17, + + input wire [7:0] reg_20, // read-only bank starts here + input wire [7:0] reg_21, + input wire [7:0] reg_22, + input wire [7:0] reg_23, + input wire [7:0] reg_24, + input wire [7:0] reg_25, + input wire [7:0] reg_26, + input wire [7:0] reg_27, + input wire [7:0] reg_28, + input wire [7:0] reg_29, + input wire [7:0] reg_2a, + input wire [7:0] reg_2b, + input wire [7:0] reg_2c, + input wire [7:0] reg_2d, + input wire [7:0] reg_2e, + input wire [7:0] reg_2f, + input wire [7:0] reg_30, + input wire [7:0] reg_31, + input wire [7:0] reg_32, + input wire [7:0] reg_33, + input wire [7:0] reg_34, + input wire [7:0] reg_35, + input wire [7:0] reg_36, + input wire [7:0] reg_37, + input wire [7:0] reg_38, + input wire [7:0] reg_39, + input wire [7:0] reg_3a, + input wire [7:0] reg_3b, + input wire [7:0] reg_3c, + input wire [7:0] reg_3d, + + input wire [7:0] reg_3e, + input wire [7:0] reg_3f + ); + + /////// I2C physical layer components + /// SDA is stable when SCL is high. + /// If SDA moves while SCL is high, this is considered a start or stop condition. + /// + /// Otherwise, SDA can move around when SCL is low (this is where we suppress bits or + /// overdrive as needed). SDA is a wired-AND bus, so you only "drive" zero. + /// + /// In an oversampled implementation, a rising and falling edge de-glitcher is needed + /// for SCL and SDA. + /// + + // rise fall time cycles computation: + // At 400kHz operation, 2.5us is a cycle. "chatter" from transition should be about + // 5% of total cycle time max (just rule of thumb), so 0.125us should be the equiv + // number of cycles. + // For the demo board, a 25 MHz clock is provided, and 0.125us ~ 4 cycles + // At 100kHz operation, 10us is a cycle, so 0.5us ~ 12 cycles + parameter TRF_CYCLES = 5'd4; // number of cycles for rise/fall time + + // just some tie-offs for future functionality not yet implemented... + assign SDA_pu = 1'b0; + assign SCL_pd = 1'b0; + + //////////////// + ///// protocol-level state machine + //////////////// + parameter I2C_START = 14'b1 << 0; // should only pass through this state for one cycle + parameter I2C_RESTART = 14'b1 << 1; + parameter I2C_DADDR = 14'b1 << 2; + parameter I2C_ACK_DADDR = 14'b1 << 3; + parameter I2C_ADDR = 14'b1 << 4; + parameter I2C_ACK_ADDR = 14'b1 << 5; + parameter I2C_WR_DATA = 14'b1 << 6; + parameter I2C_ACK_WR = 14'b1 << 7; + parameter I2C_END_WR = 14'b1 << 8; + parameter I2C_RD_DATA = 14'b1 << 9; + parameter I2C_ACK_RD = 14'b1 << 10; + parameter I2C_END_RD = 14'b1 << 11; + parameter I2C_END_RD2 = 14'b1 << 12; + parameter I2C_WAITSTOP = 14'b1 << 13; + + parameter I2C_nSTATES = 14; + + reg [(I2C_nSTATES-1):0] I2C_cstate = {{(I2C_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(I2C_nSTATES-1):0] I2C_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] I2C_state_ascii = "I2C_START "; + always @(I2C_cstate) begin + if (I2C_cstate == I2C_START) I2C_state_ascii <= "I2C_START "; + else if (I2C_cstate == I2C_RESTART) I2C_state_ascii <= "I2C_RESTART "; + else if (I2C_cstate == I2C_DADDR) I2C_state_ascii <= "I2C_DADDR "; + else if (I2C_cstate == I2C_ACK_DADDR) I2C_state_ascii <= "I2C_ACK_DADDR "; + else if (I2C_cstate == I2C_ADDR) I2C_state_ascii <= "I2C_ADDR "; + else if (I2C_cstate == I2C_ACK_ADDR) I2C_state_ascii <= "I2C_ACK_ADDR "; + else if (I2C_cstate == I2C_WR_DATA) I2C_state_ascii <= "I2C_WR_DATA "; + else if (I2C_cstate == I2C_ACK_WR) I2C_state_ascii <= "I2C_ACK_WR "; + else if (I2C_cstate == I2C_END_WR) I2C_state_ascii <= "I2C_END_WR "; + else if (I2C_cstate == I2C_RD_DATA) I2C_state_ascii <= "I2C_RD_DATA "; + else if (I2C_cstate == I2C_ACK_RD) I2C_state_ascii <= "I2C_ACK_RD "; + else if (I2C_cstate == I2C_END_RD) I2C_state_ascii <= "I2C_END_RD "; + else if (I2C_cstate == I2C_END_RD2) I2C_state_ascii <= "I2C_END_RD2 "; + else if (I2C_cstate == I2C_WAITSTOP) I2C_state_ascii <= "I2C_WAITSTOP "; + else I2C_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [3:0] I2C_bitcnt; + reg [7:0] I2C_addr; + reg [7:0] I2C_daddr; + reg [7:0] I2C_wdata; + reg [7:0] I2C_rdata; + reg I2C_reg_update; + + ///// register block definitions + parameter RAM_WIDTH = 8; + parameter RAM_ADDR_BITS = 5; // note parameter width exception in reg_a* assign block below + + reg [RAM_WIDTH-1:0] I2C_regblock [(2**RAM_ADDR_BITS)-1:0]; + reg [RAM_WIDTH-1:0] I2C_regread_async; + + wire [RAM_ADDR_BITS-1:0] I2C_ramaddr; + + reg wr_stb_d; + + ////////// code begins here + always @ (posedge clk) begin + if (reset || ((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) // stop condition always resets + I2C_cstate <= I2C_START; + else + I2C_cstate <=#1 I2C_nstate; + end + + always @ (*) begin + case (I2C_cstate) //synthesis parallel_case full_case + I2C_START: begin // wait for the start condition + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_DADDR : I2C_START; + end + I2C_RESTART: begin // repeated start moves immediately to DADDR + I2C_nstate = I2C_DADDR; + end + I2C_DADDR: begin // 8 bits to get the address + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_DADDR : I2C_DADDR; + end + I2C_ACK_DADDR: begin // depending upon W/R bit state, go to one of two branches + I2C_nstate = (SCL_cstate == SCL_FALL) ? + (I2C_daddr[7:1] == i2c_device_addr[7:1]) ? + (I2C_daddr[0] == 1'b0 ? I2C_ADDR : I2C_RD_DATA) : + I2C_WAITSTOP : // !I2C_daddr match + I2C_ACK_DADDR; // !SCL_FALL + end + + // device address branch + I2C_ADDR: begin + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_ADDR : I2C_ADDR; + end + I2C_ACK_ADDR: begin + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_WR_DATA : I2C_ACK_ADDR; + end + + // write branch + I2C_WR_DATA: begin // 8 bits to get the write data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_WR : I2C_WR_DATA; + end + I2C_ACK_WR: begin // trigger the ack response (pull SDA low until next falling edge) + // and stay in this state until the next falling edge of SCL + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_END_WR : I2C_ACK_WR; + end + I2C_END_WR: begin // one-cycle state to update address+1, reset SDA pulldown + I2C_nstate = I2C_WR_DATA; // SCL is now low + end + + // read branch + I2C_RD_DATA: begin // 8 bits to get the read data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_RD : I2C_RD_DATA; + end + I2C_ACK_RD: begin // wait for an (n)ack response + // need to sample (n)ack on a rising edge + I2C_nstate = (SCL_cstate == SCL_RISE) ? I2C_END_RD : I2C_ACK_RD; + end + I2C_END_RD: begin // if nack, just go to start state (don't explicitly check stop event) + // single cycle state for adr+1 update + I2C_nstate = (SDA_cstate == SDA_LOW) ? I2C_END_RD2 : I2C_START; + end + I2C_END_RD2: begin // before entering I2C_RD_DATA, we need to have seen a falling edge. + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_RD_DATA : I2C_END_RD2; + end + + // we're not the addressed device, so we just idle until we see a stop + I2C_WAITSTOP: begin + I2C_nstate = (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) ? // stop + I2C_START : + (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_FALL))) ? // or start + I2C_RESTART : + I2C_WAITSTOP; + end + endcase // case (cstate) + end + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + SDA_pd <=#1 1'b0; + I2C_reg_update <=#1 1'b0; + I2C_rdata <=#1 8'b0; + I2C_addr <=#1 8'b0; // this persists across transactions + end else begin + case (I2C_cstate) // synthesis parallel_case full_case + I2C_START: begin // everything in reset + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + SDA_pd <=#1 1'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + I2C_RESTART: begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + SDA_pd <=#1 1'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + // get my i2c device address (am I being talked to?) + I2C_DADDR: begin // shift in the address on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_daddr[7] <=#1 I2C_daddr[6]; + I2C_daddr[6] <=#1 I2C_daddr[5]; + I2C_daddr[5] <=#1 I2C_daddr[4]; + I2C_daddr[4] <=#1 I2C_daddr[3]; + I2C_daddr[3] <=#1 I2C_daddr[2]; + I2C_daddr[2] <=#1 I2C_daddr[1]; + I2C_daddr[1] <=#1 I2C_daddr[0]; + I2C_daddr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_daddr <=#1 I2C_daddr; + end // else: !if( SCL_cstate == SCL_RISE ) + SDA_pd <=#1 1'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end // case: I2C_DADDR + I2C_ACK_DADDR: begin + SDA_pd <=#1 1'b1; // active pull down ACK + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_regread_async; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + // get my i2c "write" address (what we want to access inside me) + I2C_ADDR: begin + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_addr[7] <=#1 I2C_addr[6]; + I2C_addr[6] <=#1 I2C_addr[5]; + I2C_addr[5] <=#1 I2C_addr[4]; + I2C_addr[4] <=#1 I2C_addr[3]; + I2C_addr[3] <=#1 I2C_addr[2]; + I2C_addr[2] <=#1 I2C_addr[1]; + I2C_addr[1] <=#1 I2C_addr[0]; + I2C_addr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_addr <=#1 I2C_addr; + end // else: !if( SCL_cstate == SCL_RISE ) + SDA_pd <=#1 1'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + end // case: I2C_ADDR + I2C_ACK_ADDR: begin + SDA_pd <=#1 1'b1; // active pull down ACK + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_regread_async; // update my read data here + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + + // write branch + I2C_WR_DATA: begin // shift in data on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_wdata[7] <=#1 I2C_wdata[6]; + I2C_wdata[6] <=#1 I2C_wdata[5]; + I2C_wdata[5] <=#1 I2C_wdata[4]; + I2C_wdata[4] <=#1 I2C_wdata[3]; + I2C_wdata[3] <=#1 I2C_wdata[2]; + I2C_wdata[2] <=#1 I2C_wdata[1]; + I2C_wdata[1] <=#1 I2C_wdata[0]; + I2C_wdata[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + I2C_wdata <=#1 I2C_wdata; + end // else: !if( SCL_cstate == SCL_RISE ) + SDA_pd <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_addr <=#1 I2C_addr; + end // case: I2C_WR_DATA + I2C_ACK_WR: begin + SDA_pd <=#1 1'b1; // active pull down ACK + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_reg_update <=#1 1'b1; // write the data now (over and over again while in state) + I2C_rdata <=#1 I2C_rdata; + I2C_addr <=#1 I2C_addr; + end + I2C_END_WR: begin + SDA_pd <=#1 1'b0; // let SDA rise (host may look for this to know ack is done + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + end + + // read branch + I2C_RD_DATA: begin // shift out data on falling edges of clock + SDA_pd <=#1 I2C_rdata[7] ? 1'b0 : 1'b1; + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + end + + if( SCL_cstate == SCL_FALL ) begin + I2C_rdata[7] <=#1 I2C_rdata[6]; + I2C_rdata[6] <=#1 I2C_rdata[5]; + I2C_rdata[5] <=#1 I2C_rdata[4]; + I2C_rdata[4] <=#1 I2C_rdata[3]; + I2C_rdata[3] <=#1 I2C_rdata[2]; + I2C_rdata[2] <=#1 I2C_rdata[1]; + I2C_rdata[1] <=#1 I2C_rdata[0]; + I2C_rdata[0] <=#1 1'b0; + end else begin + I2C_rdata <=#1 I2C_rdata; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end // case: I2C_RD_DATA + I2C_ACK_RD: begin + SDA_pd <=#1 1'b0; // in ack state don't pull down, we are listening to host + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + I2C_END_RD: begin + SDA_pd <=#1 1'b0; // let SDA rise (host may look for this to know ack is done + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_daddr <=#1 I2C_daddr; + end + I2C_END_RD2: begin + SDA_pd <=#1 1'b0; + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_regread_async; // update my read data here + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + + I2C_WAITSTOP: begin + SDA_pd <=#1 1'b0; + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + //////////////// + ///// register bank management + //////////////// + always @(posedge clk or posedge reset) begin + wr_stb_d <= wr_stb; + if( reset ) begin +// I2C_regblock[5'hc] <= 8'h36; +// I2C_regblock[5'hd] <= 8'h22; +// I2C_regblock[5'he] <= 8'h12; +// I2C_regblock[5'h0] <= 8'h80; + // nothing....adding in initializations really burn a lot of resources. + end else if (wr_stb & !wr_stb_d) begin // only act on the rising pulse of wr_stb + I2C_regblock[reg_addr] <= reg_data_in; // vestigal remnant?? changes programming model + // slightly, need to look into this.... + end else if (I2C_reg_update) begin // this should be multiple cycles + I2C_regblock[I2C_ramaddr] <= I2C_wdata; + end + end + + always @(*) begin + case (I2C_addr) + 6'h10: begin + I2C_regread_async = reg_10; + end + 6'h02: begin + I2C_regread_async = reg_data_in; /// this is a vestigal remnant + end + 6'h18: begin + I2C_regread_async = reg_18; + end + + 6'h20: begin + I2C_regread_async = reg_20; + end + 6'h21: begin + I2C_regread_async = reg_21; + end + 6'h22: begin + I2C_regread_async = reg_22; + end + 6'h23: begin + I2C_regread_async = reg_23; + end + 6'h24: begin + I2C_regread_async = reg_24; + end + 6'h25: begin + I2C_regread_async = reg_25; + end + 6'h26: begin + I2C_regread_async = reg_26; + end + 6'h27: begin + I2C_regread_async = reg_27; + end + 6'h28: begin + I2C_regread_async = reg_28; + end + 6'h29: begin + I2C_regread_async = reg_29; + end + 6'h2a: begin + I2C_regread_async = reg_2a; + end + 6'h2b: begin + I2C_regread_async = reg_2b; + end + 6'h2c: begin + I2C_regread_async = reg_2c; + end + 6'h2d: begin + I2C_regread_async = reg_2d; + end + 6'h2e: begin + I2C_regread_async = reg_2e; + end + 6'h2f: begin + I2C_regread_async = reg_2f; + end + + 6'h30: begin + I2C_regread_async = reg_30; + end + 6'h31: begin + I2C_regread_async = reg_31; + end + 6'h32: begin + I2C_regread_async = reg_32; + end + 6'h33: begin + I2C_regread_async = reg_33; + end + 6'h34: begin + I2C_regread_async = reg_34; + end + 6'h35: begin + I2C_regread_async = reg_35; + end + 6'h36: begin + I2C_regread_async = reg_36; + end + 6'h37: begin + I2C_regread_async = reg_37; + end + + 6'h38: begin + I2C_regread_async = reg_38; + end + 6'h39: begin + I2C_regread_async = reg_39; + end + 6'h3a: begin + I2C_regread_async = reg_3a; + end + 6'h3b: begin + I2C_regread_async = reg_3b; + end + 6'h3c: begin + I2C_regread_async = reg_3c; + end + 6'h3d: begin + I2C_regread_async = reg_3d; + end + + 6'h3e: begin + I2C_regread_async = reg_3e; + end + 6'h3f: begin + I2C_regread_async = reg_3f; + end + + default: begin + I2C_regread_async = I2C_regblock[I2C_ramaddr]; + end + endcase // case I2C_ramaddr + end // always @ (*) + + assign I2C_ramaddr = I2C_addr[RAM_ADDR_BITS-1:0]; + + ///////// ick, had to hard-code the width against RAM_ADDR_BITS which is parameterized + assign reg_0 = I2C_regblock[5'h0]; + assign reg_1 = I2C_regblock[5'h1]; + assign reg_2 = I2C_regblock[5'h2]; + assign reg_3 = I2C_regblock[5'h3]; + assign reg_4 = I2C_regblock[5'h4]; + assign reg_5 = I2C_regblock[5'h5]; + assign reg_6 = I2C_regblock[5'h6]; + assign reg_7 = I2C_regblock[5'h7]; + assign reg_8 = I2C_regblock[5'h8]; + assign reg_9 = I2C_regblock[5'h9]; + assign reg_a = I2C_regblock[5'ha]; + assign reg_b = I2C_regblock[5'hb]; + + assign reg_c = I2C_regblock[5'hc]; + assign reg_d = I2C_regblock[5'hd]; + assign reg_e = I2C_regblock[5'he]; + assign reg_f = I2C_regblock[5'hf]; + + assign reg_11 = I2C_regblock[5'h11]; + assign reg_12 = I2C_regblock[5'h12]; + + assign reg_13 = I2C_regblock[5'h13]; + assign reg_14 = I2C_regblock[5'h14]; + + assign reg_15 = I2C_regblock[5'h15]; + assign reg_16 = I2C_regblock[5'h16]; + assign reg_17 = I2C_regblock[5'h17]; + +// assign reg_18 = I2C_regblock[5'h18]; + + assign reg_19 = I2C_regblock[5'h19]; // lsb of Km + assign reg_1a = I2C_regblock[5'h1a]; + assign reg_1b = I2C_regblock[5'h1b]; + assign reg_1c = I2C_regblock[5'h1c]; + assign reg_1d = I2C_regblock[5'h1d]; + assign reg_1e = I2C_regblock[5'h1e]; + assign reg_1f = I2C_regblock[5'h1f]; // msb of Km + + //////////////// + ///// SCL low-level sampling state machine + //////////////// + parameter SCL_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SCL_FALL = 4'b1 << 1; + parameter SCL_LOW = 4'b1 << 2; + parameter SCL_RISE = 4'b1 << 3; + parameter SCL_nSTATES = 4; + + reg [(SCL_nSTATES-1):0] SCL_cstate = {{(SCL_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SCL_nSTATES-1):0] SCL_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SCL_state_ascii = "SCL_HIGH "; + + always @(SCL_cstate) begin + if (SCL_cstate == SCL_HIGH) SCL_state_ascii <= "SCL_HIGH "; + else if (SCL_cstate == SCL_FALL) SCL_state_ascii <= "SCL_FALL "; + else if (SCL_cstate == SCL_LOW ) SCL_state_ascii <= "SCL_LOW "; + else if (SCL_cstate == SCL_RISE) SCL_state_ascii <= "SCL_RISE "; + else SCL_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SCL_rfcnt; + reg SCL_s, SCL_sync; + reg SDA_s, SDA_sync; + + always @ (posedge clk or posedge reset) begin + if (reset) + SCL_cstate <= SCL_HIGH; // always start here even if it's wrong -- easier to test + else + SCL_cstate <=#1 SCL_nstate; + end + + always @ (*) begin + case (SCL_cstate) //synthesis parallel_case full_case + SCL_HIGH: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b0)) ? SCL_FALL : SCL_HIGH; + end + SCL_FALL: begin + SCL_nstate = SCL_LOW; + end + SCL_LOW: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b1)) ? SCL_RISE : SCL_LOW; + end + SCL_RISE: begin + SCL_nstate = SCL_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + case (SCL_cstate) // synthesis parallel_case full_case + SCL_HIGH: begin + if( SCL_sync == 1'b1 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_FALL: begin + SCL_rfcnt <=#1 5'b0; + end + SCL_LOW: begin + if( SCL_sync == 1'b0 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_RISE: begin + SCL_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + //////////////// + ///// SDA low-level sampling state machine + //////////////// + parameter SDA_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SDA_FALL = 4'b1 << 1; + parameter SDA_LOW = 4'b1 << 2; + parameter SDA_RISE = 4'b1 << 3; + parameter SDA_nSTATES = 4; + + reg [(SDA_nSTATES-1):0] SDA_cstate = {{(SDA_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SDA_nSTATES-1):0] SDA_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SDA_state_ascii = "SDA_HIGH "; + + always @(SDA_cstate) begin + if (SDA_cstate == SDA_HIGH) SDA_state_ascii <= "SDA_HIGH "; + else if (SDA_cstate == SDA_FALL) SDA_state_ascii <= "SDA_FALL "; + else if (SDA_cstate == SDA_LOW ) SDA_state_ascii <= "SDA_LOW "; + else if (SDA_cstate == SDA_RISE) SDA_state_ascii <= "SDA_RISE "; + else SDA_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SDA_rfcnt; + + always @ (posedge clk or posedge reset) begin + if (reset) + SDA_cstate <= SDA_HIGH; // always start here even if it's wrong -- easier to test + else + SDA_cstate <=#1 SDA_nstate; + end + + always @ (*) begin + case (SDA_cstate) //synthesis parallel_case full_case + SDA_HIGH: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b0)) ? SDA_FALL : SDA_HIGH; + end + SDA_FALL: begin + SDA_nstate = SDA_LOW; + end + SDA_LOW: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b1)) ? SDA_RISE : SDA_LOW; + end + SDA_RISE: begin + SDA_nstate = SDA_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + case (SDA_cstate) // synthesis parallel_case full_case + SDA_HIGH: begin + if( SDA_sync == 1'b1 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_FALL: begin + SDA_rfcnt <=#1 5'b0; + end + SDA_LOW: begin + if( SDA_sync == 1'b0 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_RISE: begin + SDA_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + + ///////////////////// + /////// synchronizers + ///////////////////// + always @ (posedge clk or posedge reset) begin + if (reset) begin + SCL_s <= 0; + SCL_sync <= 0; + SDA_s <= 0; + SDA_sync <= 0; + end else begin + SCL_s <= SCL; + SCL_sync <= SCL_s; + SDA_s <= SDA; + SDA_sync <= SDA_s; + end // else: !if(reset) + end // always @ (posedge clk or posedge reset) + +endmodule // i2c_slave diff --git a/common/i2c_slave_tb.v b/common/i2c_slave_tb.v new file mode 100755 index 0000000..1713aa9 --- /dev/null +++ b/common/i2c_slave_tb.v @@ -0,0 +1,375 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +`timescale 1ns / 1ps + +//////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 12:30:45 04/05/2011 +// Design Name: i2c_slave +// Module Name: C:/largework/fpga/hdmi/impl4/common/i2c_slave_tb.v +// Project Name: impl4 +// Target Device: +// Tool versions: +// Description: +// +// Verilog Test Fixture created by ISE for module: i2c_slave +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +//////////////////////////////////////////////////////////////////////////////// + +module i2c_slave_tb; + + reg SDA; // physical wire state + reg SCL; + + // Inputs + reg clk; + reg reset; + reg [7:0] i2c_device_addr; + reg [7:0] reg_addr; + reg wr_stb; + reg [7:0] reg_data_in; + + // Outputs + wire SCL_pd; + wire SDA_pd; + wire SDA_pu; + wire [7:0] reg_a0; + wire [7:0] reg_a1; + wire [7:0] reg_a2; + wire [7:0] reg_a3; + + // Instantiate the Unit Under Test (UUT) + i2c_slave uut ( + .SCL(SCL), + .SCL_pd(SCL_pd), + .SDA(SDA), + .SDA_pd(SDA_pd), + .SDA_pu(SDA_pu), + .clk(clk), + .reset(reset), + .i2c_device_addr(i2c_device_addr), + .reg_addr(reg_addr), + .wr_stb(wr_stb), + .reg_data_in(reg_data_in), + .reg_0(reg_a0), + .reg_1(reg_a1), + .reg_2(reg_a2), + .reg_3(reg_a3) + ); + + reg sda_host; // what the host is driving to + reg scl_host; + reg ack; // what the ack state is + reg [7:0] readdata; + +// always @(SCL_pd, SDA_pd, SDA_pu, sda_host, scl_host) begin + always @(*) begin // lazy + // scl equations + case( {SCL_pd, scl_host} ) + 2'b00: SCL <= 1'b0; + 2'b01: SCL <= 1'b1; + 2'b10: SCL <= 1'b0; + // conflict case + 2'b11: SCL <= 1'bX; + // handle tristate case + 2'b0Z: SCL <= 1'b1; + 2'b1Z: SCL <= 1'b0; + default: SCL <= 1'bX; + endcase // case ( {SCL_pd, scl_host} ) + + case( {SDA_pd, SDA_pu, sda_host} ) + 3'b000: SDA <= 1'b0; + 3'b001: SDA <= 1'b1; + 3'b010: SDA <= 1'bX; // change to 1'b1 for override + 3'b011: SDA <= 1'b1; + 3'b100: SDA <= 1'b0; + 3'b101: SDA <= 1'bX; // change to 1'b0 for override + 3'b110: SDA <= 1'bX; + 3'b111: SDA <= 1'bX; + + // tristate case + 3'b00Z: SDA <= 1'b1; + 3'b01Z: SDA <= 1'b1; + 3'b10Z: SDA <= 1'b0; + 3'b11Z: SDA <= 1'bX; + endcase // case ( {SDA_pd, SDA_pu, sda_host} ) + end + + parameter PERIOD = 16'd40; // 25 MHz + parameter I2C_PD = 16'd2464; // make it odd to try and catch non-phase synced issues + parameter I2C_TH = 16'd114; + parameter I2C_TS = 16'd217; + + always begin + clk = 1'b0; + #(PERIOD/2) clk = 1'b1; + #(PERIOD/2); + end + + task I2C_idle; + begin + scl_host = 1'bZ; + sda_host = 1'bZ; + #I2C_PD; + end + endtask // I2C_idle + + task I2C_start; + begin + scl_host = 1'bZ; + sda_host = 1'bZ; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'b0; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_stop; + begin + scl_host = 1'bZ; + sda_host = 1'b0; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'bZ; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_tx_bit; // tx from host ( from testbench ) + input bitval; + + begin + scl_host = 1'b0; + #I2C_TH; + sda_host = bitval; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = bitval; + #(I2C_PD/2); + end + endtask // I2C_tx_bit + + task I2C_rx_bit; // rx to host ( to testbench ) + output bitval; + + begin + scl_host = 1'b0; + #(I2C_TH/2); + sda_host = 1'bz; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'bz; + #1; + bitval = SDA; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_ack_low; + begin + scl_host = 1'b0; + #(I2C_PD/2); + end + endtask // I2C_ack_low + + + task I2C_tx_daddr; + input [7:0] daddr; + output rack; + + begin + I2C_tx_bit( daddr[7] ); + I2C_tx_bit( daddr[6] ); + I2C_tx_bit( daddr[5] ); + I2C_tx_bit( daddr[4] ); + I2C_tx_bit( daddr[3] ); + I2C_tx_bit( daddr[2] ); + I2C_tx_bit( daddr[1] ); + I2C_tx_bit( daddr[0] ); + I2C_rx_bit(rack); + I2C_ack_low(); + end + endtask // I2C_TX_DADDR + + task I2C_rx_daddr; + output [7:0] daddr; + input nack; + + begin + I2C_rx_bit(daddr[7]); + I2C_rx_bit(daddr[6]); + I2C_rx_bit(daddr[5]); + I2C_rx_bit(daddr[4]); + I2C_rx_bit(daddr[3]); + I2C_rx_bit(daddr[2]); + I2C_rx_bit(daddr[1]); + I2C_rx_bit(daddr[0]); + I2C_tx_bit( nack ); + I2C_ack_low(); + end + endtask // I2C_RX_DADDR + + initial begin + // Initialize Inputs + clk = 0; + reset = 0; + i2c_device_addr = 8'h72; + reg_addr = 0; + wr_stb = 0; + reg_data_in = 0; + + $stop; + + I2C_idle(); + // run an actual reset cycle + #(PERIOD*4); + reset = 1; + #(PERIOD*4); + reset = 0; + #(PERIOD*4); + + // now pre-set a few I2C registers + reg_addr = 0; + wr_stb = 1; + reg_data_in = 8'hDE; + #(PERIOD*4); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 1; + wr_stb = 1; + reg_data_in = 8'hAD; + #(PERIOD*2); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 2; + wr_stb = 1; + reg_data_in = 8'hBE; + #(PERIOD*2); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 3; + wr_stb = 1; + reg_data_in = 8'hEF; + #(PERIOD*2); + + + // let it soak for a bit for good measure + #(PERIOD*10); + + // now the real sim starts + I2C_idle(); + + // write some data + I2C_start(); + I2C_tx_daddr(8'h72, ack); // write to device 72 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'h33, ack); // data 55 + I2C_stop(); + #(I2C_PD*5); + + // do a multi-cycle read + I2C_start(); + I2C_tx_daddr(8'h72, ack); // dummy write to 72 + I2C_tx_daddr(8'h00, ack); // address 00 + I2C_start(); + I2C_tx_daddr(8'h73, ack); // read from 72 +// #(I2C_PD*3); + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 +// #(I2C_PD*3); + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 + I2C_rx_daddr(readdata, 1'bz); // data @ address 0 + I2C_stop(); + #(I2C_PD*5); + + // do a multi-cycle write + I2C_start(); + I2C_tx_daddr(8'h72, ack); // write to device 70 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'hFA, ack); + I2C_tx_daddr(8'hCE, ack); + I2C_tx_daddr(8'h69, ack); + I2C_stop(); + #(I2C_PD*5); + + // read back one address at a time + I2C_start(); + I2C_tx_daddr(8'h72, ack); // dummy write to 72 + I2C_tx_daddr(8'h00, ack); // address 00 + + #(I2C_PD*5); + I2C_start(); + I2C_tx_daddr(8'h73, ack); // read from 72 + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + // this is the only questionable vector + // if you do an isolated read, should the address have + // incremeted from the previous read, or + // should it be the same. I have implemented it so + // that it increments. + I2C_start(); + I2C_tx_daddr(8'h73, ack); // read from 72 + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + #(I2C_PD*5); + + I2C_start(); + I2C_tx_daddr(8'h73, ack); // read from 72 + I2C_rx_daddr(readdata, 1'b0); // one read + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + + // write to another device not us + I2C_start(); + I2C_tx_daddr(8'hA0, ack); // write to device a0 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'h55, ack); // data 55 -- this should be ignored + I2C_stop(); + + #I2C_PD; + #I2C_PD; + end + +endmodule + diff --git a/common/i2c_snoop.v b/common/i2c_snoop.v new file mode 100755 index 0000000..edea84e --- /dev/null +++ b/common/i2c_snoop.v @@ -0,0 +1,625 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +// An I2C bus snooper implementation. Oversampled for robustness. +// +// There are two versions, the EDID snooper and the HDCP snooper +// +// It's split because EDID records can be very large and the compiler should +// infer a block ram for the large records. However, the nature of the HDCP +// registers would cause the compiler to infer slice registers. Thus, this code +// is identical to the HDCP snoop with the exception that the HDCP read ports +// are removed which will allow the compiler to properly infer a LUT RAM. +/////////// +`timescale 1 ns / 1 ps + +module i2c_snoop ( // HDCP snooper + // external host interface + input wire SCL, // the SCL pin state + input wire SDA, + + input wire clk, // internal FPGA clock + input wire reset, // internal FPGA reset + // i2c configuration + input wire [7:0] i2c_snoop_addr, + + // internal slave interface to read snooped register + input wire [7:0] reg_addr, + output wire [7:0] reg_dout, + + output wire [63:0] An, // An (applies only to HDCP snooper) + output reg Aksv14_write // strobes on last byte of Aksv write (triggers Auth) + ); + + /////// I2C physical layer components + /// SDA is stable when SCL is high. + /// If SDA moves while SCL is high, this is considered a start or stop condition. + /// + /// Otherwise, SDA can move around when SCL is low (this is where we suppress bits or + /// overdrive as needed). SDA is a wired-AND bus, so you only "drive" zero. + /// + /// In an oversampled implementation, a rising and falling edge de-glitcher is needed + /// for SCL and SDA. + /// + + // rise fall time cycles computation: + // At 400kHz operation, 2.5us is a cycle. "chatter" from transition should be about + // 5% of total cycle time max (just rule of thumb), so 0.125us should be the equiv + // number of cycles. + // For the demo board, a 25 MHz clock is provided, and 0.125us ~ 4 cycles + // At 100kHz operation, 10us is a cycle, so 0.5us ~ 12 cycles + parameter TRF_CYCLES = 5'd4; // number of cycles for rise/fall time + + //////////////// + ///// protocol-level state machine + //////////////// + parameter I2C_START = 14'b1 << 0; // should only pass through this state for one cycle + parameter I2C_RESTART = 14'b1 << 1; + parameter I2C_DADDR = 14'b1 << 2; + parameter I2C_ACK_DADDR = 14'b1 << 3; + parameter I2C_ADDR = 14'b1 << 4; + parameter I2C_ACK_ADDR = 14'b1 << 5; + parameter I2C_WR_DATA = 14'b1 << 6; + parameter I2C_ACK_WR = 14'b1 << 7; + parameter I2C_END_WR = 14'b1 << 8; + parameter I2C_RD_DATA = 14'b1 << 9; + parameter I2C_ACK_RD = 14'b1 << 10; + parameter I2C_END_RD = 14'b1 << 11; + parameter I2C_END_RD2 = 14'b1 << 12; + parameter I2C_WAITSTOP = 14'b1 << 13; + + parameter I2C_nSTATES = 14; + + reg [(I2C_nSTATES-1):0] I2C_cstate = {{(I2C_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(I2C_nSTATES-1):0] I2C_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] I2C_state_ascii = "I2C_START "; + always @(I2C_cstate) begin + if (I2C_cstate == I2C_START) I2C_state_ascii <= "I2C_START "; + else if (I2C_cstate == I2C_RESTART) I2C_state_ascii <= "I2C_RESTART "; + else if (I2C_cstate == I2C_DADDR) I2C_state_ascii <= "I2C_DADDR "; + else if (I2C_cstate == I2C_ACK_DADDR) I2C_state_ascii <= "I2C_ACK_DADDR "; + else if (I2C_cstate == I2C_ADDR) I2C_state_ascii <= "I2C_ADDR "; + else if (I2C_cstate == I2C_ACK_ADDR) I2C_state_ascii <= "I2C_ACK_ADDR "; + else if (I2C_cstate == I2C_WR_DATA) I2C_state_ascii <= "I2C_WR_DATA "; + else if (I2C_cstate == I2C_ACK_WR) I2C_state_ascii <= "I2C_ACK_WR "; + else if (I2C_cstate == I2C_END_WR) I2C_state_ascii <= "I2C_END_WR "; + else if (I2C_cstate == I2C_RD_DATA) I2C_state_ascii <= "I2C_RD_DATA "; + else if (I2C_cstate == I2C_ACK_RD) I2C_state_ascii <= "I2C_ACK_RD "; + else if (I2C_cstate == I2C_END_RD) I2C_state_ascii <= "I2C_END_RD "; + else if (I2C_cstate == I2C_END_RD2) I2C_state_ascii <= "I2C_END_RD2 "; + else if (I2C_cstate == I2C_WAITSTOP) I2C_state_ascii <= "I2C_WAITSTOP "; + else I2C_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [3:0] I2C_bitcnt; + reg [7:0] I2C_addr; + reg [7:0] I2C_daddr; + reg [7:0] I2C_wdata; + reg [7:0] I2C_rdata; + reg I2C_reg_update; + + always @ (posedge clk) begin + if (reset || ((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) // stop condition always resets + I2C_cstate <= I2C_START; + else + I2C_cstate <=#1 I2C_nstate; + end + + always @ (*) begin + case (I2C_cstate) //synthesis parallel_case full_case + I2C_START: begin // wait for the start condition + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_DADDR : I2C_START; + end + I2C_RESTART: begin // repeated start moves immediately to DADDR + I2C_nstate = I2C_DADDR; + end + I2C_DADDR: begin // 8 bits to get the address + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_DADDR : I2C_DADDR; + end + I2C_ACK_DADDR: begin // depending upon W/R bit state, go to one of two branches + I2C_nstate = (SCL_cstate == SCL_FALL) ? + (I2C_daddr[7:1] == i2c_snoop_addr[7:1]) ? + (I2C_daddr[0] == 1'b0 ? I2C_ADDR : I2C_RD_DATA) : + I2C_WAITSTOP : // !I2C_daddr match + I2C_ACK_DADDR; // !SCL_FALL + end + + // device address branch + I2C_ADDR: begin + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_ADDR : I2C_ADDR; + end + I2C_ACK_ADDR: begin + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_WR_DATA : I2C_ACK_ADDR; + end + + // write branch + I2C_WR_DATA: begin // 8 bits to get the write data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_WR : I2C_WR_DATA; + end + I2C_ACK_WR: begin // trigger the ack response (pull SDA low until next falling edge) + // and stay in this state until the next falling edge of SCL + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_END_WR : I2C_ACK_WR; + end + I2C_END_WR: begin // one-cycle state to update address+1, reset SDA pulldown + I2C_nstate = I2C_WR_DATA; // SCL is now low + end + + // read branch + I2C_RD_DATA: begin // 8 bits to get the read data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_RD : I2C_RD_DATA; + end + I2C_ACK_RD: begin // wait for an (n)ack response + // need to sample (n)ack on a rising edge + I2C_nstate = (SCL_cstate == SCL_RISE) ? I2C_END_RD : I2C_ACK_RD; + end + I2C_END_RD: begin // if nack, just go to start state (don't explicitly check stop event) + // single cycle state for adr+1 update + I2C_nstate = (SDA_cstate == SDA_LOW) ? I2C_END_RD2 : I2C_START; + end + I2C_END_RD2: begin // before entering I2C_RD_DATA, we need to have seen a falling edge. + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_RD_DATA : I2C_END_RD2; + end + + // we're not the addressed device, so we just idle until we see a stop + I2C_WAITSTOP: begin + I2C_nstate = (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) ? // stop + I2C_START : + (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_FALL))) ? // or start + I2C_RESTART : + I2C_WAITSTOP; + end + endcase // case (cstate) + end + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_rdata <=#1 8'b0; + I2C_addr <=#1 8'b0; // this persists across transactions + end else begin + case (I2C_cstate) // synthesis parallel_case full_case + I2C_START: begin // everything in reset + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + I2C_RESTART: begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + // get my i2c device address (am I being talked to?) + I2C_DADDR: begin // shift in the address on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_daddr[7] <=#1 I2C_daddr[6]; + I2C_daddr[6] <=#1 I2C_daddr[5]; + I2C_daddr[5] <=#1 I2C_daddr[4]; + I2C_daddr[4] <=#1 I2C_daddr[3]; + I2C_daddr[3] <=#1 I2C_daddr[2]; + I2C_daddr[2] <=#1 I2C_daddr[1]; + I2C_daddr[1] <=#1 I2C_daddr[0]; + I2C_daddr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_daddr <=#1 I2C_daddr; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end // case: I2C_DADDR + I2C_ACK_DADDR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_regread_async; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + // get my i2c "write" address (what we want to access inside me) + I2C_ADDR: begin + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_addr[7] <=#1 I2C_addr[6]; + I2C_addr[6] <=#1 I2C_addr[5]; + I2C_addr[5] <=#1 I2C_addr[4]; + I2C_addr[4] <=#1 I2C_addr[3]; + I2C_addr[3] <=#1 I2C_addr[2]; + I2C_addr[2] <=#1 I2C_addr[1]; + I2C_addr[1] <=#1 I2C_addr[0]; + I2C_addr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_addr <=#1 I2C_addr; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + end // case: I2C_ADDR + I2C_ACK_ADDR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_regread_async; // update my read data here + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + + // write branch + I2C_WR_DATA: begin // shift in data on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_wdata[7] <=#1 I2C_wdata[6]; + I2C_wdata[6] <=#1 I2C_wdata[5]; + I2C_wdata[5] <=#1 I2C_wdata[4]; + I2C_wdata[4] <=#1 I2C_wdata[3]; + I2C_wdata[3] <=#1 I2C_wdata[2]; + I2C_wdata[2] <=#1 I2C_wdata[1]; + I2C_wdata[1] <=#1 I2C_wdata[0]; + I2C_wdata[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + I2C_wdata <=#1 I2C_wdata; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_addr <=#1 I2C_addr; + end // case: I2C_WR_DATA + I2C_ACK_WR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_reg_update <=#1 1'b1; // write the data now (over and over again while in state) + I2C_rdata <=#1 I2C_rdata; + I2C_addr <=#1 I2C_addr; + end + I2C_END_WR: begin + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + end + + // read branch + I2C_RD_DATA: begin // shift out data on falling edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + + I2C_rdata[7] <=#1 I2C_rdata[6]; + I2C_rdata[6] <=#1 I2C_rdata[5]; + I2C_rdata[5] <=#1 I2C_rdata[4]; + I2C_rdata[4] <=#1 I2C_rdata[3]; + I2C_rdata[3] <=#1 I2C_rdata[2]; + I2C_rdata[2] <=#1 I2C_rdata[1]; + I2C_rdata[1] <=#1 I2C_rdata[0]; + I2C_rdata[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + I2C_rdata <=#1 I2C_rdata; + end + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_rdata; // push rdata to wdata + I2C_addr <=#1 I2C_addr; + end // case: I2C_RD_DATA + I2C_ACK_RD: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b1; // commit reads even to our internal bank + I2C_wdata <=#1 I2C_rdata; // push rdata to wdata + I2C_addr <=#1 I2C_addr; + end + I2C_END_RD: begin + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_daddr <=#1 I2C_daddr; + end + I2C_END_RD2: begin + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + + I2C_WAITSTOP: begin + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + always @(posedge clk) begin + if( reset ) begin + Aksv14_write <=#1 1'b0; + end else begin + if( (I2C_addr == 8'h14) && (I2C_cstate == I2C_ACK_WR ) ) begin + Aksv14_write <=#1 1'b1; + end else begin + Aksv14_write <=#1 1'b0; + end + end + end // always @ (posedge clk) + + //////////////// + ///// register bank management + //////////////// + parameter RAM_WIDTH = 8; + parameter RAM_ADDR_BITS = 5; // note parameter width exception in An[*] assign block below + + (* RAM_STYLE="{AUTO | DISTRIBUTED | PIPE_DISTRIBUTED}" *) + reg [RAM_WIDTH-1:0] I2C_regblock [(2**RAM_ADDR_BITS)-1:0]; + wire [RAM_WIDTH-1:0] I2C_regread_async; + + wire [RAM_ADDR_BITS-1:0] I2C_ramaddr; + + reg wr_stb_d; + + always @(posedge clk) begin + // added bounds check to avoid overwriting Ksv if other sections of HDCP area is checked + if ((I2C_reg_update && (I2C_cstate == I2C_ACK_WR) && (I2C_addr[7:0] < 8'h20)) || + (I2C_reg_update && (I2C_cstate == I2C_ACK_RD) && (I2C_addr[7:0] < 8'h5)) ) begin + // this should be multiple cycles + I2C_regblock[I2C_ramaddr] <= I2C_wdata; + end + end + + assign I2C_regread_async = I2C_regblock[I2C_ramaddr]; + assign reg_dout = I2C_regblock[reg_addr[RAM_ADDR_BITS-1:0]]; + + assign I2C_ramaddr = I2C_addr[RAM_ADDR_BITS-1:0]; + + assign An[7:0] = I2C_regblock[5'h18]; + assign An[15:8] = I2C_regblock[5'h19]; + assign An[23:16] = I2C_regblock[5'h1a]; + assign An[31:24] = I2C_regblock[5'h1b]; + assign An[39:32] = I2C_regblock[5'h1c]; + assign An[47:40] = I2C_regblock[5'h1d]; + assign An[55:48] = I2C_regblock[5'h1e]; + assign An[63:56] = I2C_regblock[5'h1f]; + + //////////////// + ///// SCL low-level sampling state machine + //////////////// + parameter SCL_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SCL_FALL = 4'b1 << 1; + parameter SCL_LOW = 4'b1 << 2; + parameter SCL_RISE = 4'b1 << 3; + parameter SCL_nSTATES = 4; + + reg [(SCL_nSTATES-1):0] SCL_cstate = {{(SCL_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SCL_nSTATES-1):0] SCL_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SCL_state_ascii = "SCL_HIGH "; + + always @(SCL_cstate) begin + if (SCL_cstate == SCL_HIGH) SCL_state_ascii <= "SCL_HIGH "; + else if (SCL_cstate == SCL_FALL) SCL_state_ascii <= "SCL_FALL "; + else if (SCL_cstate == SCL_LOW ) SCL_state_ascii <= "SCL_LOW "; + else if (SCL_cstate == SCL_RISE) SCL_state_ascii <= "SCL_RISE "; + else SCL_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SCL_rfcnt; + reg SCL_s, SCL_sync; + reg SDA_s, SDA_sync; + + always @ (posedge clk or posedge reset) begin + if (reset) + SCL_cstate <= SCL_HIGH; // always start here even if it's wrong -- easier to test + else + SCL_cstate <=#1 SCL_nstate; + end + + always @ (*) begin + case (SCL_cstate) //synthesis parallel_case full_case + SCL_HIGH: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b0)) ? SCL_FALL : SCL_HIGH; + end + SCL_FALL: begin + SCL_nstate = SCL_LOW; + end + SCL_LOW: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b1)) ? SCL_RISE : SCL_LOW; + end + SCL_RISE: begin + SCL_nstate = SCL_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + case (SCL_cstate) // synthesis parallel_case full_case + SCL_HIGH: begin + if( SCL_sync == 1'b1 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_FALL: begin + SCL_rfcnt <=#1 5'b0; + end + SCL_LOW: begin + if( SCL_sync == 1'b0 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_RISE: begin + SCL_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + //////////////// + ///// SDA low-level sampling state machine + //////////////// + parameter SDA_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SDA_FALL = 4'b1 << 1; + parameter SDA_LOW = 4'b1 << 2; + parameter SDA_RISE = 4'b1 << 3; + parameter SDA_nSTATES = 4; + + reg [(SDA_nSTATES-1):0] SDA_cstate = {{(SDA_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SDA_nSTATES-1):0] SDA_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SDA_state_ascii = "SDA_HIGH "; + + always @(SDA_cstate) begin + if (SDA_cstate == SDA_HIGH) SDA_state_ascii <= "SDA_HIGH "; + else if (SDA_cstate == SDA_FALL) SDA_state_ascii <= "SDA_FALL "; + else if (SDA_cstate == SDA_LOW ) SDA_state_ascii <= "SDA_LOW "; + else if (SDA_cstate == SDA_RISE) SDA_state_ascii <= "SDA_RISE "; + else SDA_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SDA_rfcnt; + + always @ (posedge clk or posedge reset) begin + if (reset) + SDA_cstate <= SDA_HIGH; // always start here even if it's wrong -- easier to test + else + SDA_cstate <=#1 SDA_nstate; + end + + always @ (*) begin + case (SDA_cstate) //synthesis parallel_case full_case + SDA_HIGH: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b0)) ? SDA_FALL : SDA_HIGH; + end + SDA_FALL: begin + SDA_nstate = SDA_LOW; + end + SDA_LOW: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b1)) ? SDA_RISE : SDA_LOW; + end + SDA_RISE: begin + SDA_nstate = SDA_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + case (SDA_cstate) // synthesis parallel_case full_case + SDA_HIGH: begin + if( SDA_sync == 1'b1 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_FALL: begin + SDA_rfcnt <=#1 5'b0; + end + SDA_LOW: begin + if( SDA_sync == 1'b0 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_RISE: begin + SDA_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + + ///////////////////// + /////// synchronizers + ///////////////////// + always @ (posedge clk or posedge reset) begin + if (reset) begin + SCL_s <= 0; + SCL_sync <= 0; + SDA_s <= 0; + SDA_sync <= 0; + end else begin + SCL_s <= SCL; + SCL_sync <= SCL_s; + SDA_s <= SDA; + SDA_sync <= SDA_s; + end // else: !if(reset) + end // always @ (posedge clk or posedge reset) + +endmodule // i2c_slave diff --git a/common/i2c_snoop_edid.v b/common/i2c_snoop_edid.v new file mode 100755 index 0000000..547bea0 --- /dev/null +++ b/common/i2c_snoop_edid.v @@ -0,0 +1,603 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +// An I2C bus snooper implementation. Oversampled for robustness. +// +// There are two versions, the EDID snooper and the HDCP snooper +// +// It's split because EDID records can be very large and the compiler should +// infer a block ram for the large records. However, the nature of the HDCP +// registers would cause the compiler to infer slice registers. Thus, this code +// is identical to the HDCP snoop with the exception that the HDCP read ports +// are removed which will allow the compiler to properly infer a LUT RAM. +/////////// +`timescale 1 ns / 1 ps + +module i2c_snoop_edid ( + // external host interface + input wire SCL, // the SCL pin state + input wire SDA, + + input wire clk, // internal FPGA clock + input wire reset, // internal FPGA reset + // i2c configuration + input wire [7:0] i2c_snoop_addr, + + // internal slave interface to read snooped register + input wire [7:0] reg_addr, + output wire [7:0] reg_dout + + ); + + /////// I2C physical layer components + /// SDA is stable when SCL is high. + /// If SDA moves while SCL is high, this is considered a start or stop condition. + /// + /// Otherwise, SDA can move around when SCL is low (this is where we suppress bits or + /// overdrive as needed). SDA is a wired-AND bus, so you only "drive" zero. + /// + /// In an oversampled implementation, a rising and falling edge de-glitcher is needed + /// for SCL and SDA. + /// + + // rise fall time cycles computation: + // At 400kHz operation, 2.5us is a cycle. "chatter" from transition should be about + // 5% of total cycle time max (just rule of thumb), so 0.125us should be the equiv + // number of cycles. + // For the demo board, a 25 MHz clock is provided, and 0.125us ~ 4 cycles + // At 100kHz operation, 10us is a cycle, so 0.5us ~ 12 cycles + parameter TRF_CYCLES = 5'd4; // number of cycles for rise/fall time + + // just some tie-offs for future functionality not yet implemented... + assign SDA_pu = 1'b0; + assign SCL_pd = 1'b0; + + //////////////// + ///// protocol-level state machine + //////////////// + parameter I2C_START = 14'b1 << 0; // should only pass through this state for one cycle + parameter I2C_RESTART = 14'b1 << 1; + parameter I2C_DADDR = 14'b1 << 2; + parameter I2C_ACK_DADDR = 14'b1 << 3; + parameter I2C_ADDR = 14'b1 << 4; + parameter I2C_ACK_ADDR = 14'b1 << 5; + parameter I2C_WR_DATA = 14'b1 << 6; + parameter I2C_ACK_WR = 14'b1 << 7; + parameter I2C_END_WR = 14'b1 << 8; + parameter I2C_RD_DATA = 14'b1 << 9; + parameter I2C_ACK_RD = 14'b1 << 10; + parameter I2C_END_RD = 14'b1 << 11; + parameter I2C_END_RD2 = 14'b1 << 12; + parameter I2C_WAITSTOP = 14'b1 << 13; + + parameter I2C_nSTATES = 14; + + reg [(I2C_nSTATES-1):0] I2C_cstate = {{(I2C_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(I2C_nSTATES-1):0] I2C_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] I2C_state_ascii = "I2C_START "; + always @(I2C_cstate) begin + if (I2C_cstate == I2C_START) I2C_state_ascii <= "I2C_START "; + else if (I2C_cstate == I2C_RESTART) I2C_state_ascii <= "I2C_RESTART "; + else if (I2C_cstate == I2C_DADDR) I2C_state_ascii <= "I2C_DADDR "; + else if (I2C_cstate == I2C_ACK_DADDR) I2C_state_ascii <= "I2C_ACK_DADDR "; + else if (I2C_cstate == I2C_ADDR) I2C_state_ascii <= "I2C_ADDR "; + else if (I2C_cstate == I2C_ACK_ADDR) I2C_state_ascii <= "I2C_ACK_ADDR "; + else if (I2C_cstate == I2C_WR_DATA) I2C_state_ascii <= "I2C_WR_DATA "; + else if (I2C_cstate == I2C_ACK_WR) I2C_state_ascii <= "I2C_ACK_WR "; + else if (I2C_cstate == I2C_END_WR) I2C_state_ascii <= "I2C_END_WR "; + else if (I2C_cstate == I2C_RD_DATA) I2C_state_ascii <= "I2C_RD_DATA "; + else if (I2C_cstate == I2C_ACK_RD) I2C_state_ascii <= "I2C_ACK_RD "; + else if (I2C_cstate == I2C_END_RD) I2C_state_ascii <= "I2C_END_RD "; + else if (I2C_cstate == I2C_END_RD2) I2C_state_ascii <= "I2C_END_RD2 "; + else if (I2C_cstate == I2C_WAITSTOP) I2C_state_ascii <= "I2C_WAITSTOP "; + else I2C_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [3:0] I2C_bitcnt; + reg [7:0] I2C_addr; + reg [7:0] I2C_daddr; + reg [7:0] I2C_wdata; + reg [7:0] I2C_rdata; + reg I2C_reg_update; + + always @ (posedge clk) begin + if (reset || ((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) // stop condition always resets + I2C_cstate <= I2C_START; + else + I2C_cstate <=#1 I2C_nstate; + end + + always @ (*) begin + case (I2C_cstate) //synthesis parallel_case full_case + I2C_START: begin // wait for the start condition + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_DADDR : I2C_START; + end + I2C_RESTART: begin // repeated start moves immediately to DADDR + I2C_nstate = I2C_DADDR; + end + I2C_DADDR: begin // 8 bits to get the address + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_DADDR : I2C_DADDR; + end + I2C_ACK_DADDR: begin // depending upon W/R bit state, go to one of two branches + I2C_nstate = (SCL_cstate == SCL_FALL) ? + (I2C_daddr[7:1] != i2c_snoop_addr[7:1]) ? // get everything *but* HDCP + (I2C_daddr[0] == 1'b0 ? I2C_ADDR : I2C_RD_DATA) : + I2C_WAITSTOP : // !I2C_daddr match + I2C_ACK_DADDR; // !SCL_FALL + end + + // device address branch + I2C_ADDR: begin + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_ADDR : I2C_ADDR; + end + I2C_ACK_ADDR: begin + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_WR_DATA : I2C_ACK_ADDR; + end + + // write branch + I2C_WR_DATA: begin // 8 bits to get the write data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_WR : I2C_WR_DATA; + end + I2C_ACK_WR: begin // trigger the ack response (pull SDA low until next falling edge) + // and stay in this state until the next falling edge of SCL + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_END_WR : I2C_ACK_WR; + end + I2C_END_WR: begin // one-cycle state to update address+1, reset SDA pulldown + I2C_nstate = I2C_WR_DATA; // SCL is now low + end + + // read branch + I2C_RD_DATA: begin // 8 bits to get the read data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_RD : I2C_RD_DATA; + end + I2C_ACK_RD: begin // wait for an (n)ack response + // need to sample (n)ack on a rising edge + I2C_nstate = (SCL_cstate == SCL_RISE) ? I2C_END_RD : I2C_ACK_RD; + end + I2C_END_RD: begin // if nack, just go to start state (don't explicitly check stop event) + // single cycle state for adr+1 update + I2C_nstate = (SDA_cstate == SDA_LOW) ? I2C_END_RD2 : I2C_START; + end + I2C_END_RD2: begin // before entering I2C_RD_DATA, we need to have seen a falling edge. + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_RD_DATA : I2C_END_RD2; + end + + // we're not the addressed device, so we just idle until we see a stop + I2C_WAITSTOP: begin + I2C_nstate = (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) ? // stop + I2C_START : + (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_FALL))) ? // or start + I2C_RESTART : + I2C_WAITSTOP; + end + endcase // case (cstate) + end + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_rdata <=#1 8'b0; + I2C_addr <=#1 8'b0; // this persists across transactions + end else begin + case (I2C_cstate) // synthesis parallel_case full_case + I2C_START: begin // everything in reset + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + I2C_RESTART: begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + // get my i2c device address (am I being talked to?) + I2C_DADDR: begin // shift in the address on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_daddr[7] <=#1 I2C_daddr[6]; + I2C_daddr[6] <=#1 I2C_daddr[5]; + I2C_daddr[5] <=#1 I2C_daddr[4]; + I2C_daddr[4] <=#1 I2C_daddr[3]; + I2C_daddr[3] <=#1 I2C_daddr[2]; + I2C_daddr[2] <=#1 I2C_daddr[1]; + I2C_daddr[1] <=#1 I2C_daddr[0]; + I2C_daddr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_daddr <=#1 I2C_daddr; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end // case: I2C_DADDR + I2C_ACK_DADDR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_regread_async; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + // get my i2c "write" address (what we want to access inside me) + I2C_ADDR: begin + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_addr[7] <=#1 I2C_addr[6]; + I2C_addr[6] <=#1 I2C_addr[5]; + I2C_addr[5] <=#1 I2C_addr[4]; + I2C_addr[4] <=#1 I2C_addr[3]; + I2C_addr[3] <=#1 I2C_addr[2]; + I2C_addr[2] <=#1 I2C_addr[1]; + I2C_addr[1] <=#1 I2C_addr[0]; + I2C_addr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_addr <=#1 I2C_addr; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + end // case: I2C_ADDR + I2C_ACK_ADDR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_regread_async; // update my read data here + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + end + + + // write branch + I2C_WR_DATA: begin // shift in data on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_wdata[7] <=#1 I2C_wdata[6]; + I2C_wdata[6] <=#1 I2C_wdata[5]; + I2C_wdata[5] <=#1 I2C_wdata[4]; + I2C_wdata[4] <=#1 I2C_wdata[3]; + I2C_wdata[3] <=#1 I2C_wdata[2]; + I2C_wdata[2] <=#1 I2C_wdata[1]; + I2C_wdata[1] <=#1 I2C_wdata[0]; + I2C_wdata[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + I2C_wdata <=#1 I2C_wdata; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_addr <=#1 I2C_addr; + end // case: I2C_WR_DATA + I2C_ACK_WR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_reg_update <=#1 1'b1; // write the data now (over and over again while in state) + I2C_rdata <=#1 I2C_rdata; + I2C_addr <=#1 I2C_addr; + end + I2C_END_WR: begin + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_wdata <=#1 8'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + end + + // read branch + I2C_RD_DATA: begin // shift out data on falling edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + + I2C_rdata[7] <=#1 I2C_rdata[6]; + I2C_rdata[6] <=#1 I2C_rdata[5]; + I2C_rdata[5] <=#1 I2C_rdata[4]; + I2C_rdata[4] <=#1 I2C_rdata[3]; + I2C_rdata[3] <=#1 I2C_rdata[2]; + I2C_rdata[2] <=#1 I2C_rdata[1]; + I2C_rdata[1] <=#1 I2C_rdata[0]; + I2C_rdata[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + I2C_rdata <=#1 I2C_rdata; + end + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_rdata; // push rdata to wdata + I2C_addr <=#1 I2C_addr; + end // case: I2C_RD_DATA + I2C_ACK_RD: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b1; // commit reads even to our internal bank + I2C_wdata <=#1 I2C_rdata; // push rdata to wdata + I2C_addr <=#1 I2C_addr; + end + I2C_END_RD: begin + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_daddr <=#1 I2C_daddr; + end + I2C_END_RD2: begin + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + + I2C_WAITSTOP: begin + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_rdata <=#1 I2C_rdata; + I2C_reg_update <=#1 1'b0; + I2C_wdata <=#1 I2C_wdata; + I2C_addr <=#1 I2C_addr; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + //////////////// + ///// register bank management + //////////////// + parameter RAM_WIDTH = 8; + parameter RAM_ADDR_BITS = 8; + + (* RAM_STYLE="{AUTO | DISTRIBUTED | PIPE_DISTRIBUTED}" *) + reg [RAM_WIDTH-1:0] I2C_regblock [(2**RAM_ADDR_BITS)-1:0]; + wire [RAM_WIDTH-1:0] I2C_regread_async; + + wire [RAM_ADDR_BITS-1:0] I2C_ramaddr; + + reg wr_stb_d; + + always @(posedge clk) begin + if (I2C_reg_update) begin // this should be multiple cycles + I2C_regblock[I2C_ramaddr] <= I2C_wdata; + end + end + + assign I2C_regread_async = I2C_regblock[I2C_ramaddr]; + assign reg_dout = I2C_regblock[reg_addr[RAM_ADDR_BITS-1:0]]; + + assign I2C_ramaddr = I2C_addr[RAM_ADDR_BITS-1:0]; + + //////////////// + ///// SCL low-level sampling state machine + //////////////// + parameter SCL_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SCL_FALL = 4'b1 << 1; + parameter SCL_LOW = 4'b1 << 2; + parameter SCL_RISE = 4'b1 << 3; + parameter SCL_nSTATES = 4; + + reg [(SCL_nSTATES-1):0] SCL_cstate = {{(SCL_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SCL_nSTATES-1):0] SCL_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SCL_state_ascii = "SCL_HIGH "; + + always @(SCL_cstate) begin + if (SCL_cstate == SCL_HIGH) SCL_state_ascii <= "SCL_HIGH "; + else if (SCL_cstate == SCL_FALL) SCL_state_ascii <= "SCL_FALL "; + else if (SCL_cstate == SCL_LOW ) SCL_state_ascii <= "SCL_LOW "; + else if (SCL_cstate == SCL_RISE) SCL_state_ascii <= "SCL_RISE "; + else SCL_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SCL_rfcnt; + reg SCL_s, SCL_sync; + reg SDA_s, SDA_sync; + + always @ (posedge clk or posedge reset) begin + if (reset) + SCL_cstate <= SCL_HIGH; // always start here even if it's wrong -- easier to test + else + SCL_cstate <=#1 SCL_nstate; + end + + always @ (*) begin + case (SCL_cstate) //synthesis parallel_case full_case + SCL_HIGH: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b0)) ? SCL_FALL : SCL_HIGH; + end + SCL_FALL: begin + SCL_nstate = SCL_LOW; + end + SCL_LOW: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b1)) ? SCL_RISE : SCL_LOW; + end + SCL_RISE: begin + SCL_nstate = SCL_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + case (SCL_cstate) // synthesis parallel_case full_case + SCL_HIGH: begin + if( SCL_sync == 1'b1 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_FALL: begin + SCL_rfcnt <=#1 5'b0; + end + SCL_LOW: begin + if( SCL_sync == 1'b0 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_RISE: begin + SCL_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + //////////////// + ///// SDA low-level sampling state machine + //////////////// + parameter SDA_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SDA_FALL = 4'b1 << 1; + parameter SDA_LOW = 4'b1 << 2; + parameter SDA_RISE = 4'b1 << 3; + parameter SDA_nSTATES = 4; + + reg [(SDA_nSTATES-1):0] SDA_cstate = {{(SDA_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SDA_nSTATES-1):0] SDA_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SDA_state_ascii = "SDA_HIGH "; + + always @(SDA_cstate) begin + if (SDA_cstate == SDA_HIGH) SDA_state_ascii <= "SDA_HIGH "; + else if (SDA_cstate == SDA_FALL) SDA_state_ascii <= "SDA_FALL "; + else if (SDA_cstate == SDA_LOW ) SDA_state_ascii <= "SDA_LOW "; + else if (SDA_cstate == SDA_RISE) SDA_state_ascii <= "SDA_RISE "; + else SDA_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SDA_rfcnt; + + always @ (posedge clk or posedge reset) begin + if (reset) + SDA_cstate <= SDA_HIGH; // always start here even if it's wrong -- easier to test + else + SDA_cstate <=#1 SDA_nstate; + end + + always @ (*) begin + case (SDA_cstate) //synthesis parallel_case full_case + SDA_HIGH: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b0)) ? SDA_FALL : SDA_HIGH; + end + SDA_FALL: begin + SDA_nstate = SDA_LOW; + end + SDA_LOW: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b1)) ? SDA_RISE : SDA_LOW; + end + SDA_RISE: begin + SDA_nstate = SDA_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + case (SDA_cstate) // synthesis parallel_case full_case + SDA_HIGH: begin + if( SDA_sync == 1'b1 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_FALL: begin + SDA_rfcnt <=#1 5'b0; + end + SDA_LOW: begin + if( SDA_sync == 1'b0 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_RISE: begin + SDA_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + + ///////////////////// + /////// synchronizers + ///////////////////// + always @ (posedge clk or posedge reset) begin + if (reset) begin + SCL_s <= 0; + SCL_sync <= 0; + SDA_s <= 0; + SDA_sync <= 0; + end else begin + SCL_s <= SCL; + SCL_sync <= SCL_s; + SDA_s <= SDA; + SDA_sync <= SDA_s; + end // else: !if(reset) + end // always @ (posedge clk or posedge reset) + +endmodule // i2c_slave diff --git a/common/i2c_snoop_tb.v b/common/i2c_snoop_tb.v new file mode 100755 index 0000000..592c559 --- /dev/null +++ b/common/i2c_snoop_tb.v @@ -0,0 +1,418 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +`timescale 1ns / 1ps + +//////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 12:30:45 04/05/2011 +// Design Name: i2c_slave +// Module Name: C:/largework/fpga/hdmi/impl4/common/i2c_slave_tb.v +// Project Name: impl4 +// Target Device: +// Tool versions: +// Description: +// +// Verilog Test Fixture created by ISE for module: i2c_slave +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +//////////////////////////////////////////////////////////////////////////////// + +module i2c_snoop_tb; + + reg SDA; // physical wire state + reg SCL; + + // Inputs + reg clk; + reg reset; + reg [7:0] i2c_device_addr; + reg [7:0] reg_addr; + reg wr_stb; + reg [7:0] reg_data_in; + + // Outputs + wire SCL_pd; + wire SDA_pd; + wire SDA_pu; + wire [7:0] reg_a0; + wire [7:0] reg_a1; + wire [7:0] reg_a2; + wire [7:0] reg_a3; + + reg [7:0] snoop_addr; + wire [7:0] snoop_data; + + // Instantiate the Unit Under Test (UUT) + i2c_snoop uut ( + .SCL(SCL), + .SDA(SDA), + .clk(clk), + .reset(reset), + .i2c_snoop_addr(8'h74), + .reg_addr(snoop_addr), + .reg_dout(snoop_data) + ); + + // Instantiate the Unit Under Test (UUT) + i2c_slave target ( + .SCL(SCL), + .SCL_pd(SCL_pd), + .SDA(SDA), + .SDA_pd(SDA_pd), + .SDA_pu(SDA_pu), + .clk(clk), + .reset(reset), + .i2c_device_addr(i2c_device_addr), + .reg_addr(reg_addr), + .wr_stb(wr_stb), + .reg_data_in(reg_data_in), + .reg_0(reg_a0), + .reg_1(reg_a1), + .reg_2(reg_a2), + .reg_3(reg_a3) + ); + + reg sda_host; // what the host is driving to + reg scl_host; + reg ack; // what the ack state is + reg [7:0] readdata; + +// always @(SCL_pd, SDA_pd, SDA_pu, sda_host, scl_host) begin + always @(*) begin // lazy + // scl equations + case( {SCL_pd, scl_host} ) + 2'b00: SCL <= 1'b0; + 2'b01: SCL <= 1'b1; + 2'b10: SCL <= 1'b0; + // conflict case + 2'b11: SCL <= 1'bX; + // handle tristate case + 2'b0Z: SCL <= 1'b1; + 2'b1Z: SCL <= 1'b0; + default: SCL <= 1'bX; + endcase // case ( {SCL_pd, scl_host} ) + + case( {SDA_pd, SDA_pu, sda_host} ) + 3'b000: SDA <= 1'b0; + 3'b001: SDA <= 1'b1; + 3'b010: SDA <= 1'bX; // change to 1'b1 for override + 3'b011: SDA <= 1'b1; + 3'b100: SDA <= 1'b0; + 3'b101: SDA <= 1'bX; // change to 1'b0 for override + 3'b110: SDA <= 1'bX; + 3'b111: SDA <= 1'bX; + + // tristate case + 3'b00Z: SDA <= 1'b1; + 3'b01Z: SDA <= 1'b1; + 3'b10Z: SDA <= 1'b0; + 3'b11Z: SDA <= 1'bX; + endcase // case ( {SDA_pd, SDA_pu, sda_host} ) + end + + parameter PERIOD = 16'd40; // 25 MHz + parameter I2C_PD = 16'd2464; // make it odd to try and catch non-phase synced issues + parameter I2C_TH = 16'd114; + parameter I2C_TS = 16'd217; + + always begin + clk = 1'b0; + #(PERIOD/2) clk = 1'b1; + #(PERIOD/2); + end + + task I2C_idle; + begin + scl_host = 1'bZ; + sda_host = 1'bZ; + #I2C_PD; + end + endtask // I2C_idle + + task I2C_start; + begin + scl_host = 1'bZ; + sda_host = 1'bZ; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'b0; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_stop; + begin + scl_host = 1'bZ; + sda_host = 1'b0; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'bZ; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_tx_bit; // tx from host ( from testbench ) + input bitval; + + begin + scl_host = 1'b0; + #I2C_TH; + sda_host = bitval; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = bitval; + #(I2C_PD/2); + end + endtask // I2C_tx_bit + + task I2C_rx_bit; // rx to host ( to testbench ) + output bitval; + + begin + scl_host = 1'b0; + #(I2C_TH/2); + sda_host = 1'bz; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'bz; + #1; + bitval = SDA; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_ack_low; + begin + scl_host = 1'b0; + #(I2C_PD/2); + end + endtask // I2C_ack_low + + + task I2C_tx_daddr; + input [7:0] daddr; + output rack; + + begin + I2C_tx_bit( daddr[7] ); + I2C_tx_bit( daddr[6] ); + I2C_tx_bit( daddr[5] ); + I2C_tx_bit( daddr[4] ); + I2C_tx_bit( daddr[3] ); + I2C_tx_bit( daddr[2] ); + I2C_tx_bit( daddr[1] ); + I2C_tx_bit( daddr[0] ); + I2C_rx_bit(rack); + I2C_ack_low(); + end + endtask // I2C_TX_DADDR + + task I2C_rx_daddr; + output [7:0] daddr; + input nack; + + begin + I2C_rx_bit(daddr[7]); + I2C_rx_bit(daddr[6]); + I2C_rx_bit(daddr[5]); + I2C_rx_bit(daddr[4]); + I2C_rx_bit(daddr[3]); + I2C_rx_bit(daddr[2]); + I2C_rx_bit(daddr[1]); + I2C_rx_bit(daddr[0]); + I2C_tx_bit( nack ); + I2C_ack_low(); + end + endtask // I2C_RX_DADDR + + initial begin + // Initialize Inputs + clk = 0; + reset = 0; + i2c_device_addr = 8'h74; + reg_addr = 0; + wr_stb = 0; + reg_data_in = 0; + snoop_addr = 0; + + $stop; + + I2C_idle(); + // run an actual reset cycle + #(PERIOD*4); + reset = 1; + #(PERIOD*4); + reset = 0; + #(PERIOD*4); + + // now pre-set a few I2C registers + reg_addr = 0; + wr_stb = 1; + reg_data_in = 8'hDE; + #(PERIOD*4); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 1; + wr_stb = 1; + reg_data_in = 8'hAD; + #(PERIOD*2); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 2; + wr_stb = 1; + reg_data_in = 8'hBE; + #(PERIOD*2); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 3; + wr_stb = 1; + reg_data_in = 8'hEF; + #(PERIOD*2); + + + // let it soak for a bit for good measure + #(PERIOD*10); + + // now the real sim starts + I2C_idle(); + + // write some data + I2C_start(); + I2C_tx_daddr(8'h74, ack); // write to device 72 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'h33, ack); // data 55 + I2C_stop(); + #(I2C_PD*5); + + // do a multi-cycle read + I2C_start(); + I2C_tx_daddr(8'h74, ack); // dummy write to 72 + I2C_tx_daddr(8'h00, ack); // address 00 + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 +// #(I2C_PD*3); + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 +// #(I2C_PD*3); + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 + I2C_rx_daddr(readdata, 1'bz); // data @ address 0 + I2C_stop(); + #(I2C_PD*5); + + // do a multi-cycle write + I2C_start(); + I2C_tx_daddr(8'h74, ack); // write to device 70 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'hFA, ack); + I2C_tx_daddr(8'hCE, ack); + I2C_tx_daddr(8'h69, ack); + I2C_stop(); + #(I2C_PD*5); + + #I2C_PD; + #I2C_PD; + snoop_addr = 8'h01; + #I2C_PD; + snoop_addr = 8'h02; + #I2C_PD; + snoop_addr = 8'h03; + #I2C_PD; + snoop_addr = 8'h04; + #I2C_PD; + snoop_addr = 8'h05; + #I2C_PD; + #I2C_PD; + + // read back one address at a time + I2C_start(); + I2C_tx_daddr(8'h74, ack); // dummy write to 72 + I2C_tx_daddr(8'h00, ack); // address 00 + + #(I2C_PD*5); + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + // this is the only questionable vector + // if you do an isolated read, should the address have + // incremeted from the previous read, or + // should it be the same. I have implemented it so + // that it increments. + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + #(I2C_PD*5); + + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 + I2C_rx_daddr(readdata, 1'b0); // one read + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + + // write to another device not us + I2C_start(); + I2C_tx_daddr(8'hA0, ack); // write to device a0 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'h55, ack); // data 55 -- this should be ignored + I2C_stop(); + + #I2C_PD; + #I2C_PD; + snoop_addr = 8'h01; + #I2C_PD; + snoop_addr = 8'h02; + #I2C_PD; + snoop_addr = 8'h03; + #I2C_PD; + snoop_addr = 8'h04; + #I2C_PD; + snoop_addr = 8'h05; + #I2C_PD; + #I2C_PD; + #I2C_PD; + #I2C_PD; + + end + +endmodule + diff --git a/common/i2c_squash_edid.v b/common/i2c_squash_edid.v new file mode 100755 index 0000000..f8f7317 --- /dev/null +++ b/common/i2c_squash_edid.v @@ -0,0 +1,892 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +// An I2C bus snooper implementation. Oversampled for robustness. +// +// There are two versions, the EDID snooper and the HDCP snooper +// +// It's split because EDID records can be very large and the compiler should +// infer a block ram for the large records. However, the nature of the HDCP +// registers would cause the compiler to infer slice registers. Thus, this code +// is identical to the HDCP snoop with the exception that the HDCP read ports +// are removed which will allow the compiler to properly infer a LUT RAM. +/////////// +`timescale 1 ns / 1 ps + +module i2c_squash_edid ( + // external host interface + input wire SCL, // the SCL pin state + input wire SDA, + output reg SDA_pu, // overrides for SDA + output reg SDA_pd, + + input wire clk, // internal FPGA clock + input wire reset, // internal FPGA reset + // i2c configuration + input wire [7:0] i2c_snoop_addr, + + input wire [7:0] modeline_adr, + input wire [7:0] modeline_dat, + input wire modeline_write + ); + + wire [7:0] modeline; // change wire -> reg if using legacy hard-coded roms + + /////// I2C physical layer components + /// SDA is stable when SCL is high. + /// If SDA moves while SCL is high, this is considered a start or stop condition. + /// + /// Otherwise, SDA can move around when SCL is low (this is where we suppress bits or + /// overdrive as needed). SDA is a wired-AND bus, so you only "drive" zero. + /// + /// In an oversampled implementation, a rising and falling edge de-glitcher is needed + /// for SCL and SDA. + /// + + // rise fall time cycles computation: + // At 400kHz operation, 2.5us is a cycle. "chatter" from transition should be about + // 5% of total cycle time max (just rule of thumb), so 0.125us should be the equiv + // number of cycles. + // For the demo board, a 25 MHz clock is provided, and 0.125us ~ 4 cycles + // At 100kHz operation, 10us is a cycle, so 0.5us ~ 12 cycles + parameter TRF_CYCLES = 5'd4; // number of cycles for rise/fall time + + //////////////// + ///// protocol-level state machine + //////////////// + parameter I2C_START = 14'b1 << 0; // should only pass through this state for one cycle + parameter I2C_RESTART = 14'b1 << 1; + parameter I2C_DADDR = 14'b1 << 2; + parameter I2C_ACK_DADDR = 14'b1 << 3; + parameter I2C_ADDR = 14'b1 << 4; + parameter I2C_ACK_ADDR = 14'b1 << 5; + parameter I2C_WR_DATA = 14'b1 << 6; + parameter I2C_ACK_WR = 14'b1 << 7; + parameter I2C_END_WR = 14'b1 << 8; + parameter I2C_RD_DATA = 14'b1 << 9; + parameter I2C_ACK_RD = 14'b1 << 10; + parameter I2C_END_RD = 14'b1 << 11; + parameter I2C_END_RD2 = 14'b1 << 12; + parameter I2C_WAITSTOP = 14'b1 << 13; + + parameter I2C_nSTATES = 14; + + reg [(I2C_nSTATES-1):0] I2C_cstate = {{(I2C_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(I2C_nSTATES-1):0] I2C_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] I2C_state_ascii = "I2C_START "; + always @(I2C_cstate) begin + if (I2C_cstate == I2C_START) I2C_state_ascii <= "I2C_START "; + else if (I2C_cstate == I2C_RESTART) I2C_state_ascii <= "I2C_RESTART "; + else if (I2C_cstate == I2C_DADDR) I2C_state_ascii <= "I2C_DADDR "; + else if (I2C_cstate == I2C_ACK_DADDR) I2C_state_ascii <= "I2C_ACK_DADDR "; + else if (I2C_cstate == I2C_ADDR) I2C_state_ascii <= "I2C_ADDR "; + else if (I2C_cstate == I2C_ACK_ADDR) I2C_state_ascii <= "I2C_ACK_ADDR "; + else if (I2C_cstate == I2C_WR_DATA) I2C_state_ascii <= "I2C_WR_DATA "; + else if (I2C_cstate == I2C_ACK_WR) I2C_state_ascii <= "I2C_ACK_WR "; + else if (I2C_cstate == I2C_END_WR) I2C_state_ascii <= "I2C_END_WR "; + else if (I2C_cstate == I2C_RD_DATA) I2C_state_ascii <= "I2C_RD_DATA "; + else if (I2C_cstate == I2C_ACK_RD) I2C_state_ascii <= "I2C_ACK_RD "; + else if (I2C_cstate == I2C_END_RD) I2C_state_ascii <= "I2C_END_RD "; + else if (I2C_cstate == I2C_END_RD2) I2C_state_ascii <= "I2C_END_RD2 "; + else if (I2C_cstate == I2C_WAITSTOP) I2C_state_ascii <= "I2C_WAITSTOP "; + else I2C_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [3:0] I2C_bitcnt; + reg [7:0] I2C_addr; + reg [7:0] I2C_daddr; + reg I2C_reg_update; + reg [7:0] I2C_squashdata; + reg I2C_dosquash; + + always @ (posedge clk) begin + if (reset || ((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) // stop condition always resets + I2C_cstate <= I2C_START; + else + I2C_cstate <=#1 I2C_nstate; + end + + always @ (*) begin + case (I2C_cstate) //synthesis parallel_case full_case + I2C_START: begin // wait for the start condition + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_DADDR : I2C_START; + end + I2C_RESTART: begin // repeated start moves immediately to DADDR + I2C_nstate = I2C_DADDR; + end + I2C_DADDR: begin // 8 bits to get the address + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_DADDR : I2C_DADDR; + end + I2C_ACK_DADDR: begin // depending upon W/R bit state, go to one of two branches + I2C_nstate = (SCL_cstate == SCL_FALL) ? + (I2C_daddr[7:1] == i2c_snoop_addr[7:1]) ? + (I2C_daddr[0] == 1'b0 ? I2C_ADDR : I2C_RD_DATA) : + I2C_WAITSTOP : // !I2C_daddr match + I2C_ACK_DADDR; // !SCL_FALL + end + + // device address branch + I2C_ADDR: begin + I2C_nstate = ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_ADDR : I2C_ADDR; + end + I2C_ACK_ADDR: begin + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_WR_DATA : I2C_ACK_ADDR; + end + + // write branch + I2C_WR_DATA: begin // 8 bits to get the write data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_WR : I2C_WR_DATA; + end + I2C_ACK_WR: begin // trigger the ack response (pull SDA low until next falling edge) + // and stay in this state until the next falling edge of SCL + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_END_WR : I2C_ACK_WR; + end + I2C_END_WR: begin // one-cycle state to update address+1, reset SDA pulldown + I2C_nstate = I2C_WR_DATA; // SCL is now low + end + + // read branch + I2C_RD_DATA: begin // 8 bits to get the read data + I2C_nstate = ((SDA_cstate == SDA_FALL) && (SCL_cstate == SCL_HIGH)) ? I2C_RESTART : // repeated start + ((I2C_bitcnt > 4'h7) && (SCL_cstate == SCL_FALL)) ? I2C_ACK_RD : I2C_RD_DATA; + end + I2C_ACK_RD: begin // wait for an (n)ack response + // need to sample (n)ack on a rising edge + I2C_nstate = (SCL_cstate == SCL_RISE) ? I2C_END_RD : I2C_ACK_RD; + end + I2C_END_RD: begin // if nack, just go to start state (don't explicitly check stop event) + // single cycle state for adr+1 update + I2C_nstate = (SDA_cstate == SDA_LOW) ? I2C_END_RD2 : I2C_START; + end + I2C_END_RD2: begin // before entering I2C_RD_DATA, we need to have seen a falling edge. + I2C_nstate = (SCL_cstate == SCL_FALL) ? I2C_RD_DATA : I2C_END_RD2; + end + + // we're not the addressed device, so we just idle until we see a stop + I2C_WAITSTOP: begin + I2C_nstate = (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_RISE))) ? // stop + I2C_START : + (((SCL_cstate == SCL_HIGH) && (SDA_cstate == SDA_FALL))) ? // or start + I2C_RESTART : + I2C_WAITSTOP; + end + endcase // case (cstate) + end + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 8'b0; // this persists across transactions + + I2C_squashdata <=#1 8'hff; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end else begin + case (I2C_cstate) // synthesis parallel_case full_case + I2C_START: begin // everything in reset + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + + I2C_RESTART: begin + I2C_bitcnt <=#1 4'b0; + I2C_daddr <=#1 8'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + +// I2C_squashdata <=#1 I2C_squashdata; + // on restart, I2C_addr is valid, so grab squashdata again + I2C_squashdata <=#1 modeline[7:0]; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + + // get my i2c device address (am I being talked to?) + I2C_DADDR: begin // shift in the address on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_daddr[7] <=#1 I2C_daddr[6]; + I2C_daddr[6] <=#1 I2C_daddr[5]; + I2C_daddr[5] <=#1 I2C_daddr[4]; + I2C_daddr[4] <=#1 I2C_daddr[3]; + I2C_daddr[3] <=#1 I2C_daddr[2]; + I2C_daddr[2] <=#1 I2C_daddr[1]; + I2C_daddr[1] <=#1 I2C_daddr[0]; + I2C_daddr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_daddr <=#1 I2C_daddr; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end // case: I2C_DADDR + I2C_ACK_DADDR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + + // get my i2c "write" address (what we want to access inside me) + I2C_ADDR: begin + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + I2C_addr[7] <=#1 I2C_addr[6]; + I2C_addr[6] <=#1 I2C_addr[5]; + I2C_addr[5] <=#1 I2C_addr[4]; + I2C_addr[4] <=#1 I2C_addr[3]; + I2C_addr[3] <=#1 I2C_addr[2]; + I2C_addr[2] <=#1 I2C_addr[1]; + I2C_addr[1] <=#1 I2C_addr[0]; + I2C_addr[0] <=#1 (SDA_cstate == SDA_HIGH) ? 1'b1 : 1'b0; + end else begin // we're oversampled so we need a hold-state gutter + I2C_bitcnt <=#1 I2C_bitcnt; + I2C_addr <=#1 I2C_addr; + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end // case: I2C_ADDR + I2C_ACK_ADDR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + + // note mirror image of this on the I2C_END_RD2 branch +`ifdef POSTERITY + if( I2C_addr[7] ) begin // 0x80 and above + I2C_squashdata <=#1 modeline[7:0]; + I2C_dosquash <=#1 1; + end else if( I2C_addr[7:0] == 8'h75 ) begin + I2C_squashdata <=#1 8'h09; // pixclock @ 90mhz + I2C_dosquash <=#1 1; + end else if( (I2C_addr[7:0] >= 8'h36) && + (I2C_addr[7:0] <= 8'h47) ) begin + I2C_squashdata <=#1 8'h00; + I2C_dosquash <=#1 1; + end else if( I2C_addr[7:0] == 8'h7f ) begin + I2C_squashdata <=#1 8'hb7; // fixup checkusm + I2C_dosquash <=#1 1; + end else begin + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + end +`endif // `ifdef POSTERITY + // now that bank is 256 bytes, squash everything + I2C_squashdata <=#1 modeline[7:0]; + I2C_dosquash <=#1 1; + + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + + + // write branch + I2C_WR_DATA: begin // shift in data on rising edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + end // else: !if( SCL_cstate == SCL_RISE ) + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; +`ifdef SQUASHWRITES + if( SCL_cstate == SCL_FALL ) begin + I2C_squashdata[7:0] <=#1 {I2C_squashdata[6:0],I2C_squashdata[7]}; + end else begin + I2C_squashdata <=#1 I2C_squashdata; + end + + I2C_dosquash <=#1 I2C_dosquash; + if( I2C_dosquash ) begin + if( I2C_squashdata[7] ) begin + SDA_pd <=#1 0; + SDA_pu <=#1 1; + end else begin + SDA_pd <=#1 1; + SDA_pu <=#1 0; + end + end else begin + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end // else: !if( I2C_dosquash ) +`else // !`ifdef SQUASHWRITES + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; +`endif + end // case: I2C_WR_DATA + I2C_ACK_WR: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b1; // write the data now (over and over again while in state) + I2C_addr <=#1 I2C_addr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + I2C_END_WR: begin + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + + // read branch + I2C_RD_DATA: begin // shift out data on falling edges of clock + if( SCL_cstate == SCL_RISE ) begin + I2C_bitcnt <=#1 I2C_bitcnt + 4'b1; + end else begin + I2C_bitcnt <=#1 I2C_bitcnt; // hold state gutter + end + I2C_daddr <=#1 I2C_daddr; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + + if( SCL_cstate == SCL_FALL ) begin + I2C_squashdata[7:0] <=#1 {I2C_squashdata[6:0],I2C_squashdata[7]}; + end else begin + I2C_squashdata <=#1 I2C_squashdata; + end + + I2C_dosquash <=#1 I2C_dosquash; + if( I2C_dosquash ) begin + if( I2C_squashdata[7] ) begin + SDA_pd <=#1 0; + SDA_pu <=#1 1; + end else begin + SDA_pd <=#1 1; + SDA_pu <=#1 0; + end + end else begin + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + end // case: I2C_RD_DATA + I2C_ACK_RD: begin + I2C_daddr <=#1 I2C_daddr; + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b1; // commit reads even to our internal bank + I2C_addr <=#1 I2C_addr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + I2C_END_RD: begin + I2C_addr <=#1 I2C_addr + 8'b1; // this is a one-cycle state so this is safe + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b0; + I2C_daddr <=#1 I2C_daddr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + I2C_END_RD2: begin + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + +`ifdef POSTERITY + if( I2C_addr[7] ) begin // 0x80 and above + I2C_squashdata <=#1 modeline[7:0]; + I2C_dosquash <=#1 1; + end else if( I2C_addr[7:0] == 8'h75 ) begin + I2C_squashdata <=#1 8'h09; // pixclock @ 90mhz + I2C_dosquash <=#1 1; + end else if( (I2C_addr[7:0] >= 8'h36) && + (I2C_addr[7:0] <= 8'h47) ) begin + I2C_squashdata <=#1 8'h00; + I2C_dosquash <=#1 1; + end else if( I2C_addr[7:0] == 8'h7f ) begin + I2C_squashdata <=#1 8'hb7; // fixup checkusm + I2C_dosquash <=#1 1; + end else begin + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + end +`endif // `ifdef POSTERITY + // now that bank is 256 bytes, squash everything + I2C_squashdata <=#1 modeline[7:0]; + I2C_dosquash <=#1 1; + + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + + I2C_WAITSTOP: begin + I2C_daddr <=#1 8'b0; + I2C_bitcnt <=#1 4'b0; + I2C_reg_update <=#1 1'b0; + I2C_addr <=#1 I2C_addr; + + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + SDA_pd <=#1 0; + SDA_pu <=#1 0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + +`ifdef POSTERITY + if( I2C_addr[7:0] == 8'h63 ) begin + I2C_squashdata <=#1 8'h0a; + I2C_dosquash <=#1 1; + end else if( (I2C_addr[7:0] >= 8'h36) && (I2C_addr[7:0] <= 8'h59) ) begin + I2C_squashdata <=#1 modeline[7:0]; + I2C_dosquash <=#1 1; + end else begin + I2C_squashdata <=#1 I2C_squashdata; + I2C_dosquash <=#1 0; + end + SDA_pd <=#1 0; + SDA_pu <=#1 0; +`endif + + //////////////// + ///// SCL low-level sampling state machine + //////////////// + parameter SCL_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SCL_FALL = 4'b1 << 1; + parameter SCL_LOW = 4'b1 << 2; + parameter SCL_RISE = 4'b1 << 3; + parameter SCL_nSTATES = 4; + + reg [(SCL_nSTATES-1):0] SCL_cstate = {{(SCL_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SCL_nSTATES-1):0] SCL_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SCL_state_ascii = "SCL_HIGH "; + + always @(SCL_cstate) begin + if (SCL_cstate == SCL_HIGH) SCL_state_ascii <= "SCL_HIGH "; + else if (SCL_cstate == SCL_FALL) SCL_state_ascii <= "SCL_FALL "; + else if (SCL_cstate == SCL_LOW ) SCL_state_ascii <= "SCL_LOW "; + else if (SCL_cstate == SCL_RISE) SCL_state_ascii <= "SCL_RISE "; + else SCL_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SCL_rfcnt; + reg SCL_s, SCL_sync; + reg SDA_s, SDA_sync; + + always @ (posedge clk or posedge reset) begin + if (reset) + SCL_cstate <= SCL_HIGH; // always start here even if it's wrong -- easier to test + else + SCL_cstate <=#1 SCL_nstate; + end + + always @ (*) begin + case (SCL_cstate) //synthesis parallel_case full_case + SCL_HIGH: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b0)) ? SCL_FALL : SCL_HIGH; + end + SCL_FALL: begin + SCL_nstate = SCL_LOW; + end + SCL_LOW: begin + SCL_nstate = ((SCL_rfcnt > TRF_CYCLES) && (SCL_sync == 1'b1)) ? SCL_RISE : SCL_LOW; + end + SCL_RISE: begin + SCL_nstate = SCL_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + case (SCL_cstate) // synthesis parallel_case full_case + SCL_HIGH: begin + if( SCL_sync == 1'b1 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_FALL: begin + SCL_rfcnt <=#1 5'b0; + end + SCL_LOW: begin + if( SCL_sync == 1'b0 ) begin + SCL_rfcnt <=#1 5'b0; + end else begin + SCL_rfcnt <=#1 SCL_rfcnt + 5'b1; + end + end + SCL_RISE: begin + SCL_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + //////////////// + ///// SDA low-level sampling state machine + //////////////// + parameter SDA_HIGH = 4'b1 << 0; // should only pass through this state for one cycle + parameter SDA_FALL = 4'b1 << 1; + parameter SDA_LOW = 4'b1 << 2; + parameter SDA_RISE = 4'b1 << 3; + parameter SDA_nSTATES = 4; + + reg [(SDA_nSTATES-1):0] SDA_cstate = {{(SDA_nSTATES-1){1'b0}}, 1'b1}; //current and next states + reg [(SDA_nSTATES-1):0] SDA_nstate; + +//`define SIMULATION +`ifdef SIMULATION + // synthesis translate_off + reg [8*20:1] SDA_state_ascii = "SDA_HIGH "; + + always @(SDA_cstate) begin + if (SDA_cstate == SDA_HIGH) SDA_state_ascii <= "SDA_HIGH "; + else if (SDA_cstate == SDA_FALL) SDA_state_ascii <= "SDA_FALL "; + else if (SDA_cstate == SDA_LOW ) SDA_state_ascii <= "SDA_LOW "; + else if (SDA_cstate == SDA_RISE) SDA_state_ascii <= "SDA_RISE "; + else SDA_state_ascii <= "WTF "; + end + // synthesis translate_on +`endif + + reg [4:0] SDA_rfcnt; + + always @ (posedge clk or posedge reset) begin + if (reset) + SDA_cstate <= SDA_HIGH; // always start here even if it's wrong -- easier to test + else + SDA_cstate <=#1 SDA_nstate; + end + + always @ (*) begin + case (SDA_cstate) //synthesis parallel_case full_case + SDA_HIGH: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b0)) ? SDA_FALL : SDA_HIGH; + end + SDA_FALL: begin + SDA_nstate = SDA_LOW; + end + SDA_LOW: begin + SDA_nstate = ((SDA_rfcnt > TRF_CYCLES) && (SDA_sync == 1'b1)) ? SDA_RISE : SDA_LOW; + end + SDA_RISE: begin + SDA_nstate = SDA_HIGH; + end + endcase // case (cstate) + end // always @ (*) + + always @ (posedge clk or posedge reset) begin + if( reset ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + case (SDA_cstate) // synthesis parallel_case full_case + SDA_HIGH: begin + if( SDA_sync == 1'b1 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_FALL: begin + SDA_rfcnt <=#1 5'b0; + end + SDA_LOW: begin + if( SDA_sync == 1'b0 ) begin + SDA_rfcnt <=#1 5'b0; + end else begin + SDA_rfcnt <=#1 SDA_rfcnt + 5'b1; + end + end + SDA_RISE: begin + SDA_rfcnt <=#1 5'b0; + end + endcase // case (cstate) + end // else: !if( reset ) + end // always @ (posedge clk or posedge reset) + + + + ///////////////////// + /////// synchronizers + ///////////////////// + always @ (posedge clk or posedge reset) begin + if (reset) begin + SCL_s <= 0; + SCL_sync <= 0; + SDA_s <= 0; + SDA_sync <= 0; + end else begin + SCL_s <= SCL; + SCL_sync <= SCL_s; + SDA_s <= SDA; + SDA_sync <= SDA_s; + end // else: !if(reset) + end // always @ (posedge clk or posedge reset) + + ///////////////////////// + // ram that contains the override modeline + ///////////////////////// + (* RAM_STYLE="{AUTO | DISTRIBUTED | PIPE_DISTRIBUTED}" *) + reg [7:0] moderam [255:0]; + +// wire [7:0] modeline; // declared up top + + always @(posedge clk) + if (modeline_write) + moderam[modeline_adr[7:0]] <= modeline_dat[7:0]; + + assign modeline[7:0] = moderam[I2C_addr[7:0]]; + +`ifdef POSTERITY + wire [7:0] I2C_offset36; + assign I2C_offset36[7:0] = I2C_addr[7:0] - 8'h36; + + always @(*) begin + case( I2C_offset36[4:0] ) + 5'h00: modeline = 8'h01; // master copy + 5'h01: modeline = 8'h1d; + 5'h02: modeline = 8'h00; + 5'h03: modeline = 8'hbc; + 5'h04: modeline = 8'h52; + 5'h05: modeline = 8'hd0; + 5'h06: modeline = 8'h1e; + 5'h07: modeline = 8'h20; + 5'h08: modeline = 8'hb8; + 5'h09: modeline = 8'h28; + 5'h0a: modeline = 8'h55; + 5'h0b: modeline = 8'h40; + 5'h0c: modeline = 8'ha0; + 5'h0d: modeline = 8'h5a; + 5'h0e: modeline = 8'h00; + 5'h0f: modeline = 8'h00; + 5'h10: modeline = 8'h00; + 5'h11: modeline = 8'h1e; + + 5'h12: modeline = 8'h01; // alias copy + 5'h13: modeline = 8'h1d; + 5'h14: modeline = 8'h00; + 5'h15: modeline = 8'hbc; + 5'h16: modeline = 8'h52; + 5'h17: modeline = 8'hd0; + 5'h18: modeline = 8'h1e; + 5'h19: modeline = 8'h20; + 5'h1a: modeline = 8'hb8; + 5'h1b: modeline = 8'h28; + 5'h1c: modeline = 8'h55; + 5'h1d: modeline = 8'h40; + 5'h1e: modeline = 8'ha0; + 5'h1f: modeline = 8'h5a; + endcase // case ( I2C_addr ) + end // always @ (*) +`endif // !`ifdef POSTERITY +`ifdef POSTERITY + always @(*) begin + case( I2C_addr[6:0] ) + 7'h00: modeline = 8'h02; + 7'h01: modeline = 8'h03; + 7'h02: modeline = 8'h1f; + 7'h03: modeline = 8'hf2; + 7'h04: modeline = 8'h4b; + 7'h05: modeline = 8'h93; + 7'h06: modeline = 8'h04; + 7'h07: modeline = 8'h12; + 7'h08: modeline = 8'h83; + 7'h09: modeline = 8'h14; + 7'h0a: modeline = 8'h05; + 7'h0b: modeline = 8'h20; + 7'h0c: modeline = 8'h00; + 7'h0d: modeline = 8'h00; + 7'h0e: modeline = 8'h00; + 7'h0f: modeline = 8'h00; + 7'h10: modeline = 8'h23; + 7'h11: modeline = 8'h09; + 7'h12: modeline = 8'h07; + 7'h13: modeline = 8'h07; + 7'h14: modeline = 8'h83; + 7'h15: modeline = 8'h01; + 7'h16: modeline = 8'h00; + 7'h17: modeline = 8'h00; + 7'h18: modeline = 8'h66; + 7'h19: modeline = 8'h03; + 7'h1a: modeline = 8'h0c; + 7'h1b: modeline = 8'h00; + 7'h1c: modeline = 8'h10; + 7'h1d: modeline = 8'h00; + 7'h1e: modeline = 8'h80; + 7'h1f: modeline = 8'h8c; + 7'h20: modeline = 8'h0a; + 7'h21: modeline = 8'hd0; + 7'h22: modeline = 8'h8a; + 7'h23: modeline = 8'h20; + 7'h24: modeline = 8'he0; + 7'h25: modeline = 8'h2d; + 7'h26: modeline = 8'h10; + 7'h27: modeline = 8'h10; + 7'h28: modeline = 8'h3e; + 7'h29: modeline = 8'h96; + 7'h2a: modeline = 8'h00; + 7'h2b: modeline = 8'ha0; + 7'h2c: modeline = 8'h5a; + 7'h2d: modeline = 8'h00; + 7'h2e: modeline = 8'h00; + 7'h2f: modeline = 8'h00; + 7'h30: modeline = 8'h18; + 7'h31: modeline = 8'h01; + 7'h32: modeline = 8'h1d; + 7'h33: modeline = 8'h00; + 7'h34: modeline = 8'h72; + 7'h35: modeline = 8'h51; + 7'h36: modeline = 8'hd0; + 7'h37: modeline = 8'h1e; + 7'h38: modeline = 8'h20; + 7'h39: modeline = 8'h6e; + 7'h3a: modeline = 8'h28; + 7'h3b: modeline = 8'h55; + 7'h3c: modeline = 8'h00; + 7'h3d: modeline = 8'ha0; + 7'h3e: modeline = 8'h5a; + 7'h3f: modeline = 8'h00; + 7'h40: modeline = 8'h00; + 7'h41: modeline = 8'h00; + 7'h42: modeline = 8'h1e; + 7'h43: modeline = 8'h01; + 7'h44: modeline = 8'h1d; + 7'h45: modeline = 8'h80; + 7'h46: modeline = 8'hd0; + 7'h47: modeline = 8'h72; + 7'h48: modeline = 8'h1c; + 7'h49: modeline = 8'h16; + 7'h4a: modeline = 8'h20; + 7'h4b: modeline = 8'h10; + 7'h4c: modeline = 8'h2c; + 7'h4d: modeline = 8'h25; + 7'h4e: modeline = 8'h80; + 7'h4f: modeline = 8'ha0; + 7'h50: modeline = 8'h5a; + 7'h51: modeline = 8'h00; + 7'h52: modeline = 8'h00; + 7'h53: modeline = 8'h00; + 7'h54: modeline = 8'h9e; + 7'h55: modeline = 8'h01; + 7'h56: modeline = 8'h1d; + 7'h57: modeline = 8'h80; + 7'h58: modeline = 8'h18; + 7'h59: modeline = 8'h71; + 7'h5a: modeline = 8'h1c; + 7'h5b: modeline = 8'h16; + 7'h5c: modeline = 8'h20; + 7'h5d: modeline = 8'h58; + 7'h5e: modeline = 8'h2c; + 7'h5f: modeline = 8'h25; + 7'h60: modeline = 8'h00; + 7'h61: modeline = 8'ha0; + 7'h62: modeline = 8'h5a; + 7'h63: modeline = 8'h00; + 7'h64: modeline = 8'h00; + 7'h65: modeline = 8'h00; + 7'h66: modeline = 8'h9e; + 7'h67: modeline = 8'h8c; + 7'h68: modeline = 8'h0a; + 7'h69: modeline = 8'hd0; + 7'h6a: modeline = 8'h90; + 7'h6b: modeline = 8'h20; + 7'h6c: modeline = 8'h40; + 7'h6d: modeline = 8'h31; + 7'h6e: modeline = 8'h20; + 7'h6f: modeline = 8'h0c; + 7'h70: modeline = 8'h40; + 7'h71: modeline = 8'h55; + 7'h72: modeline = 8'h00; + 7'h73: modeline = 8'ha0; + 7'h74: modeline = 8'h5a; + 7'h75: modeline = 8'h00; + 7'h76: modeline = 8'h00; + 7'h77: modeline = 8'h00; + 7'h78: modeline = 8'h18; + 7'h79: modeline = 8'h00; + 7'h7a: modeline = 8'h00; + 7'h7b: modeline = 8'h00; + 7'h7c: modeline = 8'h00; + 7'h7d: modeline = 8'h00; + 7'h7e: modeline = 8'h00; + 7'h7f: modeline = 8'ha3; + endcase // case ( I2C_addr ) + end // always @ (*) +`endif // !`ifdef POSTERITY + +endmodule // i2c_slave diff --git a/common/i2c_squash_tb.v b/common/i2c_squash_tb.v new file mode 100755 index 0000000..70c76a9 --- /dev/null +++ b/common/i2c_squash_tb.v @@ -0,0 +1,445 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +`timescale 1ns / 1ps + +//////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 12:30:45 04/05/2011 +// Design Name: i2c_slave +// Module Name: C:/largework/fpga/hdmi/impl4/common/i2c_slave_tb.v +// Project Name: impl4 +// Target Device: +// Tool versions: +// Description: +// +// Verilog Test Fixture created by ISE for module: i2c_slave +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +//////////////////////////////////////////////////////////////////////////////// + +module i2c_squash_tb; + + reg SDA; // physical wire state + reg SCL; + + // Inputs + reg clk; + reg reset; + reg [7:0] i2c_device_addr; + reg [7:0] reg_addr; + reg wr_stb; + reg [7:0] reg_data_in; + + // Outputs + wire SCL_pd; + wire SDA_pd; + wire SDA_pu; + wire [7:0] reg_a0; + wire [7:0] reg_a1; + wire [7:0] reg_a2; + wire [7:0] reg_a3; + + reg [7:0] snoop_addr; + wire [7:0] snoop_data; + + wire DDC_SDA_pu; + wire DDC_SDA_pd; + wire [63:0] An; + wire Aksv14_write; + +//`define FOO +`ifdef FOO + // Instantiate the Unit Under Test (UUT) + i2c_snoop supa_snoopa ( + .SCL(SCL), + .SDA(SDA), + .clk(clk), + .reset(reset), + .i2c_snoop_addr(8'h74), + .reg_addr(snoop_addr), + .reg_dout(snoop_data), + .An(An), + .Aksv14_write(Aksv14_write) + ); +`endif + + // Instantiate the Unit Under Test (UUT) + i2c_slave target ( + .SCL(SCL), + .SCL_pd(SCL_pd), + .SDA(SDA), + .SDA_pd(SDA_pd_comb), + .SDA_pu(SDA_pu_comb), + .clk(clk), + .reset(reset), + .i2c_device_addr(i2c_device_addr), + .reg_addr(reg_addr), + .wr_stb(wr_stb), + .reg_data_in(reg_data_in), + .reg_0(reg_a0), + .reg_1(reg_a1), + .reg_2(reg_a2), + .reg_3(reg_a3) + ); + i2c_squash_edid ddc_edid_squash ( + .SCL(SCL), + .SDA(SDA), + .clk(clk), + .reset(reset), + + .SDA_pu(DDC_SDA_pu), + .SDA_pd(DDC_SDA_pd), + + .i2c_snoop_addr(8'h74) // EDID address + ); + + reg sda_host; // what the host is driving to + reg scl_host; + reg ack; // what the ack state is + reg [7:0] readdata; + + wire SDA_pu_comb; + wire SDA_pd_comb; + + assign SDA_pu = SDA_pu_comb | DDC_SDA_pu; + assign SDA_pd = SDA_pd_comb | DDC_SDA_pd; + +// always @(SCL_pd, SDA_pd, SDA_pu, sda_host, scl_host) begin + always @(*) begin // lazy + // scl equations + case( {SCL_pd, scl_host} ) + 2'b00: SCL <= 1'b0; + 2'b01: SCL <= 1'b1; + 2'b10: SCL <= 1'b0; + // conflict case + 2'b11: SCL <= 1'bX; + // handle tristate case + 2'b0Z: SCL <= 1'b1; + 2'b1Z: SCL <= 1'b0; + default: SCL <= 1'bX; + endcase // case ( {SCL_pd, scl_host} ) + + case( {SDA_pd, SDA_pu, sda_host} ) + 3'b000: SDA <= 1'b0; + 3'b001: SDA <= 1'b1; + 3'b010: SDA <= 1'bX; // change to 1'b1 for override + 3'b011: SDA <= 1'b1; + 3'b100: SDA <= 1'b0; + 3'b101: SDA <= 1'bX; // change to 1'b0 for override + 3'b110: SDA <= 1'bX; + 3'b111: SDA <= 1'bX; + + // tristate case + 3'b00Z: SDA <= 1'b1; + 3'b01Z: SDA <= 1'b1; + 3'b10Z: SDA <= 1'b0; + 3'b11Z: SDA <= 1'bX; + endcase // case ( {SDA_pd, SDA_pu, sda_host} ) + end + + parameter PERIOD = 16'd40; // 25 MHz + parameter I2C_PD = 16'd2464; // make it odd to try and catch non-phase synced issues + parameter I2C_TH = 16'd114; + parameter I2C_TS = 16'd217; + + always begin + clk = 1'b0; + #(PERIOD/2) clk = 1'b1; + #(PERIOD/2); + end + + task I2C_idle; + begin + scl_host = 1'bZ; + sda_host = 1'bZ; + #I2C_PD; + end + endtask // I2C_idle + + task I2C_start; + begin + scl_host = 1'bZ; + sda_host = 1'bZ; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'b0; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_stop; + begin + scl_host = 1'bZ; + sda_host = 1'b0; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'bZ; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_tx_bit; // tx from host ( from testbench ) + input bitval; + + begin + scl_host = 1'b0; + #I2C_TH; + sda_host = bitval; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = bitval; + #(I2C_PD/2); + end + endtask // I2C_tx_bit + + task I2C_rx_bit; // rx to host ( to testbench ) + output bitval; + + begin + scl_host = 1'b0; + #(I2C_TH/2); + sda_host = 1'bz; + #(I2C_PD/2); + scl_host = 1'bZ; + sda_host = 1'bz; + #1; + bitval = SDA; + #(I2C_PD/2); + end + endtask // I2C_start + + task I2C_ack_low; + begin + scl_host = 1'b0; + #(I2C_PD/2); + end + endtask // I2C_ack_low + + + task I2C_tx_daddr; + input [7:0] daddr; + output rack; + + begin + I2C_tx_bit( daddr[7] ); + I2C_tx_bit( daddr[6] ); + I2C_tx_bit( daddr[5] ); + I2C_tx_bit( daddr[4] ); + I2C_tx_bit( daddr[3] ); + I2C_tx_bit( daddr[2] ); + I2C_tx_bit( daddr[1] ); + I2C_tx_bit( daddr[0] ); + I2C_rx_bit(rack); + I2C_ack_low(); + end + endtask // I2C_TX_DADDR + + task I2C_rx_daddr; + output [7:0] daddr; + input nack; + + begin + I2C_rx_bit(daddr[7]); + I2C_rx_bit(daddr[6]); + I2C_rx_bit(daddr[5]); + I2C_rx_bit(daddr[4]); + I2C_rx_bit(daddr[3]); + I2C_rx_bit(daddr[2]); + I2C_rx_bit(daddr[1]); + I2C_rx_bit(daddr[0]); + I2C_tx_bit( nack ); + I2C_ack_low(); + end + endtask // I2C_RX_DADDR + + initial begin + // Initialize Inputs + clk = 0; + reset = 0; + i2c_device_addr = 8'h74; + reg_addr = 0; + wr_stb = 0; + reg_data_in = 0; + snoop_addr = 0; + + $stop; + + I2C_idle(); + // run an actual reset cycle + #(PERIOD*4); + reset = 1; + #(PERIOD*4); + reset = 0; + #(PERIOD*4); + + // now pre-set a few I2C registers + reg_addr = 0; + wr_stb = 1; + reg_data_in = 8'hDE; + #(PERIOD*4); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 1; + wr_stb = 1; + reg_data_in = 8'hAD; + #(PERIOD*2); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 2; + wr_stb = 1; + reg_data_in = 8'hBE; + #(PERIOD*2); + + wr_stb = 0; + #(PERIOD*1); + + reg_addr = 3; + wr_stb = 1; + reg_data_in = 8'hEF; + #(PERIOD*2); + + + // let it soak for a bit for good measure + #(PERIOD*10); + + // now the real sim starts + I2C_idle(); + + // write some data + I2C_start(); + I2C_tx_daddr(8'h74, ack); // write to device 72 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'h33, ack); // data 55 + I2C_stop(); + #(I2C_PD*5); + + // do a multi-cycle read + I2C_start(); + I2C_tx_daddr(8'h74, ack); // dummy write to 72 + I2C_tx_daddr(8'h00, ack); // address 00 + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 +// #(I2C_PD*3); + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 +// #(I2C_PD*3); + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 + I2C_rx_daddr(readdata, 1'b0); // data @ address 0 + I2C_rx_daddr(readdata, 1'bz); // data @ address 0 + I2C_stop(); + #(I2C_PD*5); + + // do a multi-cycle write + I2C_start(); + I2C_tx_daddr(8'h74, ack); // write to device 70 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'hFA, ack); + I2C_tx_daddr(8'hCE, ack); + I2C_tx_daddr(8'h69, ack); + I2C_stop(); + #(I2C_PD*5); + + #I2C_PD; + #I2C_PD; + snoop_addr = 8'h01; + #I2C_PD; + snoop_addr = 8'h02; + #I2C_PD; + snoop_addr = 8'h03; + #I2C_PD; + snoop_addr = 8'h04; + #I2C_PD; + snoop_addr = 8'h05; + #I2C_PD; + #I2C_PD; + + // read back one address at a time + I2C_start(); + I2C_tx_daddr(8'h74, ack); // dummy write to 72 + I2C_tx_daddr(8'h00, ack); // address 00 + + #(I2C_PD*5); + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + // this is the only questionable vector + // if you do an isolated read, should the address have + // incremeted from the previous read, or + // should it be the same. I have implemented it so + // that it increments. + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + #(I2C_PD*5); + + I2C_start(); + I2C_tx_daddr(8'h75, ack); // read from 72 + I2C_rx_daddr(readdata, 1'b0); // one read + I2C_rx_daddr(readdata, 1'bz); // one read + I2C_stop(); + + + // write to another device not us + I2C_start(); + I2C_tx_daddr(8'hA0, ack); // write to device a0 + I2C_tx_daddr(8'h01, ack); // address 01 + I2C_tx_daddr(8'h55, ack); // data 55 -- this should be ignored + I2C_stop(); + + #I2C_PD; + #I2C_PD; + snoop_addr = 8'h01; + #I2C_PD; + snoop_addr = 8'h02; + #I2C_PD; + snoop_addr = 8'h03; + #I2C_PD; + snoop_addr = 8'h04; + #I2C_PD; + snoop_addr = 8'h05; + #I2C_PD; + #I2C_PD; + #I2C_PD; + #I2C_PD; + + end + +endmodule // i2c_squash_tb + diff --git a/common/pwm.v b/common/pwm.v new file mode 100755 index 0000000..bf3fc8b --- /dev/null +++ b/common/pwm.v @@ -0,0 +1,83 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +// this module does simple PWM modulation to create the "breathing" LED effect +// + +`timescale 1 ns / 1 ps + +module pwm( + input wire clk812k, // use clock from device DNA block, 812.5kHz + input wire reset, + output reg pwmout, + input wire [11:0] bright, + input wire [11:0] dim + ); + + reg [9:0] pwm_count; + reg pwmreg; + reg [11:0] interpolate; + reg countdn; + wire [9:0] interp; + + always @(posedge clk812k or posedge reset) begin + if( reset ) begin + pwm_count <= 0; + interpolate[11:0] <= dim[11:0]; + countdn <= 0; + end else begin + if( interpolate[11:0] >= bright[11:0] ) begin + countdn <= 1; + end else if( interpolate[11:0] <= dim[11:0] ) begin + countdn <= 0; + end else begin + countdn <= countdn; + end + + if( pwm_count[9:0] == 10'h0 ) begin + if( countdn == 1'b1 ) begin + interpolate[11:0] <= interpolate[11:0] - 12'b1; + end else begin + interpolate[11:0] <= interpolate[11:0] + 12'b1; + end + end else begin + interpolate[11:0] <= interpolate[11:0]; + end + + pwm_count[9:0] <= pwm_count[9:0] + 10'b1; + end + + pwmreg <= (pwm_count[9:0] < interp[9:0]); + + end // always @ (posedge clk812k or posedge reset) + + assign interp[9:0] = interpolate[11:2]; + + always @(posedge clk812k) begin + // make it registered to ease up routing congestion to the edge + pwmout <= !pwmreg; + end + +endmodule // pwm diff --git a/common/timing_detector.v b/common/timing_detector.v new file mode 100755 index 0000000..bb4e872 --- /dev/null +++ b/common/timing_detector.v @@ -0,0 +1,390 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2011, Andrew "bunnie" Huang +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +// SHALL THE COPYRIGHT HOLDER 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. +// +////////////////////////////////////////////////////////////////////////////// +`timescale 1 ns / 1ps + +module timing_detector ( + input wire pclk, + input wire rstin, // asynchronous, as it is asserted during no-clock condition + input wire vsync, // assume positive-polarity sync on input + input wire hsync, // assume positive-polarity sync on input + input wire de, + input wire refclk, + input wire lcd_de, + input wire lcd_vsync, + + output reg [11:0] hactive, // in pixels + output reg [11:0] vactive, // in lines + output reg [11:0] htotal, // in pixels + output reg [23:0] vtotal, // ** in PIXELS ** must divide by htotal in software + output reg [7:0] h_frontporch, // in pixels + output reg [7:0] h_backporch, // in pixels + output reg [23:0] v_frontporch, // ** in PIXELS ** + output reg [23:0] v_backporch, // ** in PIXELS ** + output reg [7:0] hsync_width, // in pixels + output reg [23:0] vsync_width, // ** in PIXELS ** + output reg [11:0] lcd_de_latency, // in lines + output reg [11:0] lcd_vsync_latency, // in lines + + output reg [23:0] refclkcnt // number of refclocks in a field + ); + + reg vsync_d; + reg hsync_d; + reg de_d; + + wire vsync_rising; + wire hsync_rising; + wire de_rising; + + wire vsync_falling; + wire hsync_falling; + wire de_falling; + + reg [11:0] hcount; + reg [7:0] hsync_width_cnt; + reg [23:0] vcount; + reg [11:0] de_count; + reg [11:0] vactive_count; + reg [7:0] h_fp_count; + reg [7:0] h_bp_count; + reg [23:0] v_fp_count; + reg [23:0] v_bp_count; + + reg vsync_refclk; + reg vsync__refclk; + reg vsync___refclk; + reg vsync____refclk; + wire vsync_refclk_rising; + reg [23:0] refclkcounter; + + reg lcd_de_pclk; + reg lcd_de_1pclk; + reg lcd_de_2pclk; + + reg lcd_vsync_pclk; + reg lcd_vsync_1pclk; + reg lcd_vsync_2pclk; + + reg first_de_rising; + reg first_lcd_de_rising; + + //// vertical front/back porch machine + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + v_fp_count <= 0; + v_bp_count <= 0; + v_frontporch <= 0; + v_backporch <= 0; + first_de_rising <= 1; + end else begin + if( vsync_falling ) begin + v_fp_count <= 0; + first_de_rising <= 1; + end else begin + if( v_fp_count == 24'hFF_FFFF ) begin + v_fp_count <= v_fp_count; + end else begin + v_fp_count <= v_fp_count + 24'b1; // counting in pixels + end + + if( de_rising ) begin + first_de_rising <= 0; + end else begin + first_de_rising <= first_de_rising; + end + end + + if( de_rising && first_de_rising ) begin + v_frontporch <= v_fp_count; + end else begin + v_frontporch <= v_frontporch; + end + + if( de_falling ) begin + v_bp_count <= 0; + end else begin + if( v_bp_count == 24'hFF_FFFF ) begin + v_bp_count <= v_bp_count; + end else begin + v_bp_count <= v_bp_count + 24'b1; // counting in pixels + end + end + + if( vsync_rising ) begin + v_backporch <= v_bp_count; + end else begin + v_backporch <= v_backporch; + end + end // else: !if( rstin ) + end // always @ (posedge pclk) + + //// horizonal front/back porch machine + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + h_fp_count <= 0; + h_bp_count <= 0; + h_frontporch <= 0; + h_backporch <= 0; + end else begin + if( hsync_falling ) begin + h_fp_count <= 8'b0; + end else begin + if( h_fp_count == 8'b1111_1111 ) begin + h_fp_count <= h_fp_count; // saturate to catch de-only timings + end else begin + h_fp_count <= h_fp_count + 8'b1; + end + end + + if( de_rising ) begin + h_frontporch <= h_fp_count + 8'b10; // this is a bit of a hack, why is one pixel missing? + end else begin + h_frontporch <= h_frontporch; + end + + if( de_falling ) begin + h_bp_count <= 0; + end else begin + if( h_bp_count == 8'b1111_1111 ) begin + h_bp_count <= h_bp_count; + end else begin + h_bp_count <= h_bp_count + 8'b1; + end + end + + if( hsync_rising ) begin + h_backporch <= h_bp_count; + end else begin + h_backporch <= h_backporch; + end + end // else: !if( rstin ) + end // always @ (posedge pclk) + + //// vsync_width machine + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + vsync_width <= 0; + end else begin + if( vsync_rising ) begin + vsync_width <= vsync_width; + // vcount is reset on vsync_rising as well + end else begin + if( vsync_falling ) begin + vsync_width[23:0] <= vcount[23:0]; // counting in pixels + end else begin + vsync_width <= vsync_width; + end + end + end + end // always @ (posedge pclk) + + //// hsync_width machine + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + hsync_width <= 0; + hsync_width_cnt <= 8'b0; + end else begin + if( hsync_rising ) begin + hsync_width <= hsync_width; + hsync_width_cnt <= 8'b1; + end else begin + if( hsync_falling ) begin + hsync_width <= hsync_width_cnt; + end else begin + hsync_width <= hsync_width; + end + hsync_width_cnt <= hsync_width_cnt + 8'b1; + end + end + end // always @ (posedge pclk) + + //// vactive machine + //// add detectors for lcd_de_latency and lcd_vsync_latency + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + vactive <= 0; + vactive_count <= 0; + + lcd_de_latency <= 0; + lcd_vsync_latency <= 0; + + lcd_de_pclk <= 0; + lcd_de_1pclk <= 0; + lcd_de_2pclk <= 0; + + lcd_vsync_pclk <= 0; + lcd_vsync_1pclk <= 0; + lcd_vsync_2pclk <= 0; + + first_lcd_de_rising <= 1; + end else begin // if ( rstin ) + if( vsync_rising ) begin + vactive <= vactive_count; + vactive_count <= 0; + end else begin + if( de_rising ) begin // counting in lines + vactive_count <= vactive_count + 12'b1; + end else begin + vactive_count <= vactive_count; + end + vactive <= vactive; + end // else: !if( vsync_rising ) + + lcd_de_2pclk <= lcd_de; + lcd_de_1pclk <= lcd_de_2pclk; + lcd_de_pclk <= lcd_de_1pclk; + + lcd_vsync_2pclk <= lcd_vsync; + lcd_vsync_1pclk <= lcd_vsync_2pclk; + lcd_vsync_pclk <= lcd_vsync_1pclk; + + if( vsync_rising ) begin + first_lcd_de_rising <= 1; + end else begin + if( !lcd_de_pclk & lcd_de_1pclk ) begin // rising edge + first_lcd_de_rising <= 0; + end else begin + first_lcd_de_rising <= first_lcd_de_rising; + end + end + + // look for the rising edge + if( !lcd_de_pclk & lcd_de_1pclk & first_lcd_de_rising ) begin + lcd_de_latency <= vactive_count; + end else begin + lcd_de_latency <= lcd_de_latency; + end + + // look for the rising edge + if( !lcd_vsync_pclk & lcd_vsync_1pclk ) begin + lcd_vsync_latency <= vactive_count; + end else begin + lcd_vsync_latency <= lcd_vsync_latency; + end + end // else: !if( rstin ) + end // always @ (posedge pclk) + + //// hactive mcahine + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + de_count <= 0; + hactive <= 0; + end else begin + if( de_rising ) begin + de_count <= 12'b1; // first pixel counts + end else if( de ) begin + de_count <= de_count + 12'b1; + end else begin + de_count <= de_count; + end + + if( de_falling ) begin + hactive <= de_count; + end else begin + hactive <= hactive; + end + end // else: !if( rstin ) + end // always @ (posedge pclk) + + //// htotal, vtotal machine + always @(posedge pclk or posedge rstin) begin + if( rstin ) begin + hcount <= 0; + vcount <= 0; + + htotal <= 0; + vtotal <= 0; + end else begin + if( vsync_rising ) begin + vtotal <= vcount; + vcount <= 24'b1; + end else begin + vcount <= vcount + 24'b1; /// counting in pixels + vtotal <= vtotal; + end + + if( de_rising ) begin + htotal <= hcount; + hcount <= 12'b1; + end else begin + hcount <= hcount + 12'b1; + htotal <= htotal; + end + end // else: !if( rstin ) + end // always @ (posedge pclk) + + //// refclock machine + //// lots of cross-domain madness that might break things. + always @(posedge refclk or posedge rstin) begin + if( rstin ) begin + refclkcnt <= 0; + refclkcounter <= 0; + + vsync____refclk <= 0; + vsync___refclk <= 0; + vsync__refclk <= 0; + vsync_refclk <= 0; + end else begin + vsync_refclk <= vsync__refclk; + vsync__refclk <= vsync___refclk; + vsync___refclk <= vsync____refclk; + vsync____refclk <= vsync; + + if( vsync_refclk_rising ) begin + refclkcnt <= refclkcounter; + refclkcounter <= 24'b1; + end else begin + refclkcnt <= refclkcnt; + refclkcounter <= refclkcounter + 24'b01; + end + + end // else: !if( rstin ) + end // always @ (posedge refclk or posedge rstin) + assign vsync_refclk_rising = !vsync_refclk & vsync__refclk; + + + //// rising/falling edge extraction machine + always @(posedge pclk or posedge rstin) begin + if(rstin) begin + vsync_d <= 0; + hsync_d <= 0; + de_d <= 0; + end else begin + vsync_d <= vsync; + hsync_d <= hsync; + de_d <= de; + end + end // always @ (posedge pclk) + + assign vsync_rising = vsync & !vsync_d; + assign hsync_rising = hsync & !hsync_d; + assign de_rising = de & !de_d; + + assign vsync_falling = !vsync & vsync_d; + assign hsync_falling = !hsync & hsync_d; + assign de_falling = !de & de_d; + +endmodule // timing_detector -- cgit v1.2.3