146 lines
6.5 KiB
VHDL
146 lines
6.5 KiB
VHDL
--+-----------------------------------+-------------------------------------+--
|
|
--| ___ ___ | (c) 2013-2014 William R Sowerbutts |--
|
|
--| ___ ___ ___ ___( _ ) / _ \ | will@sowerbutts.com |--
|
|
--| / __|/ _ \ / __|_ / _ \| | | | | |--
|
|
--| \__ \ (_) | (__ / / (_) | |_| | | A Z80 FPGA computer, just for fun |--
|
|
--| |___/\___/ \___/___\___/ \___/ | |--
|
|
--| | http://sowerbutts.com/ |--
|
|
--+-----------------------------------+-------------------------------------+--
|
|
--| A rudimentary SPI master peripheral |--
|
|
--+-------------------------------------------------------------------------+--
|
|
|
|
library IEEE;
|
|
use IEEE.STD_LOGIC_1164.ALL;
|
|
use IEEE.NUMERIC_STD.ALL;
|
|
|
|
entity spimaster is
|
|
port ( clk : in std_logic;
|
|
reset : in std_logic;
|
|
cpu_address : in std_logic_vector(2 downto 0);
|
|
cpu_wait : out std_logic;
|
|
data_in : in std_logic_vector(7 downto 0);
|
|
data_out : out std_logic_vector(7 downto 0);
|
|
enable : in std_logic;
|
|
req_read : in std_logic;
|
|
req_write : in std_logic;
|
|
slave_cs : out std_logic;
|
|
slave_clk : out std_logic;
|
|
slave_mosi : out std_logic;
|
|
slave_miso : in std_logic
|
|
);
|
|
end spimaster;
|
|
|
|
-- registers:
|
|
-- base+0 -- chip select control; bit 0 is slave_cs
|
|
-- base+1 -- status register; bit 0 indicates "transmitter busy"
|
|
-- base+2 -- transmitter: write a byte here, starts SPI bus transaction
|
|
-- base+3 -- receiver: last byte received (updated on each transation)
|
|
-- base+4 -- clock divider: clk counts from 0 to whatever is in this register before proceeding
|
|
--
|
|
-- Note that if an SPI transfer is underway already the CPU will be
|
|
-- forced to wait until it completes before any register can be
|
|
-- read or written. This is very convenient as it means you can
|
|
-- just read or write bytes without checking the status register.
|
|
|
|
architecture Behavioral of spimaster is
|
|
|
|
-- start up in idle state
|
|
signal slave_cs_register : std_logic := '1';
|
|
signal slave_clk_register : std_logic := '1';
|
|
signal slave_mosi_register: std_logic := '0';
|
|
signal data_out_sr : std_logic_vector(7 downto 0) := (others => '0'); -- shifted left ie MSB <- LSB
|
|
signal data_in_sr : std_logic_vector(7 downto 0) := (others => '0'); -- shifted left ie MSB <- LSB
|
|
signal busy_sr : std_logic_vector(7 downto 0) := (others => '0'); -- shifted left ie MSB <- LSB
|
|
signal clk_divide_target : unsigned(7 downto 0) := (others => '0');
|
|
signal clk_divide_value : unsigned(7 downto 0) := (others => '0');
|
|
signal cpu_was_idle : std_logic := '1';
|
|
|
|
-- cpu visible registers
|
|
signal chip_select_out : std_logic_vector(7 downto 0);
|
|
signal status_data_out : std_logic_vector(7 downto 0);
|
|
|
|
begin
|
|
|
|
chip_select_out <= "0000000" & slave_cs_register;
|
|
status_data_out <= "0000000" & busy_sr(7);
|
|
cpu_wait <= busy_sr(7);
|
|
|
|
with cpu_address select
|
|
data_out <=
|
|
chip_select_out when "000",
|
|
status_data_out when "001",
|
|
data_out_sr when "010",
|
|
data_in_sr when "011",
|
|
std_logic_vector(clk_divide_target) when "100",
|
|
status_data_out when others;
|
|
|
|
slave_cs <= slave_cs_register;
|
|
slave_clk <= slave_clk_register;
|
|
slave_mosi <= slave_mosi_register;
|
|
|
|
spimaster_proc: process(clk)
|
|
begin
|
|
if rising_edge(clk) then
|
|
if reset = '1' then
|
|
slave_cs_register <= '1';
|
|
slave_clk_register <= '1';
|
|
slave_mosi_register <= '0';
|
|
data_out_sr <= (others => '0');
|
|
data_in_sr <= (others => '0');
|
|
busy_sr <= (others => '0');
|
|
clk_divide_target <= (others => '0');
|
|
clk_divide_value <= (others => '0');
|
|
cpu_was_idle <= '1';
|
|
else
|
|
-- divide down input clk to get 2 * spi clk
|
|
clk_divide_value <= clk_divide_value + 1;
|
|
if clk_divide_value = clk_divide_target then
|
|
clk_divide_value <= to_unsigned(0, 8);
|
|
end if;
|
|
|
|
if busy_sr(7) = '1' then
|
|
if clk_divide_value = clk_divide_target then
|
|
-- we're in the midst of a transaction! whoo!
|
|
if slave_clk_register = '1' then
|
|
-- clk is high; next cycle will be falling edge of clk
|
|
slave_clk_register <= '0';
|
|
slave_mosi_register <= data_out_sr(7);
|
|
-- shift data out
|
|
data_out_sr <= data_out_sr(6 downto 0) & '0';
|
|
else
|
|
-- clk is low; next cycle will be rising edge of clk
|
|
slave_clk_register <= '1';
|
|
-- shift busy
|
|
busy_sr <= busy_sr(6 downto 0) & '0';
|
|
-- latch data in
|
|
data_in_sr <= data_in_sr(6 downto 0) & slave_miso;
|
|
end if;
|
|
end if;
|
|
end if;
|
|
|
|
|
|
if enable = '1' and req_write = '1' then
|
|
if busy_sr(7) = '0' and cpu_was_idle = '1' then
|
|
cpu_was_idle <= '0';
|
|
case cpu_address is
|
|
when "000" =>
|
|
slave_cs_register <= data_in(0);
|
|
when "010" =>
|
|
-- only allow writes when transmitter is idle
|
|
data_out_sr <= data_in;
|
|
busy_sr <= (others => '1');
|
|
when "100" =>
|
|
clk_divide_target <= unsigned(data_in);
|
|
when others => -- no change
|
|
end case;
|
|
else
|
|
cpu_was_idle <= cpu_was_idle;
|
|
end if;
|
|
else
|
|
cpu_was_idle <= '1';
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
end Behavioral;
|