257 lines
7.1 KiB
VHDL
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;
|
||
|
|