`include "tune.v"

// PentEvo project (c) NedoPC 2008-2009
//
// Z80 memory manager: routes ROM/RAM accesses, makes wait-states for 14MHz or stall condition, etc.
//
//
// clk    _/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\_/`\
//          |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
// zclk     /```\___/```\___/```\___/```````\_______/```````\_______/```````````````\_______________/```````````````\_______________/`
//          |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
// zpos     `\___/```\___/```\___/```\___________/```\___________/```\___________________________/```\___________________________/```\
//          |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
// zneg     _/```\___/```\___/```\_______/```\___________/```\___________________/```\___________________________/```\________________

module zmem
(
  input  wire clk,
  input  wire c1, c2, c3,
  input  wire zneg, // strobes which show positive and negative edges of zclk

// Z80
  input  wire        rst,
  input  wire [15:0] za,
  output wire [ 7:0] zd_out,  // output to Z80 bus
  output wire        zd_ena,         // output to Z80 bus enable

  input  wire        opfetch,
  input  wire        opfetch_s,
  input  wire        memrd,
  input  wire        memwr,
  input  wire        memwr_s,

  input  wire [ 1:0] turbo,     // 2'b00 - 3.5,
                                // 2'b01 - 7.0,
                                // 2'b1x - 14.0
  input wire [3:0]   cache_en,
  input wire [3:0]   memconf,
  input wire [31:0]  xt_page,

  output wire [4:0]  rompg,
  output wire        csrom,
  output wire        romoe_n,
  output wire        romwe_n,

  output wire        dos,
  output wire        dos_on,
  output wire        dos_off,
  output wire        vdos,
  output reg         pre_vdos,
  input  wire        vdos_on,
  input  wire        vdos_off,

// DRAM
  output wire        cpu_req,
  output wire [20:0] cpu_addr,
  output wire        cpu_wrbsel,
  input  wire [15:0] cpu_rddata,
  input  wire        cpu_next,
  input  wire        cpu_strobe,
  input  wire        cpu_latch,
  output wire        cpu_stall    // for zclock
);

  // controls
  wire rom128   = memconf[0];
  wire w0_we    = memconf[1];
  wire w0_map_n = memconf[2];
  wire w0_ram   = memconf[3];

  // pager
  wire [7:0] xtpage[0:3];
  assign xtpage[0] = vdos ? 8'hFF : {xt_page[7:2], w0_map_n ? xt_page[1:0] : {~dos, rom128}};
  assign xtpage[1] = xt_page[15:8];
  assign xtpage[2] = xt_page[23:16];
  assign xtpage[3] = xt_page[31:24];

  wire [1:0] win = za[15:14];
  wire win0 = ~|win;
  wire ramwr_en = !win0 || w0_we || vdos;
  wire rom_n_ram = win0 && !w0_ram && !vdos;
  wire [7:0] page = xtpage[win];

  // DOS signal control
  reg dos_r;

  assign dos_on = win0 && opfetch_s && (za[13:8]==6'h3D) && rom128 && !w0_map_n;
  assign dos_off = !win0 && opfetch_s && !vdos;
  assign dos = dos_on ? 1'b1 : (dos_off ? 1'b0 : dos_r);  // to make dos appear 1 clock earlier than dos_r

  always @(posedge clk)
  if (rst)
    dos_r <= 1'b0;

  else if (dos_off)
    dos_r <= 1'b0;
  else if (dos_on)
    dos_r <= 1'b1;

  // VDOS signal control
  // vdos switching is delayed till next opfetch due to INIR that writes right after iord cycle

  reg vdos_r;

  assign vdos = opfetch ? pre_vdos : vdos_r;  // vdos appears as soon as first opfetch

  always @(posedge clk)
  if (rst || vdos_off)
  begin
    pre_vdos <= 1'b0;
    vdos_r <= 1'b0;
  end
  else if (vdos_on)
    pre_vdos <= 1'b1;
  else if (opfetch_s)
    vdos_r <= pre_vdos;

  // RAM
  wire cache_hit_en;
  assign zd_ena = !rom_n_ram && memrd;
  wire ramreq = ((memrd && !cache_hit_en) || (memwr && ramwr_en));

