|
---------------------------------------------------------------------------
|
|
-- (c) 2017 mark watson
|
|
-- I am happy for anyone to use this for non-commercial use.
|
|
-- If my vhdl files are used commercially or otherwise sold,
|
|
-- please contact me for explicit permission at scrameta (gmail).
|
|
-- This applies for source and binary form and derived works.
|
|
---------------------------------------------------------------------------
|
|
LIBRARY ieee;
|
|
USE ieee.std_logic_1164.all;
|
|
use ieee.numeric_std.all;
|
|
use IEEE.STD_LOGIC_MISC.all;
|
|
|
|
-- Now USB is handled by ZPU its dropping SIO commands on the floor while polling USB
|
|
-- Capture the SIO command here, so it can poll the latest command instead
|
|
-- All other processing is direct to pokey...
|
|
|
|
-- memory map
|
|
-- 0 = transmit (w)
|
|
-- 1 = tx fifo status (r)
|
|
-- 2 = fetch/receive (r) - requests next data - i.e. first read trash
|
|
-- 3 = rx fifo status (r)
|
|
-- 4 = divisor (w) - applied after transmit done
|
|
-- 5 = framing error (auto clear) (r) (command&serial)
|
|
ENTITY sio_handler IS
|
|
PORT
|
|
(
|
|
CLK : IN STD_LOGIC;
|
|
ADDR : IN STD_LOGIC_VECTOR(4 DOWNTO 0);
|
|
CPU_DATA_IN : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
|
|
EN : IN STD_LOGIC;
|
|
WR_EN : IN STD_LOGIC;
|
|
|
|
RESET_N : IN STD_LOGIC;
|
|
|
|
-- clock for pokey
|
|
POKEY_ENABLE : in std_logic;
|
|
|
|
-- ATARI interface (in future we can also turbo load by directly hitting memory...)
|
|
SIO_DATA_IN : out std_logic;
|
|
SIO_COMMAND : in std_logic;
|
|
SIO_DATA_OUT : in std_logic;
|
|
SIO_CLK_OUT : in std_logic;
|
|
|
|
-- CPU interface
|
|
DATA_OUT : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)
|
|
);
|
|
END sio_handler;
|
|
|
|
ARCHITECTURE vhdl OF sio_handler IS
|
|
|
|
signal sio_data_out_reg : std_logic;
|
|
signal sio_data_out_next : std_logic;
|
|
|
|
signal sio_command_next : std_logic;
|
|
signal sio_command_reg : std_logic;
|
|
signal sio_command_count_next : std_logic_vector(6 downto 0);
|
|
signal sio_command_count_reg : std_logic_vector(6 downto 0);
|
|
signal sio_command_framing_error_reg : std_logic;
|
|
signal sio_command_framing_error_next : std_logic;
|
|
signal sio_command_rising : std_logic;
|
|
|
|
signal receive_write : std_logic;
|
|
|
|
signal addr_decoded : std_logic_vector(15 downto 0);
|
|
|
|
signal receive_enable : std_logic;
|
|
signal receive_detect : std_logic;
|
|
signal transmit_enable : std_logic;
|
|
|
|
signal fifo_tx_write : std_logic;
|
|
signal fifo_tx_full : std_logic;
|
|
signal fifo_tx_empty : std_logic;
|
|
signal fifo_tx_advance : std_logic;
|
|
signal fifo_tx_data : std_logic_vector(7 downto 0);
|
|
signal fifo_tx_count : std_logic_vector(7 downto 0);
|
|
|
|
signal fifo_rx_data : std_logic_vector(14 downto 0);
|
|
signal fifo_rx_full : std_logic;
|
|
signal fifo_rx_empty : std_logic;
|
|
signal fifo_rx_advance : std_logic;
|
|
signal fifo_rx_count : std_logic_vector(7 downto 0);
|
|
|
|
signal divisor_next : std_logic_vector(7 downto 0);
|
|
signal divisor_reg : std_logic_vector(7 downto 0);
|
|
|
|
signal pending_divisor_next : std_logic_vector(7 downto 0);
|
|
signal pending_divisor_reg : std_logic_vector(7 downto 0);
|
|
|
|
signal transmit_divisor_count_next : std_logic_vector(7 downto 0);
|
|
signal transmit_divisor_count_reg : std_logic_vector(7 downto 0);
|
|
|
|
signal receive_divisor_count_next : std_logic_vector(7 downto 0);
|
|
signal receive_divisor_count_reg : std_logic_vector(7 downto 0);
|
|
signal receive_divisor_next : std_logic_vector(7 downto 0);
|
|
signal receive_divisor_reg : std_logic_vector(7 downto 0);
|
|
|
|
signal sio_clk_out_reg : std_logic;
|
|
|
|
signal data_out_next : std_logic_vector(15 downto 0);
|
|
signal data_out_reg : std_logic_vector(15 downto 0); -- have to return results NEXT cycle!
|
|
|
|
constant P2S_STATE_WAIT : std_logic_vector(3 downto 0) := "0000";
|
|
constant P2S_STATE_STOP : std_logic_vector(3 downto 0) := "0001";
|
|
constant P2S_STATE_SHIFT_0 : std_logic_vector(3 downto 0) := "1000";
|
|
constant P2S_STATE_SHIFT_1 : std_logic_vector(3 downto 0) := "1001";
|
|
constant P2S_STATE_SHIFT_2 : std_logic_vector(3 downto 0) := "1010";
|
|
constant P2S_STATE_SHIFT_3 : std_logic_vector(3 downto 0) := "1011";
|
|
constant P2S_STATE_SHIFT_4 : std_logic_vector(3 downto 0) := "1100";
|
|
constant P2S_STATE_SHIFT_5 : std_logic_vector(3 downto 0) := "1101";
|
|
constant P2S_STATE_SHIFT_6 : std_logic_vector(3 downto 0) := "1110";
|
|
constant P2S_STATE_SHIFT_7 : std_logic_vector(3 downto 0) := "1111";
|
|
signal p2s_state_next : std_logic_vector(3 downto 0);
|
|
signal p2s_state_reg : std_logic_vector(3 downto 0);
|
|
signal p2s_shift_next : std_logic_vector(7 downto 0);
|
|
signal p2s_shift_reg : std_logic_vector(7 downto 0);
|
|
signal p2s_transmit_next : std_logic;
|
|
signal p2s_transmit_reg : std_logic;
|
|
signal p2s_idle : std_logic;
|
|
|
|
constant S2P_STATE_WAIT : std_logic_vector(3 downto 0) := "0000";
|
|
constant S2P_STATE_STOP : std_logic_vector(3 downto 0) := "0001";
|
|
constant S2P_STATE_SHIFT_0 : std_logic_vector(3 downto 0) := "1000";
|
|
constant S2P_STATE_SHIFT_1 : std_logic_vector(3 downto 0) := "1001";
|
|
constant S2P_STATE_SHIFT_2 : std_logic_vector(3 downto 0) := "1010";
|
|
constant S2P_STATE_SHIFT_3 : std_logic_vector(3 downto 0) := "1011";
|
|
constant S2P_STATE_SHIFT_4 : std_logic_vector(3 downto 0) := "1100";
|
|
constant S2P_STATE_SHIFT_5 : std_logic_vector(3 downto 0) := "1101";
|
|
constant S2P_STATE_SHIFT_6 : std_logic_vector(3 downto 0) := "1110";
|
|
constant S2P_STATE_SHIFT_7 : std_logic_vector(3 downto 0) := "1111";
|
|
signal s2p_state_next : std_logic_vector(3 downto 0);
|
|
signal s2p_state_reg : std_logic_vector(3 downto 0);
|
|
signal s2p_shift_next : std_logic_vector(6 downto 0);
|
|
signal s2p_shift_reg : std_logic_vector(6 downto 0);
|
|
signal s2p_write : std_logic;
|
|
signal s2p_framing_error_reg : std_logic;
|
|
signal s2p_framing_error_next : std_logic;
|
|
signal s2p_start : std_logic;
|
|
|
|
signal framing_error_clear : std_logic;
|
|
|
|
begin
|
|
-- register
|
|
process(clk,reset_n)
|
|
begin
|
|
if (reset_n = '0') then
|
|
sio_data_out_reg <= '1';
|
|
sio_command_reg <= '1';
|
|
sio_command_count_reg <= (others=>'0');
|
|
sio_command_framing_error_reg <= '0';
|
|
divisor_reg <= (others=>'0');
|
|
pending_divisor_reg <= (others=>'0');
|
|
transmit_divisor_count_reg <= (others=>'0');
|
|
receive_divisor_count_reg <= (others=>'0');
|
|
receive_divisor_reg <= (others=>'0');
|
|
s2p_state_reg <= S2P_STATE_WAIT;
|
|
s2p_shift_reg <= (others=>'1');
|
|
s2p_framing_error_reg <= '0';
|
|
p2s_state_reg <= P2S_STATE_WAIT;
|
|
p2s_shift_reg <= (others=>'1');
|
|
p2s_transmit_reg <= '1';
|
|
data_out_reg <= (others=>'0');
|
|
sio_clk_out_reg <='0';
|
|
elsif (clk'event and clk='1') then
|
|
sio_data_out_reg <= sio_data_out_next;
|
|
sio_command_reg <= sio_command_next;
|
|
sio_command_count_reg <= sio_command_count_next;
|
|
sio_command_framing_error_reg <= sio_command_framing_error_next;
|
|
divisor_reg <= divisor_next;
|
|
pending_divisor_reg <= pending_divisor_next;
|
|
transmit_divisor_count_reg <= transmit_divisor_count_next;
|
|
receive_divisor_count_reg <= receive_divisor_count_next;
|
|
receive_divisor_reg <= receive_divisor_next;
|
|
s2p_state_reg <= s2p_state_next;
|
|
s2p_shift_reg <= s2p_shift_next;
|
|
s2p_framing_error_reg <= s2p_framing_error_next;
|
|
p2s_state_reg <= p2s_state_next;
|
|
p2s_shift_reg <= p2s_shift_next;
|
|
p2s_transmit_reg <= p2s_transmit_next;
|
|
data_out_reg <= data_out_next;
|
|
sio_clk_out_reg <= sio_clk_out;
|
|
end if;
|
|
end process;
|
|
|
|
-- decode address
|
|
decode_addr1 : entity work.complete_address_decoder
|
|
generic map(width=>4)
|
|
port map (addr_in=>addr(3 downto 0), addr_decoded=>addr_decoded);
|
|
|
|
-- Writes to registers
|
|
process(cpu_data_in,wr_en,addr_decoded, addr, pending_divisor_reg)
|
|
begin
|
|
fifo_tx_write <= '0';
|
|
pending_divisor_next <= pending_divisor_reg;
|
|
|
|
if (wr_en = '1') then
|
|
if (addr_decoded(0) = '1') then
|
|
fifo_tx_write <= '1';
|
|
end if;
|
|
if (addr_decoded(4) = '1') then
|
|
pending_divisor_next <= cpu_data_in(7 downto 0);
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
-- Only change divisor once we are done transmitting
|
|
process(divisor_reg,pending_divisor_reg,p2s_idle)
|
|
begin
|
|
divisor_next<=divisor_reg;
|
|
|
|
if (not(pending_divisor_reg = divisor_reg) and p2s_idle='1') then
|
|
divisor_next <= pending_divisor_reg;
|
|
end if;
|
|
end process;
|
|
|
|
-- Count command packets rather than storing command position in fifo
|
|
process(s2p_start,sio_command_reg,sio_command,sio_command_count_reg,sio_command_framing_error_reg,framing_error_clear)
|
|
begin
|
|
sio_command_next <= sio_command;
|
|
sio_command_count_next <= sio_command_count_reg;
|
|
sio_command_framing_error_next <= sio_command_framing_error_reg;
|
|
sio_command_rising <= '0';
|
|
|
|
if (sio_command_reg='1') then -- not a command
|
|
sio_command_count_next <= (others=>'0');
|
|
end if;
|
|
|
|
if (s2p_start='1' and sio_command_reg='0') then
|
|
sio_command_count_next <= std_logic_vector(unsigned(sio_command_count_reg)+1);
|
|
end if;
|
|
|
|
if (sio_command_reg='0' and sio_command='1') then -- rising edge
|
|
sio_command_rising <= '1';
|
|
if (sio_command_count_reg /= "0000101") then
|
|
sio_command_framing_error_next <= '1';
|
|
end if;
|
|
end if;
|
|
|
|
if (framing_error_clear='1') then
|
|
sio_command_framing_error_next <= '0';
|
|
end if;
|
|
end process;
|
|
|
|
-- Read from registers
|
|
process(en,addr_decoded, fifo_rx_data, fifo_tx_full, fifo_tx_empty, fifo_tx_count, fifo_rx_full, fifo_rx_empty, fifo_rx_count, s2p_framing_error_reg, sio_command_framing_error_reg, receive_divisor_reg)
|
|
begin
|
|
data_out_next <= X"0000";
|
|
fifo_rx_advance <= '0';
|
|
framing_error_clear <= '0';
|
|
|
|
if (en = '1') then
|
|
if (addr_decoded(1) = '1') then
|
|
data_out_next(9 downto 0) <= fifo_tx_full&fifo_tx_empty&fifo_tx_count;
|
|
end if;
|
|
if (addr_decoded(2) = '1') then
|
|
if fifo_rx_empty = '0' then
|
|
data_out_next(14 downto 0) <= fifo_rx_data; -- assumed to be already valid
|
|
fifo_rx_advance <= '1'; -- data read, next byte please
|
|
end if;
|
|
end if;
|
|
if (addr_decoded(3) = '1') then
|
|
data_out_next(9 downto 0) <= fifo_rx_full&fifo_rx_empty&fifo_rx_count;
|
|
end if;
|
|
if (addr_decoded(4) = '1') then
|
|
data_out_next(7 downto 0) <= receive_divisor_reg;
|
|
end if;
|
|
if (addr_decoded(5) = '1') then
|
|
data_out_next(1 downto 0) <= sio_command_framing_error_reg&s2p_framing_error_reg;
|
|
framing_error_clear <= '1';
|
|
end if;
|
|
end if;
|
|
|
|
end process;
|
|
|
|
-- serial enable generation
|
|
process(divisor_reg,pokey_enable,transmit_divisor_count_reg,sio_data_out_reg, sio_data_out_next, s2p_state_reg)
|
|
begin
|
|
transmit_divisor_count_next <= transmit_divisor_count_reg;
|
|
transmit_enable <= '0';
|
|
|
|
if (pokey_enable='1') then
|
|
transmit_divisor_count_next <= std_logic_vector(unsigned(transmit_divisor_count_reg)-to_unsigned(1,1));
|
|
if or_reduce(transmit_divisor_count_reg)='0' then
|
|
transmit_divisor_count_next <= divisor_reg;
|
|
transmit_enable <= '1';
|
|
end if;
|
|
end if;
|
|
end process;
|
|
|
|
process(sio_clk_out, sio_clk_out_reg)
|
|
begin
|
|
-- 0->1 pokey transmit
|
|
-- 1->0 receive...
|
|
|
|
receive_enable <= '0';
|
|
if (sio_clk_out_reg='1' and sio_clk_out='0') then
|
|
receive_enable <= '1';
|
|
end if;
|
|
|
|
end process;
|
|
|
|
process(pokey_enable,receive_enable,receive_detect,receive_divisor_reg,receive_divisor_count_reg)
|
|
begin
|
|
receive_divisor_next <= receive_divisor_reg;
|
|
receive_divisor_count_next <= receive_divisor_count_reg;
|
|
|
|
if (pokey_enable='1') then
|
|
receive_divisor_count_next <= std_logic_vector(unsigned(receive_divisor_count_reg)+1);
|
|
end if;
|
|
|
|
if (receive_enable='1') then
|
|
receive_divisor_count_next <= (others=>'0');
|
|
end if;
|
|
|
|
if (receive_enable='1' and receive_detect='1') then
|
|
receive_divisor_next <= receive_divisor_count_reg;
|
|
end if;
|
|
|
|
end process;
|
|
|
|
-- Transmit fifo (7-0= data)
|
|
--transmit_fifo : work.std_fifo
|
|
-- generic_map (
|
|
-- DATA_WIDTH => 8,
|
|
-- FIFO_DEPTH => 256
|
|
-- )
|
|
-- port_map (
|
|
-- CLK => CLK,
|
|
-- RST => RESET,
|
|
-- WriteEn => fifo_tx_write,
|
|
-- DataIn => cpu_data_in(7 downto 0),
|
|
-- ReadEn => fifo_tx_advance,
|
|
-- DataOut => fifo_tx_data,
|
|
-- Empty => fifo_tx_empty,
|
|
-- Full => fifo_tx_full
|
|
-- );
|
|
|
|
|
|
transmit_fifo : work.fifo_transmit
|
|
PORT MAP
|
|
(
|
|
clock => clk,
|
|
data => cpu_data_in(7 downto 0),
|
|
rdreq => fifo_tx_advance,
|
|
wrreq => fifo_tx_write,
|
|
empty => fifo_tx_empty,
|
|
full => fifo_tx_full,
|
|
q => fifo_tx_data,
|
|
usedw => fifo_tx_count
|
|
);
|
|
|
|
-- parallel to serial converter
|
|
process(p2s_state_reg, p2s_transmit_reg,p2s_shift_reg,fifo_tx_data,fifo_tx_empty,transmit_enable)
|
|
begin
|
|
p2s_state_next <= p2s_state_reg;
|
|
p2s_transmit_next <= p2s_transmit_reg;
|
|
p2s_shift_next <= p2s_shift_reg;
|
|
fifo_tx_advance <= '0';
|
|
p2s_idle <= '0';
|
|
|
|
if transmit_enable='1' then
|
|
p2s_shift_next <= '1'&p2s_shift_reg(7 downto 1);
|
|
case p2s_state_reg is
|
|
when P2S_STATE_WAIT =>
|
|
p2s_idle <= fifo_tx_empty;
|
|
if fifo_tx_empty='0' then
|
|
p2s_state_next <= P2S_STATE_SHIFT_0;
|
|
fifo_tx_advance <= '1';
|
|
p2s_shift_next <= fifo_tx_data; -- already valid (depends on fifo type: todo)
|
|
p2s_transmit_next <= '0'; --start
|
|
end if;
|
|
when P2S_STATE_SHIFT_0 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_1;
|
|
when P2S_STATE_SHIFT_1 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_2;
|
|
when P2S_STATE_SHIFT_2 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_3;
|
|
when P2S_STATE_SHIFT_3 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_4;
|
|
when P2S_STATE_SHIFT_4 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_5;
|
|
when P2S_STATE_SHIFT_5 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_6;
|
|
when P2S_STATE_SHIFT_6 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_SHIFT_7;
|
|
when P2S_STATE_SHIFT_7 =>
|
|
p2s_transmit_next <= p2s_shift_reg(0);
|
|
p2s_state_next <= P2S_STATE_STOP;
|
|
when P2S_STATE_STOP =>
|
|
p2s_transmit_next <= '1'; --stop
|
|
p2s_state_next <= P2S_STATE_WAIT; -- send stop bit, check fifo
|
|
when others =>
|
|
p2s_transmit_next <= '1'; --stop
|
|
p2s_state_next <= P2S_STATE_WAIT; -- send stop bit, check fifo
|
|
end case;
|
|
end if;
|
|
end process;
|
|
|
|
-- Receive fifo (8=command, 7-0=data)
|
|
--receive_fifo : work.std_fifo
|
|
-- generic_map (
|
|
-- DATA_WIDTH => 9,
|
|
-- FIFO_DEPTH => 256
|
|
-- )
|
|
-- port_map (
|
|
-- CLK => CLK,
|
|
-- RST => RESET,
|
|
-- WriteEn => s2p_write,
|
|
-- DataIn => sio_command&s2p_data,
|
|
-- ReadEn => fifo_rx_advance,
|
|
-- DataOut => fifo_rx_data,
|
|
-- Empty => fifo_rx_empty,
|
|
-- Full => fifo_rx_full
|
|
-- );
|
|
|
|
|
|
sio_data_out_next <= sio_data_out;
|
|
|
|
receive_write <= s2p_write or sio_command_rising;
|
|
|
|
receive_fifo : entity work.fifo_receive
|
|
PORT MAP
|
|
(
|
|
clock => clk,
|
|
data => sio_command_count_reg&sio_data_out_reg&s2p_shift_reg(6 downto 0),
|
|
rdreq => fifo_rx_advance,
|
|
wrreq => receive_write,
|
|
empty => fifo_rx_empty,
|
|
full => fifo_rx_full,
|
|
q => fifo_rx_data,
|
|
usedw => fifo_rx_count
|
|
);
|
|
|
|
-- serial to parallel converter
|
|
-- 0 = start bit (space)
|
|
-- 8 data bits
|
|
-- 1 = stop bit (mask)
|
|
-- Note:sio_data_out_reg = computer out, zpu in...
|
|
process(s2p_state_reg,s2p_shift_reg,receive_enable,sio_data_out_reg,framing_error_clear, s2p_framing_error_reg)
|
|
begin
|
|
s2p_state_next <= s2p_state_reg;
|
|
s2p_shift_next <= s2p_shift_reg;
|
|
s2p_framing_error_next <= s2p_framing_error_reg;
|
|
|
|
s2p_start <= '0';
|
|
s2p_write <= '0';
|
|
receive_detect <= '0';
|
|
|
|
if (framing_error_clear='1') then
|
|
s2p_framing_error_next <= '0';
|
|
end if;
|
|
|
|
if (receive_enable='1') then
|
|
s2p_shift_next <= sio_data_out_reg&s2p_shift_reg(6 downto 1);
|
|
|
|
case s2p_state_reg is
|
|
when S2P_STATE_WAIT =>
|
|
if (sio_data_out_reg='0') then -- start bit -- TODO - sync clock??
|
|
s2p_state_next <= S2P_STATE_SHIFT_0;
|
|
s2p_start <= '1';
|
|
end if;
|
|
when S2P_STATE_SHIFT_0 =>
|
|
receive_detect <= '1';
|
|
s2p_state_next <= S2P_STATE_SHIFT_1;
|
|
when S2P_STATE_SHIFT_1 =>
|
|
receive_detect <= '1';
|
|
s2p_state_next <= S2P_STATE_SHIFT_2;
|
|
when S2P_STATE_SHIFT_2 =>
|
|
receive_detect <= '1';
|
|
s2p_state_next <= S2P_STATE_SHIFT_3;
|
|
when S2P_STATE_SHIFT_3 =>
|
|
s2p_state_next <= S2P_STATE_SHIFT_4;
|
|
when S2P_STATE_SHIFT_4 =>
|
|
s2p_state_next <= S2P_STATE_SHIFT_5;
|
|
when S2P_STATE_SHIFT_5 =>
|
|
s2p_state_next <= S2P_STATE_SHIFT_6;
|
|
when S2P_STATE_SHIFT_6 =>
|
|
s2p_state_next <= S2P_STATE_SHIFT_7;
|
|
when S2P_STATE_SHIFT_7 =>
|
|
s2p_write <= '1';
|
|
s2p_state_next <= S2P_STATE_STOP;
|
|
when S2P_STATE_STOP =>
|
|
s2p_framing_error_next <= s2p_framing_error_reg or not(sio_data_out_reg);
|
|
s2p_state_next <= S2P_STATE_WAIT;
|
|
when others =>
|
|
s2p_state_next <= S2P_STATE_WAIT;
|
|
end case;
|
|
end if;
|
|
|
|
end process;
|
|
|
|
-- output
|
|
sio_data_in <= p2s_transmit_reg;
|
|
data_out <= data_out_reg;
|
|
|
|
end vhdl;
|
|
|
|
|