
`timescale 1 ns / 1 ps

module axis_decimator #
(
  parameter integer AXIS_TDATA_WIDTH = 32
)
(
  // System signals
  input  wire                        aclk,
  input  wire                        aresetn,

  // I2S signals
  inout  wire [3:0]                  gpio_data,

  // ALEX signals
  input wire                         alex_flag,
  input wire [3:0]                   alex_data,

  // Slave side
  output wire                        s_axis_tready,
  input  wire [AXIS_TDATA_WIDTH-1:0] s_axis_tdata,
  input  wire                        s_axis_tvalid,

  // Master side
  input  wire                        m_axis_tready,
  output wire [AXIS_TDATA_WIDTH-1:0] m_axis_tdata,
  output wire                        m_axis_tvalid
);

  localparam I2S_DATA_WIDTH = AXIS_TDATA_WIDTH / 2;
  localparam CNTR_WIDTH = 6;

  reg [2:0] int_bclk_reg, int_lrclk_reg;
  reg [1:0] int_data_reg;

  reg [1:0] adc_case_reg, adc_case_next;
  reg [CNTR_WIDTH-1:0] adc_cntr_reg, adc_cntr_next;
  reg [I2S_DATA_WIDTH-1:0] adc_data_reg, adc_data_next;
  reg [AXIS_TDATA_WIDTH-1:0] adc_tdata_reg, adc_tdata_next;
  reg adc_tvalid_reg, adc_tvalid_next;

  reg [1:0] dac_case_reg, dac_case_next;
  reg [I2S_DATA_WIDTH-1:0] dac_data_reg, dac_data_next;
  reg [AXIS_TDATA_WIDTH-1:0] dac_tdata_reg, dac_tdata_next;

  wire i2s_bclk, i2s_lrclk, i2s_adc_data, i2s_dac_data;

  wire not_alex_flag = ~alex_flag;

  IOBUF buf_bclk (.O(i2s_bclk), .IO(gpio_data[0]), .I(alex_data[0]), .T(not_alex_flag));
  IOBUF buf_adc_data (.O(i2s_adc_data), .IO(gpio_data[1]), .I(alex_data[1]), .T(not_alex_flag));
  IOBUF buf_dac_data (.O(), .IO(gpio_data[2]), .I(alex_flag ? alex_data[2] : i2s_dac_data), .T(1'b0));
  IOBUF buf_lrclk (.O(i2s_lrclk), .IO(gpio_data[3]), .I(alex_data[3]), .T(not_alex_flag));

  always @(posedge aclk)
  begin
    if(~aresetn)
    begin
      int_bclk_reg <= 3'd0;
      int_lrclk_reg <= 3'd0;
      int_data_reg <= 2'd0;
      adc_case_reg <= 2'd0;
      adc_cntr_reg <= {(CNTR_WIDTH){1'b0}};
      adc_data_reg <= {(I2S_DATA_WIDTH){1'b0}};
      adc_tdata_reg <= {(AXIS_TDATA_WIDTH){1'b0}};
      adc_tvalid_reg <= 1'b0;
      dac_case_reg <= 2'd0;
      dac_data_reg <= {(I2S_DATA_WIDTH){1'b0}};
      dac_tdata_reg <= {(AXIS_TDATA_WIDTH){1'b0}};
    end
    else
    begin
      int_bclk_reg <= {int_bclk_reg[1:0], i2s_bclk};
      int_lrclk_reg <= {int_lrclk_reg[1:0], i2s_lrclk};
      int_data_reg <= {int_data_reg[0], i2s_adc_data};
      adc_case_reg <= adc_case_next;
      adc_cntr_reg <= adc_cntr_next;
      adc_data_reg <= adc_data_next;
      adc_tdata_reg <= adc_tdata_next;
      adc_tvalid_reg <= adc_tvalid_next;
      dac_case_reg <= dac_case_next;
      dac_data_reg <= dac_data_next;
      dac_tdata_reg <= dac_tdata_next;
    end
  end

  assign int_bclk_posedge = int_bclk_reg[1] & ~int_bclk_reg[2];
  assign int_bclk_negedge = ~int_bclk_reg[1] & int_bclk_reg[2];
  assign int_lrclk_negedge = ~int_lrclk_reg[1] & int_lrclk_reg[2];

  always @*
  begin
    adc_case_next = adc_case_reg;
    adc_cntr_next = adc_cntr_reg;
    adc_data_next = adc_data_reg;
    adc_tdata_next = adc_tdata_reg;
    adc_tvalid_next = adc_tvalid_reg;

    if(int_bclk_posedge & (adc_cntr_reg < I2S_DATA_WIDTH))
    begin
      adc_data_next = {adc_data_reg[I2S_DATA_WIDTH-2:0], int_data_reg[1]};
      adc_cntr_next = adc_cntr_reg + 1'b1;
    end

    if(m_axis_tready & adc_tvalid_reg)
    begin
      adc_tvalid_next = 1'b0;
    end

    case(adc_case_reg)
      2'd0:
      begin
        if(int_lrclk_reg[1] ^ int_lrclk_reg[2])
        begin
          adc_case_next = 2'd1;
        end
      end
      2'd1:
      begin
        if(int_bclk_posedge)
        begin
          adc_cntr_next = {(CNTR_WIDTH){1'b0}};
          adc_case_next = 2'd2;
        end
      end
      2'd2:
      begin
        if(int_bclk_posedge)
        begin
          adc_tvalid_next = ~int_lrclk_reg[1];
          adc_tdata_next = {adc_tdata_reg[I2S_DATA_WIDTH-1:0], adc_data_reg};
          adc_case_next = 2'd0;
        end
      end
    endcase
  end

  always @*
  begin
    dac_case_next = dac_case_reg;
    dac_data_next = dac_data_reg;
    dac_tdata_next = dac_tdata_reg;

    if(int_bclk_negedge)
    begin
      dac_data_next = {dac_data_reg[I2S_DATA_WIDTH-2:0], 1'b0};
    end

    if(int_lrclk_negedge)
    begin
      dac_tdata_next = s_axis_tvalid ? s_axis_tdata : {(AXIS_TDATA_WIDTH){1'b0}};
    end

    case(dac_case_reg)
      2'd0:
      begin
        if(int_lrclk_reg[1] ^ int_lrclk_reg[2])
        begin
          dac_case_next = 2'd1;
        end
      end
      2'd1:
      begin
        if(int_bclk_posedge)
        begin
          dac_case_next = 2'd2;
        end
      end
      2'd2:
      begin
        if(int_bclk_negedge)
        begin
          dac_data_next = int_lrclk_reg[1] ? dac_tdata_reg[I2S_DATA_WIDTH-1:0] : dac_tdata_reg[AXIS_TDATA_WIDTH-1:I2S_DATA_WIDTH];
          dac_case_next = 2'd0;
        end
      end
    endcase
  end

  assign i2s_dac_data = dac_data_reg[I2S_DATA_WIDTH-1];
  assign s_axis_tready = int_lrclk_negedge;
  assign m_axis_tdata = adc_tdata_reg;
  assign m_axis_tvalid = adc_tvalid_reg;

endmodule