// address, data in and data out
  wire [15:0] cache_d;
  wire cache_v;

  assign cpu_wrbsel = za[0];
  assign cpu_addr[20:0] = {page, za[13:1]};
  wire [15:0] mem_d = cpu_latch ? cpu_rddata : cache_d;
  assign zd_out = trdos_3dxx_hit ? trdos_3dxx_do : ~cpu_wrbsel ? mem_d[7:0] : mem_d[15:8];

// Z80 controls

// 7/3.5MHz support
  reg ramreq_r;
  reg pre_ramreq_r;
  reg pending_cpu_req;
  reg stall14_cycrd;
  reg stall14_fin;

  wire dram_beg = ramreq && !pre_ramreq_r && zneg;
  wire cpureq_357 = ramreq && !ramreq_r;
  wire cpureq_14 = dram_beg || pending_cpu_req;
  wire stall357 = cpureq_357 && !cpu_next;
  wire stall14_ini = dram_beg && (!cpu_next || opfetch || memrd);  // no wait at all in write cycles, if next dram cycle is available
  wire stall14_cyc = memrd ? stall14_cycrd : !cpu_next;
  wire stall14 = stall14_ini || stall14_cyc || stall14_fin;

  wire turbo14 = turbo[1];
  assign cpu_req = turbo14 ? cpureq_14 : cpureq_357;
  assign cpu_stall = turbo14 ? stall14 : stall357;

// 14MHz support
  // wait tables:
  //
  // M1 opcode fetch, dram_beg concurs with:
  // c3:      +3
  // c2:      +4
  // c1:      +5
  // c0:      +6
  //
  // memory read, dram_beg concurs with:
  // c3:      +2
  // c2:      +3
  // c1:      +4
  // c0:      +5
  //
  // memory write: no wait
  //
  // special case: if dram_beg pulses 1 when cpu_next is 0,
  // unconditional wait has to be performed until cpu_next is 1, and
  // then wait as if dram_beg would concur with c0

  // memrd, opfetch - wait till c3 && cpu_next,
  // memwr - wait till cpu_next

  always @(posedge clk)
    if (c3 && !cpu_stall)
      ramreq_r <= ramreq;

  always @(posedge clk)
    if (zneg)
      pre_ramreq_r <= ramreq;

  always @(posedge clk)
  if (rst)
    pending_cpu_req <= 1'b0;
  else if (cpu_next && c3)
    pending_cpu_req <= 1'b0;
  else if (dram_beg)
    pending_cpu_req <= 1'b1;

  always @(posedge clk)
  if (rst)
    stall14_cycrd <= 1'b0;
  else if (cpu_next && c3)
    stall14_cycrd <= 1'b0;
  else if (dram_beg && (!c3 || !cpu_next) && (opfetch || memrd))
    stall14_cycrd <= 1'b1;

  always @(posedge clk)
  if (rst)
    stall14_fin <= 1'b0;
  else if (stall14_fin && ((opfetch && c1) || (memrd && c2)))
    stall14_fin <= 1'b0;
  else if (cpu_next && c3 && cpu_req && (opfetch || memrd))
    stall14_fin <= 1'b1;

    // cache
  wire [13:0] cache_a;
  wire [13:0] cpu_hi_addr = {rom_n_ram, page[7:0], za[13:9]};
  // wire cache_hit = (ch_addr[7:2] != 6'b011100) && (cpu_hi_addr == cache_a) && cache_v;  // debug for BM
  wire cache_hit = (cpu_hi_addr == cache_a) && cache_v;  // asynchronous signal meaning that address requested by CPU is cached and valid
  assign cache_hit_en = cache_hit && cache_en[win];
  wire cache_inv = cache_hit && memwr_s && ramwr_en;    // cache invalidation should be only performed if write happens to cached address

  wire [7:0] ch_addr = cpu_addr[7:0];
  // wire [14:0] cpu_hi_addr = {page[7:0], za[13:7]};
  // wire [14:0] cache_a;
  // wire [7:0] ch_addr = {2'b0, cpu_addr[5:0]};

  dpram #(.DATAWIDTH(16), .ADDRWIDTH(8)) cache_data
  (
    .clock(clk),
    .address_a(ch_addr),
    .data_a(cpu_rddata),
    .wren_a(cpu_strobe),
    .address_b(ch_addr),
    .q_b(cache_d)
  );

  dpram #(.DATAWIDTH(15), .ADDRWIDTH(8)) cache_addr
  (
    .clock(clk),
    .address_a(ch_addr),
    .data_a({!cache_inv, cpu_hi_addr}),
    .wren_a(cpu_strobe || cache_inv),
    .address_b(ch_addr),
    .q_b({cache_v, cache_a})
  );
