//
// dcpktfifo.sv
//
// Dual-clock RAM-based FIFO with transaction support (commit/abort)
//

// Parametric synchronous RAM module, dual clock,
// one read and one write port; output data not
// registered
module dcqram
#(
  parameter wbits,		// log2(size in words)
  parameter width = 8
)
   (
    input	       wclk,
    input	       wstb,
    input [wbits-1:0]  waddr,
    input [width-1:0]  wdata,

    input	       rclk,
    input [wbits-1:0]  raddr,
    output [width-1:0] rdata
    );

`ifdef ALTERA_RESERVED_QIS
   (* ramstyle = "no_rw_check" *)
`endif
   reg [width-1:0]     mem[0:(1 << wbits)-1];

   always @(posedge wclk)
     if (wstb)
       mem[waddr] <= wdata;

   reg [wbits-1:0]     raddr_q;

   assign rdata = mem[raddr_q];

   always @(posedge rclk)
     raddr_q <= raddr;
endmodule // dcqram

module dcpktfifo
  #(
    parameter	    wbits     = 10,
    parameter	    width     = 8,
    parameter [0:0] wtrans    = 1'b1, // Support transactions on write side
    parameter [0:0] rtrans    = 1'b1  // Support transactions on read side
    )
   (
    input		   rst_n,

    input		   wclk,
    input		   wstb,
    input		   wcommit,
    input		   wabort,
    input [width-1:0]	   wdata,
    output reg [wbits-1:0] wnfree,
    output reg		   wempty,
    output reg		   wfull,

    input		   rclk,
    input		   rstb,
    input		   rcommit,
    input		   rabort,
    output [width-1:0]	   rdata,
    output reg [wbits-1:0] rnavail,
    output reg		   rempty,
    output reg		   rlast,
    output reg		   remptyh, // Cleared only on abort/commit
    output reg		   rlasth,  // Cleared only on abort/commit
    output reg		   rfull
    );

   reg [wbits-1:0]  wtaddr;	// Transient write address
   reg [wbits-1:0]  wcaddr;	// Committed write address
   wire [wbits-1:0] w_rcaddr;	// rcaddr latched in the wclk domain

   wire		    wcommit_w = wtrans ? wcommit : 1'b1;
   wire		    wabort_w  = wtrans ? wabort : 1'b0;
   wire [wbits-1:0] wcaddr_w  = wtrans ? wcaddr : wtaddr;

   wire		    wstb_w = wstb & ~wfull;
   wire [wbits-1:0] wnused = wtaddr + wstb_w - w_rcaddr;

   always @(negedge rst_n or posedge wclk)
     if (~rst_n)
       begin
	  wtaddr   <= 'b0;
	  wcaddr   <= 'b0;
	  wempty   <= 1'b1;
	  wfull    <= 1'b0;
       end
     else
       begin
	  if (wabort_w)
	    wtaddr <= wcaddr_w;
	  else
	    begin
	       wtaddr <= wtaddr + wstb_w;
	       if (wcommit_w)
		 wcaddr <= wtaddr + wstb_w;
	    end

	  wnfree <= ~wnused;
	  wempty <= ~|wnused;
	  wfull  <= &wnused;
       end // else: !if(~rst_n)

   reg [wbits-1:0]  rtaddr;	// Transient read address
   reg [wbits-1:0]  rcaddr;	// Committed read address
   wire [wbits-1:0] r_wcaddr;	// wcaddr latched in the rclk domain

   wire		    rcommit_w = rtrans ? rcommit : 1'b1;
   wire		    rabort_w  = rtrans ? rabort : 1'b0;
   wire [wbits-1:0] rcaddr_w  = rtrans ? rcaddr : rtaddr;

   wire		    rstb_w = rstb & ~rempty;

   wire [wbits-1:0] rtaddr_next = (rabort_w & ~rcommit_w)
		    ? rcaddr : rtaddr + rstb_w;
   wire [wbits-1:0] rnused_w    = r_wcaddr - rtaddr_next;

   always @(negedge rst_n or posedge rclk)
     if (~rst_n)
       begin
	  rtaddr   <= 'b0;
	  rcaddr   <= 'b0;
	  rnavail  <= 'b0;
	  rempty   <= 1'b1;
	  rlast    <= 1'b1;
	  rfull    <= 1'b0;
       end
     else
       begin
	  rtaddr <= rtaddr_next;
	  if (rcommit_w)
	    rcaddr <= rtaddr_next;

	  rnavail <= rnused_w;
	  rempty  <= ~|rnused_w;
	  remptyh <= ~|rnused_w | (remptyh & ~(rcommit_w|rabort_w));
	  rlast   <= ~|rnused_w[wbits-1:1];
	  rlasth  <= ~|rnused_w[wbits-1:1] | (rlasth & ~(rcommit_w|rabort_w));
	  rfull   <= &rnused_w;
       end // else: !if(~rst_n)

   // Address pointer synchronizers.

   synchronizer #(.width(wbits))
   syncrcaddr (
	       .rst_n (rst_n),
	       .clk   (wclk),
	       .d     (rcaddr_w),
	       .q     (w_rcaddr)
	       );

   synchronizer #(.width(wbits))
   syncwcaddr (
	       .rst_n (rst_n),
	       .clk   (rclk),
	       .d     (wcaddr_w),
	       .q     (r_wcaddr)
	       );

   //
   // Memory array
   //
   dcqram #(.wbits(wbits), .width(width))
   ram (
	.wclk  (wclk),
	.wstb  (wstb),
	.waddr (wtaddr),
	.wdata (wdata),

	.rclk  (rclk),
	.raddr (rtaddr_next),
	.rdata (rdata)
	);

   // For debugging
   (* preserve, noprune *) reg wflag;
   always @(posedge wclk)
     wflag <= (wflag | wstb_w) & ~(wcommit|wabort);

   (* preserve, noprune *) reg rflag;
   always @(posedge rclk)
     rflag <= (rflag | rstb) & ~(rcommit|rabort);

endmodule // dcpktfifo
