--------------------------------------------------------------------------- -- (c) 2013 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; ENTITY pia IS PORT ( CLK : IN STD_LOGIC; ADDR : IN STD_LOGIC_VECTOR(1 DOWNTO 0); CPU_DATA_IN : IN STD_LOGIC_VECTOR(7 DOWNTO 0); EN : IN STD_LOGIC; WR_EN : IN STD_LOGIC; ENABLE_ORIG : IN STD_LOGIC; RESET_N : IN STD_LOGIC; CA1 : IN STD_LOGIC; CB1 : IN STD_LOGIC; CA2_DIR_OUT : OUT std_logic; CA2_OUT : OUT std_logic; CA2_IN : IN STD_LOGIC; CB2_DIR_OUT : OUT std_logic; CB2_OUT : OUT std_logic; CB2_IN : IN STD_LOGIC; -- remember these two are different if connecting to gpio (push pull vs pull up - check 6520 data sheet...) PORTA_DIR_OUT : OUT STD_LOGIC_VECTOR(7 downto 0); -- pull up - i.e. 0 driven only PORTA_OUT : OUT STD_LOGIC_VECTOR(7 downto 0); -- set bit to 1 to enable output mode PORTA_IN : IN STD_LOGIC_VECTOR(7 downto 0); PORTB_DIR_OUT : OUT STD_LOGIC_VECTOR(7 downto 0); PORTB_OUT : OUT STD_LOGIC_VECTOR(7 downto 0); -- push pull PORTB_IN : IN STD_LOGIC_VECTOR(7 downto 0); -- push pull -- CPU interface DATA_OUT : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); IRQA_N : OUT STD_LOGIC; IRQB_N : OUT STD_LOGIC ); END pia; ARCHITECTURE vhdl OF pia IS component synchronizer IS PORT ( CLK : IN STD_LOGIC; RAW : IN STD_LOGIC; SYNC : OUT STD_LOGIC ); END component; COMPONENT complete_address_decoder IS generic (width : natural := 1); PORT ( addr_in : in std_logic_vector(width-1 downto 0); addr_decoded : out std_logic_vector((2**width)-1 downto 0) ); END component; signal addr_decoded : std_logic_vector(3 downto 0); signal porta_output_next : std_logic_vector(7 downto 0); signal porta_output_reg : std_logic_vector(7 downto 0); signal porta_input_reg : std_logic_vector(7 downto 0); signal porta_input_next : std_logic_vector(7 downto 0); signal porta_direction_next : std_logic_vector(7 downto 0); signal porta_direction_reg : std_logic_vector(7 downto 0); signal porta_control_next : std_logic_vector(5 downto 0); signal porta_control_reg : std_logic_vector(5 downto 0); signal portb_output_next : std_logic_vector(7 downto 0); signal portb_output_reg : std_logic_vector(7 downto 0); signal portb_input_reg : std_logic_vector(7 downto 0); signal portb_input_next : std_logic_vector(7 downto 0); signal portb_direction_next : std_logic_vector(7 downto 0); signal portb_direction_reg : std_logic_vector(7 downto 0); signal portb_control_next : std_logic_vector(5 downto 0); signal portb_control_reg : std_logic_vector(5 downto 0); signal irqa_next : std_logic_vector(1 downto 0); signal irqa_reg : std_logic_vector(1 downto 0); signal irqb_next : std_logic_vector(1 downto 0); signal irqb_reg : std_logic_vector(1 downto 0); signal CA1_SYNC : std_logic; signal CA2_IN_SYNC : std_logic; signal CB1_SYNC : std_logic; signal CB2_IN_SYNC : std_logic; signal CA1_reg : std_logic; signal CA2_reg : std_logic; signal CB1_reg : std_logic; signal CB2_reg : std_logic; signal CA2_output_next : std_logic; signal CA2_output_reg : std_logic; signal CB2_output_next : std_logic; signal CB2_output_reg : std_logic; signal read_ora : std_logic; signal read_orb : std_logic; signal write_ora : std_logic; signal write_orb : std_logic; signal ca1_edge_next : std_logic; signal ca1_edge_reg : std_logic; signal ca2_edge_next : std_logic; signal ca2_edge_reg : std_logic; signal cb1_edge_next : std_logic; signal cb1_edge_reg : std_logic; signal cb2_edge_next : std_logic; signal cb2_edge_reg : std_logic; begin ca1_synchronizer : synchronizer port map (clk=>clk, raw=>CA1, sync=>CA1_SYNC); ca2_synchronizer : synchronizer port map (clk=>clk, raw=>CA2_IN, sync=>CA2_IN_SYNC); cb1_synchronizer : synchronizer port map (clk=>clk, raw=>CB1, sync=>CB1_SYNC); cb2_synchronizer : synchronizer port map (clk=>clk, raw=>CB2_IN, sync=>CB2_IN_SYNC); -- register process(clk,reset_n) begin if (reset_n = '0') then porta_output_reg <= (others=>'0'); porta_input_reg <= (others=>'0'); porta_direction_reg <= (others=>'0'); -- default to input porta_control_reg <= (others=>'0'); portb_output_reg <= X"FF"; -- all high to ensure we have OS Rom enabled portb_input_reg <= (others=>'0'); portb_direction_reg <= (others=>'1'); -- default to ouput portb_control_reg <= (others=>'0'); irqa_reg <= (others=>'0'); irqb_reg <= (others=>'0'); CA1_reg <= '0'; CA2_reg <= '0'; CB1_reg <= '0'; CB2_reg <= '0'; CA2_output_reg <= '0'; CB2_output_reg <= '0'; ca1_edge_reg <= '0'; ca2_edge_reg <= '0'; cb1_edge_reg <= '0'; cb2_edge_reg <= '0'; elsif (clk'event and clk='1') then porta_output_reg <= porta_output_next; porta_input_reg <= porta_input_next; porta_direction_reg <= porta_direction_next; porta_control_reg <= porta_control_next; portb_output_reg <= portb_output_next; portb_input_reg <= PORTB_input_next; portb_direction_reg <= portb_direction_next; portb_control_reg <= portb_control_next; irqa_reg <= irqa_next; irqb_reg <= irqb_next; CA1_reg <= CA1_SYNC; CA2_reg <= CA2_in_SYNC; CB1_reg <= CB1_SYNC; CB2_reg <= CB2_in_SYNC; CA2_output_reg <= CA2_output_next; CB2_output_reg <= CB2_output_next; ca1_edge_reg <= ca1_edge_next; ca2_edge_reg <= ca2_edge_next; cb1_edge_reg <= cb1_edge_next; cb2_edge_reg <= cb2_edge_next; end if; end process; -- decode address decode_addr1 : complete_address_decoder generic map(width=>2) port map (addr_in=>addr, addr_decoded=>addr_decoded); -- Writes to registers process(cpu_data_in,wr_en,addr_decoded, porta_output_reg, portb_output_reg, porta_direction_reg, portb_direction_reg, porta_control_reg, portb_control_reg) begin porta_output_next <= porta_output_reg; portb_output_next <= portb_output_reg; porta_direction_next <= porta_direction_reg; portb_direction_next <= portb_direction_reg; porta_control_next(5 downto 0) <= porta_control_reg(5 downto 0); portb_control_next(5 downto 0) <= portb_control_reg(5 downto 0); write_ora <= '0'; write_orb <= '0'; if (wr_en = '1') then if(addr_decoded(0) = '1') then if (porta_control_reg(2) = '1') then porta_output_next <= cpu_data_in; write_ora <= '1'; else porta_direction_next <= cpu_data_in; end if; end if; if(addr_decoded(1) = '1') then if (portb_control_reg(2) = '1') then portb_output_next <= cpu_data_in; write_orb <= '1'; else portb_direction_next <= cpu_data_in; end if; end if; if(addr_decoded(2) = '1') then porta_control_next(5 downto 0) <= cpu_data_in(5 downto 0); end if; if(addr_decoded(3) = '1') then portb_control_next(5 downto 0) <= cpu_data_in(5 downto 0); end if; end if; end process; -- Read from registers process(addr_decoded, en, porta_input_reg, portb_input_reg, porta_direction_reg, portb_direction_reg, porta_control_reg, portb_control_reg, irqa_reg, irqb_reg) begin data_out <= X"FF"; read_ora <= '0'; read_orb <= '0'; if (EN = '1') then -- since reads have an effect here... if (addr_decoded(0) = '1') then if (porta_control_reg(2) = '1') then data_out <= porta_input_reg; read_ora <= '1'; else data_out <= porta_direction_reg; -- can this be read? end if; end if; if (addr_decoded(1) = '1') then if (portb_control_reg(2) = '1') then data_out <= portb_input_reg; read_orb <= '1'; else data_out <= portb_direction_reg; -- can this be read? end if; end if; if (addr_decoded(2) = '1') then data_out <= irqa_reg(1)&(irqa_reg(0) and not(porta_control_reg(5)))&porta_control_reg; end if; if (addr_decoded(3) = '1') then data_out <= irqb_reg(1)&(irqb_reg(0) and not(portb_control_reg(5)))&portb_control_reg; end if; end if; end process; -- irq handing -- TODO REVIEW, this stuff is complicated! I think Atari does not need it anyway... process (irqa_next, irqa_reg, porta_control_next, porta_control_reg, read_ora, write_ora, ca2_output_reg, CA1_SYNC, CA1_reg, ca2_in_SYNC, ca2_reg, ca1_edge_reg, ca2_edge_reg, ENABLE_ORIG) begin irqa_next(1) <= irqa_reg(1) and not(read_ora); irqa_next(0) <= irqa_reg(0) and not(read_ora); ca2_output_next <= ca2_output_reg; if (CA1_SYNC = '1' and CA1_reg = '0') then irqa_next(1) <= ca1_edge_reg or irqa_next(1); end if; if (CA1_SYNC = '0' and CA1_reg = '1') then irqa_next(1) <= not(ca1_edge_reg) or irqa_next(1); end if; if (CA2_in_SYNC = '1' and CA2_reg = '0') then irqa_next(0) <= ca2_edge_reg or (irqa_next(0) and not(porta_control_reg(5))); end if; if (CA2_in_SYNC = '0' and CA2_reg = '1') then irqa_next(0) <= not(ca2_edge_reg) or (irqa_next(0) and not(porta_control_reg(5))); end if; ca1_edge_next <= porta_control_next(1); -- delay 1 cycle, so I am still set to detect falling edge on the rising edge ca2_edge_next <= porta_control_next(4); -- delay 1 cycle, so I am still set to detect falling edge on the rising edge if (porta_control_next(5) = '0') then -- CA2 is an input else -- CA2 is an output --irqa_next(0) <= '0'; case porta_control_next(4 downto 3) is when "10" => ca2_output_next <= '0'; -- direct control when "11" => ca2_output_next <= '1'; -- direct control when "01" => if (read_ora = '1') then ca2_output_next <= '0'; elsif (ENABLE_ORIG='1') then -- clock restore ca2_output_next <= '1'; end if; when "00" => if (read_ora = '1') then ca2_output_next <= '0'; elsif (irqa_reg(1) = '1') then -- ca1 restore ca2_output_next <= '1'; end if; when others => -- nop end case; end if; end process; process (irqb_next, irqb_reg, portb_control_next, portb_control_reg, read_orb, write_orb, cb2_output_reg, CB1_SYNC, CB1_reg, cb2_in_SYNC, cb2_reg, cb1_edge_reg, cb2_edge_reg,enable_orig) begin irqb_next(1) <= irqb_reg(1) and not(read_orb); irqb_next(0) <= irqb_reg(0) and not(read_orb); cb2_output_next <= cb2_output_reg; if (CB1_SYNC = '1' and CB1_reg = '0') then irqb_next(1) <= cb1_edge_reg or irqb_next(1); end if; if (CB1_SYNC = '0' and CB1_reg = '1') then irqb_next(1) <= not(cb1_edge_reg) or irqb_next(1); end if; if (CB2_in_SYNC = '1' and CB2_reg = '0') then irqb_next(0) <= cb2_edge_reg or (irqb_next(0) and not(portb_control_reg(5))); end if; if (CB2_in_SYNC = '0' and CB2_reg = '1') then irqb_next(0) <= not(cb2_edge_reg) or (irqb_next(0) and not(portb_control_reg(5))); end if; cb1_edge_next <= portb_control_next(1); -- delay 1 cycle, so I am still set to detect falling edge on the rising edge cb2_edge_next <= portb_control_next(4); -- delay 1 cycle, so I am still set to detect falling edge on the rising edge if (portb_control_next(5) = '0') then -- CB2 is an input else -- CB2 is an output --irqb_next(0) <= '0'; case portb_control_next(4 downto 3) is when "10" => cb2_output_next <= '0'; -- direct control when "11" => cb2_output_next <= '1'; -- direct control when "01" => if (write_orb = '1') then cb2_output_next <= '0'; elsif (ENABLE_ORIG='1') then -- clock restore cb2_output_next <= '1'; end if; when "00" => if (write_orb = '1') then cb2_output_next <= '0'; elsif (irqb_reg(1) = '1') then -- cb1 restore cb2_output_next <= '1'; end if; when others => --nop end case; end if; end process; -- output -- TODO - review if ca2 and cb2 are push pull or pull up... --ca2 <= CA2_output_reg when porta_control_reg(5) = '1' else 'Z'; --cb2 <= CB2_output_reg when portb_control_reg(5) = '1' else 'Z'; ca2_out <= CA2_output_reg; ca2_dir_out <= porta_control_reg(5); cb2_out <= CB2_output_reg; cb2_dir_out <= portb_control_reg(5); porta_out <= porta_output_reg; porta_dir_out <= porta_direction_reg; porta_input_next <= porta_in; --portb_out <= portb_output_reg; -- forced to 1 when in input mode - star raiders relies on this portb_out(0) <= portb_output_reg(0) when portb_direction_reg(0)='1' else '1'; portb_out(1) <= portb_output_reg(1) when portb_direction_reg(1)='1' else '1'; portb_out(2) <= portb_output_reg(2) when portb_direction_reg(2)='1' else '1'; portb_out(3) <= portb_output_reg(3) when portb_direction_reg(3)='1' else '1'; portb_out(4) <= portb_output_reg(4) when portb_direction_reg(4)='1' else '1'; portb_out(5) <= portb_output_reg(5) when portb_direction_reg(5)='1' else '1'; portb_out(6) <= portb_output_reg(6) when portb_direction_reg(6)='1' else '1'; portb_out(7) <= portb_output_reg(7) when portb_direction_reg(7)='1' else '1'; portb_dir_out <= portb_direction_reg; portb_input_next <= portb_in; irqa_n <= not((irqa_reg(1) and porta_control_reg(0)) or (irqa_reg(0) and porta_control_reg(3) and not(porta_control_reg(5)))); irqb_n <= not((irqb_reg(1) and portb_control_reg(0)) or (irqb_reg(0) and portb_control_reg(3) and not(portb_control_reg(5)))); end vhdl;