module mvsmcbridge(
  input wb_rst_i,
  input wbm_clk_i,
  input sync_i,
  output sync_o,

  /* WISHBONE master synchronous to 62.5Mhz SMC clock */
  output wbm_clk_o,
  output wbm_cyc_o,
  output wbm_stb_o,
  output [15:0] wbm_dat_o,
  output [31:0] wbm_adr_o,
  output wbm_we_o,
  input [15:0] wbm_dat_i,
  input wbm_ack_i,
  output [1:0] wbm_sel_o,

  /* WISHBONE master asynchronous (feed external clock) */
  input wbm2_en_i, /* ext. logic uses wbm_adr_o combinationally to enable */
  input wbm2_clk_i,
  output wbm2_cyc_o,
  output wbm2_stb_o,
  output [15:0] wbm2_dat_o,
  output [31:0] wbm2_adr_o,
  output wbm2_we_o,
  input [15:0] wbm2_dat_i,
  input wbm2_ack_i,
  output [1:0] wbm2_sel_o,

  /* Asynchronous WISHBONE slave, receives writes and immediately
   * places them into a FIFO to be picked up by SMC read bursts.  */
  input wbs_clk_i, // All DMA is on the same clock for now

  input wbs0_cyc_i,
  input wbs0_stb_i,
  input wbs0_we_i,
  input [31:0] wbs0_adr_i,
  input [15:0] wbs0_dat_i,
  output [15:0] wbs0_dat_o,
  input [2:0] wbs0_cti_i,
  input [1:0] wbs0_bte_i,
  output wbs0_ack_o,

  input wbs1_cyc_i,
  input wbs1_stb_i,
  input wbs1_we_i,
  input [31:0] wbs1_adr_i,
  input [15:0] wbs1_dat_i,
  output [15:0] wbs1_dat_o,
  input [2:0] wbs1_cti_i,
  input [1:0] wbs1_bte_i,
  output wbs1_ack_o,

  input wbs2_cyc_i,
  input wbs2_stb_i,
  input wbs2_we_i,
  input [31:0] wbs2_adr_i,
  input [15:0] wbs2_dat_i,
  output [15:0] wbs2_dat_o,
  input [2:0] wbs2_cti_i,
  input [1:0] wbs2_bte_i,
  output wbs2_ack_o,

  input wbs3_cyc_i,
  input wbs3_stb_i,
  input wbs3_we_i,
  input [31:0] wbs3_adr_i,
  input [15:0] wbs3_dat_i,
  output [15:0] wbs3_dat_o,
  input [2:0] wbs3_cti_i,
  input [1:0] wbs3_bte_i,
  output wbs3_ack_o,

  /* Marvell SMC bus, operating in synchronous mode, fastest timing,
   * and with RDY support enabled */
  input [15:0] smc_dat_i,
  output [15:0] smc_dat_o,
  output smc_dat_oe_o,
  input smc_rdn_i,
  input smc_wrn_i,
  input smc_cs1n_i,
  input smc_be1n_i,
  input smc_be2n_i,
  input smc_advn_i,
  output smc_waitn_o,

  output [3:0] wr_fifo_empty_o
);

reg [15:0] smc_d_out;
reg burst_wr, fifo_rd;
reg [3:0] burst_adr, burst_q;
reg [2:0] smc_rdy;
reg [3:0] burst_cnt;
reg wbm_cyc, wbm_stb, wbm_we, update_sel;
reg [1:0] wbm_sel;
reg [31:0] wbm_adr;
wire wb_ack, resync_wb_ack, flushack;
wire [31:0] wb_dat, resync_wb_dat;
reg [14:0] ts4710_decodes;
assign smc_dat_oe_o = !smc_rdn_i;
assign smc_dat_o = smc_d_out;
wire [3:0] fifo_full, fifo_empty;

reg wbs0_ack;
assign wbs0_ack_o = (wbs0_cyc_i & wbs0_stb_i & wbs0_we_i & !fifo_full[0]) |
                     wbs0_ack;
