        -- works as follows: the last address from the register space (in our
        -- case "111" is dedicated to the programming port. It consists of
        -- 8 bit register + some signals. Programming is done by sending
        -- multiple commands over the VME interface. The programming register
        -- bits have following meaning:
        -- X X X X X X X X X X X X X X X X X X X X X X X X 7 6 5 4 3 2 1 0
        --                                       ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
        --                                       | | | | | +-+-+-+-+-+-+-+----  DATA TO BE SHIFTED INTO THE MEMORY
        --                                       | | | | +--------------------  FPGA nCE
        --                                       | | | +----------------------  FPGA nCONFIG
        --                                       | | +------------------------  DATA TO WRITE VALID
        --                                       | +--------------------------  CONFIG TO WRITE VALID
        --                                       +----------------------------  ACTIVE SERIAL MEMORY CHIP SELECT
        -- the rest of the registers can be used as general purpose because
        -- as long as 'DATA TO WRITE VALID' is '0', no action is taken. When
        -- it contains '1', immediatelly after the data are got, it is written
        -- into the memory.
        -- The same concerns the 'CONFIG TO WRITE VALID', which handles the
        -- nCONFIG and nCE signals
        -- NOTE: THIS CODE DOESN"T SUPPORT READING FROM THE MEMORY. As this
        -- function is not very useful when you program your stuff

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;
use work.Types.all;

entity as_programmer is
  
  port (
    testout    : out std_logic;
    clk        : in  std_logic;         -- clock
    reset      : in  std_logic;         -- reset

    -- fpga output pins
    fpga_nconfig : out std_logic;       -- nconfig is mapped into one register
                                        -- bit
    fpga_nce     : out std_logic;       -- chip enable mapped into one bit
    as_asdo      : out std_logic;       -- AS data out for the EPCS memory
    as_data      : in  std_logic;       -- AS data for the memory
    as_dclk      : out std_logic;       -- AS clock shifhed into EPCS device
    as_ncs       : out std_logic;       -- AS chip enable, active low
    
    dataIn     : in  VLong;             -- data to be written into the AS interface
    dataOut    : out VByte;             -- every data read are just 8 bits
    dataRdy    : out std_logic;         -- when '1' then it announces that the
                                        -- operation is finished
    startRead  : in  std_logic;         -- '1' pulse starts read process
    startWrite : in  std_logic);  -- '1' pulse starts the write of the data into the stuff

end as_programmer;


architecture as_programmer of as_programmer is
-------------------------------------------------------------------------------
-- component declarations
-------------------------------------------------------------------------------
  component clkdivider
    generic (
      divider : integer);
    port (
      clk       : in  std_logic;
      reset     : in  std_logic;
      clkenable : out std_logic);
  end component;

-------------------------------------------------------------------------------
-- types
-------------------------------------------------------------------------------
  type States is (Idle, WriteControl, FinishOperation, WriteData, WaitDataWritten, ReadData, WaitDataRead);           -- defines states of the statemachine for programming
-------------------------------------------------------------------------------
-- signals
-------------------------------------------------------------------------------
  -- clock enable for the processes interacting with active serial interface.
  signal prgclkenable             : std_logic;
  signal CurrentState : States;         -- current state of the machine
  signal NextState : States;            -- next state of the machine. These are asynchronous but registered
  signal ShiftRegister : VByte;         -- asynchronously loaded shift register
  signal ShiftFinished : std_logic;     -- '1' when finished shifting of the data
  signal ShiftLoad : std_logic;         -- '1' to force the loading of the register
  signal ShiftCnt : std_logic_vector(3 downto 0);               -- counter for shift
  signal DataDirection : std_logic;     -- '1' for shifting the data from the memory to the VME, '0' VME->MEMORY
  signal as_asdo_in : std_logic;        -- non-tristate copy of the signal
  signal weAreMaster : std_logic;       -- '1' when it is us who puts fpga into off state and requests the activeserial bus
  signal dataOut_in : VByte;            -- used to readback the data
  signal Counter : std_logic_vector(1 downto 0);  -- counter for clock output to memory
begin  -- as_programmer
-------------------------------------------------------------------------------
-- instances of the components:
-------------------------------------------------------------------------------
  clkdivider_1: clkdivider
    generic map (
      divider => 4)
    port map (
      clk       => clk,
      reset     => reset,
      clkenable => prgclkenable);
-------------------------------------------------------------------------------
-- asynchronous assignments
-------------------------------------------------------------------------------
  testout <= weAreMaster;
  as_dclk <= Counter(1) when weAreMaster = '1' else 'Z';
  as_asdo <= as_asdo_in when (weAreMaster = '1' and DataDirection='0') else 'Z';
  dataOut <= dataOut_in;
-------------------------------------------------------------------------------
-- processes
-------------------------------------------------------------------------------
  -- shift register
  process (clk, reset, ShiftLoad, dataIn, DataDirection)
  begin  -- process
    -- asynchronous load and reset
    if reset = '0' then                 -- asynchronous reset (active low)
      as_asdo_in <= '0';
      ShiftCnt <= "0000";
      ShiftFinished <= '1';
    elsif ShiftLoad = '1' then
      -- this instruction is valid only for writing,
      -- however it doesn't harm when reading!
      ShiftRegister <= dataIn(ShiftRegister'range);
      ShiftCnt <= "1000";
      ShiftFinished <= '0';
    -- synchronous process driven by slow clock
    elsif clk'event and clk = '1' then  -- rising clock edge
      if prgclkenable = '1' and ShiftCnt /= "0000" then
        -- this process is driven by slow clock
        as_asdo_in <= ShiftRegister(ShiftRegister'left);
        -- shift data in
        dataOut_in <= dataOut_in (dataOut_in'left-1 downto 0) & as_data;
        -- and out. But out goes through tristate buffer :)
        ShiftRegister <= ShiftRegister(ShiftRegister'left-1 downto 0) & '0';
        ShiftCnt <= ShiftCnt - '1';
      elsif prgclkenable = '1' and ShiftCnt = "0000" then
        ShiftFinished <= '1';
      end if;
    end if;
  end process;

  -- process generating dclk trigger. Simple counter used as divider, but
  -- with count enable 
  process (clk, reset, ShiftLoad)
  begin  -- process
    -- asynchronous reset is also linked to restart of counting when
    -- sending/receiving next data, otherwise we could never assure
    -- at which edge the clock starts
    if reset = '0' or ShiftLoad = '1' then                 -- asynchronous reset (active low)
      Counter <= STD_LOGIC_VECTOR(TO_UNSIGNED (0, Counter'length));
    elsif clk'event and clk = '1' then  -- rising clock edge
      if ShiftFinished = '0' and ShiftCnt /= "1000"
      then
        -- clock is enabled, count to
        Counter <= Counter - '1';
      end if;
    end if;
  end process;

  -- the state is changed on falling edge because all the inputs are read on
  -- rising one
  process (clk, reset)
  begin  -- process
    if reset = '0' then                 -- asynchronous reset (active low)
      CurrentState <= Idle;
    elsif clk'event and clk = '0' then  -- rising clock edge
        CurrentState <= NextState;
    end if;
  end process;

  process (CurrentState, startWrite, ShiftFinished, dataIn, startRead)
  begin  -- process
    case CurrentState is
      when Idle =>
        if startWrite = '1' and dataIn(11) = '1' then
          -- writing of nCONFIG and nCE and nCS
          NextState <= WriteControl;
        elsif startWrite = '1' and dataIn(10) = '1' then
          -- writing of data present into the shift register
          NextState <= WriteData;
        elsif startRead = '1' then
          -- reading from memory
          NextState <= ReadData;
        else
          NextState <= Idle;
        end if;

      when ReadData =>
        NextState <= WaitDataRead;

      when WaitDataRead =>
        -- wait till the operation is complete and shift register contains the
        -- data
        if ShiftFinished = '1' then
          NextState <= FinishOperation;
        else
          NextState <= WaitDataRead;
        end if;

      when WriteData =>
        NextState <= WaitDataWritten;

      when WaitDataWritten =>
        if ShiftFinished = '1' then
          NextState <= FinishOperation;
        else
          NextState <= WaitDataWritten;
        end if;

      when WriteControl =>
        NextState <= FinishOperation;
        
      when others =>
          NextState <= Idle;
    end case;
    
  end process;
  
  process (clk, reset)
  begin  -- process
    if reset = '0' then                 -- asynchronous reset (active low)
      dataRdy <= '0';
      fpga_nconfig <= 'Z';              -- config and CE is driven by pullup resistances
      fpga_nce <= 'Z';
      ShiftLoad <= '0';
      as_ncs <= 'Z';
      DataDirection <= '1';             -- read by default -> asdi tristated
      weAreMaster <= '0';
    elsif clk'event and clk = '1' then  -- rising clock edge
        case CurrentState is
          when WriteControl =>
            -- set the status of nCONFIG and nCE depending on
            --  data pins. When the signal is inactive, we drive into
            -- high impedance instead of logic level. This is because
            -- there are pull-up/dn resistors installed on these pins

            -- dataIn(8) corresponds to fpga chip select
            if dataIn(8) = '1' then
              fpga_nce <= '1';
            else
              fpga_nce <= 'Z' ;
            end if;

            -- dataIn(9) corresponds to fpga nconfig pin
            if dataIn(9) = '0' then
              fpga_nconfig <= '0';
            else
              fpga_nconfig <= 'Z';
            end if;

            -- dataIn(12) is chip select for the active serial memory
            if dataIn(12) = '0' then
              as_ncs <= '0';
            else
              as_ncs <= 'Z';
            end if;

            -- if fpga is inactive by our interaction, it is us who takes
            -- the control over the activeserial bus, thus we indicate this
            -- by bit flag, which turns off the tri-state buffer on dclk and
            -- asdi. 
            if dataIn(8) = '1' and dataIn(9) = '0' then
              weAreMaster <= '1';
            else
              weAreMaster <= '0';
            end if;

          when ReadData =>
            ShiftLoad <= '1';
            DataDirection <= '1';

          when WriteData =>
            -- we load the stuff into the shift register. Data are on the
            -- fpga_ad bus so we have to store them into the register
            ShiftLoad <= '1';
            DataDirection <= '0';
         
          when WaitDataWritten =>
            ShiftLoad <= '0';

          when FinishOperation =>
            dataRdy <= '1';
            
          when others =>
            ShiftLoad <= '0';
            dataRdy <= '0';
            
        end case;
    end if;
  end process;
  
  
end as_programmer;
