----------------------------------------------------------------------------------
-- Company:        CERN, BE-CO-HT 
-- Engineer:       Carlos Gil Soriano
-- 
-- Create Date:    11:51:39 02/20/2012 
-- Design Name:    multiboot module
-- Module Name:    multiboot_core - Behavioral 
-- Project Name:   CONV-TTL-BLO
-- Target Devices: Spartan 6 LX45T
-- Tool versions:  ISE 13.3
-- Description:    This module is a wishbone to ICAP wrapper. It permits to
--                 to control the access to certain Spartan 6 registersi
--
-- Revision        Comments
--  0.01            File created
--  0.2             Added synchronization fields in CTRL for assuring ICAP
--                  correctness
--
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

library UNISIM;
use UNISIM.VComponents.all;

entity multiboot_core is
   port(
          clk             : in   STD_LOGIC;
          rst             : in   STD_LOGIC;
          CTRL0_i         : in   STD_LOGIC_VECTOR (15 downto 0);
          CTRL1_o         : out  STD_LOGIC_VECTOR (15 downto 0);
          STAT_ICAP_o     : out  STD_LOGIC_VECTOR (15 downto 0);
          GENERAL1_i      : in   STD_LOGIC_VECTOR (15 downto 0);
          GENERAL2_i      : in   STD_LOGIC_VECTOR (15 downto 0);
          GENERAL3_i      : in   STD_LOGIC_VECTOR (15 downto 0);
          GENERAL4_i      : in   STD_LOGIC_VECTOR (15 downto 0);
          GENERAL1_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0);
          GENERAL2_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0);
          GENERAL3_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0);
          GENERAL4_ICAP_o : out  STD_LOGIC_VECTOR (15 downto 0) 
       );
end multiboot_core;