reg wbs1_ack;
assign wbs1_ack_o = (wbs1_cyc_i & wbs1_stb_i & wbs1_we_i & !fifo_full[1]) |
                     wbs1_ack;
reg wbs2_ack;
assign wbs2_ack_o = (wbs2_cyc_i & wbs2_stb_i & wbs2_we_i & !fifo_full[2]) |
                     wbs2_ack;
reg wbs3_ack;
assign wbs3_ack_o = (wbs3_cyc_i & wbs3_stb_i & wbs3_we_i & !fifo_full[3]) |
                     wbs3_ack;

reg first_cyc;
reg smc_wrn_q, smc_sync_switch;
wire smc_sync_en = sync_i;
assign sync_o = smc_sync_switch;
assign wbm_clk_o = wbm_clk_i;

reg smc_advn_q, smc_cs1n_q, smc_be2n_q, smc_be1n_q, smc_rdn_q;
reg [15:0] smc_dat_q;
wire smc_advn = smc_sync_en ? smc_advn_i : smc_advn_q;
wire smc_rdn = smc_sync_en ? smc_rdn_i : smc_rdn_q;
wire smc_wrn = smc_sync_en ? smc_wrn_i : smc_wrn_q;
wire smc_cs1n = smc_sync_en ? smc_cs1n_i : smc_cs1n_q;
wire smc_be1n = smc_sync_en ? smc_be1n_i : smc_be1n_q;
wire smc_be2n = smc_sync_en ? smc_be2n_i : smc_be2n_q;
wire [15:0] smc_dat = smc_sync_en ? smc_dat_i : smc_dat_q;
assign smc_waitn_o = smc_rdy[0] & smc_advn; 
wire [3:0] wbm_osel;
assign wbm_sel_o = wbm_osel[1:0];

always @(posedge wbm_clk_o) begin
  smc_advn_q <= smc_advn_i;
  smc_cs1n_q <= smc_cs1n_i;
  smc_be1n_q <= smc_be1n_i;
  smc_be2n_q <= smc_be2n_i;
  smc_rdn_q <= smc_rdn_i;
  smc_wrn_q <= smc_wrn_i;
  smc_dat_q <= smc_dat_i;
end

