/*
Distributed under the MIT license.
Copyright (c) 2016 Dave McCoy (dave.mccoy@cospandesign.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
 * Author:
 * Description:
 *
 * Changes:
 */

`timescale 1ps / 1ps

`define MAJOR_VERSION             1
`define MINOR_VERSION             0
`define REVISION                  0

`define MAJOR_RANGE               31:28
`define MINOR_RANGE               27:20
`define REVISION_RANGE            19:16

`define FRAME_STORE_COUNT         3

module axi_atomic_gpio #(
  parameter integer C_S_AXI_ADDR_WIDTH          = 8,
  parameter integer C_S_AXI_DATA_WIDTH          = 32,
  parameter STROBE_WIDTH                        = (C_S_AXI_DATA_WIDTH / 8)
)(
  input                                   axi_clk,
  input                                   axi_rst_n,

  //AXI Lite Interface

  //Write Address Channel
  input                                   i_awvalid,
  input       [C_S_AXI_ADDR_WIDTH - 1: 0] i_awaddr,
  output                                  o_awready,

  //Write Data Channel
  input                                   i_wvalid,
  output                                  o_wready,
  input       [C_S_AXI_DATA_WIDTH - 1: 0] i_wdata,

  //Write Response Channel
  output                                  o_bvalid,
  input                                   i_bready,
  output      [1:0]                       o_bresp,

  //Read Address Channel
  input                                   i_arvalid,
  output                                  o_arready,
  input       [C_S_AXI_ADDR_WIDTH - 1: 0] i_araddr,

  //Read Data Channel
  output                                  o_rvalid,
  input                                   i_rready,
  output      [1:0]                       o_rresp,
  output      [C_S_AXI_DATA_WIDTH - 1: 0] o_rdata,

  //GPO
  output  reg [15:0]                      o_gpo,

  //Frame Pointer
  input       [5:0]                       i_cam0_slave_frame_ptr,
  input       [5:0]                       i_cam1_slave_frame_ptr,
  input       [5:0]                       i_cam2_slave_frame_ptr,
  output  reg [5:0]                       o_master_frame_ptr = 1,

  //Interrupt For the MCU
  output  reg                             o_interrupt
);
//local parameters

//Address Map
localparam                  REG_CONTROL               = 0;
localparam                  REG_GPO_VALUE             = 1;
localparam                  REG_GPO_ACK               = 2;
localparam                  REG_MASTER_FRAME_PTR      = 3;
localparam                  REG_CAM0_SLAVE_FRAME_PTR  = 4;
localparam                  REG_CAM1_SLAVE_FRAME_PTR  = 5;
localparam                  REG_CAM2_SLAVE_FRAME_PTR  = 6;
localparam                  REG_VERSION               = 7;

localparam                  CTRL_BIT_ENABLE           = 0;


//Register/Wire

//AXI Signals
wire        [31:0]              status;

//Simple User Interface
wire  [C_S_AXI_ADDR_WIDTH - 1: 0]           w_reg_address;
wire   [((C_S_AXI_ADDR_WIDTH - 1) - 2): 0]  w_reg_32bit_address;
reg                                         r_reg_invalid_addr;

wire                                        w_reg_in_rdy;
reg                                         r_reg_in_ack_stb;
wire  [C_S_AXI_DATA_WIDTH - 1: 0]           w_reg_in_data;

wire                                        w_reg_out_req;
reg                                         r_reg_out_rdy_stb;
reg   [C_S_AXI_DATA_WIDTH - 1: 0]           r_reg_out_data;

wire                                        w_axi_rst;
reg   [15: 0]                               r_gpo_value;
reg                                         r_enable;
reg                                         r_new_value_stb;


//Submodules
//Convert AXI Slave signals to a simple register/address strobe
axi_lite_slave #(
  .ADDR_WIDTH         (C_S_AXI_ADDR_WIDTH   ),
  .DATA_WIDTH         (C_S_AXI_DATA_WIDTH   )

) axi_lite_reg_interface (
  .clk                (axi_clk              ),
  .rst                (w_axi_rst            ),


  .i_awvalid          (i_awvalid            ),
  .i_awaddr           (i_awaddr             ),
  .o_awready          (o_awready            ),

  .i_wvalid           (i_wvalid             ),
  .o_wready           (o_wready             ),
  .i_wdata            (i_wdata              ),

  .o_bvalid           (o_bvalid             ),
  .i_bready           (i_bready             ),
  .o_bresp            (o_bresp              ),

  .i_arvalid          (i_arvalid            ),
  .o_arready          (o_arready            ),
  .i_araddr           (i_araddr             ),

  .o_rvalid           (o_rvalid             ),
  .i_rready           (i_rready             ),
  .o_rresp            (o_rresp              ),
  .o_rdata            (o_rdata              ),


  .o_reg_address      (w_reg_address        ),
  .i_reg_invalid_addr (r_reg_invalid_addr   ),

  .o_reg_in_rdy       (w_reg_in_rdy         ),
  .i_reg_in_ack_stb   (r_reg_in_ack_stb     ),
  .o_reg_in_data      (w_reg_in_data        ),

  .o_reg_out_req      (w_reg_out_req        ),
  .i_reg_out_rdy_stb  (r_reg_out_rdy_stb    ),
  .i_reg_out_data     (r_reg_out_data       )
);

