-- -------------------------------------------------------------------------- -- -- TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs -- -- -------------------------------------------------------------------------- -- -- TODO -- -------------------------------------------------------------------------- -- -- Author : Markus Koch -- Contributors : None -- License : Mozilla Public License (MPL) Version 2 -- -------------------------------------------------------------------------- -- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity aps6404l_controller is port( clk : in std_logic; -- Max clock 132 MHz -> 66 MHz SPI rst : in std_logic; -- addr : in std_logic_vector(23 downto 0); data_in : in std_logic_vector(7 downto 0); data_in_next : out std_logic; -- Apply next data word at `data` data_out : out std_logic_vector(7 downto 0); data_out_valid : out std_logic; write_en : in std_logic; read_en : in std_logic; -- psram_ce_n : out std_logic; psram_sclk : out std_logic; -- Max clock 66 MHz because of RW mode; Else max clock 90 MHz, else we might violate t_ACLK psram_sio : inout std_logic_vector(3 downto 0) -- ); end entity aps6404l_controller; architecture rtl of aps6404l_controller is signal if_latch : std_logic; signal if_write : std_logic; signal if_output : std_logic; -- Actively drive sio type opmode_t is (COMMAND, READ, WRITE); signal opmode : opmode_t; constant SR_SIZE : integer := 16; type sr_element_vector is array (natural range <>) of std_logic_vector(psram_sio'range); subtype sr_t is sr_element_vector(0 to SR_SIZE - 1); subtype sr_cnt_t is integer range -SR_SIZE to SR_SIZE - 1; signal sr : sr_t; signal sr_preload : sr_t; signal sr_preload_cnt : sr_cnt_t; signal sr_load : std_logic; signal sr_cnt : sr_cnt_t; signal active : std_logic; signal rd_second_nibble : std_logic; constant RD_WAIT_CNT_MAX : integer := 3 - 1; signal rd_wait_cnt : integer range 0 to RD_WAIT_CNT_MAX; type state_t is (QPI_EXIT, SPI_RESET1, SPI_RESET2, SPI_ENABLE_QPI, QPI_SET_BOUNDARY, IDLE, READ, WRITE); signal state : state_t; signal requested : std_logic; begin clkgen : process(clk, rst) is begin if rst then psram_sclk <= '0'; if_latch <= '0'; if_write <= '0'; elsif rising_edge(clk) then if_latch <= '0'; if_write <= '0'; if (active) then psram_sclk <= not psram_sclk; if psram_sclk then -- rising edge if_latch <= '1'; -- is the latch edge for both else -- falling edge if_write <= '1'; -- is a write edge for both end if; else psram_sclk <= '0'; end if; end if; end process clkgen; sr_p : process(clk, rst) is begin if rst then sr_cnt <= -1; psram_ce_n <= '1'; if_output <= '0'; active <= '0'; rd_second_nibble <= '0'; data_out_valid <= '0'; data_in_next <= '0'; rd_wait_cnt <= RD_WAIT_CNT_MAX; elsif rising_edge(clk) then data_out_valid <= '0'; data_in_next <= '0'; if active then if if_write then sr <= sr(sr'low + 1 to sr'high) & x"-"; end if; if sr_cnt = 0 then if if_latch then rd_second_nibble <= not rd_second_nibble; data_out <= data_out(3 downto 0) & psram_sio; end if; case opmode is when COMMAND => if if_write then -- Transaction end only allowed on write edge active <= '0'; if_output <= '0'; psram_ce_n <= '1'; end if; when READ => if_output <= '0'; -- Configure as input if if_latch and rd_second_nibble then if rd_wait_cnt = 0 then data_out_valid <= '1'; else rd_wait_cnt <= rd_wait_cnt - 1; end if; end if; when WRITE => if if_write and not rd_second_nibble then sr(sr'low to sr'low + 1) <= (data_in(7 downto 4), data_in(3 downto 0)); data_in_next <= '1'; end if; end case; else if if_latch then sr_cnt <= sr_cnt - 1; end if; end if; else rd_wait_cnt <= RD_WAIT_CNT_MAX; rd_second_nibble <= '0'; if_output <= '0'; if sr_load then sr <= sr_preload; sr_cnt <= sr_preload_cnt; psram_ce_n <= '0'; if_output <= '1'; active <= '1'; end if; end if; end if; end process sr_p; psram_sio <= sr(sr'low) when if_output else (others => 'Z'); fsm : process(clk, rst) is function qpi_to_spi(constant DATA : in sr_element_vector) return sr_element_vector is variable vec : sr_element_vector(0 to DATA'length * 4 - 1); begin vec := (others => (others => '-')); for i in DATA'range loop for j in 0 to 3 loop vec(i * 4 + j)(0) := DATA(i)(3 - j); end loop; end loop; return vec; end function; function addr_to_nibbles(constant ADDR : in std_logic_vector) return sr_element_vector is variable vec : sr_element_vector(0 to ADDR'length / 4 - 1); begin for i in vec'range loop vec(i) := ADDR(ADDR'length - (i * 4) - 1 downto ADDR'length - (i + 1) * 4); end loop; return vec; end function addr_to_nibbles; impure function qpi_xfer(NIBBLES : sr_element_vector; constant OPM : in opmode_t := COMMAND) return boolean is begin if (requested) then if not active and not sr_load then requested <= '0'; return true; end if; else if not active then sr_preload <= (others => x"-"); sr_preload(NIBBLES'range) <= NIBBLES; sr_preload_cnt <= NIBBLES'length - 1; sr_load <= '1'; opmode <= OPM; requested <= '1'; end if; end if; return false; end function qpi_xfer; procedure qpi_stop is begin opmode <= COMMAND; end procedure qpi_stop; begin if rst then state <= QPI_EXIT; sr_load <= '0'; requested <= '0'; opmode <= COMMAND; elsif rising_edge(clk) then sr_load <= '0'; case state is when QPI_EXIT => state <= SPI_RESET1 when qpi_xfer((x"F", x"5")); when SPI_RESET1 => state <= SPI_RESET2 when qpi_xfer(qpi_to_spi((x"6", x"6"))); when SPI_RESET2 => state <= SPI_ENABLE_QPI when qpi_xfer(qpi_to_spi((x"9", x"9"))); when SPI_ENABLE_QPI => state <= QPI_SET_BOUNDARY when qpi_xfer(qpi_to_spi((x"3", x"5"))); when QPI_SET_BOUNDARY => state <= IDLE when qpi_xfer((x"C", x"0")); when IDLE => if read_en then state <= READ; elsif write_en then state <= WRITE; end if; when READ => state <= IDLE when qpi_xfer(sr_element_vector'(x"0", x"B") & addr_to_nibbles(addr), READ); if not read_en then qpi_stop; end if; when WRITE => state <= IDLE when qpi_xfer(sr_element_vector'(x"0", x"2") & addr_to_nibbles(addr), WRITE); if not write_en then qpi_stop; end if; end case; end if; end process fsm; end architecture rtl;