wb_writebuffer writebuf(
  .wb_clk_i(wbm_clk_o),
  .wb_rst_i(wb_rst_i),
  .wb_adr_i({ts4710_decodes[14:0], wbm_adr[16:0]}),
  .wb_dat_i({16'd0, smc_dat}),
  .wb_cyc_i(wbm_cyc),
  .wb_stb_i(wbm_stb && (!first_cyc || flushack)),
  .wb_we_i(wbm_we),
  .wb_dat_o(wb_dat),
  .wb_ack_o(wb_ack),
  .wb_sel_i({2'b00, wbm_sel}),

  .wbm_adr_o(wbm_adr_o),
  .wbm_cyc_o(wbm_cyc_o),
  .wbm_stb_o(wbm_stb_o),
  .wbm_we_o(wbm_we_o),
  .wbm_ack_i(wbm2_en_i ? resync_wb_ack : (wbm_ack_i|(wbm_cyc_o && wbm_stb_o && wbm_adr_o[31]))),
  .wbm_dat_i(wbm2_en_i ? resync_wb_dat : {16'd0, wbm_dat_i}),
  .wbm_dat_o(wbm_dat_o),
  .wbm_sel_o(wbm_osel),

  .flushreq_i(first_cyc),
  .flushack_o(flushack)
);

wb_resync resynccore(
  .wb_clk_i(wbm_clk_o),
  .wb_rst_i(wb_rst_i),
  .wbs1_cyc_i(wbm_cyc_o),
  .wbs1_stb_i(wbm_stb_o && wbm2_en_i),
  .wbs1_we_i(wbm_we_o),
  .wbs1_dat_i(wbm_dat_o),
  .wbs1_dat_o(resync_wb_dat),
  .wbs1_ack_o(resync_wb_ack),
  .wbs1_adr_i(wbm_adr_o),
  .wbs1_sel_i(wbm_sel_o),

  .wbm_clk_i(wbm2_clk_i),
  .wbm_cyc_o(wbm2_cyc_o),
  .wbm_stb_o(wbm2_stb_o),
  .wbm_we_o(wbm2_we_o),
  .wbm_adr_o(wbm2_adr_o),
  .wbm_dat_o(wbm2_dat_o),
  .wbm_dat_i(wbm2_dat_i),
  .wbm_ack_i(wbm2_ack_i),
  .wbm_sel_o(wbm2_sel_o)
);

wire [15:0] fifo0_q;
wire [15:0] fifo1_q;
wire [15:0] fifo2_q;
wire [15:0] fifo3_q;
wire [3:0] wr_fifo_empty, wr_fifo_full;
assign wr_fifo_empty_o = wr_fifo_empty;

xilinx_fifo_async_1kb read_fifo0 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbs_clk_i && smc_sync_en),
  .rd_clk(wbm_clk_o && smc_sync_en),
  .din(wbs0_dat_i),
  .wr_en(wbs0_cyc_i && wbs0_stb_i && wbs0_we_i && !fifo_full[0]),
  .rd_en(burst_adr[0] && !smc_rdn),
  .dout(fifo0_q),
  .full(fifo_full[0]),
  .empty(fifo_empty[0])
);

// XXX is A31 needed here?
xilinx_fifo_async_1kb write_fifo0 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbm_clk_o && smc_sync_en),
  .rd_clk(wbs_clk_i && smc_sync_en),
  .din(wbm_dat_o),
  //.wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && wbm_adr_o[31] && burst_adr[0]),
  .wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && burst_q[0]),
  .rd_en(wbs0_cyc_i && wbs0_stb_i && !wbs0_we_i && !wr_fifo_empty[0] && !wbs0_ack),
  .dout(wbs0_dat_o),
  .full(wr_fifo_full[0]),
  .empty(wr_fifo_empty[0])
);

xilinx_fifo_async_1kb read_fifo1 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbs_clk_i && smc_sync_en),
  .rd_clk(wbm_clk_o && smc_sync_en),
  .din(wbs1_dat_i),
  .wr_en(wbs1_cyc_i && wbs1_stb_i && wbs1_we_i && !fifo_full[1]),
  .rd_en(burst_adr[1] && !smc_rdn),
  .dout(fifo1_q),
  .full(fifo_full[1]),
  .empty(fifo_empty[1])
);

// XXX is A31 needed here?
xilinx_fifo_async_1kb write_fifo1 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbm_clk_o && smc_sync_en),
  .rd_clk(wbs_clk_i && smc_sync_en),
  .din(wbm_dat_o),
  //.wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && wbm_adr_o[31] && burst_adr[1]),
  .wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && burst_q[1]),
  .rd_en(wbs1_cyc_i && wbs1_stb_i && !wbs1_we_i && !wr_fifo_empty[1] && !wbs1_ack),
  .dout(wbs1_dat_o),
  .full(wr_fifo_full[1]),
  .empty(wr_fifo_empty[1])
);

xilinx_fifo_async_1kb read_fifo2 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbs_clk_i && smc_sync_en),
  .rd_clk(wbm_clk_o && smc_sync_en),
  .din(wbs2_dat_i),
  .wr_en(wbs2_cyc_i && wbs2_stb_i && wbs2_we_i && !fifo_full[2]),
  .rd_en(burst_adr[2] && !smc_rdn),
  .dout(fifo2_q),
  .full(fifo_full[2]),
  .empty(fifo_empty[2])
);

// XXX is A31 needed here?
xilinx_fifo_async_1kb write_fifo2 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbm_clk_o && smc_sync_en),
  .rd_clk(wbs_clk_i && smc_sync_en),
  .din(wbm_dat_o),
  //.wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && wbm_adr_o[31] && burst_adr[2]),
  .wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && burst_q[2]),
  .rd_en(wbs2_cyc_i && wbs2_stb_i && !wbs2_we_i && !wr_fifo_empty[2] && !wbs2_ack),
  .dout(wbs2_dat_o),
  .full(wr_fifo_full[2]),
  .empty(wr_fifo_empty[2])
);

