From cb2d44f4b65489179d6d2080714e6af06c19fbae Mon Sep 17 00:00:00 2001 From: Nikolay Puzanov Date: Tue, 14 Jun 2022 12:50:47 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + i2c_io_output.sv | 89 +++++++++++++++++++++ i2c_io_output_tb/.dir-locals.el | 1 + i2c_io_output_tb/Makefile | 7 ++ i2c_io_output_tb/i2c_io_output_tb.sv | 111 +++++++++++++++++++++++++++ i2c_receiver.sv | 106 +++++++++++++++++++++++++ i2c_receiver_tb/.dir-locals.el | 1 + i2c_receiver_tb/Makefile | 7 ++ i2c_receiver_tb/i2c_receiver_tb.sv | 109 ++++++++++++++++++++++++++ 9 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 i2c_io_output.sv create mode 100644 i2c_io_output_tb/.dir-locals.el create mode 100644 i2c_io_output_tb/Makefile create mode 100644 i2c_io_output_tb/i2c_io_output_tb.sv create mode 100644 i2c_receiver.sv create mode 100644 i2c_receiver_tb/.dir-locals.el create mode 100644 i2c_receiver_tb/Makefile create mode 100644 i2c_receiver_tb/i2c_receiver_tb.sv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95f22f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.vvp +*.fst diff --git a/i2c_io_output.sv b/i2c_io_output.sv new file mode 100644 index 0000000..1b8cdda --- /dev/null +++ b/i2c_io_output.sv @@ -0,0 +1,89 @@ +`timescale 1ps/1ps +`default_nettype none + +module i2c_io_output #(parameter [6:0] I2C_ADDR = 7'h5a, + parameter IO_BYTES_COUNT = 2, + localparam DATA_WIDTH = IO_BYTES_COUNT * 8) + (input wire clock, + input wire reset, + + input wire i_scl, + input wire i_sdi, + output wire o_sdo, + + output reg [DATA_WIDTH-1:0] o_data); + + /* -------- I2C Receiver -------- */ + logic [7:0] i2c_data; + logic i2c_strobe; + logic i2c_start; + logic i2c_stop; + logic i2c_ack; + + i2c_receiver i2c_receiver_impl + (.clock(clock), .reset(reset), + .i_scl(i_scl), .i_sdi(i_sdi), .o_sdo(o_sdo), + .o_data(i2c_data), + .o_strobe(i2c_strobe), + .o_start(i2c_start), + .o_stop(i2c_stop), + .i_ack(i2c_ack)); + + /* -------- FSM -------- */ + enum int unsigned { + ST_IDLE = 0, + ST_RECEIVE_ADDR, + ST_RECEIVE_BYTE, + ST_COMPLETE + } state; + + localparam BYTE_CNTR_W = $clog2(IO_BYTES_COUNT); + + logic [BYTE_CNTR_W-1:0] byte_cntr; + logic [DATA_WIDTH-1:0] data; + + always_ff @(posedge clock) + if (reset) begin + state <= ST_IDLE; + byte_cntr <= '0; + end + else begin + if (i2c_stop) + state <= ST_IDLE; + case (state) + ST_IDLE: begin + if (i2c_start) + state <= ST_RECEIVE_ADDR; + end + + ST_RECEIVE_ADDR: begin + if (i2c_strobe) + if (i2c_data == {I2C_ADDR, 1'b0}) begin + i2c_ack <= 1'b1; + byte_cntr <= '0; + state <= ST_RECEIVE_BYTE; + end + else begin + i2c_ack <= 1'b0; + state <= ST_IDLE; + end + end + + ST_RECEIVE_BYTE: begin + if (i2c_strobe) begin + data <= {data[DATA_WIDTH-8-1:0], i2c_data}; // MSB first + byte_cntr <= byte_cntr + 1'b1; + + if (byte_cntr == BYTE_CNTR_W'(IO_BYTES_COUNT-1)) + state <= ST_COMPLETE; + end + end + + ST_COMPLETE: begin + o_data <= data; + state <= ST_IDLE; + end + endcase + end + +endmodule // i2c_io_output diff --git a/i2c_io_output_tb/.dir-locals.el b/i2c_io_output_tb/.dir-locals.el new file mode 100644 index 0000000..e0b768f --- /dev/null +++ b/i2c_io_output_tb/.dir-locals.el @@ -0,0 +1 @@ +((verilog-mode . ((flycheck-verilator-include-path . ("../"))))) diff --git a/i2c_io_output_tb/Makefile b/i2c_io_output_tb/Makefile new file mode 100644 index 0000000..5eca96a --- /dev/null +++ b/i2c_io_output_tb/Makefile @@ -0,0 +1,7 @@ +SOURCES = ../i2c_io_output.sv ../i2c_receiver.sv ./i2c_io_output_tb.sv + +all: run + +run: $(SOURCES) + iverilog -g2012 -DDUMP -o out.vvp $(SOURCES) + vvp ./out.vvp -fst diff --git a/i2c_io_output_tb/i2c_io_output_tb.sv b/i2c_io_output_tb/i2c_io_output_tb.sv new file mode 100644 index 0000000..24b605d --- /dev/null +++ b/i2c_io_output_tb/i2c_io_output_tb.sv @@ -0,0 +1,111 @@ +`timescale 1ps/1ps + +/* verilator lint_off DECLFILENAME */ +/* verilator lint_off MULTITOP */ +/* verilator lint_off STMTDLY */ +/* verilator lint_off INFINITELOOP */ +/* verilator lint_off INITIALDLY */ + +module i2c_io_output_tb; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock = ~clock; + + parameter SCL_PERIOD = 364793; // ~300ns + logic scl_async; + logic sda_async; + + event scl_low_ev; + event scl_high_ev; + + initial begin + scl_async = 1'b1; + + forever begin + #(SCL_PERIOD/4) scl_async = 1'b1; + #(SCL_PERIOD/4) -> scl_high_ev; + #(SCL_PERIOD/4) scl_async = 1'b0; + #(SCL_PERIOD/4) -> scl_low_ev; + end + end + + parameter IO_BYTES_COUNT = 2; + parameter [6:0] I2C_ADDRESS = 7'h5a; + localparam DATA_WIDTH = IO_BYTES_COUNT * 8; + + logic i_scl; + logic i_sdi; + logic o_sdo; + logic [DATA_WIDTH-1:0] o_data; + + i2c_io_output #(.I2C_ADDR(I2C_ADDRESS), + .IO_BYTES_COUNT(IO_BYTES_COUNT)) + DUT (.*); + + logic [1:0] scl_sync; + logic [1:0] sda_sync; + + always_ff @(posedge clock) + if (reset) begin + scl_sync <= '1; + sda_sync <= '1; + end + else begin + scl_sync <= {scl_sync[0], scl_async}; + sda_sync <= {sda_sync[0], sda_async}; + end + + assign i_scl = scl_sync[1]; + assign i_sdi = sda_sync[1]; + + task send_start; + wait(scl_async == 1'b0); + sda_async = 1'b1; + @(scl_high_ev); + sda_async = 1'b0; + endtask + + task send_stop; + wait(scl_async == 1'b0); + sda_async = 1'b0; + @(scl_high_ev); + sda_async = 1'b1; + endtask + + task send_byte(logic [7:0] b); + for (int n = 7; n >= 0; n -= 1) begin + @(scl_low_ev); + sda_async = b[n]; + end + + @(scl_low_ev); + wait(scl_async == 1'b1); + endtask + + initial begin + reset = 1'b1; + repeat(2) @(posedge clock) #1; + reset = 1'b0; + repeat(2) @(posedge clock) #1; + + send_start; + send_byte(8'hb4); + send_byte(8'ha5); + send_byte(8'h38); + send_byte(8'h42); + send_stop; + + repeat(1000) @(posedge clock) #1; + $finish; + end + +`ifdef DUMP + initial begin + $dumpfile("i2c_io_output_tb.fst"); + $dumpvars(0, i2c_io_output_tb); + end +`endif + +endmodule // i2c_io_output_tb diff --git a/i2c_receiver.sv b/i2c_receiver.sv new file mode 100644 index 0000000..c21b3fc --- /dev/null +++ b/i2c_receiver.sv @@ -0,0 +1,106 @@ +`timescale 1ps/1ps +`default_nettype none + +module i2c_receiver + (input wire clock, + input wire reset, + + input wire i_scl, + input wire i_sdi, + output wire o_sdo, + + output wire [7:0] o_data, + output wire o_strobe, + output wire o_start, + output wire o_stop, + input wire i_ack); // Active ONE + + logic sdi_prev; + logic scl_prev; + logic start_condition; + logic stop_condition; + logic scl_rise; + logic scl_fall; + + always_ff @(posedge clock) + if (reset) begin + sdi_prev <= 1'b1; + scl_prev <= 1'b1; + end + else begin + sdi_prev <= i_sdi; + scl_prev <= i_scl; + end + + assign start_condition = (sdi_prev == 1'b1) && (i_sdi == 1'b0) && (scl_prev == 1'b1) && (i_scl == 1'b1); + assign stop_condition = (sdi_prev == 1'b0) && (i_sdi == 1'b1) && (scl_prev == 1'b1) && (i_scl == 1'b1); + assign scl_rise = (scl_prev == 1'b0) && (i_scl == 1'b1); + assign scl_fall = (scl_prev == 1'b1) && (i_scl == 1'b0); + + enum int unsigned { + ST_WAIT_FOR_START = 0, + ST_RECEIVE_BITS, + ST_WAIT_FOR_SCL_LOW, + ST_SEND_ACK + } state; + + logic [2:0] bit_cntr; + logic [7:0] data_sr; + logic byte_strobe; + logic sdo; + + always_ff @(posedge clock) + if (reset) begin + state <= ST_WAIT_FOR_START; + bit_cntr <= '0; + byte_strobe <= 1'b0; + sdo <= 1'b1; + end + else begin + byte_strobe <= 1'b0; + + if (stop_condition) + state <= ST_WAIT_FOR_START; + else + case (state) + ST_WAIT_FOR_START: begin + sdo <= 1'b1; + + if (start_condition) begin + bit_cntr <= '0; + state <= ST_RECEIVE_BITS; + end + end + + ST_RECEIVE_BITS: + if (scl_rise) begin + data_sr <= {data_sr[6:0], i_sdi}; + bit_cntr <= bit_cntr + 1'b1; + + if (bit_cntr == 3'd7) begin + state <= ST_WAIT_FOR_SCL_LOW; + byte_strobe <= 1'b1; + end + end + + ST_WAIT_FOR_SCL_LOW: + if (scl_fall) begin + sdo <= i_ack ? 1'b0 : 1'b1; + state <= ST_SEND_ACK; + end + + ST_SEND_ACK: + if (scl_fall) begin + sdo <= 1'b1; + state <= ST_RECEIVE_BITS; + end + endcase + end + + assign o_data = data_sr; + assign o_strobe = byte_strobe; + assign o_start = start_condition; + assign o_stop = stop_condition; + assign o_sdo = sdo; + +endmodule // i2c_receiver diff --git a/i2c_receiver_tb/.dir-locals.el b/i2c_receiver_tb/.dir-locals.el new file mode 100644 index 0000000..e0b768f --- /dev/null +++ b/i2c_receiver_tb/.dir-locals.el @@ -0,0 +1 @@ +((verilog-mode . ((flycheck-verilator-include-path . ("../"))))) diff --git a/i2c_receiver_tb/Makefile b/i2c_receiver_tb/Makefile new file mode 100644 index 0000000..b71804c --- /dev/null +++ b/i2c_receiver_tb/Makefile @@ -0,0 +1,7 @@ +SOURCES = ../i2c_receiver.sv ./i2c_receiver_tb.sv + +all: run + +run: $(SOURCES) + iverilog -g2012 -DDUMP -o out.vvp $(SOURCES) + vvp ./out.vvp -fst diff --git a/i2c_receiver_tb/i2c_receiver_tb.sv b/i2c_receiver_tb/i2c_receiver_tb.sv new file mode 100644 index 0000000..759e1fb --- /dev/null +++ b/i2c_receiver_tb/i2c_receiver_tb.sv @@ -0,0 +1,109 @@ +`timescale 1ps/1ps + +/* verilator lint_off DECLFILENAME */ +/* verilator lint_off MULTITOP */ +/* verilator lint_off STMTDLY */ +/* verilator lint_off INFINITELOOP */ +/* verilator lint_off INITIALDLY */ + +module i2c_receiver_tb; + logic clock = 1'b0; + logic reset = 1'b1; + + /* Master clock 100MHz (10ns period) */ + always #(10ns/2) clock = ~clock; + + parameter SCL_PERIOD = 364793; // ~300ns + logic scl_async; + logic sda_async; + + event scl_low_ev; + event scl_high_ev; + + initial begin + scl_async = 1'b1; + + forever begin + #(SCL_PERIOD/4) scl_async = 1'b1; + #(SCL_PERIOD/4) -> scl_high_ev; + #(SCL_PERIOD/4) scl_async = 1'b0; + #(SCL_PERIOD/4) -> scl_low_ev; + end + end + + logic i_scl; + logic i_sdi; + logic o_sdo; + logic [7:0] o_data; + logic o_strobe; + logic o_start; + logic o_stop; + logic i_ack; + + i2c_receiver DUT (.*); + + logic [1:0] scl_sync; + logic [1:0] sda_sync; + + always_ff @(posedge clock) + if (reset) begin + scl_sync <= '1; + sda_sync <= '1; + end + else begin + scl_sync <= {scl_sync[0], scl_async}; + sda_sync <= {sda_sync[0], sda_async}; + end + + assign i_scl = scl_sync[1]; + assign i_sdi = sda_sync[1]; + + task send_start; + wait(scl_async == 1'b0); + sda_async = 1'b1; + @(scl_high_ev); + sda_async = 1'b0; + endtask + + task send_stop; + wait(scl_async == 1'b0); + sda_async = 1'b0; + @(scl_high_ev); + sda_async = 1'b1; + endtask + + task send_byte(logic [7:0] b); + for (int n = 7; n >= 0; n -= 1) begin + @(scl_low_ev); + sda_async = b[n]; + end + + @(scl_low_ev); + wait(scl_async == 1'b1); + endtask + + initial begin + reset = 1'b1; + repeat(2) @(posedge clock) #1; + reset = 1'b0; + repeat(2) @(posedge clock) #1; + + i_ack = 1'b1; + + send_start; + send_byte(8'h6a); + send_byte(8'ha5); + send_stop; + + repeat(1000) @(posedge clock) #1; + $finish; + end + +`ifdef DUMP + initial begin + $dumpfile("i2c_receiver_tb.fst"); + $dumpvars(0, i2c_receiver_tb); + end +`endif + +endmodule // i2c_receiver_tb