module pipe_register #(
  parameter WIDTH = 8,
  // 0 to bypass, 1 for simple buffer, 2 for skid buffer
  parameter TYPE  = 2
) (
  input  wire             clk,
  input  wire             rst,

  input  wire             din_valid,
  input  wire [WIDTH-1:0] din,
  output wire             din_ready,

  output wire             dout_valid,
  output wire [WIDTH-1:0] dout,
  input  wire             dout_ready
);

  if (TYPE > 1) begin // Skid buffer, practically a 2 element FIFO

    // Since dout_valid=!empty and din_ready=!full, their negate version
    // are used to make those signals registered without the not
    reg n_empty, n_full;

    // Two registers to buffer the data
    reg [WIDTH-1:0] dout_reg;
    reg [WIDTH-1:0] tmp_reg;

    always @ (posedge clk) begin
      if (rst) begin
        // Using the error state of both full and empty to detect cycle after reset
        // We don't want the din_ready to stay 1 during rst
        n_full  <= 1'b0; // full  <= 1'b1;
        n_empty <= 1'b0; // empty <= 1'b1;
      end else begin
        if (!n_full) begin // if (full)
          // ** both full and empty can only happen the cycle after reset
          if (dout_ready || !n_empty)
            n_full <= 1'b1; // full <= 1'b0;
        end else if (!n_empty) begin // if (empty)
          if (din_valid)
            n_empty <= 1'b1; // empty <= 1'b0;
        end else begin
          if (din_valid && !dout_ready)
            n_full  <= 1'b0; // full <= 1'b1;
          else if (!din_valid && dout_ready)
            n_empty <= 1'b0; // empty <= 1'b1;
        end
      end
    end

    always @ (posedge clk) begin
      if (!n_full) begin // if (full)
        if (dout_ready)
          dout_reg <= tmp_reg;
      end else begin
        // Can check for din_valid, but without it dout_valid won't be asserted
        if (dout_ready || !n_empty) // (dout_ready || empty)
          dout_reg <= din;
      end

      // Always load tmp_reg from input if there was a transaction,
      // to simplify the overall logic, even if not used
      if (din_valid && din_ready)
        tmp_reg <= din;
    end

    assign dout       = dout_reg;
    assign dout_valid = n_empty;
    assign din_ready  = n_full;

  end else if (TYPE == 1) begin // Simple buffer, inserts bubble cycle

    reg empty, full;
    reg [WIDTH-1:0] dout_reg;

    always @(posedge clk) begin
        if (rst) begin
          empty  <= 1'b0;
          full   <= 1'b0;
        end else if (full && dout_ready) begin
          full   <= 1'b0;
          empty  <= 1'b1;
        end else if (empty) begin
          full   <=  din_valid;
          empty  <= !din_valid; // To become 1 the cycle after rst
        end

        // Can check for din_valid, but without it dout_valid won't be asserted
        if (empty)
            dout_reg <= din;
    end

    assign dout_valid = full;
    assign din_ready  = empty;
    assign dout       = dout_reg;

  end else begin // Bypass

    assign dout       = din;
    assign dout_valid = din_valid;
    assign din_ready  = dout_ready;

  end

endmodule