module axi_dmac_reset_manager #(
  parameter ASYNC_CLK_REQ_SRC = 1,
  parameter ASYNC_CLK_SRC_DEST = 1,
  parameter ASYNC_CLK_DEST_REQ = 1
) (
  input clk,
  input resetn,

  input ctrl_enable,
  input ctrl_pause,

  output req_resetn,
  output req_enable,
  input req_enabled,

  input dest_clk,
  input dest_ext_resetn,
  output dest_resetn,
  output dest_enable,
  input dest_enabled,

  input src_clk,
  input src_ext_resetn,
  output src_resetn,
  output src_enable,
  input src_enabled,

  output [11:0] dbg_status
);

/*
 * TODO:
 * If an external reset is asserted for a domain that domain will go into reset
 * immediately. If a transfer is currently active the transfer will be aborted
 * and other domains will be shutdown gracefully. The reset manager will stay in
 * the shutdown state until all external resets have been de-asserted.
 */

localparam STATE_DO_RESET = 3'h0;
localparam STATE_RESET = 3'h1;
localparam STATE_DISABLED = 3'h2;
localparam STATE_STARTUP = 3'h3;
localparam STATE_ENABLED = 3'h4;
localparam STATE_SHUTDOWN = 3'h5;

reg [2:0] state = 3'b000;
reg needs_reset = 1'b0;
reg do_reset = 1'b0;
reg do_enable = 1'b0;

wire enabled_dest;
wire enabled_src;

wire enabled_all;
wire disabled_all;

assign enabled_all = req_enabled & enabled_src & enabled_dest;
assign disabled_all = ~(req_enabled | enabled_src | enabled_dest);

assign req_enable = do_enable;

assign dbg_status = {needs_reset,req_resetn,src_resetn,dest_resetn,1'b0,req_enabled,enabled_src,enabled_dest,1'b0,state};

always @(posedge clk) begin
  if (state == STATE_DO_RESET) begin
    do_reset <= 1'b1;
  end else begin
    do_reset <= 1'b0;
  end
end

always @(posedge clk) begin
  if (state == STATE_STARTUP || state == STATE_ENABLED) begin
    do_enable <= 1'b1;
  end else begin
    do_enable <= 1'b0;
  end
end

/*
 * If ctrl_enable goes from 1 to 0 a shutdown procedure is initiated. During the
 * shutdown procedure all domains are signaled that a shutdown should occur. The
 * domains will then complete any active transactions that are required to
 * complete according to the interface semantics. Once a domain has completed
 * its transactions it will indicate that it has been shutdown. Once all domains
 * indicate that they have been disabled a reset pulse will be generated to all
 * domains to clear all residual state. The reset pulse is long enough so that it
 * is active in all domains for at least 4 clock cycles.
 *
 * Once the reset signal is de-asserted the DMA is in an idle state and can be
 * enabled again. If the DMA receives a enable while it is performing a shutdown
 * sequence it will only be re-enabled once the shutdown sequence has
 * successfully completed.
 *
 * If ctrl_pause is asserted all domains will be disabled. But there will be no
 * reset, so when the ctrl_pause signal is de-asserted again the DMA will resume
 * with its previous state.
 *
 */

/*
 * If ctrl_enable goes low, even for a single clock cycle, we want to go through
 * a full reset sequence. This might happen when the state machine is busy, e.g.
 * going through a startup sequence. To avoid missing the event store it for
 * later.
 */
