// Secure EEPROM

`ifdef DEBUG
  `define DEBUG_DISPLAY(_args) $display("%s", $sformatf _args);
`else
  `define DEBUG_DISPLAY(_args)
`endif

module seeprom (
  input i_clk,
  input logic i_i2c_scl,
  input logic i_i2c_sda,
  output logic o_i2c_sda
);

initial begin
  o_i2c_sda = 1;
  i2c_state = I2C_IDLE;
  mem_secure = 0;
  i2c_scl_state = I2C_SCL_STABLE_HIGH;
  i2c_address_valid = 0;
end

/* verilator lint_off UNDRIVEN */
enum {
  I2C_IDLE,
  I2C_START,
  I2C_LOAD_CONTROL,
  I2C_ACK_THEN_LOAD_ADDRESS,
  I2C_ACK_THEN_READ,
  I2C_ACK_THEN_WRITE,
  I2C_LOAD_ADDRESS,
  I2C_READ,
  I2C_WRITE,
  I2C_ACK,
  I2C_NACK
} i2c_state;
/* verilator lint_on UNDRIVEN */

logic [255:0][7:0] mem_storage;
logic [3:0] mem_secure;

logic [7:0] i2c_control;
logic [3:0] i2c_control_bits;
logic [7:0] i2c_address;
logic [3:0] i2c_address_bits;
logic       i2c_address_valid;
logic [7:0] i2c_data;
logic [3:0] i2c_data_bits;

wire i2c_address_secure = mem_secure[i2c_address / 64];
wire i2c_next_address_secure = mem_secure[(i2c_address + 1) / 64];

wire [3:0] i2c_control_prefix = i2c_control[7:4];
wire [3:0] i2c_control_bank = i2c_control[3:0];
wire i2c_control_rw = i2c_control[0];

`define I2C_CONTROL_EEPROM 4'b1010
`define I2C_CONTROL_SECURE 4'b0101

logic i2c_last_scl;
logic i2c_last_sda;

always_ff @(posedge i_clk) begin
  i2c_last_scl <= i_i2c_scl;
  i2c_last_sda <= i_i2c_sda;
end

/* verilator lint_off UNDRIVEN */
enum {
  I2C_SCL_STABLE_HIGH,
  I2C_SCL_STABLE_LOW,
  I2C_SCL_FALLING,
  I2C_SCL_RISING
} i2c_scl_state;
/* verilator lint_on UNDRIVEN */

always_comb begin
  if (i2c_last_scl && i_i2c_scl) begin
    i2c_scl_state = I2C_SCL_STABLE_HIGH;
  end else if (!i2c_last_scl && !i_i2c_scl) begin
    i2c_scl_state = I2C_SCL_STABLE_LOW;
  end else if (i2c_last_scl && !i_i2c_scl) begin
    i2c_scl_state = I2C_SCL_FALLING;
  end else if (!i2c_last_scl && i_i2c_scl) begin
    i2c_scl_state = I2C_SCL_RISING;
  end
end

wire i2c_start;
wire i2c_stop;

always_comb begin
  if (i2c_scl_state == I2C_SCL_STABLE_HIGH) begin
    i2c_start = i2c_last_sda && !i_i2c_sda;
    i2c_stop = !i2c_last_sda && i_i2c_sda;
  end else begin
    i2c_start = 0;
    i2c_stop = 0;
  end
end

`define ACK_THEN_TRANSITION(_ack, _next_state) \
  begin \
    if (i2c_scl_state == I2C_SCL_FALLING) begin \
      o_i2c_sda <= _ack; \
    end else if (i2c_scl_state == I2C_SCL_RISING) begin \
      i2c_state <= _next_state; \
    end \
  end