architecture Behavioral of multiboot_core is


   ----------------------------------------------------------------------------
   -- ICAP configuration data codes
   ----------------------------------------------------------------------------
   --  For custom data codes like the ones for reading back, take a look to:
   --    [OPCODES]        UG380 page 88   table  5-23
   --    [CFG PACKETS]    UG380 page 89   tables 5-{24, 25}
   --    [CG REGISTERS]   UG380 page 90   table  5-30
   ----------------------------------------------------------------------------
   constant DUMMY            : STD_LOGIC_VECTOR(15 downto 0) := X"FFFF";  
   constant NOOP             : STD_LOGIC_VECTOR(15 downto 0) := X"2000";
   constant NULL_value       : STD_LOGIC_VECTOR(15 downto 0) := X"1111";
   constant DESYNC           : STD_LOGIC_VECTOR(15 downto 0) := X"000D";
   constant SYNC_H           : STD_LOGIC_VECTOR(15 downto 0) := X"AA99";
   constant SYNC_L           : STD_LOGIC_VECTOR(15 downto 0) := X"5566";
   constant CMD_wr_GEN1      : STD_LOGIC_VECTOR(15 downto 0) := X"3261";
   constant CMD_wr_GEN2      : STD_LOGIC_VECTOR(15 downto 0) := X"3281";
   constant CMD_wr_GEN3      : STD_LOGIC_VECTOR(15 downto 0) := X"32A1";
   constant CMD_wr_GEN4      : STD_LOGIC_VECTOR(15 downto 0) := X"32C1";
   constant CMD_wr_CMD       : STD_LOGIC_VECTOR(15 downto 0) := X"30A1";
   constant CMD_IPROG        : STD_LOGIC_VECTOR(15 downto 0) := X"000E";
   constant CMD_rd_GEN1      : STD_LOGIC_VECTOR(15 downto 0) := X"2A61";
   constant CMD_rd_GEN2      : STD_LOGIC_VECTOR(15 downto 0) := X"2A81";
   constant CMD_rd_GEN3      : STD_LOGIC_VECTOR(15 downto 0) := X"2AA1";
   constant CMD_rd_GEN4      : STD_LOGIC_VECTOR(15 downto 0) := X"2AC1";
   constant CMD_rd_STAT      : STD_LOGIC_VECTOR(15 downto 0) := X"2901";

   constant OP_FULLMULTIBOOT : STD_LOGIC_VECTOR(3 downto 0) := X"0";
   constant OP_WR_GENERAL1   : STD_LOGIC_VECTOR(3 downto 0) := X"1";
   constant OP_WR_GENERAL2   : STD_LOGIC_VECTOR(3 downto 0) := X"2";
   constant OP_WR_GENERAL3   : STD_LOGIC_VECTOR(3 downto 0) := X"3";
   constant OP_WR_GENERAL4   : STD_LOGIC_VECTOR(3 downto 0) := X"4";
   constant OP_RD_GENERAL1   : STD_LOGIC_VECTOR(3 downto 0) := X"5";
   constant OP_RD_GENERAL2   : STD_LOGIC_VECTOR(3 downto 0) := X"6";
   constant OP_RD_GENERAL3   : STD_LOGIC_VECTOR(3 downto 0) := X"7";
   constant OP_RD_GENERAL4   : STD_LOGIC_VECTOR(3 downto 0) := X"8";
   constant OP_IPROG         : STD_LOGIC_VECTOR(3 downto 0) := X"C";
   constant OP_RD_STAT       : STD_LOGIC_VECTOR(3 downto 0) := X"D";


   type multi_ICAP_fsm is (S0_IDLE, S1_DUMMY, S2_SYNC_H, S3_SYNC_L,
                           S4_GEN1_CMD, S5_GEN1_ADDR, S6_GEN2_CMD,
                           S7_GEN2_ADDR, S8_GEN3_CMD, S9_GEN3_ADDR,
                           S10_GEN4_CMD, S11_GEN4_ADDR, S12_CMD_CMD,
                           S13_IPROG, Q0_OK, Q1_ERROR);

   type readback_ICAP_fsm is (S0_IDLE, S1_DUMMY1, S2_DUMMY2, S3_SYNC_H, S4_SYNC_L,
   S5_NOOP, S6_READ_CMD, S7_NOOP, S8_NOOP, S9_NOOP,
   S10_NOOP, S11_FETCH_REG, S12_WRITE_CMD, S13_DESYNC_CMD,
   S14_NOOP, S15_NOOP, Q0_OK, Q1_ERROR);

   signal multiboot_fsm :  multi_ICAP_fsm;
   signal readback_fsm  :  readback_ICAP_fsm;

   signal icap_busy_s   : STD_LOGIC;
   signal icap_o_s      : STD_LOGIC_VECTOR(15 downto 0);
   signal icap_ce_s     : STD_LOGIC;
   signal icap_i_s      : STD_LOGIC_VECTOR(15 downto 0);
   signal icap_write_s  : STD_LOGIC;

   signal CTRL1_s          : STD_LOGIC_VECTOR (15 downto 0);
   signal STAT_ICAP_s      : STD_LOGIC_VECTOR (15 downto 0);
   signal GENERAL1_ICAP_s  : STD_LOGIC_VECTOR (15 downto 0); 
   signal GENERAL2_ICAP_s  : STD_LOGIC_VECTOR (15 downto 0); 
   signal GENERAL3_ICAP_s  : STD_LOGIC_VECTOR (15 downto 0); 
   signal GENERAL4_ICAP_s  : STD_LOGIC_VECTOR (15 downto 0); 