xilinx_fifo_async_1kb read_fifo3 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbs_clk_i && smc_sync_en),
  .rd_clk(wbm_clk_o && smc_sync_en),
  .din(wbs3_dat_i),
  .wr_en(wbs3_cyc_i && wbs3_stb_i && wbs3_we_i && !fifo_full[3]),
  .rd_en(burst_adr[3] && !smc_rdn),
  .dout(fifo3_q),
  .full(fifo_full[3]),
  .empty(fifo_empty[3])
);

// XXX is A31 needed here?
xilinx_fifo_async_1kb write_fifo3 (
  .rst(wb_rst_i | !smc_sync_en),
  .wr_clk(wbm_clk_o && smc_sync_en),
  .rd_clk(wbs_clk_i && smc_sync_en),
  .din(wbm_dat_o),
  //.wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && wbm_adr_o[31] && burst_adr[3]),
  .wr_en(wbm_cyc_o && wbm_stb_o && wbm_we_o && burst_q[3]),
  .rd_en(wbs3_cyc_i && wbs3_stb_i && !wbs3_we_i && !wr_fifo_empty[3] && !wbs3_ack),
  .dout(wbs3_dat_o),
  .full(wr_fifo_full[3]),
  .empty(wr_fifo_empty[3])
);

always @(posedge wbs_clk_i) begin
  wbs0_ack <=
    wbs0_cyc_i && wbs0_stb_i && !wbs0_we_i && !wr_fifo_empty[0] && !wbs0_ack;
  wbs1_ack <=
    wbs1_cyc_i && wbs1_stb_i && !wbs1_we_i && !wr_fifo_empty[1] && !wbs1_ack;
  wbs2_ack <=
    wbs2_cyc_i && wbs2_stb_i && !wbs2_we_i && !wr_fifo_empty[2] && !wbs2_ack;
  wbs3_ack <=
    wbs3_cyc_i && wbs3_stb_i && !wbs3_we_i && !wr_fifo_empty[3] && !wbs3_ack;
end

