From fbc4b5ff9a742f4b4c3d4e1d6e976d2807ef271a Mon Sep 17 00:00:00 2001 From: AngeloJacobo Date: Sun, 29 Dec 2024 12:18:37 +0800 Subject: [PATCH] added initial files for spd --- rtl/spd/clk_wiz.v | 39 ++ rtl/spd/i2c_master.sv | 694 ++++++++++++++++++++++++++++++ rtl/spd/spd_reader.v | 103 +++++ rtl/spd/spd_reader_top.v | 60 +++ testbench/spd_tb/i2c_slave.v | 363 ++++++++++++++++ testbench/spd_tb/spd_reader_tb.sv | 44 ++ 6 files changed, 1303 insertions(+) create mode 100644 rtl/spd/clk_wiz.v create mode 100644 rtl/spd/i2c_master.sv create mode 100644 rtl/spd/spd_reader.v create mode 100644 rtl/spd/spd_reader_top.v create mode 100644 testbench/spd_tb/i2c_slave.v create mode 100644 testbench/spd_tb/spd_reader_tb.sv diff --git a/rtl/spd/clk_wiz.v b/rtl/spd/clk_wiz.v new file mode 100644 index 0000000..830dd21 --- /dev/null +++ b/rtl/spd/clk_wiz.v @@ -0,0 +1,39 @@ +`timescale 1ps/1ps + +module clk_wiz + ( + input clk_in1, + output clk_out1, + input reset, + output locked + ); + wire clk_out1_clk_wiz_0; + + + wire clkfbout; + + PLLE2_ADV + #(.BANDWIDTH ("OPTIMIZED"), + .COMPENSATION ("INTERNAL"), + .STARTUP_WAIT ("FALSE"), + .DIVCLK_DIVIDE (1), + .CLKFBOUT_MULT (5), // 200 MHz * 5 = 1000 MHz + .CLKFBOUT_PHASE (0.000), + .CLKOUT0_DIVIDE (10), // 1000 MHz / 10 = 100 MHz + .CLKOUT0_PHASE (0.000), + .CLKOUT0_DUTY_CYCLE (0.500), + .CLKIN1_PERIOD (5.000) // 200 MHz input + ) + plle2_adv_inst + ( + .CLKFBOUT (clkfbout), + .CLKOUT0 (clk_out1_clk_wiz_0), + .CLKFBIN (clkfbout), + .CLKIN1 (clk_in1), + .LOCKED (locked), + .RST (reset) + ); + BUFG clkout1_buf + (.O (clk_out1), + .I (clk_out1_clk_wiz_0)); +endmodule diff --git a/rtl/spd/i2c_master.sv b/rtl/spd/i2c_master.sv new file mode 100644 index 0000000..9cdf000 --- /dev/null +++ b/rtl/spd/i2c_master.sv @@ -0,0 +1,694 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: www.circuitden.com +// Engineer: Artin Isagholian +// artinisagholian@gmail.com +// +// Create Date: 01/20/2021 05:47:22 PM +// Design Name: +// Module Name: i2c_master +// Project Name: +// Target Devices: +// Tool Versions: +// Description: +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module i2c_master#( + parameter DATA_WIDTH = 8, + parameter REGISTER_WIDTH = 8, + parameter ADDRESS_WIDTH = 7 +)( + input wire clock, + input wire reset_n, + input wire enable, + input wire read_write, + input wire [DATA_WIDTH-1:0] mosi_data, + input wire [REGISTER_WIDTH-1:0] register_address, + input wire [ADDRESS_WIDTH-1:0] device_address, + input wire [15:0] divider, + + output reg [DATA_WIDTH-1:0] miso_data, + output reg busy, + + inout external_serial_data, + inout external_serial_clock, + // debug if slave NACKs + output reg slave_nack // high at same time as busy deasserts if slave nacks at previous transaction. otherwise low +); + + + /*INSTANTATION TEMPLATE +i2c_master #(.DATA_WIDTH(8),.REGISTER_WIDTH(8),.ADDRESS_WIDTH(7)) + i2c_master_inst( + .clock (), + .reset_n (), + .enable (), + .read_write (), + .mosi_data (), + .register_address (), + .device_address (), + .divider (), + + .miso_data (), + .busy (), + + .external_serial_data (), + .external_serial_clock (), + .slave_nack () + ); +*/ + +typedef enum +{ + S_IDLE = 0, + S_START = 1, + S_WRITE_ADDR_W = 2, + S_CHECK_ACK = 3, + S_WRITE_REG_ADDR = 4, + S_RESTART = 5, + S_WRITE_ADDR_R = 6, + S_READ_REG = 7, + S_SEND_NACK = 8, + S_SEND_STOP = 9, + S_WRITE_REG_DATA = 10, + S_WRITE_REG_ADDR_MSB = 11, + S_WRITE_REG_DATA_MSB = 12, + S_READ_REG_MSB = 13, + S_SEND_ACK = 14 +} state_type; + +state_type state; +state_type _state; +state_type post_state; +state_type _post_state; +reg serial_clock; +logic _serial_clock; +reg [ADDRESS_WIDTH:0] saved_device_address; +logic [ADDRESS_WIDTH:0] _saved_device_address; +reg [REGISTER_WIDTH-1:0] saved_register_address; +logic [REGISTER_WIDTH-1:0] _saved_register_address; +reg [DATA_WIDTH-1:0] saved_mosi_data; +logic [DATA_WIDTH-1:0] _saved_mosi_data; +reg [1:0] process_counter; +logic [1:0] _process_counter; +reg [7:0] bit_counter; +logic [7:0] _bit_counter; +reg serial_data; +logic _serial_data; +reg post_serial_data; +logic _post_serial_data; +reg last_acknowledge; +logic _last_acknowledge; +logic _saved_read_write; +reg saved_read_write; +reg [15:0] divider_counter; +logic [15:0] _divider_counter; +reg divider_tick; +logic [DATA_WIDTH-1:0] _miso_data; +logic _busy; +logic serial_data_output_enable; +logic serial_clock_output_enable; + +assign external_serial_clock = (serial_clock_output_enable) ? serial_clock : 1'bz; +assign external_serial_data = (serial_data_output_enable) ? serial_data : 1'bz; + +// determine if slave NACK +reg last_acknowledge_q; +always_ff @(posedge clock) begin + if (!reset_n) begin + last_acknowledge_q <= 1'b0; + slave_nack <= 1'b0; + end + else begin + last_acknowledge_q <= last_acknowledge? 1'b1 : last_acknowledge_q; // if last_ack becomes high, store that high + if(!_busy && busy) begin // if busy is about to deassert next clk + last_acknowledge_q <= 1'b0; // reset to zero + slave_nack <= !last_acknowledge_q;// if last_acknowledge_q never becomes 1 then the slave NACKs + end + else if(_busy) begin + slave_nack <= 1'b0; + end + end +end + +always_comb begin + _state = state; + _post_state = post_state; + _process_counter = process_counter; + _bit_counter = bit_counter; + _last_acknowledge = last_acknowledge; + _miso_data = miso_data; + _saved_read_write = saved_read_write; + _busy = busy; + _divider_counter = divider_counter; + _saved_register_address = saved_register_address; + _saved_device_address = saved_device_address; + _saved_mosi_data = saved_mosi_data; + _serial_data = serial_data; + _serial_clock = serial_clock; + _post_serial_data = post_serial_data; + + if (divider_counter == divider) begin + _divider_counter = 0; + divider_tick = 1; + end + else begin + _divider_counter = divider_counter + 1; + divider_tick = 0; + end + + if (state!=S_IDLE && state!=S_CHECK_ACK && state!=S_READ_REG && state!=S_READ_REG_MSB) begin + serial_data_output_enable = 1; + end + else begin + serial_data_output_enable = 0; + end + + if (state!=S_IDLE && process_counter!=1 && process_counter!=2) begin + serial_clock_output_enable = 1; + end + else begin + serial_clock_output_enable = 0; + end + + case (state) + S_IDLE: begin + _process_counter = 0; + _bit_counter = 0; + _last_acknowledge = 0; + _busy = 0; + _saved_read_write = read_write; + _saved_register_address = register_address; + _saved_device_address = {device_address,1'b0}; + _saved_mosi_data = mosi_data; + _serial_data = 1; + _serial_clock = 1; + + if (enable) begin + _state = S_START; + _post_state = S_WRITE_ADDR_W; + _busy = 1; + end + end + S_START: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _process_counter = 1; + end + 1: begin + _serial_data = 0; + _process_counter = 2; + end + 2: begin + _bit_counter = 8; + _process_counter = 3; + end + 3: begin + _serial_clock = 0; + _process_counter = 0; + _state = post_state; + _serial_data = saved_device_address[ADDRESS_WIDTH]; + end + endcase + end + end + S_WRITE_ADDR_W: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _post_serial_data = saved_register_address[REGISTER_WIDTH-1]; + + if (REGISTER_WIDTH == 16) begin + _post_state = S_WRITE_REG_ADDR_MSB; + end + else begin + _post_state = S_WRITE_REG_ADDR; + end + + _state = S_CHECK_ACK; + _bit_counter = 8; + end + else begin + _serial_data = saved_device_address[bit_counter-1]; + end + _process_counter = 0; + end + endcase + end + end + S_CHECK_ACK: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + + if (external_serial_data == 0) begin + _last_acknowledge = 1; + end + _process_counter = 3; + end + 3: begin + if (last_acknowledge == 1) begin + _last_acknowledge = 0; + _serial_data = post_serial_data; + _state = post_state; + end + else begin + _state = S_SEND_STOP; + end + _process_counter = 0; + end + endcase + end + end + S_WRITE_REG_ADDR_MSB: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _post_state = S_WRITE_REG_ADDR; + _post_serial_data = saved_register_address[7]; + _bit_counter = 8; + _serial_data = 0; + _state = S_CHECK_ACK; + end + else begin + _serial_data = saved_register_address[bit_counter+7]; + end + _process_counter = 0; + end + endcase + end + end + S_WRITE_REG_ADDR: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + if (read_write == 0) begin + if (DATA_WIDTH == 16) begin + _post_state = S_WRITE_REG_DATA_MSB; + _post_serial_data = saved_mosi_data[DATA_WIDTH == 16? 15 : 7]; + end + else begin + _post_state = S_WRITE_REG_DATA; + _post_serial_data = saved_mosi_data[7]; + end + end + else begin + _post_state = S_RESTART; + _post_serial_data = 1; + end + _bit_counter = 8; + _serial_data = 0; + _state = S_CHECK_ACK; + end + else begin + _serial_data = saved_register_address[bit_counter-1]; + end + _process_counter = 0; + end + endcase + end + end + S_WRITE_REG_DATA_MSB: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _state = S_CHECK_ACK; + _post_state = S_WRITE_REG_DATA; + _post_serial_data = saved_mosi_data[7]; + _bit_counter = 8; + _serial_data = 0; + end + else begin + _serial_data = saved_mosi_data[bit_counter+7]; + end + _process_counter = 0; + end + endcase + end + end + S_WRITE_REG_DATA: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _state = S_CHECK_ACK; + _post_state = S_SEND_STOP; + _post_serial_data = 0; + _bit_counter = 8; + _serial_data = 0; + end + else begin + _serial_data = saved_mosi_data[bit_counter-1]; + end + _process_counter = 0; + end + endcase + end + end + S_RESTART: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _process_counter = 1; + end + 1: begin + _process_counter = 2; + _serial_clock = 1; + end + 2: begin + _process_counter = 3; + end + 3: begin + _state = S_START; + _post_state = S_WRITE_ADDR_R; + _saved_device_address[0] = 1; + _process_counter = 0; + end + endcase + end + end + S_WRITE_ADDR_R: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + if (DATA_WIDTH == 16) begin + _post_state = S_READ_REG_MSB; + _post_serial_data = 0; + end + else begin + _post_state = S_READ_REG; + _post_serial_data = 0; + end + _state = S_CHECK_ACK; + _bit_counter = 8; + end + else begin + _serial_data = saved_device_address[bit_counter-1]; + end + _process_counter = 0; + end + endcase + end + end + S_READ_REG_MSB: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + //sample data on this rising edge of scl + _miso_data[bit_counter+7] = external_serial_data; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _post_state = S_READ_REG; + _state = S_SEND_ACK; + _bit_counter = 8; + _serial_data = 0; + end + _process_counter = 0; + end + endcase + end + end + S_READ_REG: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _serial_clock = 0; + //sample data on this rising edge of scl + _miso_data[bit_counter-1] = external_serial_data; + _bit_counter = bit_counter - 1; + _process_counter = 3; + end + 3: begin + if (bit_counter == 0) begin + _state = S_SEND_NACK; + _serial_data = 0; + end + _process_counter = 0; + end + endcase + end + end + S_SEND_NACK: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _serial_data = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _process_counter = 3; + _serial_clock = 0; + end + 3: begin + _state = S_SEND_STOP; + _process_counter = 0; + _serial_data = 0; + end + endcase + end + end + S_SEND_ACK: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + _serial_data = 0; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _process_counter = 3; + _serial_clock = 0; + end + 3: begin + _state = post_state; + _process_counter = 0; + end + endcase + end + end + S_SEND_STOP: begin + if (divider_tick) begin + case (process_counter) + 0: begin + _serial_clock = 1; + _process_counter = 1; + end + 1: begin + //check for clock stretching + if (external_serial_clock == 1) begin + _last_acknowledge = 0; + _process_counter = 2; + end + end + 2: begin + _process_counter = 3; + _serial_data = 1; + end + 3: begin + _state = S_IDLE; + end + endcase + end + end + endcase +end + +always_ff @(posedge clock) begin + if (!reset_n) begin + state <= S_IDLE; + post_state <= S_IDLE; + process_counter <= 0; + bit_counter <= 0; + last_acknowledge <= 0; + miso_data <= 0; + saved_read_write <= 0; + divider_counter <= 0; + saved_device_address <= 0; + saved_register_address <= 0; + saved_mosi_data <= 0; + serial_clock <= 0; + serial_data <= 0; + saved_mosi_data <= 0; + post_serial_data <= 0; + busy <= 0; + end + else begin + state <= _state; + post_state <= _post_state; + process_counter <= _process_counter; + bit_counter <= _bit_counter; + last_acknowledge <= _last_acknowledge; + miso_data <= _miso_data; + saved_read_write <= _saved_read_write; + divider_counter <= _divider_counter; + saved_device_address <= _saved_device_address; + saved_register_address <= _saved_register_address; + saved_mosi_data <= _saved_mosi_data; + serial_clock <= _serial_clock; + serial_data <= _serial_data; + post_serial_data <= _post_serial_data; + busy <= _busy; + end + end + +endmodule \ No newline at end of file diff --git a/rtl/spd/spd_reader.v b/rtl/spd/spd_reader.v new file mode 100644 index 0000000..320a417 --- /dev/null +++ b/rtl/spd/spd_reader.v @@ -0,0 +1,103 @@ +`default_nettype none +`timescale 1ns / 1ps + +module spd_reader ( + // clock and reset + input wire i_clk, + input wire i_rst_n, + // i2c interface + inout wire i2c_scl, + inout wire i2c_sda + // uart interface + // input uart_rx, + // output uart_tx +); + + // byte 2: DRAM Device Type (DDR3 SDRAM = 0x0B) + // byte 3: Module Type (SO-DIMM = 0x03) + // byte 4: SDRAM Density and Banks ([6:4] = BA_BITS, [3:0] = SDRAM capacity) + // byte 5: SDRAM Addressing ([5:3] = Row Addr , [2:0] = Column Addr) + // byte 7: Module Organization ([5:3] = Ranks , Device Width = [2:0]) + // byte 8: Module Memory Bus Width ([2:0] = Bus Width) + // byte 10,11: Medium Timebase (MTB) Dividend (0x01), Medium Timebase (MTB) Divisor (0x08 = 0.125ns , 0x10 = 0.0625ns) (tXX = tXX(MTB) * MTB) + // byte 12: SDRAM Minimum Cycle Time tCK + + localparam I2C_ADDRESS = 7'h30; + localparam IDLE = 0, + READ_ADDRESS = 1, + WAIT_ACK = 2; + (* mark_debug = "true" *) reg[1:0] state_find_i2c_address; + (* mark_debug = "true" *) reg find_i2c_address_done; + (* mark_debug = "true" *) reg[6:0] i2c_address; + + // i2c master interface + reg enable; + reg read_write; + reg[7:0] register_address; + reg[6:0] device_address; + wire[7:0] miso_data; + wire busy; + wire slave_nack; + + always @(posedge i_clk, negedge i_rst_n) begin + if(!i_rst_n) begin + state_find_i2c_address <= IDLE; + find_i2c_address_done <= 0; + enable <= 1'b0; + read_write <= 1'b0; + register_address <= 8'd0; + i2c_address <= 7'd0; + end + else begin + // Find I2C Address of SPD + case(state_find_i2c_address) + IDLE: if(!find_i2c_address_done) begin + state_find_i2c_address <= READ_ADDRESS; + i2c_address <= 7'd0; + end + READ_ADDRESS: if(!busy) begin + enable <= 1'b1; + read_write <= 1'b1; // read i2c + register_address <= 8'h00; // just always read byte 0 + device_address <= i2c_address; + state_find_i2c_address <= WAIT_ACK; + end + WAIT_ACK: if(!busy && !enable) begin + if(slave_nack) begin // if wrong i2c_address + i2c_address <= i2c_address + 1; // increment i2c address + state_find_i2c_address <= READ_ADDRESS; + end + else begin // I2C acks so i2c_address is correct! + state_find_i2c_address <= IDLE; + find_i2c_address_done <= 1'b1; + end + end + else begin + enable <= 1'b0; + end + endcase + end + end + + // module instantiations + i2c_master #(.DATA_WIDTH(8),.REGISTER_WIDTH(8),.ADDRESS_WIDTH(7)) + i2c_master_inst( + .clock (i_clk), + .reset_n (i_rst_n), + .enable (enable), + .read_write (read_write), + .mosi_data (8'd0), + .register_address (register_address), + .device_address (device_address), + .divider (249), // 100MHz/(4*(249+1)) = 100KHz + + .miso_data (miso_data), + .busy (busy), + + .external_serial_data (i2c_sda), + .external_serial_clock (i2c_scl), + .slave_nack (slave_nack) + ); +endmodule + + diff --git a/rtl/spd/spd_reader_top.v b/rtl/spd/spd_reader_top.v new file mode 100644 index 0000000..436dffb --- /dev/null +++ b/rtl/spd/spd_reader_top.v @@ -0,0 +1,60 @@ +`default_nettype none +`timescale 1ns / 1ps + +module spd_reader_top ( + // clock and reset + input wire sys_clk_p, + input wire sys_clk_n, + input wire i_rst_n, + // i2c interface + inout wire i2c_scl, + inout wire i2c_sda, + output wire i2c_lsb, + // fan + output wire fan_pwm +); + assign fan_pwm = 1'b0; // turn on fan + assign i2c_lsb = 1'b0; + wire clk_locked; + wire main_clk_100; + + + //=========================================================================== + //Differentia system clock to single end clock + //=========================================================================== + wire sys_clk; // 200MHz + IBUFGDS u_ibufg_sys_clk + ( + .I (sys_clk_p), + .IB (sys_clk_n), + .O (sys_clk) + ); + + //=========================================================================== + // Generate 100MHz + //=========================================================================== + clk_wiz clk_wiz_inst + ( + // Clock out ports + .clk_out1(main_clk_100), + // Status and control signals + .reset(!i_rst_n), + .locked(clk_locked), + // Clock in ports + .clk_in1(sys_clk) + ); + + //=========================================================================== + // Instantiate SPD reader + //=========================================================================== + spd_reader spd_reader_inst ( + .i_clk(main_clk_100), + .i_rst_n(i_rst_n && clk_locked), + .i2c_scl(i2c_scl), + .i2c_sda(i2c_sda) + ); + + +endmodule + + diff --git a/testbench/spd_tb/i2c_slave.v b/testbench/spd_tb/i2c_slave.v new file mode 100644 index 0000000..c10c1a6 --- /dev/null +++ b/testbench/spd_tb/i2c_slave.v @@ -0,0 +1,363 @@ +`timescale 1ns / 1ps +///////////////////////////////////////////////////////////////////// +//// //// +//// WISHBONE rev.B2 compliant synthesizable I2C Slave model //// +//// //// +//// //// +//// Authors: Richard Herveille (richard@asics.ws) www.asics.ws //// +//// John Sheahan (jrsheahan@optushome.com.au) //// +//// //// +//// Downloaded from: http://www.opencores.org/projects/i2c/ //// +//// //// +///////////////////////////////////////////////////////////////////// +//// //// +//// Copyright (C) 2001,2002 Richard Herveille //// +//// richard@asics.ws //// +//// //// +//// This source file may be used and distributed without //// +//// restriction provided that this copyright statement is not //// +//// removed from the file and that any derivative work contains //// +//// the original copyright notice and the associated disclaimer.//// +//// //// +//// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY //// +//// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED //// +//// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS //// +//// FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR //// +//// 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. //// +//// //// +///////////////////////////////////////////////////////////////////// + +// CVS Log +// +// $Id: i2c_slave_model.v,v 1.7 2006-09-04 09:08:51 rherveille Exp $ +// +// $Date: 2006-09-04 09:08:51 $ +// $Revision: 1.7 $ +// $Author: rherveille $ +// $Locker: $ +// $State: Exp $ +// +// Change History: +// $Log: not supported by cvs2svn $ +// Revision 1.6 2005/02/28 11:33:48 rherveille +// Fixed Tsu:sta timing check. +// Added Thd:sta timing check. +// +// Revision 1.5 2003/12/05 11:05:19 rherveille +// Fixed slave address MSB='1' bug +// +// Revision 1.4 2003/09/11 08:25:37 rherveille +// Fixed a bug in the timing section. Changed 'tst_scl' into 'tst_sto'. +// +// Revision 1.3 2002/10/30 18:11:06 rherveille +// Added timing tests to i2c_model. +// Updated testbench. +// +// Revision 1.2 2002/03/17 10:26:38 rherveille +// Fixed some race conditions in the i2c-slave model. +// Added debug information. +// Added headers. +// + +module i2c_slave (scl, sda); + + // + // parameters + // + parameter I2C_ADR = 7'b001_0000; + + // + // input && outpus + // + input scl; + inout sda; + + // + // Variable declaration + // + wire debug = 1'b1; + genvar i; + + reg [7:0] mem [3:0]; // initiate memory + + initial begin + mem[0] = 8'd00; + mem[1] = 8'd01; + mem[2] = 8'd02; + mem[3] = 8'd03; + end + + reg [7:0] mem_adr; // memory address + reg [7:0] mem_do; // memory data output + + reg sta, d_sta; + reg sto, d_sto; + + reg [7:0] sr; // 8bit shift register + reg rw; // read/write direction + + wire my_adr; // my address called ?? + wire i2c_reset; // i2c-statemachine reset + reg [2:0] bit_cnt; // 3bit downcounter + wire acc_done; // 8bits transfered + reg ld; // load downcounter + + reg sda_o; // sda-drive level + wire sda_dly; // delayed version of sda + + // statemachine declaration + parameter idle = 3'b000; + parameter slave_ack = 3'b001; + parameter get_mem_adr = 3'b010; + parameter gma_ack = 3'b011; + parameter data = 3'b100; + parameter data_ack = 3'b101; + + reg [2:0] state; // synopsys enum_state + + // + // module body + // + + initial + begin + sda_o = 1'b1; + state = idle; + end + + // generate shift register + always @(posedge scl) + sr <= #1 {sr[6:0],sda}; + + //detect my_address + assign my_adr = (sr[7:1] == I2C_ADR); + // FIXME: This should not be a generic assign, but rather + // qualified on address transfer phase and probably reset by stop + + //generate bit-counter + always @(posedge scl) + if(ld) + bit_cnt <= #1 3'b111; + else + bit_cnt <= #1 bit_cnt - 3'h1; + + //generate access done signal + assign acc_done = !(|bit_cnt); + + // generate delayed version of sda + // this model assumes a hold time for sda after the falling edge of scl. + // According to the Phillips i2c spec, there s/b a 0 ns hold time for sda + // with regards to scl. If the data changes coincident with the clock, the + // acknowledge is missed + // Fix by Michael Sosnoski + assign #1 sda_dly = sda; + + + //detect start condition + always @(negedge sda) + if(scl) + begin + sta <= #1 1'b1; + d_sta <= #1 1'b0; + sto <= #1 1'b0; + + if(debug) + $display("DEBUG i2c_slave; start condition detected at %t", $time); + end + else + sta <= #1 1'b0; + + always @(posedge scl) + d_sta <= #1 sta; + + // detect stop condition + always @(posedge sda) + if(scl) + begin + sta <= #1 1'b0; + sto <= #1 1'b1; + + if(debug) + $display("DEBUG i2c_slave; stop condition detected at %t", $time); + end + else + sto <= #1 1'b0; + + //generate i2c_reset signal + assign i2c_reset = sta || sto; + + // generate statemachine + always @(negedge scl or posedge sto) + if (sto || (sta && !d_sta) ) + begin + state <= #1 idle; // reset statemachine + + sda_o <= #1 1'b1; + ld <= #1 1'b1; + end + else + begin + // initial settings + sda_o <= #1 1'b1; + ld <= #1 1'b0; + + case(state) // synopsys full_case parallel_case + idle: // idle state + if (acc_done && my_adr) + begin + state <= #1 slave_ack; + rw <= #1 sr[0]; + sda_o <= #1 1'b0; // generate i2c_ack + + #2; + if(debug && rw) + $display("DEBUG i2c_slave; command byte received (read) at %t", $time); + if(debug && !rw) + $display("DEBUG i2c_slave; command byte received (write) at %t", $time); + + if(rw) + begin + mem_do <= #1 mem[mem_adr]; + + if(debug) + begin + #2 $display("DEBUG i2c_slave; data block read %x from address %x (1)", mem_do, mem_adr); + #2 $display("DEBUG i2c_slave; memcheck [0]=%x, [1]=%x, [2]=%x", mem[4'h0], mem[4'h1], mem[4'h2]); + end + end + end + + slave_ack: + begin + if(rw) + begin + state <= #1 data; + sda_o <= #1 mem_do[7]; + end + else + state <= #1 get_mem_adr; + + ld <= #1 1'b1; + end + + get_mem_adr: // wait for memory address + if(acc_done) + begin + state <= #1 gma_ack; + mem_adr <= #1 sr; // store memory address + sda_o <= #1 !(sr <= 15); // generate i2c_ack, for valid address + + if(debug) + #1 $display("DEBUG i2c_slave; address received. adr=%x, ack=%b", sr, sda_o); + end + + gma_ack: + begin + state <= #1 data; + ld <= #1 1'b1; + end + + data: // receive or drive data + begin + if(rw) + sda_o <= #1 mem_do[7]; + + if(acc_done) + begin + state <= #1 data_ack; + mem_adr <= #2 mem_adr + 8'h1; + sda_o <= #1 (rw && (mem_adr <= 15) ); // send ack on write, receive ack on read + + if(rw) + begin + #3 mem_do <= mem[mem_adr]; + + if(debug) + #5 $display("DEBUG i2c_slave; data block read %x from address %x (2)", mem_do, mem_adr); + end + + if(!rw) + begin + mem[ mem_adr[3:0] ] <= #1 sr; // store data in memory + + if(debug) + #2 $display("DEBUG i2c_slave; data block write %x to address %x", sr, mem_adr); + end + end + end + + data_ack: + begin + ld <= #1 1'b1; + + if(rw) + if(sr[0]) // read operation && master send NACK + begin + state <= #1 idle; + sda_o <= #1 1'b1; + end + else + begin + state <= #1 data; + sda_o <= #1 mem_do[7]; + end + else + begin + state <= #1 data; + sda_o <= #1 1'b1; + end + end + + endcase + end + + // read data from memory + always @(posedge scl) + if(!acc_done && rw) + mem_do <= #1 {mem_do[6:0], 1'b1}; // insert 1'b1 for host ack generation + + // generate tri-states + assign sda = sda_o ? 1'bz : 1'b0; + + + // + // Timing checks + // + + wire tst_sto = sto; + wire tst_sta = sta; + + specify + specparam normal_scl_low = 4700, + normal_scl_high = 4000, + normal_tsu_sta = 4700, + normal_thd_sta = 4000, + normal_tsu_sto = 4000, + normal_tbuf = 4700, + + fast_scl_low = 1300, + fast_scl_high = 600, + fast_tsu_sta = 1300, + fast_thd_sta = 600, + fast_tsu_sto = 600, + fast_tbuf = 1300; + + $width(negedge scl, normal_scl_low); // scl low time + $width(posedge scl, normal_scl_high); // scl high time + + $setup(posedge scl, negedge sda &&& scl, normal_tsu_sta); // setup start + $setup(negedge sda &&& scl, negedge scl, normal_thd_sta); // hold start + $setup(posedge scl, posedge sda &&& scl, normal_tsu_sto); // setup stop + + $setup(posedge tst_sta, posedge tst_sto, normal_tbuf); // stop to start time + endspecify + +endmodule diff --git a/testbench/spd_tb/spd_reader_tb.sv b/testbench/spd_tb/spd_reader_tb.sv new file mode 100644 index 0000000..543cdd8 --- /dev/null +++ b/testbench/spd_tb/spd_reader_tb.sv @@ -0,0 +1,44 @@ +`timescale 1ns / 1ps + +module spd_reader_tb; + reg clk, rst_n; + wire scl, sda; + + // spd_reader DUT ( + // .i_clk(clk), + // .i_rst_n(rst_n), + // .i2c_scl(scl), + // .i2c_sda(sda) + // ); + spd_reader_top DUT ( + // clock and reset + .sys_clk_p(clk), + .sys_clk_n(!clk), + .i_rst_n(rst_n), + // i2c interface + .i2c_scl(scl), + .i2c_sda(sda), + .i2c_lsb(), + // fan + .fan_pwm() + ); + + initial begin + clk = 0; + rst_n = 0; + #100; + rst_n = 1; + wait(DUT.spd_reader_inst.find_i2c_address_done); + #10_000; + $stop; + end + always #2.5 clk = !clk; // 200MHz + + pullup pullup_scl(scl); // pullup scl line + pullup pullup_sda(sda); // pullup sda line + + i2c_slave i2c_slave( + .scl(scl), + .sda(sda) + ); +endmodule \ No newline at end of file