trashernet-soc/fpga/hdl/design/aps6404l_controller.vhd

257 lines
7.1 KiB
VHDL

-- -------------------------------------------------------------------------- --
-- TRASHERNET SoC - A Trashy Ethernet SoC for FPGAs --
-- -------------------------------------------------------------------------- --
-- TODO
-- -------------------------------------------------------------------------- --
-- Author : Markus Koch <markus@notsyncing.net>
-- 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;