|
@@ -0,0 +1,205 @@
|
|
|
+//
|
|
|
+// Simple I2C master devic, controlled by programmed I/O.
|
|
|
+//
|
|
|
+// Registers:
|
|
|
+// 0 - write data/command
|
|
|
+// [15:8] - data bits (must be 1 for read)
|
|
|
+// [7] - ACK bit (must be 1 for write)
|
|
|
+// [2] - follow with P condition
|
|
|
+// [1] - follow with Sr condition
|
|
|
+//
|
|
|
+// 1 - read data/status
|
|
|
+// [15:8] - data bits
|
|
|
+// [7] - ack bit
|
|
|
+// [1] - bus idle (after P condition)
|
|
|
+// [0] - busy
|
|
|
+//
|
|
|
+// 2 - baud rate divisor (f = clk/(4*(divisor+1)))
|
|
|
+//
|
|
|
+// This unit handles S(r) and P conditions by considering two classes of
|
|
|
+// symbols: "normal", when SCL is asserted at the end of the
|
|
|
+
|
|
|
+module i2c (
|
|
|
+ input rst_n,
|
|
|
+ input clk,
|
|
|
+
|
|
|
+ input valid,
|
|
|
+ input [1:0] addr,
|
|
|
+ input [31:0] wdata,
|
|
|
+ input [3:0] wstrb,
|
|
|
+ output reg [31:0] rdata,
|
|
|
+ output irq,
|
|
|
+
|
|
|
+ inout i2c_scl,
|
|
|
+ inout i2c_sda
|
|
|
+ );
|
|
|
+
|
|
|
+ reg [7:0] divisor;
|
|
|
+ reg [7:0] baudctr;
|
|
|
+ reg [3:0] bitctr;
|
|
|
+ reg [1:0] phase;
|
|
|
+
|
|
|
+ reg [8:0] wreg; // Output shift register
|
|
|
+ reg [8:0] rreg; // Input shift register
|
|
|
+ reg do_read; // Shift in a data bit next cycle
|
|
|
+
|
|
|
+ reg busy; // Data received, running
|
|
|
+ reg end_s, end_p; // Trailing S(r) or P
|
|
|
+
|
|
|
+ reg [1:0] outsymb; // Output symbol [abnormal, data]
|
|
|
+
|
|
|
+ reg scl_out = 1'b1;
|
|
|
+ reg sda_out = 1'b1;
|
|
|
+
|
|
|
+ assign i2c_scl = scl_out ? 1'bz : 1'b0;
|
|
|
+ assign i2c_sda = sda_out ? 1'bz : 1'b0;
|
|
|
+
|
|
|
+ always @(negedge rst_n or posedge clk)
|
|
|
+ if (~rst_n)
|
|
|
+ begin
|
|
|
+ bitctr <= 4'd14; // Idle line - wait for start condition
|
|
|
+ busy <= 1'b0;
|
|
|
+ outsymb <= 2'b11; // A1
|
|
|
+ baudctr <= 8'd0;
|
|
|
+ divisor <= 8'd209; // 84 MHz -> 100 kHz
|
|
|
+ scl_out <= 1'b1;
|
|
|
+ sda_out <= 1'b1;
|
|
|
+ phase <= 2'b00;
|
|
|
+ do_read <= 1'b0;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ //
|
|
|
+ // I2C state machine
|
|
|
+ //
|
|
|
+
|
|
|
+ if (|baudctr)
|
|
|
+ begin
|
|
|
+ baudctr <= baudctr - 1'b1;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ // I2C clock event
|
|
|
+ baudctr <= divisor;
|
|
|
+
|
|
|
+ phase <= phase + 1'b1;
|
|
|
+ if ((phase == 2'b10) & ~i2c_scl)
|
|
|
+ phase <= 2'b10; // Clock stretch
|
|
|
+
|
|
|
+ // Phase 0: data is shifted out
|
|
|
+ // Phase 1: SCL goes high
|
|
|
+ // Phase 2: if SCL is low, hold
|
|
|
+ // Phase 3: SCL does low if symbol is normal,
|
|
|
+ // sample SDA on read
|
|
|
+ if (phase[0])
|
|
|
+ scl_out <= outsymb[1] | ~phase[1];
|
|
|
+
|
|
|
+ sda_out <= outsymb[0];
|
|
|
+
|
|
|
+ if (phase == 2'b11)
|
|
|
+ begin
|
|
|
+ // Sample input and set up for the next cycle
|
|
|
+
|
|
|
+ if (do_read)
|
|
|
+ rreg <= { rreg[7:0], i2c_sda };
|
|
|
+
|
|
|
+ do_read <= 1'b0;
|
|
|
+
|
|
|
+ // Unit idle; send A0 or A1 depending on if we are
|
|
|
+ // started or not.
|
|
|
+ if (~busy)
|
|
|
+ begin
|
|
|
+ outsymb[1] <= 1'b1;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ bitctr <= bitctr + 1'b1;
|
|
|
+
|
|
|
+ case (bitctr)
|
|
|
+ 4'd0, 4'd1, 4'd2, 4'd3, 4'd4,
|
|
|
+ 4'd5, 4'd6, 4'd7, 4'd8:
|
|
|
+ begin
|
|
|
+ outsymb <= { 1'b0, wreg[8] }; // Nx
|
|
|
+ wreg <= { wreg[7:0], 1'b1 };
|
|
|
+ do_read <= 1'b1;
|
|
|
+ if (bitctr[3])
|
|
|
+ begin
|
|
|
+ bitctr <= end_p ? 4'd12 :
|
|
|
+ end_s ? 4'd14 : 4'd0;
|
|
|
+ busy <= end_p;
|
|
|
+ end
|
|
|
+ end // case: 4'd0, 4'd1, 4'd2, 4'd3, 4'd4,...
|
|
|
+
|
|
|
+ // Stop condition
|
|
|
+ 4'd12: begin
|
|
|
+ outsymb <= 2'b10; // A0
|
|
|
+ end
|
|
|
+
|
|
|
+ 4'd13: begin
|
|
|
+ outsymb <= 2'b11; // A1
|
|
|
+ busy <= 1'b0;
|
|
|
+ end
|
|
|
+
|
|
|
+ // Start condition
|
|
|
+ 4'd14: begin
|
|
|
+ outsymb <= 2'b11; // A1
|
|
|
+ end
|
|
|
+
|
|
|
+ 4'd15: begin
|
|
|
+ outsymb <= 2'b00; // N0
|
|
|
+ end
|
|
|
+
|
|
|
+ default: begin
|
|
|
+ outsymb <= 2'bxx;
|
|
|
+ end
|
|
|
+ endcase // case (bitctr)
|
|
|
+ end // else: !if(~busy)
|
|
|
+ end // if (phase == 2'b11)
|
|
|
+ end // else: !if(|baudctr)
|
|
|
+
|
|
|
+ //
|
|
|
+ // CPU write interface
|
|
|
+ //
|
|
|
+ if (valid)
|
|
|
+ case (addr)
|
|
|
+ 2'b00:
|
|
|
+ if (~busy)
|
|
|
+ begin
|
|
|
+ if (wstrb[1])
|
|
|
+ wreg[8:1] <= wdata[15:8];
|
|
|
+ if (wstrb[0])
|
|
|
+ begin
|
|
|
+ wreg[0] <= wdata[7];
|
|
|
+ end_p <= wdata[2];
|
|
|
+ end_s <= wdata[1];
|
|
|
+ busy <= 1'b1;
|
|
|
+ end
|
|
|
+ end // if (~busy)
|
|
|
+ 2'b10: begin
|
|
|
+ if (wstrb[0])
|
|
|
+ divisor <= wdata[7:0];
|
|
|
+ end
|
|
|
+ default: begin
|
|
|
+ // Do nothing
|
|
|
+ end
|
|
|
+ endcase // case (addr)
|
|
|
+ end // else: !if(~rst_n)
|
|
|
+
|
|
|
+ //
|
|
|
+ // CPU read interface
|
|
|
+ //
|
|
|
+ always_comb
|
|
|
+ case (addr)
|
|
|
+ 2'b00: rdata = { 16'b0, wreg, 4'b0, end_p, end_s, busy | do_read };
|
|
|
+ 2'b01: rdata = { 16'b0, rreg, 4'b0, end_p, end_s, busy | do_read };
|
|
|
+ 2'b10: rdata = { 24'b0, divisor };
|
|
|
+ default: rdata = 32'b0;
|
|
|
+ endcase // casez (addr)
|
|
|
+
|
|
|
+ //
|
|
|
+ // IRQ (edge) when unit idle
|
|
|
+ //
|
|
|
+ assign irq = ~(busy | do_read);
|
|
|
+
|
|
|
+endmodule // i2c
|
|
|
+
|