begin

   CTRL1_o           <= CTRL1_s;
   STAT_ICAP_o       <= STAT_ICAP_s;
   GENERAL1_ICAP_o   <= GENERAL1_ICAP_s; 
   GENERAL2_ICAP_o   <= GENERAL2_ICAP_s; 
   GENERAL3_ICAP_o   <= GENERAL3_ICAP_s; 
   GENERAL4_ICAP_o   <= GENERAL4_ICAP_s; 


   ICAP_SPARTAN6_inst : ICAP_SPARTAN6
   generic map (
                  DEVICE_ID => X"02000093",     -- Specifies the pre-programmed 'ID code' value
                  SIM_CFG_FILE_NAME => "NONE"  
               )
   port map (
               BUSY   => icap_busy_s,     -- 1-bit output: Busy/Ready output
               O      => icap_o_s,        -- 16-bit output: Configuartion data output bus
               CE     => icap_ce_s,       -- 1-bit input: Active-Low ICAP Enable input
               CLK    => clk,             -- 1-bit input: Clock input
               I      => icap_i_s,        -- 16-bit input: Configuration data input bus
               WRITE  => icap_write_s     -- 1-bit input: Read/Write control input
            );


   reg_proc: process(clk)

      procedure selectMAP_order_and_write(value: STD_LOGIC_VECTOR(15 downto 0);
                                          fsm_next: multi_ICAP_fsm) is
        begin

              -- First we order the bits according to UG380 Table2-5 page 37, as specified
              -- in page 126

                 icap_i_s(0)  <= value(7);  
                 icap_i_s(1)  <= value(6);
                 icap_i_s(2)  <= value(5);
                 icap_i_s(3)  <= value(4);
                 icap_i_s(4)  <= value(3);  
                 icap_i_s(5)  <= value(2);
                 icap_i_s(6)  <= value(1);
                 icap_i_s(7)  <= value(0);
                 icap_i_s(8)  <= value(15);  
                 icap_i_s(9)  <= value(14);
                 icap_i_s(10) <= value(13);
                 icap_i_s(11) <= value(12);
                 icap_i_s(12) <= value(11);  
                 icap_i_s(13) <= value(10);
                 icap_i_s(14) <= value(9);
                 icap_i_s(15) <= value(8);

                 icap_write_s   <= '0';
                 icap_ce_s      <= '0';
                 multiboot_fsm  <= fsm_next;

        end procedure;


   procedure selectMAP_order_and_write_readback(value: STD_LOGIC_VECTOR(15 downto 0);
                                                fsm_next: readback_ICAP_fsm) is
   begin
         -- First we order the bits according to UG380 Table2-5 page 37, as specified
         -- in page 126

      icap_i_s(0)  <= value(7);  
      icap_i_s(1)  <= value(6);
      icap_i_s(2)  <= value(5);
      icap_i_s(3)  <= value(4);
      icap_i_s(4)  <= value(3);  
      icap_i_s(5)  <= value(2);
      icap_i_s(6)  <= value(1);
      icap_i_s(7)  <= value(0);
      icap_i_s(8)  <= value(15);  
      icap_i_s(9)  <= value(14);
      icap_i_s(10) <= value(13);
      icap_i_s(11) <= value(12);
      icap_i_s(12) <= value(11);  
      icap_i_s(13) <= value(10);
      icap_i_s(14) <= value(9);
      icap_i_s(15) <= value(8);

      icap_write_s   <= '0';
      icap_ce_s      <= '0';
      readback_fsm  <= fsm_next;

      -- TODO: we have to read and assign to correct signal
   end procedure;

   procedure selectMAP_read_readback(value: STD_LOGIC_VECTOR(15 downto 0);
                                     fsm_next: readback_ICAP_fsm) is
   begin
      case value is
         when CMD_rd_GEN1 =>
            GENERAL1_ICAP_s     <= icap_o_s;
         when CMD_rd_GEN2 =>
            GENERAL2_ICAP_s     <= icap_o_s;
         when CMD_rd_GEN3 =>
            GENERAL3_ICAP_s     <= icap_o_s;
         when CMD_rd_GEN4 =>
            GENERAL4_ICAP_s     <= icap_o_s;
         when others =>
      end case;

      icap_write_s   <= '1';     -- Read
      icap_ce_s      <= '0';
      readback_fsm  <= fsm_next;
   end procedure;

   procedure fsm_readback(CMD_rd_reg: STD_LOGIC_VECTOR(15 downto 0);
   reg_o: STD_LOGIC_VECTOR(15 downto 0)) is
   begin
      case readback_fsm is
         when S0_IDLE        =>
            selectMAP_order_and_write_readback(DUMMY, S1_DUMMY1);
         when S1_DUMMY1        =>
            selectMAP_order_and_write_readback(DUMMY, S2_DUMMY2);
         when S2_DUMMY2        =>
            selectMAP_order_and_write_readback(SYNC_H, S3_SYNC_H);
         when S3_SYNC_H        =>
            selectMAP_order_and_write_readback(SYNC_L, S4_SYNC_L);
         when S4_SYNC_L        =>
            selectMAP_order_and_write_readback(NOOP, S5_NOOP);
         when S5_NOOP          =>
            selectMAP_order_and_write_readback(CMD_rd_reg, S6_READ_CMD);
         when S6_READ_CMD      =>
            selectMAP_order_and_write_readback(NOOP, S7_NOOP);
         when S7_NOOP          =>
            selectMAP_order_and_write_readback(NOOP, S8_NOOP);
         when S8_NOOP          =>
            selectMAP_order_and_write_readback(NOOP, S9_NOOP);
         when S9_NOOP          =>
            selectMAP_order_and_write_readback(NOOP, S10_NOOP);
         when S10_NOOP          =>
            selectMAP_read_readback(CMD_rd_reg, S11_FETCH_REG);
         when S11_FETCH_REG    =>
            selectMAP_order_and_write_readback(CMD_wr_CMD, S12_WRITE_CMD);
         when S12_WRITE_CMD    =>
            selectMAP_order_and_write_readback(DESYNC, S13_DESYNC_CMD);
         when S13_DESYNC_CMD   =>
            selectMAP_order_and_write_readback(NOOP, S14_NOOP);
         when S14_NOOP         =>
            selectMAP_order_and_write_readback(NOOP, S15_NOOP);
         when S15_NOOP         =>
            icap_write_s     <= '1';
            icap_ce_s        <= '1';
            CTRL1_s(0)       <= '1';
            CTRL1_s(2)       <= '0';
            readback_fsm     <= Q0_OK;
         when Q0_OK            =>
            CTRL1_s(0)      <= '0';
            CTRL1_s(2)      <= '0';
            readback_fsm    <= S0_IDLE;
         when others           =>
            CTRL1_s(0)      <= '0';
            CTRL1_s(2)      <= '1';
            icap_write_s    <= '0';
            readback_fsm    <= Q0_OK;
      end case;
   end procedure;
   
   procedure check_sync_GENERALx is
   begin
      if (GENERAL1_ICAP_s  = GENERAL1_i) then
         CTRL1_s(2) <= '1';
      else
         CTRL1_s(2) <= '0';
      end if;
      if (GENERAL2_ICAP_s  = GENERAL2_i) then
         CTRL1_s(3) <= '1';
      else
         CTRL1_s(3) <= '0';
      end if;
      if (GENERAL3_ICAP_s  = GENERAL3_i) then
         CTRL1_s(4) <= '1';
      else
         CTRL1_s(4) <= '0';
      end if;
      if (GENERAL4_ICAP_s  = GENERAL4_i) then
         CTRL1_s(5) <= '1';
      else
         CTRL1_s(5) <= '0';
      end if;
   end procedure;




