// // 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:1] - 00 = normal byte (S if needed, no following Sr/P) // 01 = follow with Sr (S if needed) // 10 = follow with P (S if needed) // 11 = dummy clocks (no S, for synchronization) // // 1 - read data/status // [15:8] - data bits // [7] - ack bit // [6] - SDA // [5] - SCL // [4] - "started" (no S will be issued before next byte) // [2:1] - bits [2:1] from last command // [0] - busy // // 2 - baud rate divisor (f = clk/(4*(divisor+1))) // // 3 - bit [0] - send dummy clocks on SCL when idle // // 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 started; // S sent, but not 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_s <= 1'bx; end_p <= 1'bx; started <= 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'b1, ~started }; end else begin bitctr <= bitctr + 1'b1; started <= 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_s ? 4'd14 : end_p ? 4'd12 : 4'b0; busy <= end_p & ~end_s; // If we are to be followed by // an S(r) condition, we are not // really "started". started <= ~(end_s | end_p); end end // case: 4'd0, 4'd1, 4'd2, 4'd3, 4'd4,... // Stop condition 4'd12: begin started <= 1'b0; outsymb <= 2'b10; // A0 end 4'd13: begin started <= 1'b0; outsymb <= 2'b11; // A1 busy <= 1'b0; end // Start condition 4'd14: begin started <= ~(end_s & end_p); outsymb <= 2'b11; // A1 end 4'd15: begin // N0, unless dummy in which case N1 outsymb <= { 1'b0, ~started }; 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, i2c_sda, i2c_scl, started, 1'b0, end_p, end_s, busy | do_read }; 2'b01: rdata = { 16'b0, rreg, i2c_sda, i2c_scl, started, 1'b0, end_p, end_s, busy | do_read }; 2'b10: rdata = { 24'b0, divisor }; default: rdata = 32'bx; endcase // casez (addr) // // IRQ (level) when unit idle // assign irq = ~(busy | do_read); endmodule // i2c