//Asynchronous Logic
assign        w_axi_rst               = ~axi_rst_n;
assign        w_reg_32bit_address     = w_reg_address[(C_S_AXI_ADDR_WIDTH - 1): 2];

always @(*) begin
  if (w_axi_rst) begin
    r_gpo_value <=  0;
  end
  else begin
    if (r_enable) begin
      case(o_master_frame_ptr)
        6'h01: begin
          //r_gpo_value = 16'h1 << 2;
          r_gpo_value = 16'h4 << 2;
        end
        6'h03: begin
          //r_gpo_value = 16'h2 << 2;
          r_gpo_value = 16'h1 << 2;
        end
        6'h02: begin
          //r_gpo_value = 16'h4 << 2;
          r_gpo_value = 16'h2 << 2;
        end
        6'h06: begin
          //r_gpo_value = 16'h1 << 2;
          r_gpo_value = 16'h4 << 2;
        end
        6'h07: begin
          //r_gpo_value = 16'h2 << 2;
          r_gpo_value = 16'h1 << 2;
        end
        6'h05: begin
          //r_gpo_value = 16'h4 << 2;
          r_gpo_value = 16'h2 << 2;
        end
        default: begin
          r_gpo_value = 0;
        end
      endcase
    end
  end
end

//blocks
always @ (posedge axi_clk) begin
  //De-assert Strobes
  r_reg_in_ack_stb                        <=  0;
  r_reg_out_rdy_stb                       <=  0;
  r_reg_invalid_addr                      <=  0;
  r_new_value_stb                         <=  0;

  if (w_axi_rst) begin
    o_gpo                                 <=  0;
    r_reg_out_data                        <=  0;
    r_enable                              <=  0;
    o_master_frame_ptr                    <=  0;
    o_interrupt                           <=  0;
  end
  else begin
    if (r_new_value_stb) begin
      o_gpo                               <=  r_gpo_value;
    end
    if (w_reg_in_rdy && !r_reg_in_ack_stb) begin
      //From master
      case (w_reg_32bit_address)
        REG_CONTROL: begin
          r_enable                        <= w_reg_in_data[CTRL_BIT_ENABLE];
          o_interrupt                     <= 0;
        end
        REG_GPO_ACK: begin
          o_gpo                           <= 0;
          o_interrupt                     <= 1;
        end
        REG_MASTER_FRAME_PTR: begin
          o_master_frame_ptr              <= w_reg_in_data[5:0];
          o_interrupt                     <= 0;
          r_new_value_stb                 <= 1;
        end
        default: begin
        end
      endcase
      if (w_reg_32bit_address > REG_VERSION) begin
        r_reg_invalid_addr                <= 1;
      end
      r_reg_in_ack_stb                    <= 1;
    end
    else if (w_reg_out_req && !r_reg_out_rdy_stb) begin
      //To master
      case (w_reg_32bit_address)
        REG_CONTROL: begin
          r_reg_out_data[CTRL_BIT_ENABLE] <= r_enable;
        end
        REG_GPO_VALUE: begin
          r_reg_out_data                  <= o_gpo;
        end
        REG_MASTER_FRAME_PTR: begin
          r_reg_out_data                  <= {26'h0, o_master_frame_ptr};
        end
        REG_CAM0_SLAVE_FRAME_PTR: begin
          r_reg_out_data                  <= {26'h0, i_cam0_slave_frame_ptr};
        end
        REG_CAM1_SLAVE_FRAME_PTR: begin
          r_reg_out_data                  <= {26'h0, i_cam1_slave_frame_ptr};
        end
        REG_CAM2_SLAVE_FRAME_PTR: begin
          r_reg_out_data                  <= {26'h0, i_cam2_slave_frame_ptr};
        end
        REG_VERSION: begin
          r_reg_out_data                  <= 32'h00;
          r_reg_out_data[`MAJOR_RANGE]    <= `MAJOR_VERSION;
          r_reg_out_data[`MINOR_RANGE]    <= `MINOR_VERSION;
          r_reg_out_data[`REVISION_RANGE] <= `REVISION;
        end
        default: begin
          r_reg_out_data                  <= 32'h00;
        end
      endcase
      if (w_reg_32bit_address > REG_VERSION) begin
        r_reg_invalid_addr                <= 1;
      end
      r_reg_out_rdy_stb                   <= 1;
    end
  end
end





endmodule