/*
  altdpram cache_data
  (
    .inclock (clk),
    .outclock (clk),
    .data (cpu_rddata),
    .rdaddress (ch_addr),
    .wraddress (ch_addr),
    .wren (cpu_strobe),
    .q (cache_d),
    .aclr (1'b0),
    .byteena (1'b1),
    .inclocken (1'b1),
    .outclocken (1'b1),
    .rdaddressstall (1'b0),
    .rden (1'b1),
    .wraddressstall (1'b0)
  );

  defparam
    cache_data.indata_aclr = "OFF",
    cache_data.indata_reg = "INCLOCK",
    cache_data.intended_device_family = "ACEX1K",
    cache_data.lpm_type = "altdpram",
    cache_data.outdata_aclr = "OFF",
    cache_data.outdata_reg = "OUTCLOCK",
    cache_data.rdaddress_aclr = "OFF",
    cache_data.rdaddress_reg = "UNREGISTERED",
    cache_data.rdcontrol_aclr = "OFF",
    cache_data.rdcontrol_reg = "UNREGISTERED",
    cache_data.width = 16,
    cache_data.widthad = 8,
    cache_data.wraddress_aclr = "OFF",
    cache_data.wraddress_reg = "INCLOCK",
    cache_data.wrcontrol_aclr = "OFF",
    cache_data.wrcontrol_reg = "INCLOCK";

  altdpram cache_addr
  (
    .inclock (clk),
    .outclock (clk),
    .data ({!cache_inv, cpu_hi_addr}),
    .rdaddress (ch_addr),
    .wraddress (ch_addr),
    .wren (cpu_strobe || cache_inv),
    .q ({cache_v, cache_a}),
    .aclr (1'b0),
    .byteena (1'b1),
    .inclocken (1'b1),
    .outclocken (1'b1),
    .rdaddressstall (1'b0),
    .rden (1'b1),
    .wraddressstall (1'b0)
  );

  defparam
    cache_addr.indata_aclr = "OFF",
    cache_addr.indata_reg = "INCLOCK",
    cache_addr.intended_device_family = "ACEX1K",
    cache_addr.lpm_type = "altdpram",
    cache_addr.outdata_aclr = "OFF",
    cache_addr.outdata_reg = "OUTCLOCK",
    cache_addr.rdaddress_aclr = "OFF",
    cache_addr.rdaddress_reg = "UNREGISTERED",
    cache_addr.rdcontrol_aclr = "OFF",
    cache_addr.rdcontrol_reg = "UNREGISTERED",
    cache_addr.width = 14,
    cache_addr.widthad = 8,
    cache_addr.wraddress_aclr = "OFF",
    cache_addr.wraddress_reg = "INCLOCK",
    cache_addr.wrcontrol_aclr = "OFF",
    cache_addr.wrcontrol_reg = "INCLOCK";
*/

  // ROM chip
  assign csrom = rom_n_ram;
  assign romoe_n = !memrd;
  assign romwe_n = !(memwr && w0_we);
  assign rompg = xtpage[0][4:0];


  // XXX ugly workarround
  // There are SDRAM latency-related issues with fetching opcode from correct page when entering DOS
  // With current HDL design ROM page number isn't valid at moment of cpu_req asserting
  // That is, there is a chance that SDRAM controller will start read cycle with an incorrect address
  wire [7:0] trdos_3dxx_do;
  wire trdos_3dxx_hit = dos && csrom && rompg==5'h1 && za[13:8]==6'h3D;
  dpram #(.DATAWIDTH(8), .ADDRWIDTH(8), .MEM_INIT_FILE("rtl/z80/trdos504T_3DXX.mif")) trdos_3dxx
  (
    .clock (clk),
    .address_a (za[7:0]),
    .q_a (trdos_3dxx_do)
  );

endmodule