always_ff @(posedge i_clk) begin
  `DEBUG_DISPLAY(("i2c_state = %s", i2c_state.name));
  `DEBUG_DISPLAY(("i2c_scl_state = %s", i2c_scl_state.name));
  `DEBUG_DISPLAY(("i2c_address_valid = %b", i2c_address_valid));
  `DEBUG_DISPLAY(("mem_secure = %b", mem_secure));
  case (i2c_state)
    I2C_IDLE: begin
      if (i2c_start) begin
        i2c_state <= I2C_START;
      end
    end
    I2C_START: begin
      if (i2c_scl_state == I2C_SCL_FALLING) begin
        i2c_control_bits <= 0;
        i2c_state <= I2C_LOAD_CONTROL;
      end
    end
    I2C_LOAD_CONTROL: begin
      `DEBUG_DISPLAY(("i2c_control = %b", i2c_control));
      `DEBUG_DISPLAY(("i2c_control_bits = %d", i2c_control_bits));
      if (i2c_control_bits == 8) begin
        case (i2c_control_prefix)
          `I2C_CONTROL_EEPROM: begin
            if (i2c_control_rw) begin
              if (i2c_address_valid) begin
                i2c_data_bits <= 0;
                i2c_state <= I2C_ACK_THEN_READ;
              end else begin
                i2c_state <= I2C_NACK;
              end
            end else begin
              i2c_address_bits <= 0;
              i2c_state <= I2C_ACK_THEN_LOAD_ADDRESS;
            end
          end
          `I2C_CONTROL_SECURE: begin
            mem_secure <= mem_secure | i2c_control_bank;
            i2c_state <= I2C_ACK;
          end
          default: begin
            i2c_state <= I2C_NACK;
          end
        endcase
        i2c_control_bits <= 0;
      end else if (i2c_scl_state == I2C_SCL_RISING) begin
        i2c_control <= {i2c_control[6:0], i_i2c_sda};
        i2c_control_bits <= i2c_control_bits + 1;
      end
    end
    I2C_LOAD_ADDRESS: begin
      `DEBUG_DISPLAY(("i2c_address = %b", i2c_address));
      `DEBUG_DISPLAY(("i2c_address_bits = %d", i2c_address_bits));
      if (i2c_address_bits == 8) begin
        if (i2c_address_secure) begin
          i2c_address_valid <= 0;
          i2c_state <= I2C_NACK;
        end else begin
          i2c_data_bits <= 0;
          i2c_address_valid <= 1;
          i2c_state <= I2C_ACK_THEN_WRITE;
        end
      end else if (i2c_scl_state == I2C_SCL_RISING) begin
        i2c_address <= {i2c_address[6:0], i_i2c_sda};
        i2c_address_bits <= i2c_address_bits + 1;
      end
    end
    I2C_WRITE: begin
      `DEBUG_DISPLAY(("i2c_data_ = %b", i2c_data));
      `DEBUG_DISPLAY(("i2c_data_bits = %d", i2c_data_bits));
      if (i2c_data_bits == 8) begin
        i2c_data_bits <= 0;
        if (i2c_address_secure == i2c_next_address_secure) begin
          `DEBUG_DISPLAY(("WRITE: i2c_address = 0x%x, i2c_data = 0x%x", i2c_address, i2c_data));
          mem_storage[i2c_address] <= i2c_data;
          i2c_address <= i2c_address + 1;
          i2c_state <= I2C_ACK_THEN_WRITE;
        end else begin
          i2c_state <= I2C_NACK;
        end
      end else if (i2c_scl_state == I2C_SCL_RISING) begin
        i2c_data <= {i2c_data[6:0], i_i2c_sda};
        i2c_data_bits <= i2c_data_bits + 1;
      end
    end
    I2C_READ: begin
      `DEBUG_DISPLAY(("i2c_data_bits = %d", i2c_data_bits));
      if (i2c_data_bits == 8 && i2c_scl_state == I2C_SCL_RISING) begin
        i2c_data_bits <= 0;
        if (i2c_address_secure == i2c_next_address_secure) begin
          `DEBUG_DISPLAY(("READ: i2c_address = 0x%x", i2c_address));
          i2c_address <= i2c_address + 1;
          i2c_state <= I2C_ACK_THEN_READ;
        end else begin
          i2c_state <= I2C_NACK;
        end
      end else if (i2c_scl_state == I2C_SCL_FALLING) begin
        `DEBUG_DISPLAY(("READ (bit): i2c_address = 0x%x", i2c_address));
        o_i2c_sda <= mem_storage[i2c_address][7 - i2c_data_bits[2:0]];
        i2c_data_bits <= i2c_data_bits + 1;
      end
    end
    I2C_ACK: `ACK_THEN_TRANSITION(0, I2C_IDLE)
    I2C_ACK_THEN_LOAD_ADDRESS: `ACK_THEN_TRANSITION(0, I2C_LOAD_ADDRESS)
    I2C_ACK_THEN_READ: `ACK_THEN_TRANSITION(0, I2C_READ)
    I2C_ACK_THEN_WRITE: `ACK_THEN_TRANSITION(0, I2C_WRITE)
    I2C_NACK: `ACK_THEN_TRANSITION(1, I2C_IDLE)
  endcase

  if (i2c_stop) begin
    i2c_address_valid <= 0;
    i2c_state <= I2C_IDLE;
  end else if (i2c_start) begin
    i2c_state <= I2C_START;
  end
end

endmodule