always @(posedge wbm_clk_o or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    wbm_sel <= 2'd0;
    smc_d_out <= 16'd0;
    smc_rdy <= 3'd0;
    wbm_cyc <= 1'b0;
    wbm_stb <= 1'b0;
    wbm_we <= 1'b0;
    wbm_adr <= 32'd0;
    burst_adr <= 4'd0;
    burst_cnt <= 4'd0;
    burst_wr <= 1'b0;
    smc_sync_switch <= 1'b0;
    update_sel <= 1'b0;
    fifo_rd <= 1'b0;
    first_cyc <= 1'b1;
    burst_q <= 4'd0;
  end else begin
    burst_q <= burst_adr;
    if (smc_cs1n && !smc_advn) begin
      wbm_adr <= { 15'd0, smc_dat, 1'b0 };
      burst_adr <= 4'd0;
      if ({smc_dat, 1'b0} == 17'h4e20) burst_adr <= 4'b0001;
      if ({smc_dat, 1'b0} == 17'h4e60) burst_adr <= 4'b0010;
      if ({smc_dat, 1'b0} == 17'h4ea0) burst_adr <= 4'b0100;
      if ({smc_dat, 1'b0} == 17'h4ee0) burst_adr <= 4'b1000;
      update_sel <= 1'b1;
    end else if (!smc_cs1n && !smc_advn) begin
      wbm_adr <= { 15'd4, 1'b0, smc_dat[15:1], 1'b0 };
      burst_adr <= 4'd0;
      wbm_sel <= smc_dat[0] ? 2'b10 : 2'b01;  
      update_sel <= 1'b0;
    end
  
    smc_rdy[2] <= smc_rdy[1];
    smc_rdy[1] <= smc_rdy[0];
    if (!smc_advn) smc_rdy[2:0] <= 3'd0;
    if (!smc_wrn && flushack) smc_rdy[0] <= 1'b1;

    if (!smc_rdn && !wbm_cyc) begin
      wbm_cyc <= 1'b1;
      if (burst_adr == 4'd0) wbm_stb <= 1'b1;
      wbm_we <= 1'b0;
      if (update_sel) wbm_sel <= 2'b11;
    end else if (!smc_wrn && ((burst_adr != 4'd0) ? smc_rdy[0] : smc_rdy[1]) && !wbm_cyc) begin
      if (burst_adr != 4'd0) burst_wr <= 1'b1;
      wbm_stb <= 1'b1;
      wbm_cyc <= 1'b1;
      wbm_we <= 1'b1;
      if (update_sel) wbm_sel <= {!smc_be2n, !smc_be1n};
    end

    fifo_rd <= !smc_rdn && (burst_adr != 4'd0);
    if (!smc_rdn && (burst_adr != 4'd0)) begin
      smc_rdy[0] <= wbm_cyc;
      burst_cnt <= burst_cnt + 1'b1;
      if (burst_cnt == 4'hf) burst_adr <= 4'd0;
    end
    if (fifo_rd && burst_q[0]) smc_d_out <= fifo0_q;
    if (fifo_rd && burst_q[1]) smc_d_out <= fifo1_q;
    if (fifo_rd && burst_q[2]) smc_d_out <= fifo2_q;
    if (fifo_rd && burst_q[3]) smc_d_out <= fifo3_q;

    if (wb_ack && wbm_stb && wbm_cyc) begin
      smc_d_out <= wb_dat[15:0];
      if (!burst_wr) wbm_stb <= 1'b0;
      else begin
        burst_cnt <= burst_cnt + 1'b1;
        if (burst_cnt == 4'hf) begin
          burst_wr <= 1'b0;
          wbm_stb <= 1'b0;
        end
      end
      smc_rdy[0] <= 1'b1;
    end 

    if (smc_rdn && smc_wrn) begin
      wbm_cyc <= 1'b0;
      if (wbm_we) smc_sync_switch <= 1'b1;
    end

    if (wbm_cyc && wbm_stb && wb_ack) first_cyc <= 1'b0;
    else if (!wbm_cyc) first_cyc <= 1'b1;
  end
end


/* The SMC bus has 16 address lines that address 16 bit quantities, so it
 * is actually 128KB of space.  I am now modifying it to use all 128KB.
 */

/* Since 100MHz cores can no longer use decodes, I am putting them all on
 * one decode now.
 */

wire [16:0] add = wbm_adr[16:0];
always @(posedge wbm_clk_o or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    ts4710_decodes <= 15'h080;
  end else begin
    if (add < 17'h4000) begin
      ts4710_decodes <= 15'h001; // blockram

    end else if (add[16]) begin
      ts4710_decodes <= 15'h2000; // LM32 program memory

    end else if (add >= 17'h8000) begin
      ts4710_decodes <= 15'h2000; // external bus

    end else if (add >= 17'h5800) begin
      ts4710_decodes <= 15'h2000; // SPI flash controller

    end else if (add >= 17'h5400) begin
      ts4710_decodes <= 15'h004; // XUART IO registers

    end else if (add >= 17'h5000) begin
      ts4710_decodes <= 15'h008; // touchscreen

    end else if (add >= 17'h4f00) begin
      ts4710_decodes <= 15'h400; // CAN core #2

    end else if (add >= 17'h4e00) begin
      ts4710_decodes <= 15'h2000; // DMA core

    end else if (add >= 17'h4d00) begin
      ts4710_decodes <= 15'h2000; // SD core

    end else if (add >= 17'h4C00) begin
      ts4710_decodes <= 15'h010; // CAN core

    end else if (add >= 17'h4800) begin
      ts4710_decodes <= 15'h2000; // SPI core

    end else if (add >= 17'h4400) begin
      ts4710_decodes <= 15'h2000; // ADC core

    end else begin
      ts4710_decodes <= 15'h2000; // Syscon
    end

    if (burst_adr != 4'd0) ts4710_decodes <= 15'h4000;
  end
end

endmodule