always @(posedge clk) begin
  if (state == STATE_RESET) begin
    needs_reset <= 1'b0;
  end else if (ctrl_enable == 1'b0) begin
    needs_reset <= 1'b1;
  end
end

always @(posedge clk) begin
  if (resetn == 1'b0) begin
    state <= STATE_DO_RESET;
  end else begin
    case (state)
    STATE_DO_RESET: begin
      state <= STATE_RESET;
    end
    STATE_RESET: begin
      /*
       * Wait for the reset sequence to complete. Stay in this state when
       * ctrl_enable == 1'b0, otherwise we'd go through the reset sequence
       * again and again.
       */
      if (ctrl_enable == 1'b1 && req_resetn == 1'b1) begin
        state <= STATE_DISABLED;
      end
    end
    STATE_DISABLED: begin
      if (needs_reset == 1'b1) begin
        state <= STATE_DO_RESET;
      end else if (ctrl_pause == 1'b0) begin
        state <= STATE_STARTUP;
      end
    end
    STATE_STARTUP: begin
      /* Wait for all domains to be ready */
      if (enabled_all == 1'b1) begin
        state <= STATE_ENABLED;
      end
    end
    STATE_ENABLED: begin
      if (needs_reset == 1'b1 || ctrl_pause == 1'b1) begin
        state <= STATE_SHUTDOWN;
      end
    end
    STATE_SHUTDOWN: begin
      /* Wait for all domains to complete outstanding transactions */
      if (disabled_all == 1'b1) begin
        state <= STATE_DISABLED;
      end
    end
    endcase
  end
end

/*
 * Chain the reset through all clock domains. This makes sure that is asserted
 * for at least 4 clock cycles of the slowest domain, no matter what. If
 * successive domains have the same clock they'll share their reset signal.
 */

wire [3:0] reset_async_chain;
wire [3:0] reset_sync_chain;
wire [2:0] reset_chain_clks = {clk, src_clk, dest_clk};

localparam GEN_ASYNC_RESET = {
  ASYNC_CLK_REQ_SRC ? 1'b1 : 1'b0,
  ASYNC_CLK_SRC_DEST ? 1'b1 : 1'b0,
  1'b1
};

assign reset_async_chain[0] = 1'b0;
assign reset_sync_chain[0] = reset_async_chain[3];

generate
genvar i;

for (i = 0; i < 3; i = i + 1) begin: reset_gen

  if (GEN_ASYNC_RESET[i] == 1'b1) begin

    reg [3:0] reset_async = 4'b1111;
    reg [1:0] reset_sync = 2'b11;
    reg reset_sync_in = 1'b1;

    always @(posedge reset_chain_clks[i] or posedge reset_sync_chain[i]) begin
      if (reset_sync_chain[i] == 1'b1) begin
        reset_sync_in <= 1'b1;
      end else begin
        reset_sync_in <= reset_async[0];
      end
    end

    always @(posedge reset_chain_clks[i] or posedge do_reset) begin
      if (do_reset == 1'b1) begin
        reset_async <= 4'b1111;
      end else begin
        reset_async <= {reset_async_chain[i], reset_async[3:1]};
      end
    end

    always @(posedge reset_chain_clks[i]) begin
      reset_sync <= {reset_sync_in,reset_sync[1]};
    end

    assign reset_async_chain[i+1] = reset_async[0];
    assign reset_sync_chain[i+1] = reset_sync[0];

  end else begin
    assign reset_async_chain[i+1] = reset_async_chain[i];
    assign reset_sync_chain[i+1] = reset_sync_chain[i];
  end
end

endgenerate

/* De-assertions in the opposite direction of the data flow: dest, src, request */
assign dest_resetn = ~reset_sync_chain[1];
assign src_resetn = ~reset_sync_chain[2];
assign req_resetn = ~reset_sync_chain[3];

sync_bits #(
  .NUM_OF_BITS (1),
  .ASYNC_CLK (ASYNC_CLK_DEST_REQ)
) i_sync_control_dest (
  .out_clk (dest_clk),
  .out_resetn (1'b1),
  .in_bits (do_enable),
  .out_bits (dest_enable)
);

sync_bits #(
  .NUM_OF_BITS (1),
  .ASYNC_CLK (ASYNC_CLK_DEST_REQ)
) i_sync_status_dest (
  .out_clk (clk),
  .out_resetn (1'b1),
  .in_bits (dest_enabled),
  .out_bits (enabled_dest)
);

sync_bits #(
  .NUM_OF_BITS (1),
  .ASYNC_CLK (ASYNC_CLK_REQ_SRC)
) i_sync_control_src (
  .out_clk (src_clk),
  .out_resetn (1'b1),
  .in_bits (do_enable),
  .out_bits (src_enable)
);

sync_bits #(
  .NUM_OF_BITS (1),
  .ASYNC_CLK (ASYNC_CLK_REQ_SRC)
) i_sync_status_src (
  .out_clk (clk),
  .out_resetn (1'b1),
  .in_bits (src_enabled),
  .out_bits (enabled_src)
);

endmodule