`include "tune.v"

// PentEvo project (c) NedoPC 2008-2011
//
// DRAM arbiter. Shares DRAM between CPU, video data fetcher and other devices
//

// Arbitration is made on full 8-cycle access blocks. Each cycle is defined by dram.v and consists of 4 fpga clocks.
// During each access block, there can be either no videodata access, 1 videodata access, 2, 4 or 8 accesses.
// All spare cycles can be used by CPU or other devices. If no device uses memory in the given cycle, refresh cycle is performed.
//
// In each access block, videodata accesses are spreaded all over the block so that CPU receives cycle
// as fast as possible, until there is absolute need to fetch remaining video data.
//
// Examples:
//
// |                 access block                  | 4 video accesses during block, no processor accesses. video accesses are done
// | vid | vid | vid | vid | ref | ref | ref | ref | as soon as possible, spare cycles are refresh ones
//
// |                 access block                  | 4 video accesses during block, processor requests access every other cycle
// | vid | prc | vid | prc | vid | prc | vid | prc |
//
// |                 access block                  | 4 video accesses, processor begins requesting cycles continously from second one
// | vid | prc | prc | prc | prc | vid | vid | vid | so it is given cycles while there is such possibility. after that processor
//                                                   can't access mem until the end of access block and stalls
//
// |                 access block                  | 8 video accesses, processor stalls, if it is requesting cycles
// | vid | vid | vid | vid | vid | vid | vid | vid |
//
// |                 access block                  | 2 video accesses, single processor request, other cycles are refresh ones
// | vid | vid | ref | ref | cpu | ref | ref | ref |
//
// |                 access block                  | 4 video accesses, single processor request, other cycles are refresh ones
// | vid | vid | cpu | vid | vid | ref | ref | ref |
//
// access block begins at any dram cycle, then blocks video_go back-to-back
//
// key signals are video_go and XXX_req, sampled at the end of each dram cycle. Must be set to the module at c3 clock cycle.

// CPU can have either normal or lower access priority to the DRAM.
// At the INT active (32 of 3.5MHz clocks) the priority is raised to normal, so that CPU won't miss its interrupt.
// This should be considered if dummy RAM access used for waiting for the end of DMA operation instead of status bit polling.
//
// DRAM access priority:
// Z80 normal       Z80 low
// - VIDEO          - VIDEO
// - CPU            - TS
// - TM             - TM
// - TS             - DMA
// - DMA            - CPU

module arbiter
(
  input  wire        clk,
  input  wire        c1,
  input  wire        c2,
  input  wire        c3,
  input  wire        cyc,

  // dram.v interface
  output wire [22:0] dram_addr,       // address for dram access
  output wire        dram_req,        // dram request
  output wire        dram_rnw,        // Read-NotWrite
  output wire [ 1:0] dram_bsel,       // byte select: bsel[1] for wrdata[15:8], bsel[0] for wrdata[7:0]
  output wire [15:0] dram_wrdata,     // data to be written

  // video
  input  wire [20:0] video_addr,      // during access block, only when video_strobe==1
  input  wire        video_go,        // start video access blocks
  input  wire [ 4:0] video_bw,        // [4:3] - total cycles: 11 = 8 / 01 = 4 / 00 = 2
                                      // [2:0] - need cycles
  output wire        video_pre_next,  // (c1)
  output wire        video_next,      // (c2) at this signal video_addr may be changed; it is one clock leading the video_strobe
  output wire        video_strobe,    // (c3) one-cycle strobe meaning that video_data is available
  output wire        next_vid,        // used for TM prefetch

  // CPU
  input  wire [20:0] cpu_addr,
  input  wire [ 7:0] cpu_wrdata,
  input  wire        cpu_req,
  input  wire        cpu_rnw,
  input  wire        cpu_wrbsel,
  input  wire        cpu_csrom,
  output reg         cpu_next,        // next cycle is allowed to be used by CPU
  output reg         cpu_strobe,
  output reg         cpu_latch,
  output wire        curr_cpu_o,

  // DMA
  input  wire [20:0] dma_addr,
  input  wire [15:0] dma_wrdata,
  input  wire        dma_req,
  input  wire        dma_rnw,
  output wire        dma_next,

  // TS
  input  wire [20:0] ts_addr,
  input  wire        ts_req,
  output wire        ts_pre_next,
  output wire        ts_next,

  // TM
  input  wire [20:0] tm_addr,
  input  wire        tm_req,
  output wire        tm_next,

  // ROM loader
  input wire         loader_clk,
  input wire [15:0]  loader_addr,
  input wire  [7:0]  loader_data,
  input wire         loader_wr,
  input wire         loader_cs_rom_main,
  input wire         loader_cs_rom_gs
);

  localparam CYCLES    = 6;

  localparam CYC_CPU    = 6'b000001;
  localparam CYC_VID    = 6'b000010;
  localparam CYC_TS     = 6'b000100;
  localparam CYC_TM     = 6'b001000;
  localparam CYC_DMA    = 6'b010000;
  localparam CYC_LOADER = 6'b100000;
  localparam CYC_FREE   = 6'b000000;

  localparam CPU    = 0;
  localparam VIDEO  = 1;
  localparam TS     = 2;
  localparam TM     = 3;
  localparam DMA    = 4;
  localparam LOADER = 5;

  reg [CYCLES-1:0] curr_cycle; // type of the cycle in progress
  reg [CYCLES-1:0] next_cycle; // type of the next cycle

  reg [2:0] blk_rem = 0;      // remaining accesses in a block (7..0)
  reg [2:0] vid_rem = 0;      // remaining video accesses in block
  reg stall = 0;

  wire dev_over_cpu = 0;  // can be used to rise devices priority over CPU

  wire next_cpu = next_cycle[CPU];
  assign next_vid = next_cycle[VIDEO];
  wire next_ts  = next_cycle[TS];
  wire next_tm  = next_cycle[TM];
  wire next_dma = next_cycle[DMA];
  wire next_loader = next_cycle[LOADER];

  wire curr_cpu = curr_cycle[CPU];
  wire curr_vid = curr_cycle[VIDEO];
  wire curr_ts  = curr_cycle[TS];
  wire curr_tm  = curr_cycle[TM];
  wire curr_dma = curr_cycle[DMA];
  wire curr_loader = curr_cycle[LOADER];

  assign curr_cpu_o = curr_cpu;


  // track blk_rem counter:
  // how many cycles left to the end of block (7..0)
  wire video_start = ~|blk_rem;
  wire [2:0] blk_nrem = (video_start && video_go) ? {video_bw[4:3], 1'b1} : (video_start ? 3'd0 : (blk_rem - 3'd1));
  wire bw_full = ~|{video_bw[4] & video_bw[2], video_bw[3] & video_bw[1], video_bw[0]};   // stall when 000/00/0
  wire video_only = stall || (vid_rem == blk_rem);
  wire video_idle = ~|vid_rem;

  always @(posedge clk) if (c3)
  begin
    blk_rem <= blk_nrem;

    if (video_start)
      stall <= bw_full & video_go;
  end

  // track vid_rem counter
  // how many video cycles left to the end of block (7..0)
  wire [2:0] vidmax = {video_bw[2:0]};    // number of cycles for video access
  wire [2:0] vid_nrem_next = video_idle ? 3'd0 : (vid_rem - 3'd1);
  wire [2:0] vid_nrem_start = (cpu_req && !dev_over_cpu) ? vidmax : (vidmax - 3'd1);
  wire [2:0] vid_nrem = (video_go && video_start) ? vid_nrem_start : (next_vid ? vid_nrem_next : vid_rem);

  always @(posedge clk) if (c3)
    vid_rem <= vid_nrem;

  reg loader_wr0;
  reg [7:0] loader_data0;
  reg [1:0] loader_hiaddr;
  always @(posedge loader_clk) begin
    if (loader_wr && (loader_cs_rom_main || loader_cs_rom_gs)) begin
      loader_wr0 <= 1'd1;
      loader_data0 <= loader_data;
      loader_hiaddr <= { loader_cs_rom_gs, loader_cs_rom_main };
    end
    else if (cyc) begin
      loader_wr0 <= 1'd0;
    end
  end

  // next cycle decision
  wire [CYCLES-1:0] cyc_dev = tm_req ? CYC_TM : (ts_req ? CYC_TS : CYC_DMA);
  wire dev_req = ts_req || tm_req || dma_req;

  always @*
    if (loader_wr0) begin
      cpu_next = 1'b0;
      next_cycle = CYC_LOADER;
    end
    else
    if (video_start)    // video burst start
      if (video_go)           // video active
      begin
        cpu_next = dev_over_cpu ? 1'b0 : !bw_full;
        next_cycle = dev_over_cpu ? CYC_VID : (bw_full ? CYC_VID : (cpu_req ? CYC_CPU : CYC_VID));
      end

      else              // video idle
      begin
        cpu_next = !dev_over_cpu;
        next_cycle = dev_over_cpu ? cyc_dev : (cpu_req ? CYC_CPU : (dev_req ? cyc_dev : CYC_FREE));
      end

    else                // video burst in progress
    begin
      cpu_next = dev_over_cpu ? 1'b0 : !video_only;
      next_cycle = video_only ? CYC_VID : (dev_over_cpu ? cyc_dev : (cpu_req ? CYC_CPU : (!video_idle ? CYC_VID : (dev_req ? cyc_dev : CYC_FREE))));
    end

  always @(posedge clk) if (c3)
    curr_cycle <= next_cycle;


  // DRAM interface
  assign dram_wrdata = curr_loader? {2{loader_data0}} : curr_dma ? dma_wrdata : {2{cpu_wrdata[7:0]}};    // write data has to be clocked at c0 in dram.v
  assign dram_bsel[1:0] = next_loader? {loader_addr[0], ~loader_addr[0]} : next_dma ? 2'b11 : {cpu_wrbsel, ~cpu_wrbsel};
  assign dram_req = |next_cycle;
  assign dram_rnw = next_loader? 1'b0 : next_cpu ? cpu_rnw : (next_dma ? dma_rnw : 1'b1);
  assign dram_addr =  {23{next_loader}} & { loader_hiaddr, 6'b000000, loader_addr[15:1] }
                    | {23{next_cpu}}    & { 1'b0, cpu_csrom, {6{~cpu_csrom}} & cpu_addr[20:15], cpu_addr[14:0] }
                    | {23{next_vid}}    & { 2'b0, video_addr }
                    | {23{next_ts}}     & { 2'b0, ts_addr }
                    | {23{next_tm}}     & { 2'b0, tm_addr }
                    | {23{next_dma}}    & { 2'b0, dma_addr };

  reg cpu_rnw_r;
  always @(posedge clk) if (c3)
    cpu_rnw_r <= cpu_rnw;

  // read strobes generation for video and cpu
  always @(posedge clk)
    if (c1)
    begin
      cpu_strobe <= curr_cpu && cpu_rnw_r;
      cpu_latch <= curr_cpu && cpu_rnw_r;
    end
    else if (c2)
      cpu_strobe <= 1'b0;
    else if (c3)
      cpu_latch <= 1'b0;

  assign video_pre_next = curr_vid & c1;
  assign video_next = curr_vid & c2;
  assign video_strobe = curr_vid && c3;

  assign ts_pre_next = curr_ts & c1;
  assign ts_next = curr_ts & c2;

  assign tm_next = curr_tm & c2;

  assign dma_next = curr_dma & c2;

endmodule
