module  ogfx_backend_frame_fifo (

// OUTPUTs
    frame_data_o,                               // Frame data
    frame_data_ready_o,                         // Frame data ready

    vid_ram_addr_o,                             // Video-RAM address
    vid_ram_cen_o,                              // Video-RAM enable (active low)

// INPUTs
    mclk,                                       // Main system clock
    puc_rst,                                    // Main system reset

    display_width_i,                            // Display width
    display_height_i,                           // Display height
    display_size_i,                             // Display size (number of pixels)
    display_y_swap_i,                           // Display configuration: swap Y axis (horizontal symmetry)
    display_x_swap_i,                           // Display configuration: swap X axis (vertical symmetry)
    display_cl_swap_i,                          // Display configuration: swap column/lines

    frame_data_request_i,                       // Request for next frame data

    gfx_mode_i,                                 // Video mode (1xx:16bpp / 011:8bpp / 010:4bpp / 001:2bpp / 000:1bpp)

    vid_ram_dout_i,                             // Video-RAM data output
    vid_ram_dout_rdy_nxt_i,                     // Video-RAM data output ready during next cycle

    refresh_active_i,                           // Display refresh on going
    refresh_frame_base_addr_i                   // Refresh frame base address
);

// OUTPUTs
//=========
output       [15:0] frame_data_o;               // Frame data
output              frame_data_ready_o;         // Frame data ready

output[`VRAM_MSB:0] vid_ram_addr_o;             // Video-RAM address
output              vid_ram_cen_o;              // Video-RAM enable (active low)

// INPUTs
//=========
input               mclk;                       // Main system clock
input               puc_rst;                    // Main system reset

input [`LPIX_MSB:0] display_width_i;            // Display width
input [`LPIX_MSB:0] display_height_i;           // Display height
input [`SPIX_MSB:0] display_size_i;             // Display size (number of pixels)
input               display_y_swap_i;           // Display configuration: swap Y axis (horizontal symmetry)
input               display_x_swap_i;           // Display configuration: swap X axis (vertical symmetry)
input               display_cl_swap_i;          // Display configuration: swap column/lines

input               frame_data_request_i;       // Request for next frame data

input         [2:0] gfx_mode_i;                 // Video mode (1xx:16bpp / 011:8bpp / 010:4bpp / 001:2bpp / 000:1bpp)

input        [15:0] vid_ram_dout_i;             // Video-RAM data output
input               vid_ram_dout_rdy_nxt_i;     // Video-RAM data output ready during next cycle

input               refresh_active_i;           // Display refresh on going
input [`APIX_MSB:0] refresh_frame_base_addr_i;  // Refresh frame base address


//=============================================================================
// 1)  WIRE, REGISTERS AND PARAMETER DECLARATION
//=============================================================================

// Some parameter(s)
parameter   FIFO_EMPTY        =  2'h0,
            FIFO_FULL         =  2'h3;

// Video modes decoding
wire        gfx_mode_1_bpp    =  (gfx_mode_i == 3'b000);
wire        gfx_mode_2_bpp    =  (gfx_mode_i == 3'b001);
wire        gfx_mode_4_bpp    =  (gfx_mode_i == 3'b010);
wire        gfx_mode_8_bpp    =  (gfx_mode_i == 3'b011);
wire        gfx_mode_16_bpp   = ~(gfx_mode_8_bpp | gfx_mode_4_bpp |
                                  gfx_mode_2_bpp | gfx_mode_1_bpp);

// Others
reg   [1:0] fifo_counter;
wire  [1:0] fifo_counter_nxt;
wire        fifo_data_ready;
wire        read_from_fifo;
reg         vid_ram_data_mux_ready;
reg         vid_ram_dout_ready;
wire [15:0] vid_ram_dout_processed;


//============================================================================
// 1) FRAME ADDRESS GENERATION
//============================================================================

//--------------------------------
// FIFO data request
//--------------------------------
// The FIFO requests for new data whenever it is not full (or not about to get full)

reg   fifo_data_request;
wire  fifo_data_request_nxt = refresh_active_i                  &
                               (fifo_counter_nxt !=  FIFO_FULL) &                      // FIFO is full
                             ~((fifo_counter_nxt == (FIFO_FULL-1)) & fifo_data_ready); // FIFO is about to be full

always @(posedge mclk or posedge puc_rst)
  if (puc_rst)  fifo_data_request  <= 1'h0;
  else          fifo_data_request  <= fifo_data_request_nxt;

//--------------------------------
// Video RAM Address generation
//--------------------------------
reg    [`APIX_MSB:0] vid_ram_pixel_addr;
reg    [`APIX_MSB:0] vid_ram_line_addr;
reg    [`LPIX_MSB:0] vid_ram_column_count;

