--
-- Copyright 2021 Ettus Research, a National Instruments Brand
--
-- SPDX-License-Identifier: LGPL-3.0-or-later
--
-- Module: x410_rf_reset_controller
--
-- Description:
--
--   Control RFDC, ADC, and DAC resets.
--

library IEEE;
  use IEEE.std_logic_1164.all;
  use IEEE.numeric_std.all;

library WORK;
  use WORK.PkgRFDC_REGS_REGMAP.all;

entity x410_rf_reset_controller is
  port(
    -- Clocks
    -- Config clock is async to all the others.
    ConfigClk          : in  std_logic;
    DataClk            : in  std_logic;
    PllRefClk          : in  std_logic;
    RfClk              : in  std_logic;
    RfClk2x            : in  std_logic;
    DataClk2x          : in  std_logic;

    -- Master resets from the Radio
    dAdcResetPulse     : in  std_logic;
    dDacResetPulse     : in  std_logic;

    -- ADC Resets
    dAdcDataOutReset_n : out std_logic;
    r2AdcFirReset_n    : out std_logic;
    rAdcRfdcAxiReset_n : out std_logic;
    rAdcEnableData     : out std_logic;
    rAdcGearboxReset_n : out std_logic;

    -- DAC Resets
    dDacDataInReset_n  : out std_logic;
    r2DacFirReset_n    : out std_logic;
    d2DacFirReset_n    : out std_logic;
    rDacRfdcAxiReset_n : out std_logic;
    rDacGearboxReset_n : out std_logic;

    -- SW Control and Status
    -- Control to initiate resets to RFDC and decimation block including the
    -- gearboxes. The reset status is a sticky status of both ADC and DAC.
    cSoftwareControl   : in  std_logic_vector(31 downto 0);
    cSoftwareStatus    : out std_logic_vector(31 downto 0)
  );
end x410_rf_reset_controller;


architecture RTL of x410_rf_reset_controller is

  component pulse_synchronizer
    generic (
      MODE   : string := "PULSE";
      STAGES : integer := 2);
    port (
      clk_a   : in  std_logic;
      rst_a   : in  std_logic;
      pulse_a : in  std_logic;
      busy_a  : out std_logic;
      clk_b   : in  std_logic;
      pulse_b : out std_logic);
  end component;

  -- POR value for all resets are high.
  signal cTriggerAdcReset     : std_logic := '1';
  signal cTriggerAdcResetDlyd : std_logic := '1';
  signal cTriggerDacReset     : std_logic := '1';
  signal cTriggerDacResetDlyd : std_logic := '1';

  signal dTriggerAdcReset_ms : std_logic := '1';
  signal dTriggerAdcReset    : std_logic := '1';
  signal dTriggerDacReset_ms : std_logic := '1';
  signal dTriggerDacReset    : std_logic := '1';

  -- POR value of all reset done signals are set to low.
  signal cTriggerAdcResetDone_ms : std_logic := '0';
  signal cTriggerAdcResetDone    : std_logic := '0';
  signal cAdcResetDoneSticky     : std_logic := '0';
  signal cTriggerDacResetDone_ms : std_logic := '0';
  signal cTriggerDacResetDone    : std_logic := '0';
  signal cDacResetDoneSticky     : std_logic := '0';

  attribute ASYNC_REG : string;
  attribute ASYNC_REG of dTriggerAdcReset        : signal is "TRUE";
  attribute ASYNC_REG of dTriggerDacReset        : signal is "TRUE";
  attribute ASYNC_REG of cTriggerAdcResetDone    : signal is "TRUE";
  attribute ASYNC_REG of cTriggerDacResetDone    : signal is "TRUE";
  attribute ASYNC_REG of dTriggerAdcReset_ms     : signal is "TRUE";
  attribute ASYNC_REG of dTriggerDacReset_ms     : signal is "TRUE";
  attribute ASYNC_REG of cTriggerAdcResetDone_ms : signal is "TRUE";
  attribute ASYNC_REG of cTriggerDacResetDone_ms : signal is "TRUE";

  signal cTriggerAdcGearboxReset : std_logic;
  signal cTriggerDacGearboxReset : std_logic;

  signal dTriggerAdcGearboxReset : std_logic;
  signal dTriggerDacGearboxReset : std_logic;

