library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
-------------------------------------------------------------------------------
-- **** Mercury USB->SPI communications module ****
-- Provides a register interface from the PC via USB
-------------------------------------------------------------------------------
-- SPI standard:
--    -> we shift IN on rising
--    -> we shift OUT on falling
--    -> shift MSB first
--
-- Master operation:
--   Shifts  0-7  -> Out 8-bits address
--   Shifts  8-15 -> Out 8-bits command  (0x01 = read, 0x02 = write)
--   Shifts 16-47 -> Out/in 32-bits data
--
-- FPGA interface:
--   fpga_din  = data bus from SPI -> FPGA
--   fpga_addr = addr bus from SPI -> FPGA
--   fpga_dout = data bus from FPGA -> SPI
--   fpga_we   = pulse indicating din valid


entity MercurySPI is
  port (
    clk  : in  std_logic;
    -- SPI interface    
    csn  : in  std_logic;
    mosi : in  std_logic;
    miso : out std_logic;
    sck  : in  std_logic;
    -- FPGA interface
    addr : out std_logic_vector(7 downto 0);
    din  : in  std_logic_vector(31 downto 0);
    dout : out std_logic_vector(31 downto 0);
    wr   : out std_logic;
    rd   : out std_logic
    ); 
end MercurySPI;

architecture rtl of MercurySPI is

-- SPI edge detectors
  signal csn_reg            : std_logic_vector(1 downto 0) := (others => '0');
  signal sck_reg            : std_logic_vector(1 downto 0) := (others => '0');
  signal csn_rise, csn_fall : std_logic;
  signal sck_rise, sck_fall : std_logic;

-- SPI shift counter
  signal shift_count_reg : unsigned(3 downto 0) := (others => '0');
  signal shift_addr_val  : std_logic;

-- SPI shift registers
  signal data_out_reg : std_logic_vector(31 downto 0) := (others => '0');
  signal data_in_reg  : std_logic_vector(31 downto 0) := (others => '0');
  signal addr_in_reg  : std_logic_vector(15 downto 0) := (others => '0');

-- address and command
  signal addr_val_reg    : std_logic := '0';
  signal dout_loaded_reg : std_logic := '0';
  signal read_cmnd       : std_logic;
  signal write_cmnd      : std_logic;

-- read/write pulses
  signal wr_pulse : std_logic;
  signal rd_pulse : std_logic;
  
begin

-------------------------------------------------------------------------------
-- SPI EDGE DETECTION
-------------------------------------------------------------------------------

  spi_signal_shiftregs : process(clk)
  begin
    if clk'event and clk = '1' then
      -- CSN shift registers
      csn_reg(0) <= csn;
      csn_reg(1) <= csn_reg(0);
      -- SCK shift registers
      if csn = '0' then
        sck_reg(0) <= sck;
        sck_reg(1) <= sck_reg(0);
      end if;
    end if;
  end process;

  -- edge detectors
  csn_rise <= '1' when csn_reg(0) = '1' and csn_reg(1) = '0' else '0';
  csn_fall <= '1' when csn_reg(0) = '0' and csn_reg(1) = '1' else '0';
  sck_rise <= '1' when sck_reg(0) = '1' and sck_reg(1) = '0' else '0';
  sck_fall <= '1' when sck_reg(0) = '0' and sck_reg(1) = '1' else '0';

-------------------------------------------------------------------------------
-- SHIFT COUNTER
-------------------------------------------------------------------------------

  -- count number of shifts
  shift_counter : process(clk)
  begin
    if clk'event and clk = '1' then
      if csn_fall = '1' then
        -- reset on CSN fall
        shift_count_reg <= (others => '0');
      elsif sck_rise = '1' and addr_val_reg = '0' then
        -- increment on SCK rise
        shift_count_reg <= shift_count_reg + 1;
      end if;
    end if;
  end process;

-------------------------------------------------------------------------------
-- SPI SHIFT-REGISTERS
-------------------------------------------------------------------------------

  shift_in : process(clk)
  begin
    if clk'event and clk = '1' then
      if csn_fall = '1' then
        -- reset on CSN fall
        addr_in_reg <= (others => '0');
        data_in_reg <= (others => '0');
      elsif sck_rise = '1' then
        -- shift-in on SCK rise
        if addr_val_reg = '0' then
          -- shift-in address
          addr_in_reg(0)           <= mosi;
          addr_in_reg(15 downto 1) <= addr_in_reg(14 downto 0);
        elsif write_cmnd = '1' then
          -- shift-in data
          data_in_reg(0)           <= mosi;
          data_in_reg(31 downto 1) <= data_in_reg(30 downto 0);
        end if;
      end if;
    end if;
  end process;

  -- read/write pulses
  wr_pulse <= '1' when csn_rise = '1' and write_cmnd = '1'                          else '0';
  rd_pulse <= '1' when sck_fall = '1' and dout_loaded_reg = '0' and read_cmnd = '1' else '0';

  shift_out : process(clk)
  begin
    if clk'event and clk = '1' then
      if csn_fall = '1' then
        -- reset on CSN fall
        data_out_reg    <= (others => '0');
        dout_loaded_reg <= '0';
      elsif rd_pulse = '1' then
        -- load register
        data_out_reg    <= din;
        dout_loaded_reg <= '1';
      elsif sck_fall = '1' then
        -- shift out
        data_out_reg(31 downto 1) <= data_out_reg(30 downto 0);
      end if;
    end if;
  end process;

  -- SPI data out pin
  miso <= data_out_reg(31) when (dout_loaded_reg = '1') and (csn = '0') else 'Z';

-------------------------------------------------------------------------------
-- COMMAND
-------------------------------------------------------------------------------

  -- address valid
  addr_valid : process(clk)
  begin
    if clk'event and clk = '1' then
      if csn_fall = '1' then
        -- reset on CSN fall
        addr_val_reg <= '0';
      elsif shift_count_reg = X"0F" and sck_rise = '1' then
        -- address valid after 16th shift
        addr_val_reg <= '1';
      end if;
    end if;
  end process;

  -- decode commands
  read_cmnd  <= '1' when (addr_in_reg(7 downto 0) = X"01") and (addr_val_reg = '1') else '0';
  write_cmnd <= '1' when (addr_in_reg(7 downto 0) = X"02") and (addr_val_reg = '1') else '0';

-------------------------------------------------------------------------------
-- WIRE OUTPUTS
-------------------------------------------------------------------------------

  wr   <= wr_pulse;
  rd   <= rd_pulse;
  addr <= addr_in_reg(15 downto 8);
  dout <= data_in_reg;


end rtl;