// Detect when the fifo is done reading the current pixel data
wire                 vid_ram_pixel_done = fifo_data_request & fifo_data_ready;

// Detect when the current line refresh is done
wire   [`LPIX_MSB:0] line_length        = display_cl_swap_i ? display_height_i : display_width_i;
wire                 vid_ram_line_done  = vid_ram_pixel_done & (vid_ram_column_count==(line_length-{{`LPIX_MSB{1'b0}}, 1'b1}));

// Zero extension for LINT cleanup
wire [`VRAM_MSB*3:0] display_size_norm  =  {{`VRAM_MSB*3-`SPIX_MSB{1'b0}}, display_size_i};
wire [`VRAM_MSB*3:0] display_width_norm =  {{`VRAM_MSB*3-`LPIX_MSB{1'b0}}, display_width_i};

// Based on the display configuration (i.e. X-Swap / Y-Swap / CL-Swap)
// the screen is not going to be refreshed in the same way.
// The screen refresh is the performed according to the following
// pseudo-code procedure:
//
// for (l_idx=0; l_idx<HEIGHT; l_idx++)
//    for (c_idx=0; c_idx<WIDTH; c_idx++)
//        addr = FIRST +    0    + WIDTH*l_idx + c_idx // Normal
//        addr = FIRST + WIDTH-1 + WIDTH*l_idx - c_idx // X-Swap
//        addr = LAST  - WIDTH+1 - WIDTH*l_idx + c_idx // Y-Swap
//        addr = LAST  -    0    - WIDTH*l_idx - c_idx // X/Y-Swap
//

wire [`APIX_MSB:0] next_base_addr     =  ~refresh_active_i  ? refresh_frame_base_addr_i :
                                          vid_ram_line_done ? vid_ram_line_addr         :
                                                              vid_ram_pixel_addr        ;

wire [`APIX_MSB:0] next_addr          =   next_base_addr
                                        + (display_size_norm[`APIX_MSB:0]  & {`APIX_MSB+1{refresh_active_i ?  1'b0                                                          : display_y_swap_i}})
                                        + (display_width_norm[`APIX_MSB:0] & {`APIX_MSB+1{refresh_active_i ? (~display_y_swap_i &  (display_cl_swap_i ^ vid_ram_line_done)) : display_x_swap_i}})
                                        - (display_width_norm[`APIX_MSB:0] & {`APIX_MSB+1{refresh_active_i ? ( display_y_swap_i &  (display_cl_swap_i ^ vid_ram_line_done)) : display_y_swap_i}})
                                        + ({{`APIX_MSB{1'b0}}, 1'b1}       & {`APIX_MSB+1{refresh_active_i ? (~display_x_swap_i & ~(display_cl_swap_i ^ vid_ram_line_done)) : 1'b0            }})
                                        - ({{`APIX_MSB{1'b0}}, 1'b1}       & {`APIX_MSB+1{refresh_active_i ? ( display_x_swap_i & ~(display_cl_swap_i ^ vid_ram_line_done)) : display_x_swap_i}});

wire               update_line_addr   =  ~refresh_active_i | vid_ram_line_done;
wire               update_pixel_addr  =   update_line_addr | vid_ram_pixel_done;

// Start RAM address of currentely refreshed line
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)               vid_ram_line_addr  <=  {`APIX_MSB+1{1'b0}};
  else if (update_line_addr) vid_ram_line_addr  <=  next_addr;

// Current RAM address of the currentely refreshed pixel
wire [`APIX_MSB:0] vid_ram_pixel_addr_nxt = update_pixel_addr ? next_addr : vid_ram_pixel_addr;

always @(posedge mclk or posedge puc_rst)
  if (puc_rst) vid_ram_pixel_addr  <=  {`APIX_MSB+1{1'b0}};
  else         vid_ram_pixel_addr  <=  vid_ram_pixel_addr_nxt;

// Count the pixel number in the current line
// (used to detec the end of a line)
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)                 vid_ram_column_count  <=  {`LPIX_MSB+1{1'b0}};
  else if (~refresh_active_i)  vid_ram_column_count  <=  {`LPIX_MSB+1{1'b0}};
  else if (vid_ram_line_done)  vid_ram_column_count  <=  {`LPIX_MSB+1{1'b0}};
  else if (vid_ram_pixel_done) vid_ram_column_count  <=  vid_ram_column_count + {{`LPIX_MSB{1'b0}}, 1'b1};

// Depending on the color mode, format the address for doing the RAM accesses.
assign              vid_ram_addr_o   = ({`VRAM_MSB+1{gfx_mode_1_bpp }} & vid_ram_pixel_addr[`VRAM_MSB+4:4]) |
                                       ({`VRAM_MSB+1{gfx_mode_2_bpp }} & vid_ram_pixel_addr[`VRAM_MSB+3:3]) |
                                       ({`VRAM_MSB+1{gfx_mode_4_bpp }} & vid_ram_pixel_addr[`VRAM_MSB+2:2]) |
                                       ({`VRAM_MSB+1{gfx_mode_8_bpp }} & vid_ram_pixel_addr[`VRAM_MSB+1:1]) |
                                       ({`VRAM_MSB+1{gfx_mode_16_bpp}} & vid_ram_pixel_addr[`VRAM_MSB+0:0]) ;

// Compute the next RAM address to detect when a new address is generated
wire [`VRAM_MSB:0] vid_ram_addr_nxt = ({`VRAM_MSB+1{gfx_mode_1_bpp }} & vid_ram_pixel_addr_nxt[`VRAM_MSB+4:4]) |
                                      ({`VRAM_MSB+1{gfx_mode_2_bpp }} & vid_ram_pixel_addr_nxt[`VRAM_MSB+3:3]) |
                                      ({`VRAM_MSB+1{gfx_mode_4_bpp }} & vid_ram_pixel_addr_nxt[`VRAM_MSB+2:2]) |
                                      ({`VRAM_MSB+1{gfx_mode_8_bpp }} & vid_ram_pixel_addr_nxt[`VRAM_MSB+1:1]) |
                                      ({`VRAM_MSB+1{gfx_mode_16_bpp}} & vid_ram_pixel_addr_nxt[`VRAM_MSB+0:0]) ;

// Detect when a new word needs to be fetched from the memory
// (i.e. detect when the RAM address is updated)
reg  vid_ram_addr_update;
wire vid_ram_addr_update_nxt = (vid_ram_addr_o != vid_ram_addr_nxt);
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)                 vid_ram_addr_update  <=  1'h0;
  else if (~refresh_active_i)  vid_ram_addr_update  <=  1'h1;
  else if (vid_ram_pixel_done) vid_ram_addr_update  <=  vid_ram_addr_update_nxt;


// Disable RAM access if there is no need to fetch a new word
assign vid_ram_cen_o   = vid_ram_addr_update ? ~fifo_data_request     : 1'b1;

// If the next FIFO data doesn't come from the RAM, then it is ready as
// soon as it is requested
assign fifo_data_ready = vid_ram_addr_update ? vid_ram_dout_rdy_nxt_i : fifo_data_request;


//============================================================================
// 2) FRAME DATA-PRE-PROCESSING (PRIOR BEING PUSHED INTO FIFO)
//============================================================================

//--------------------------------
// Data buffer
//--------------------------------
// For the LUT modes, it is not necessary to access the RAM for
// every pixel. In that case, the FIFO is filled with the values
// coming from the buffer.
// (i.e. we only take data directly from the RAM when it is just read)
reg [15:0] vid_ram_dout_buf;
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)                 vid_ram_dout_buf <=  16'h0000;
  else if (vid_ram_dout_ready) vid_ram_dout_buf <=  vid_ram_dout_i;

wire [15:0] vid_ram_dout_mux = vid_ram_dout_ready ? vid_ram_dout_i : vid_ram_dout_buf;

//--------------------------------
// Data formating
//--------------------------------
// Depending on the mode, the address LSBs are used to select which bits
// of the current data word need to be put in the FIFO
wire [3:0] vid_ram_data_sel_nxt    = ({4{gfx_mode_1_bpp}} & {vid_ram_pixel_addr[3:0]         }) |
                                     ({4{gfx_mode_2_bpp}} & {vid_ram_pixel_addr[2:0], 1'b0   }) |
                                     ({4{gfx_mode_4_bpp}} & {vid_ram_pixel_addr[1:0], 2'b00  }) |
                                     ({4{gfx_mode_8_bpp}} & {vid_ram_pixel_addr[0],   3'b000 }) ;

reg  [3:0] vid_ram_data_sel;
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)                 vid_ram_data_sel <=  4'h0;
  else if (vid_ram_pixel_done) vid_ram_data_sel <=  vid_ram_data_sel_nxt;


wire [15:0] vid_ram_dout_shifted   = (vid_ram_dout_mux >> vid_ram_data_sel);

// Format data output for LUT processing
// (8 bit LSBs are used to address the LUT memory, MSBs are ignored)
assign      vid_ram_dout_processed = ({16{gfx_mode_1_bpp }} & {8'h00, 7'b0000000, vid_ram_dout_shifted[0]  }) |
                                     ({16{gfx_mode_2_bpp }} & {8'h00, 6'b000000 , vid_ram_dout_shifted[1:0]}) |
                                     ({16{gfx_mode_4_bpp }} & {8'h00, 4'b0000   , vid_ram_dout_shifted[3:0]}) |
                                     ({16{gfx_mode_8_bpp }} & {8'h00,             vid_ram_dout_shifted[7:0]}) |
                                     ({16{gfx_mode_16_bpp}} & {       vid_ram_dout_shifted[15:0]           }) ;

//--------------------------------
// Data Ready
//--------------------------------
// Data is available on the bus one cycle after the rdy_nxt signals
always @(posedge mclk or posedge puc_rst)
  if (puc_rst) vid_ram_data_mux_ready  <=  1'b0;
  else         vid_ram_data_mux_ready  <=  fifo_data_ready;

always @(posedge mclk or posedge puc_rst)
  if (puc_rst) vid_ram_dout_ready      <=  1'b0;
  else         vid_ram_dout_ready      <=  vid_ram_dout_rdy_nxt_i;


//============================================================================
// 3) FIFO COUNTER
//============================================================================

// Declaration
// Control signals
wire      fifo_push =  vid_ram_data_mux_ready & (fifo_counter != FIFO_FULL);
wire      fifo_pop  =  read_from_fifo         & (fifo_counter != FIFO_EMPTY);

// Fifo counter
assign fifo_counter_nxt = ~refresh_active_i      ?  FIFO_EMPTY          : // Initialize
                          (fifo_push & fifo_pop) ?  fifo_counter        : // Keep value (pop & push at the same time)
                           fifo_push             ?  fifo_counter + 2'h1 : // Push
                           fifo_pop              ?  fifo_counter - 2'h1 : // Pop
                                                    fifo_counter;         // Hold

always @(posedge mclk or posedge puc_rst)
  if (puc_rst) fifo_counter <= FIFO_EMPTY;
  else         fifo_counter <= fifo_counter_nxt;


//============================================================================
// 4) FIFO MEMORY & RD/WR POINTERS
//============================================================================

// Write pointer
reg [1:0] wr_ptr;
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)                    wr_ptr  <=  2'h0;
  else if (~refresh_active_i)     wr_ptr  <=  2'h0;
  else if (fifo_push)
    begin
       if (wr_ptr==(FIFO_FULL-1)) wr_ptr  <=  2'h0;
       else                       wr_ptr  <=  wr_ptr + 2'h1;
    end

// Memory
reg [15:0] fifo_mem [0:2];
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)
    begin
       fifo_mem[0]      <=  16'h0000;
       fifo_mem[1]      <=  16'h0000;
       fifo_mem[2]      <=  16'h0000;
    end
  else if (fifo_push)
    begin
       fifo_mem[wr_ptr] <=  vid_ram_dout_processed;
    end

// Read pointer
reg [1:0] rd_ptr;
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)                    rd_ptr  <=  2'h0;
  else if (~refresh_active_i)     rd_ptr  <=  2'h0;
  else if (fifo_pop)
    begin
       if (rd_ptr==(FIFO_FULL-1)) rd_ptr  <=  2'h0;
       else                       rd_ptr  <=  rd_ptr + 2'h1;
    end


//============================================================================
// 5) FRAME DATA FROM FIFO
//============================================================================

// RAW Data is valid
reg  frame_data_init;
wire frame_data_init_nxt = ~refresh_active_i ? 1'h0 :
                            fifo_pop         ? 1'b1 : frame_data_init;

always @(posedge mclk or posedge puc_rst)
  if (puc_rst)       frame_data_init <= 1'h0;
  else               frame_data_init <= frame_data_init_nxt;

// RAW Data from the frame buffer
reg [15:0] frame_data_o;
always @(posedge mclk or posedge puc_rst)
  if (puc_rst)       frame_data_o    <= 16'h0000;
  else if (fifo_pop) frame_data_o    <= fifo_mem[rd_ptr];

// Data is ready
assign    frame_data_ready_o       = frame_data_init_nxt & (fifo_counter != FIFO_EMPTY);

// Read from FIFO command
assign    read_from_fifo = ~refresh_active_i |
                           ~frame_data_init  |
                            ((fifo_counter != FIFO_EMPTY) & frame_data_request_i);


endmodule // ogfx_backend_frame_fifo