begin

  -- rAdcEnableData is set to '1' as we don't control the flow of RX data.
  rAdcEnableData <= '1';

  cTriggerAdcReset        <= cSoftwareControl(kADC_RESET);
  cTriggerDacReset        <= cSoftwareControl(kDAC_RESET);
  cTriggerAdcGearboxReset <= cSoftwareControl(kADC_GEARBOX_RESET);
  cTriggerDacGearboxReset <= cSoftwareControl(kDAC_GEARBOX_RESET);

  cSoftwareStatus <= (
                       kADC_SEQ_DONE => cAdcResetDoneSticky,
                       kDAC_SEQ_DONE => cDacResetDoneSticky,
                       others => '0'
                     );

  -----------------------------------------------------------------------------
  -- High-Level Resets Using ConfigClk
  -----------------------------------------------------------------------------
  -- Pass the master FSM reset around to the other clock domains and then
  -- return them back to the ConfigClk domain. This is also a handy way to
  -- prove all your clocks are toggling to some extent.
  -----------------------------------------------------------------------------

  SeqResetDataClk : process(DataClk)
  begin
    if rising_edge(DataClk) then
      -- double-syncs have no sync reset!
      dTriggerAdcReset_ms  <= cTriggerAdcReset;
      dTriggerAdcReset     <= dTriggerAdcReset_ms;
      dTriggerDacReset_ms  <= cTriggerDacReset;
      dTriggerDacReset     <= dTriggerDacReset_ms;
    end if;
  end process;

  -----------------------------------------------------------------------------
  -- Reset Sequence Done Status
  -----------------------------------------------------------------------------
  -- Now back to ConfigClk! We provide the status for all software controlled
  -- resets. We move the signal from ConfigClk to DataClk domain and move it
  -- back to ConfigClk domain. This just proves that DataClk is toggling and
  -- the reset requested by software is sampled in the DataClk.
  -----------------------------------------------------------------------------

  SeqResetDone : process(ConfigClk)
  begin
    if rising_edge(ConfigClk) then
      -- double-syncs have no sync reset!
      cTriggerAdcResetDone_ms   <= dTriggerAdcReset;
      cTriggerAdcResetDone      <= cTriggerAdcResetDone_ms;
      cTriggerDacResetDone_ms   <= dTriggerDacReset;
      cTriggerDacResetDone      <= cTriggerDacResetDone_ms;
    end if;
  end process;

  -- ADC reset done
  SwAdcResetDone: process(ConfigClk)
  begin
    if rising_edge(ConfigClk) then
      cTriggerAdcResetDlyd <= cTriggerAdcReset;
      -- De-assert reset status on the rising edge of SW ADC reset.
      if cTriggerAdcReset = '1' and cTriggerAdcResetDlyd = '0' then
        cAdcResetDoneSticky <= '0';
      -- Assert and hold the ADC reset status on ADC reset strobe.
      elsif cTriggerAdcResetDone = '1' then
        cAdcResetDoneSticky <= '1';
      end if;
    end if;
  end process SwAdcResetDone;

  -- DAC reset done
  SwDacResetDone: process(ConfigClk)
  begin
    if rising_edge(ConfigClk) then
      cTriggerDacResetDlyd <= cTriggerDacReset;
      -- De-assert reset status on the rising edge of SW DAC reset.
      if cTriggerDacReset = '1' and cTriggerDacResetDlyd = '0' then
        cDacResetDoneSticky <= '0';
      -- Assert and hold the DAC reset status on DAC reset strobe.
      elsif cTriggerDacResetDone = '1' then
        cDacResetDoneSticky <= '1';
      end if;
    end if;
  end process SwDacResetDone;

  -----------------------------------------------------------------------------
  -- transfer posedge gearbox triggers to DataClk domain
  -----------------------------------------------------------------------------
  adc_gearbox_reset_pulse_sync : pulse_synchronizer
    generic map (
      MODE   => "POSEDGE",
      STAGES => 2
    )
    port map (
      clk_a   => ConfigClk,
      rst_a   => '0',
      pulse_a => cTriggerAdcGearboxReset,
      busy_a  => open,
      clk_b   => DataClk,
      pulse_b => dTriggerAdcGearboxReset
    );

  dac_gearbox_reset_pulse_sync : pulse_synchronizer
    generic map (
      MODE   => "POSEDGE",
      STAGES => 2
    )
    port map (
      clk_a   => ConfigClk,
      rst_a   => '0',
      pulse_a => cTriggerDacGearboxReset,
      busy_a  => open,
      clk_b   => DataClk,
      pulse_b => dTriggerDacGearboxReset
    );

  -----------------------------------------------------------------------------
  -- rf_reset Instances
  -----------------------------------------------------------------------------

  AdcResets: entity work.rf_reset (RTL)
    port map (
      DataClk     => DataClk,
      PllRefClk   => PllRefClk,
      RfClk       => RfClk,
      RfClk2x     => RfClk2x,
      DataClk2x   => DataClk2x,
      dTimedReset => (dAdcResetPulse or dTriggerAdcGearboxReset),
      dSwReset    => dTriggerAdcReset,
      dReset_n    => dAdcDataOutReset_n,
      d2Reset_n   => open,
      r2Reset_n   => r2AdcFirReset_n,
      rAxiReset_n => rAdcRfdcAxiReset_n,
      rReset_n    => rAdcGearboxReset_n
    );

  DacResets: entity work.rf_reset (RTL)
    port map (
      DataClk     => DataClk,
      PllRefClk   => PllRefClk,
      RfClk       => RfClk,
      RfClk2x     => RfClk2x,
      DataClk2x   => DataClk2x,
      dTimedReset => (dDacResetPulse or dTriggerDacGearboxReset),
      dSwReset    => dTriggerDacReset,
      dReset_n    => dDacDataInReset_n,
      d2Reset_n   => d2DacFirReset_n,
      r2Reset_n   => r2DacFirReset_n,
      rAxiReset_n => rDacRfdcAxiReset_n,
      rReset_n    => rDacGearboxReset_n
    );

end RTL;
