`include "tune.v"

// This is the Tile Sprite Processing Unit

// Tiles map address:
//  bits    desc
//  20:13   tmpage
//  12:

// Graphics address:
//  bits    desc
//  20:13   Xgpage
//  15:7    line (bits 15:13 are added) - 512 lines
//  6:0     word within line - 128 words = 512 pixels


module video_ts
(
  // clocks
  input wire clk,

  // video controls
  input wire start,
  input wire [8:0] line,  // = vcount - vpix_beg + 9'b1;
  input wire v_ts,
  input wire v_pf,        // vertical tilemap prefetch window

  // video config
  input wire [7:0] tsconf,
  input wire [7:0] t0gpage,
  input wire [7:0] t1gpage,
  input wire [7:0] sgpage,
  input wire [7:0] tmpage,
  input wire [5:0] num_tiles,
  input wire [8:0] t0x_offs,
  input wire [8:0] t1x_offs,
  input wire [8:0] t0y_offs,
  input wire [8:0] t1y_offs,
  input wire [1:0] t0_palsel,
  input wire [1:0] t1_palsel,

  // SFYS interface
  input  wire  [7:0] sfile_addr_in,
  input  wire [15:0] sfile_data_in,
  input  wire sfile_we,

  // renderer interface
  output wire tsr_go,
  output wire [5:0] tsr_addr,  // graphics address within the line
  output wire [8:0] tsr_line,  // bitmap line
  output wire [7:0] tsr_page,  // bitmap 1st page
  output wire [8:0] tsr_x,     // addr in buffer (0-359 visibles)
  output wire [2:0] tsr_xs,    // size (8-64 pix)
  output wire tsr_xf,          // X flip
  output wire [3:0] tsr_pal,   // palette
  input wire tsr_rdy,          // renderer is done and ready to receive a new task

  // DRAM interface
  output wire [20:0] dram_addr,
  output wire        dram_req,
  input  wire        dram_next,
  input  wire [15:0] dram_rdata
);

  // config
  wire s_en = tsconf[7];
  wire t1_en = tsconf[6];
  wire t0_en = tsconf[5];
  wire t1z_en = tsconf[3];
  wire t0z_en = tsconf[2];

  // tile descriptor fields
  wire [15:0] tmb_rdata;

  wire [11:0] t_tnum = tmb_rdata[11:0];
  wire [1:0]  t_pal  = tmb_rdata[13:12];
  wire        t_xflp = tmb_rdata[14];
  wire        t_yflp = tmb_rdata[15];

  // Layer selectors control
  localparam LAYERS = 6;    // total number of layers to process

  localparam TM = 0;    // Individual layers
  localparam S0 = 1;
  localparam T0 = 2;
  localparam S1 = 3;
  localparam T1 = 4;
  localparam S2 = 5;

  reg [LAYERS-1:0] layer_active;
  reg [LAYERS-1:0] layer_skip;

  wire tmap = layer_active[TM];
  wire sprites = layer_active[S0] || layer_active[S1] || layer_active[S2];
  wire tiles = layer_active[T0] || layer_active[T1];

  reg [LAYERS-1:0] layer;
  always @(posedge clk)
    if (start)
      layer <= 1;
    else if (|(layer & layer_skip))
      layer <= {layer[LAYERS-2:0], 1'b0};

  always @(posedge clk)
    if (start)
      layer_active <= 0;
    else
      layer_active <= layer & ~layer_skip;

  wire [2:0] spr_end;
  wire [1:0] tile_end;
  wire tm_end;
  wire [LAYERS-1:0] layer_enabled = {s_en, t1_en, s_en, t0_en, s_en, t1_en || t0_en};
  wire [LAYERS-1:0] layer_allowed = {{5{v_ts}}, v_pf};
  wire [LAYERS-1:0] layer_end = {spr_end[2], tile_end[1], spr_end[1], tile_end[0], spr_end[0], tm_end};

  always @(posedge clk)
    if (start)
      layer_skip <= ~(layer_enabled & layer_allowed);
    else
      layer_skip <= layer_skip | layer_end;

  // --- Tile map prefetch ---
  // TMB control
  reg [4:0] tm_x;

  wire [8:0] tm_line = line + 9'd16;
  wire [2:0] tm_b_line = tm_line[2:0];
  wire [2:0] tm_num = tm_x[2:0];
  wire tm_layer = tm_x[3];
  wire [8:0] tmb_waddr = {tm_line[4:3], tm_b_line, tm_num, tm_layer};  // 8:7 - buffer #, 6:4 - burst number (of 8 bursts), 3:1 - number in burst (8 tiles per burst), 0 - layer
  wire [5:0] tm_b_row = tm_line[8:3] + (tm_layer ? t1y_offs[8:3] : t0y_offs[8:3]);

  // DRAM controls
  assign dram_addr = {tmpage, tm_b_row, tm_layer, tm_b_line, tm_num};  // 20:13 - page, 12:7 - row, 6 - layer, 5:0 - column (burst number : number in burst)
  assign dram_req = tmap;

  // internal layers control
  assign tm_end = tm_x == (t1_en ? 5'd16 : 5'd8);
  wire tm_next = dram_next && tmap;

  reg [1:0] m_layer;
  always @(posedge clk)
    if (start)
      m_layer <= 2'b1;
    else if (tm_end)
      m_layer <= {m_layer[0], 1'b0};

  // tilemap X coordinate
  always @(posedge clk)
    if (start)
      tm_x <= t0_en ? 5'd0 : 5'd8;
    else if (tm_next)
      tm_x <= tm_x + 5'd1;

  // --- Tiles ---
  // layer parameter selectors
  reg [1:0] t_layer;
  reg [5:0] tx;

  wire t_sel = t_layer[0];
  wire [8:0] tx_offs = t_sel ? t0x_offs : t1x_offs;
  wire [3:0] ty_offs = t_sel ? t0y_offs[2:0] : t1y_offs[2:0];

  // TSR control
  wire [4:0] t_line;
  wire [7:0] tile_page = t_sel ? t0gpage : t1gpage;
  wire [8:0] tile_line = {t_tnum[11:6], (t_line[2:0] ^ {3{t_yflp}})};
  wire [5:0] tile_addr = t_tnum[5:0];
  wire [8:0] tile_x = {(tx - 6'd1), 3'd0} - tx_offs[2:0];
  wire [3:0] tile_pal = {t_sel ? t0_palsel : t1_palsel, t_pal};

  // TMB control
  wire [8:0] tmb_raddr = {t_line[4:3], tx + tx_offs[8:3], ~t_sel};

  // internal layers control
  wire t_layer_end = tx == num_tiles;
  wire t_layer_start = start || t_layer_end;
  assign tile_end = {2{t_layer_end}} & t_layer[1:0];

  always @(posedge clk)
    if (start)
      t_layer <= t0_en ? 2'b01 : 2'b10;
    else if (t_layer_end)
      t_layer <=  {t_layer[0], 1'b0};

  // TMBUF control
    // condition        write to tx    write to tm_valid
    //  t_layer_start    0              TM_PRE_VALID
    //  tm_pre_valid     tx+1           TM_VALID
    //  tile_skip        tx+1           -
    //  tile_go          tx+1           TM_VALID
    //  tile_wait        tx-1           TM_PRE_VALID

  localparam TM_PRE_VALID  = 2'b01;
  localparam TM_VALID    = 2'b10;

  reg [1:0] tm_valid_r;
  wire tm_valid = tm_valid_r[1];
  wire tm_pre_valid = tm_valid_r[0];

  wire tile_valid = |t_tnum || (t_sel ? t0z_en : t1z_en);
  wire tsr_allowed = tiles && tsr_rdy;
  wire tile_good = tm_valid && tile_valid;
  wire tile_wait = tile_good && !tsr_allowed;
  wire tile_skip = tm_valid && !tile_valid;
  wire tile_go = tile_good && tsr_allowed;

  always @(posedge clk)
    if (t_layer_start || tile_wait)
      tm_valid_r <= TM_PRE_VALID;
    else if (tm_pre_valid || tile_go)
      tm_valid_r <= TM_VALID;

  always @(posedge clk)
    if (t_layer_start)
      tx <= 6'd0;
    else if (tm_pre_valid || tile_skip || tile_go)
      tx <= tx + 6'd1;
    else if (tile_wait)
      tx <= tx - 6'd1;

  // tile Y geometry
  assign t_line = line[4:0] + ty_offs;

  // --- Sprites ---
  // sprite descriptor fields
  // R0
  wire [15:0] sfile_rdata;

  wire [8:0]  s_ycrd = sfile_rdata[8:0];
  wire [2:0]  s_ysz  = sfile_rdata[11:9];
  wire        s_act  = sfile_rdata[13];
  wire        s_leap = sfile_rdata[14];
  wire        s_yflp = sfile_rdata[15];
  // R1
  wire [8:0]  s_xcrd = sfile_rdata[8:0];
  wire [2:0]  s_xsz  = sfile_rdata[11:9];
  wire        s_xflp = sfile_rdata[15];
  // R2
  wire [11:0] s_tnum = sfile_rdata[11:0];
  wire [3:0]  s_pal  = sfile_rdata[15:12];

  // TSR control
  wire [5:0] sprites_addr = s_tnum[5:0];

  // internal layers control
  reg sprites_last_r;
  reg [2:0] s_layer;
  reg [7:0] sreg;
  reg [4:0] sr_valid;
  reg s_leap_r;

  wire sr0_pre_valid = sr_valid[0];
  wire sr0_valid     = sr_valid[1];
  wire sr1_pre_valid = sr_valid[2];
  wire sr1_valid     = sr_valid[3];
  wire sr2_valid     = sr_valid[4];

  wire sprite_go;
  wire s_visible;
  wire spr_valid = s_visible && s_act;
  wire s_layer_end = (sr0_valid && !spr_valid && s_leap) || (sprite_go && s_leap_r);
  wire sprites_last = sr0_valid && sprites_last_r;
  assign spr_end = ({3{s_layer_end}} & s_layer[2:0]) | {3{sprites_last}};

  always @(posedge clk)
    sprites_last_r <= sreg == 8'd255;

  always @(posedge clk)
    if (start)
      s_layer <= 3'b1;
    else if (s_layer_end)
      s_layer <= {s_layer[1:0], 1'b0};

  // SFile registers control
    // condition                 write to sreg  write to sr_valid  action
    //  start                     0              SR0_PRE_VALID      Start
    //  sr0_pre_valid             sreg+3         SR0_VALID          SR0 pre-read
    //  sr0_valid && !spr_valid   sreg+3         -                  Skip sprite
    //  sr0_valid && spr_valid    sreg-2         SR1_PRE_VALID      SR1 pre-read
    //  sr1_pre_valid             sreg+1         SR1_VALID          SR1 read
    //  sr1_valid                 sreg+1         SR2_VALID          SR2 pre-read
    //  sr2_valid && !tsr_rdy     -              -                  Wait for TSR ready
    //  sr2_valid && tsr_rdy      sreg+1         SR0_PRE_VALID      Next sprite
    //  sprites_last              -              NONE_VALID         End

  localparam NONE_VALID    = 5'b00000;
  localparam SR0_PRE_VALID  = 5'b00001;
  localparam SR0_VALID    = 5'b00010;
  localparam SR1_PRE_VALID  = 5'b00100;
  localparam SR1_VALID    = 5'b01000;
  localparam SR2_VALID    = 5'b10000;

  assign sprite_go = sr2_valid && sprites && tsr_rdy;    // a kick to renderer

  always @(posedge clk)
    if (start)
      sr_valid <= SR0_PRE_VALID;
    else if (sprites_last)
      sr_valid <= NONE_VALID;
    else if (sr0_pre_valid)
      sr_valid <= SR0_VALID;
    else if (sr0_valid && spr_valid)
      sr_valid <= SR1_PRE_VALID;
    else if (sr1_pre_valid)
      sr_valid <= SR1_VALID;
    else if (sr1_valid)
      sr_valid <= SR2_VALID;
    else if (sprite_go)
      sr_valid <= SR0_PRE_VALID;

  always @(posedge clk)
    if (start)
      sreg <= 8'd0;
    else if (sr0_pre_valid)
      sreg <= sreg + 8'd3;
    else if (sr0_valid)
      sreg <= spr_valid ? (sreg - 8'd2) : (sreg + 8'd3);
    else if (sr1_pre_valid || sprite_go)
      sreg <= sreg + 8'd1;

  // sprite Y geometry
  reg [5:0] s_bmline_offset_r;

  wire [8:0] s_line = line - s_ycrd;      // visible line of sprite in current video line
  wire [5:0] s_ymax = {s_ysz, 3'b111};
  assign s_visible = (s_line <= s_ymax);    // check if sprite line is within Y size

  wire [8:0] sprites_line = {s_tnum[11:6], 3'b0} + s_bmline_offset_r;
  wire [5:0] s_bmline_offset = s_yflp ? (s_ymax - s_line[5:0]) : s_line[5:0];

  // SFile control
  reg [8:0] sprites_x;
  reg [2:0] sprites_xs;
  reg sprites_xf;

  always @(posedge clk)
  begin
    if (sr0_valid)
    begin
      s_leap_r <= s_leap;
      s_bmline_offset_r <= s_bmline_offset;
    end

    if (sr1_valid)
    begin
      sprites_x <= s_xcrd;
      sprites_xs <= s_xsz;
      sprites_xf <= s_xflp;
    end
  end

  // SFile
  dpram #(.DATAWIDTH(16), .ADDRWIDTH(8)) video_sfile
  (
    .clock      (clk),
    .address_a  (sfile_addr_in),
    .data_a     (sfile_data_in),
    .wren_a     (sfile_we),
    .address_b  (sreg),
    .q_b        (sfile_rdata)
  );
/*
  altdpram video_sfile
  (
    .outclock       (clk),
    .wren           (sfile_we),
    .inclock        (clk),
    .data           (sfile_data_in),
    .rdaddress      (sreg),
    .wraddress      (sfile_addr_in),
    .q              (sfile_rdata),
    .aclr           (1'b0),
    .byteena        (1'b1),
    .inclocken      (1'b1),
    .outclocken     (1'b1),
    .rdaddressstall (1'b0),
    .rden           (1'b1),
    .wraddressstall (1'b0)
  );

  defparam
    video_sfile.indata_aclr = "OFF",
    video_sfile.indata_reg = "INCLOCK",
    video_sfile.intended_device_family = "ACEX1K",
    video_sfile.lpm_type = "altdpram",
    video_sfile.outdata_aclr = "OFF",
    video_sfile.outdata_reg = "OUTCLOCK",
    video_sfile.rdaddress_aclr = "OFF",
    video_sfile.rdaddress_reg = "UNREGISTERED",
    video_sfile.rdcontrol_aclr = "OFF",
    video_sfile.rdcontrol_reg = "UNREGISTERED",
    video_sfile.width = 16,
    video_sfile.widthad = 8,
    video_sfile.wraddress_aclr = "OFF",
    video_sfile.wraddress_reg = "INCLOCK",
    video_sfile.wrcontrol_aclr = "OFF",
    video_sfile.wrcontrol_reg = "INCLOCK";
*/
  // 4 buffers * 2 tile-planes * 64 tiles * 16 bits (9x16) - used to prefetch tiles
  // (2 altdprams)
  dpram #(.DATAWIDTH(16), .ADDRWIDTH(9))  video_tmbuf
  (
    .clock      (clk),
    .address_a  (tmb_waddr),
    .data_a     (dram_rdata),
    .wren_a     (tm_next),
    .address_b  (tmb_raddr),
    .q_b        (tmb_rdata)
  );
/*
  altdpram video_tmbuf
  (
    .outclock       (clk),
    .wren           (tm_next),
    .inclock        (clk),
    .data           (dram_rdata),
    .rdaddress      (tmb_raddr),
    .wraddress      (tmb_waddr),
    .q              (tmb_rdata),
    .aclr           (1'b0),
    .byteena        (1'b1),
    .inclocken      (1'b1),
    .outclocken     (1'b1),
    .rdaddressstall (1'b0),
    .rden           (1'b1),
    .wraddressstall (1'b0)
  );

  defparam
    video_tmbuf.indata_aclr = "OFF",
    video_tmbuf.indata_reg = "INCLOCK",
    video_tmbuf.intended_device_family = "ACEX1K",
    video_tmbuf.lpm_type = "altdpram",
    video_tmbuf.outdata_aclr = "OFF",
    video_tmbuf.outdata_reg = "OUTCLOCK",
    video_tmbuf.rdaddress_aclr = "OFF",
    video_tmbuf.rdaddress_reg = "UNREGISTERED",
    video_tmbuf.rdcontrol_aclr = "OFF",
    video_tmbuf.rdcontrol_reg = "UNREGISTERED",
    video_tmbuf.width = 16,
    video_tmbuf.widthad = 9,
    video_tmbuf.wraddress_aclr = "OFF",
    video_tmbuf.wraddress_reg = "INCLOCK",
    video_tmbuf.wrcontrol_aclr = "OFF",
    video_tmbuf.wrcontrol_reg = "INCLOCK";
*/
  // TS renderer interface
  assign tsr_go = sprite_go || tile_go;
  assign tsr_x = sprites ? sprites_x : tile_x;
  assign tsr_xs = sprites ? sprites_xs : 3'd0;
  assign tsr_xf = sprites ? sprites_xf : t_xflp;
  assign tsr_page = sprites ? sgpage : tile_page;
  assign tsr_line = sprites ? sprites_line : tile_line;
  assign tsr_addr = sprites ? sprites_addr : tile_addr;
  assign tsr_pal = sprites ? s_pal : tile_pal;

endmodule