begin
   if rising_edge(clk) then

      if (rst = '1') then
      multiboot_fsm    <= S0_IDLE;
   icap_i_s         <= (others => '0');
         icap_write_s     <= '0';
         icap_ce_s        <= '0';
         CTRL1_s          <= (others => '0');
         STAT_ICAP_s      <= (others => '0');
      GENERAL1_ICAP_s  <= (others => '0');        
         GENERAL2_ICAP_s  <= (others => '0');
         GENERAL3_ICAP_s  <= (others => '0');
         GENERAL4_ICAP_s  <= (others => '0');
      else

         check_sync_GENERALx;

         if CTRL0_i(4) = '1' then
            case CTRL0_i(3 downto 0) is
               when OP_RD_GENERAL1   =>
                  fsm_readback(CMD_rd_GEN1, GENERAL1_ICAP_s);
               when OP_RD_GENERAL2   =>
                  fsm_readback(CMD_rd_GEN2, GENERAL2_ICAP_s);
               when OP_RD_GENERAL3   =>
                  fsm_readback(CMD_rd_GEN3, GENERAL3_ICAP_s);
               when OP_RD_GENERAL4   =>
                  fsm_readback(CMD_rd_GEN4, GENERAL4_ICAP_s);
               when OP_RD_STAT       =>
                  fsm_readback(CMD_rd_STAT, STAT_ICAP_s);


               when OP_WR_GENERAL1 =>
                  case multiboot_fsm is
                     when S0_IDLE      =>
                        selectMAP_order_and_write(DUMMY, S1_DUMMY);
                     when S1_DUMMY     =>
                        selectMAP_order_and_write(SYNC_H, S2_SYNC_H);
                     when S2_SYNC_H    =>
                        selectMAP_order_and_write(SYNC_L, S3_SYNC_L);
                     when S3_SYNC_L    =>
                        selectMAP_order_and_write(CMD_wr_GEN1, S4_GEN1_CMD);
                     when S4_GEN1_CMD  =>
                        selectMAP_order_and_write(GENERAL1_i, S5_GEN1_ADDR);
                     when S5_GEN1_ADDR =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '1';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm <= Q0_OK;
                     when Q0_OK        =>
                        CTRL1_s(0)       <= '0';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm    <= S0_IDLE;
                     when others       =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '0';
                        CTRL1_s(2)       <= '1';
                        multiboot_fsm    <= Q0_OK;
                  end case;
               when OP_WR_GENERAL2 => 
                  case multiboot_fsm is
                     when S0_IDLE      =>
                        selectMAP_order_and_write(SYNC_H, S1_DUMMY);
                     when S1_DUMMY     =>
                        selectMAP_order_and_write(SYNC_H, S2_SYNC_H);
                     when S2_SYNC_H    =>
                        selectMAP_order_and_write(SYNC_L, S3_SYNC_L);
                     when S3_SYNC_L    =>
                        selectMAP_order_and_write(CMD_wr_GEN2, S6_GEN2_CMD);
                     when S6_GEN2_CMD  =>
                        selectMAP_order_and_write(GENERAL2_i, S7_GEN2_ADDR);
                     when S7_GEN2_ADDR =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '1';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm    <= Q0_OK;
                     when Q0_OK        =>
                        CTRL1_s(0)      <= '0';
                        CTRL1_s(2)      <= '0';
                        multiboot_fsm   <= S0_IDLE;
                     when others       =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)      <= '0';
                        CTRL1_s(2)      <= '1';
                        multiboot_fsm   <= Q0_OK;
                  end case;
               when OP_WR_GENERAL3 =>
                  case multiboot_fsm is
                     when S0_IDLE      =>
                        selectMAP_order_and_write(DUMMY, S1_DUMMY);
                     when S1_DUMMY      =>
                        selectMAP_order_and_write(SYNC_H, S2_SYNC_H);
                     when S2_SYNC_H    =>
                        selectMAP_order_and_write(SYNC_L, S3_SYNC_L);
                     when S3_SYNC_L    =>
                        selectMAP_order_and_write(CMD_wr_GEN3, S8_GEN3_CMD);
                     when S8_GEN3_CMD  =>
                        selectMAP_order_and_write(GENERAL3_i, S9_GEN3_ADDR);
                     when S9_GEN3_ADDR =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '1';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm <= Q0_OK;
                     when Q0_OK        =>
                        CTRL1_s(0)      <= '0';
                        multiboot_fsm   <= S0_IDLE;
                     when others       =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)      <= '0';
                        CTRL1_s(2)      <= '1';
                        multiboot_fsm   <= Q0_OK;
                  end case;
               when OP_WR_GENERAL4 =>
                  case multiboot_fsm is
                     when S0_IDLE      =>
                        selectMAP_order_and_write(DUMMY, S1_DUMMY);
                     when S1_DUMMY     =>
                        selectMAP_order_and_write(SYNC_H, S2_SYNC_H);
                     when S2_SYNC_H    =>
                        selectMAP_order_and_write(SYNC_L, S3_SYNC_L);
                     when S3_SYNC_L    =>
                        selectMAP_order_and_write(CMD_wr_GEN4, S10_GEN4_CMD);
                     when S10_GEN4_CMD  =>
                        selectMAP_order_and_write(GENERAL4_i, S11_GEN4_ADDR);
                     when S11_GEN4_ADDR =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '1';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm <= Q0_OK;
                     when Q0_OK       =>
                        CTRL1_s(0)       <= '0';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm   <= S0_IDLE;
                     when others       =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)      <= '0';
                        CTRL1_s(2)      <= '1';
                        multiboot_fsm   <= Q0_OK;
                  end case;
               when OP_FULLMULTIBOOT =>
                  case multiboot_fsm is 
                     when S0_IDLE      =>
                        selectMAP_order_and_write(DUMMY, S1_DUMMY);
                     when S1_DUMMY     =>
                        selectMAP_order_and_write(SYNC_H, S2_SYNC_H);
                     when S2_SYNC_H    =>
                        selectMAP_order_and_write(SYNC_L, S3_SYNC_L);
                     when S3_SYNC_L    =>
                        selectMAP_order_and_write(CMD_wr_GEN1, S4_GEN1_CMD);
                     when S4_GEN1_CMD    =>
                        selectMAP_order_and_write(GENERAL1_i, S5_GEN1_ADDR);
                     when S5_GEN1_ADDR   =>
                        selectMAP_order_and_write(CMD_wr_GEN2, S6_GEN2_CMD);
                     when S6_GEN2_CMD    =>
                        selectMAP_order_and_write(GENERAL2_i, S7_GEN2_ADDR);
                     when S7_GEN2_ADDR   =>
                        selectMAP_order_and_write(CMD_wr_GEN3, S8_GEN3_CMD);
                     when S8_GEN3_CMD    =>
                        selectMAP_order_and_write(GENERAL3_i, S9_GEN3_ADDR);
                     when S9_GEN3_ADDR   =>
                        selectMAP_order_and_write(CMD_wr_GEN4, S10_GEN4_CMD);
                     when S10_GEN4_CMD    =>
                        selectMAP_order_and_write(GENERAL4_i, S11_GEN4_ADDR);
                     when S11_GEN4_ADDR  =>
                        selectMAP_order_and_write(CMD_wr_CMD, S12_CMD_CMD);
                     when S12_CMD_CMD    =>
                        selectMAP_order_and_write(CMD_IPROG, S13_IPROG);
                     when S13_IPROG      =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '1';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm <= Q0_OK;
                     when Q0_OK          =>
                        CTRL1_s(0)       <= '0';
                        CTRL1_s(2)       <= '0';
                        multiboot_fsm     <= S0_IDLE;
                     when others         =>
                        icap_write_s     <= '1';
                        icap_ce_s        <= '1';
                        CTRL1_s(0)       <= '0';
                        CTRL1_s(2)       <= '1';
                        multiboot_fsm    <= Q0_OK;
                  end case;
               when others          =>
            -- TODO: we have to implement OP_IPROG and OP_STAT
            end case;
         else
         end if; 
      end if;

   else
   end if;

end process;

end Behavioral;
