--
--   Copyright (C) 2003 by J. Kearney, Bolton, Massachusetts
--
--   This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
--   This program is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-- for more details.
--
--   You should have received a copy of the GNU General Public License along
-- with this program; if not, write to the Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
--

-----------------------------------------------------------------------------------------
-- KL8JA serial port
-----------------------------------------------------------------------------------------
-- 2003-04-04 jdk	added parity check/generate

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.ALL;

use work.IOB_Config.ALL;

entity KL8JA is
	generic
	(
	r_addr: DevID := O"03";
	t_addr: DevID := O"04";

	stop_bits: integer := 1;
	data_bits: integer := 8;
	parity: ParitySel := None;

	default_baud_sel: integer := 14;	-- 9600 bps

   instance: integer := 0
	);
	port
	( -- clocks
	clk: in std_logic;
	brg: in unsigned(18 downto 0);

	--DEBUGIO: inout std_logic_vector(0 to 11);

	-- CPU bus interface
	reset : in boolean;
	IOTact: in boolean;
	IOTdev: in DevID;
	IOTcmd: in DevCmd;

	cpu_write_n : in std_logic;
	clk_write_n : in std_logic;
	clk_lxdar_n : in std_logic;
	IOTread : in boolean;
	dx : inout std_logic_vector(0 to 11);
	cpu_c0_n : out std_logic;
	cpu_c1_n : out std_logic;
	cpu_skip_n : out std_logic;
	IRQ: out std_logic;

	-- serial port
	txd : out std_logic;
	rxd : in std_logic;
	rts : out std_logic;
	cts : in std_logic
	);
end KL8JA;

architecture RTL of KL8JA is

	constant parity_bits: integer := boolean'pos(parity /= None); -- 0 or 1

	-- PosEdge: in this implementation, most processes are
	-- synchronous to clk, so this component is used to sample
	-- slower signals, and generates a 1-clock pulse on their rising edge
	component PosEdge
	port
	  (reset: in boolean;
		clk: in std_logic;
		inp: in std_logic;          
		outp: out std_logic);
	end component;

	-- common
	signal keyb_flag,														-- "keyboard flag"
			printer_flag,														-- "printer flag"
			IE,															-- Interrupt Enable
			SE: std_logic;												-- Status Enable

	signal baud_sel: integer range 0 to brg'left;			-- baud config register

	signal clk16_raw, clk16: std_logic;					-- clocking
	signal mod16: unsigned(3 downto 0);

	-- receive part
	signal rsel, rclrflag: boolean;
	signal RBR: std_logic_vector(data_bits - 1 downto 0);	-- receive buffer register
	signal RR: std_logic_vector(data_bits + parity_bits - 1 downto 0); -- receive data register
	signal RR_parity, framing_error, overrun_error, parity_error: std_logic;
	signal rbit: integer range 0 to data_bits + stop_bits + 2;
		-- 0 = waiting
		-- 1 = start bit
		-- 2.. data_bits, parity_bit, stop_bits
	signal inpv: std_logic_vector(0 to 2);
	signal startdet: boolean;
	signal samplet: unsigned(0 to 3);
	signal RBRF_1, RBRF_2: std_logic;							-- Receive Buffer Register Full

	-- transmit part
	signal tsel, write: boolean;
	signal tsetflag, tclrflag: boolean;
	signal sendt: unsigned(0 to 3);
	signal TRE: boolean;												-- Transmit Register Empty
	signal TR: std_logic_vector(0 to data_bits);				-- Transmit Register (space for start)
	signal tbit: integer range 0 to data_bits + parity_bits + stop_bits + 1;
		-- 0 = start bit
		--	1-x = data bits
		-- x+y = parity bit (opt)
		-- x+y+z = stop bit(s)
		-- x+y+z+1 = ready
	signal THR: std_logic_vector(0 to data_bits-1);						-- Transmit Holding Register
	signal TR_parity, THRE_1, THRE_2, THRE, THRE_pe: std_logic;	-- Transmit Holding Register Empty

--	attribute clock_signal: string;
--	attribute clock_signal of THRE: signal is "yes";

begin

	-- clock select
	clk16_raw <= brg(baud_sel);
	baud: PosEdge port map (reset, clk, clk16_raw, clk16);

	-- generate /16 clock
	process (reset, clk, clk16, mod16)
	begin
		if reset then
			mod16 <= (others => '0');
		elsif rising_edge(clk) then
			if clk16 = '1' then
				if mod16 = 15 then
					mod16 <= "0000";
				else
					mod16 <= mod16 + 1;
				end if;
			end if;
		end if;
	end process;

	rsel <= IOTact and (IOTdev = r_addr);
	IRQ <= IE and (printer_flag or keyb_flag);

	tsel <= IOTact and (IOTdev = t_addr);
	write <= tsel and (cpu_write_n = '0');

	-- manage the registers set by KIE
	-- the IOT is extended here for serial format and speed setting
	-- format:
	--     X X X X X X X X X X S I	set SE and IE
	process (reset, clk_write_n)
	begin
		if reset then
			IE <= '1';
			SE <= '0';
		elsif rising_edge(clk_write_n) then
			if rsel and (IOTcmd = KIE) then
				SE <= dx(10);
				IE <= dx(11);
			end if;
		end if;
	end process;

	-- c0/1/skip feedback
	process (rsel, iotcmd, keyb_flag)
	begin
		if rsel then

			-- c0/c1
			case IOTcmd is

				when KCC =>
					cpu_c0_n <= '0';
					cpu_c1_n <= 'Z';

				when KRS =>
					cpu_c0_n <= 'Z';
					cpu_c1_n <= '0';

				when KRB =>
					cpu_c0_n <= '0';
					cpu_c1_n <= '0';

				when others =>
					cpu_c0_n <= 'Z';
					cpu_c1_n <= 'Z';

			end case;

			-- skip
			case IOTcmd is

				when KSF =>
					if keyb_flag = '1' then
						cpu_skip_n <= '0';
					else
						cpu_skip_n <= 'Z';
					end if;

				when others =>
					cpu_skip_n <= 'Z';

			end case;
		else
			cpu_c0_n <= 'Z';
			cpu_c1_n <= 'Z';
			cpu_skip_n <= 'Z';
		end if;
	end process;

	-- put data out to the CPU when requested
	process (rsel, IOTread, RBR, se, framing_error, overrun_error, parity_error)
	begin
		if rsel and IOTread then
			dx(4 to 11) <= RBR;
			if SE = '1' then
				if parity_bits /= 0 then
					dx(0 to 3) <= (framing_error or overrun_error or parity_error) &
									  parity_error & framing_error & overrun_error;
				else
					dx(0 to 3) <= (framing_error or overrun_error) &
									  '0' & framing_error & overrun_error;
				end if;
			else
				dx(0 to 3) <= (others => '0');
			end if;
		else
			dx <= (others => 'Z');
		end if;
	end process;

	-- manage "keyboard flag"
	keyb_flag <= '1' when RBRF_1 /= RBRF_2 else '0';

	-- RBR is 'cleared' after IOT that resets flag
	process (rsel, clk_write_n, IOTcmd)
	begin
		if rising_edge(clk_write_n) then
			if rsel and ((IOTcmd = KCF) or (IOTcmd = KCC) or (IOTcmd = KRB)) then
				rclrflag <= true;
			else
				rclrflag <= false;
			end if;
		end if;
	end process;

	process (reset, clk_lxdar_n, rclrflag, RBRF_1)
	begin
		if reset then
			RBRF_2 <= '0';
		elsif rising_edge(clk_lxdar_n) then
			if rclrflag then
				RBRF_2 <= RBRF_1;
			end if;
		end if;
	end process;

	-- receive data
	---- 'vote' last three samples for start bit detection
	startdet <= (inpv = "000");

	-- generate "Request To Send".  This is sort of a misnomer, as
	-- the signal actually means "you may send now"
	rts <= '0' when (rbit = 0) or (RBRF_1 = RBRF_2) else '1';

	process (reset, clk, clk16, RBRF_2)
	begin
		if reset then

			rbit <= 0;
			RBRF_1 <= '0';
			inpv <= (others => '1');
			if parity_bits /= 0 then
				parity_error <= '0';
			end if;
			framing_error <= '0';
			overrun_error <= '0';
			samplet <= (others => '0');

		elsif rising_edge(clk) then

			if clk16 = '1' then

				inpv <= rxd & inpv(0 to 1);	-- keep last 3 samples

				if rbit = 0 then

					if startdet then

						rbit <= 1;						-- start receiving
						if parity_bits /= 0 then
							RR_parity <= '0';
						end if;
						samplet <= mod16 + "0111";	-- should be the middle of the baud period

					end if;

				elsif mod16 = samplet then

					if rbit = data_bits + parity_bits + 2 then	-- end of receive?
						
						-- set overrun if previous char hasn't been read
						if (overrun_error or keyb_flag) = '1' then
							overrun_error <= '1';
						else
							overrun_error <= '0';
						end if;

						-- check parity and set error if bad
						case parity is

							when Mark =>
								if RR(data_bits) /= '1' then
									parity_error <= '1';
								else
									parity_error <= '0';
								end if;

							when Space =>
								if RR(data_bits) /= '0' then
									parity_error <= '1';
								else
									parity_error <= '0';
								end if;

							when Odd =>
								if RR_parity /= '1' then
									parity_error <= '1';
								else
									parity_error <= '0';
								end if;

							when Even =>
								if RR_parity /= '0' then
									parity_error <= '1';
								else
									parity_error <= '0';
								end if;

							when None =>
								null;

						end case;

						-- transfer received byte into RBR and set flag
						RBRF_1 <= not RBRF_2;
						RBR <= RR(data_bits - 1 downto 0);
						
						-- check framing; this should be a stop bit
						if inpv(0) /= '1' then
							framing_error <= '1';
						else
							framing_error <= '0';
						end if;
	
						rbit <= 0;							-- receiver ready for another

					else

						-- update parity and shift in serial data bit
						case parity is

							when Odd | Even =>
								RR_parity <= RR_parity xor inpv(0);

							when others =>
								null;
				
						end case;

						RR <= inpv(0) & RR(RR'left downto 1);
						rbit <= rbit + 1;
					end if;
				end if;
			end if;
		end if;
	end process;

-- DEBUG
--   process (printer_flag, tsetflag, tclrflag, tsel, THRE, THRE_1, THRE_2, TRE)
--   begin
--      if DEBUG and (instance = 0) then
--         DEBUGIO(0) <= printer_flag;
--         if tsetflag then DEBUGIO(1) <= '1'; else DEBUGIO(1) <= '0'; end if;
--         if tclrflag then DEBUGIO(2) <= '1'; else DEBUGIO(2) <= '0'; end if;
--         if tsel     then DEBUGIO(3) <= '1'; else DEBUGIO(3) <= '0'; end if;
--         DEBUGIO(4) <= THRE;
--         DEBUGIO(5) <= THRE_1;
--         DEBUGIO(6) <= THRE_2;
--         if TRE      then DEBUGIO(7) <= '1'; else DEBUGIO(7) <= '0'; end if;
--      end if;
--   end process;
--

	tsetflag <= write and (IOTcmd = TFL);
	tclrflag <= write and ((IOTcmd = TCF) or (IOTcmd = TLS));

	-- manage baud rate, transmit buffer register
	process (reset, clk_write_n, tsel, THRE_2)
	begin
		if reset then
			THRE_1 <= '0';
			baud_sel <= default_baud_sel;
		elsif rising_edge(clk_write_n) then
			if tsel then
			
				case IOTcmd is

					when TSB =>
						baud_sel <= to_integer(unsigned(dx(7 to 11)));
				
					when TPC | TLS =>
						THR <= dx(4 to 11);
						THRE_1 <= not THRE_2;

					when others =>
						null;

				end case;

			end if;
		end if;
	end process;

	-- "printer flag" management
	THRE <= '1' when (THRE_1 = THRE_2) and (cts = '0') else '0';
	THRE_det: PosEdge port map (reset, clk, THRE, THRE_pe);

	process (reset, clk, tsetflag, tclrflag)
	begin
		if reset or tclrflag then
			printer_flag <= '0';
		elsif rising_edge(clk) then
			if (THRE_pe = '1') or tsetflag then
				printer_flag <= '1';
			end if;
		end if;
	end process;

	-- c0/1/skip feedback
	process (tsel, iotcmd, printer_flag, keyb_flag)
	begin
		if tsel then
			-- skip
			case IOTcmd is

				when TSF =>
					if printer_flag = '1' then
						cpu_skip_n <= '0';
					else
						cpu_skip_n <= 'Z';
					end if;

				when TSK =>
					if (printer_flag or keyb_flag) = '1' then
						cpu_skip_n <= '0';
					else
						cpu_skip_n <= 'Z';
					end if;

				when others =>
					cpu_skip_n <= 'Z';

			end case;
		else
			cpu_skip_n <= 'Z';
		end if;
	end process;

	-- serial output
	txd <= TR(data_bits);
	TRE <= (tbit = 1 + data_bits + parity_bits + stop_bits);

	process (reset, clk, clk16, THRE_1, THRE)
	begin
		if reset then

			TR(data_bits) <= '1';
			tbit <= 1 + data_bits + parity_bits + stop_bits;
			THRE_2 <= '0';

		elsif rising_edge(clk) then

			if TRE then

				-- if buffered data, start it
				if THRE = '0' then
					tbit <= 0; 	-- thus TRE => '1'

					-- initialize parity.  In the case of Mark|Space, it will never change
					case parity is

						when Odd | Mark =>
							TR_parity <= '1';
					
						when Even | Space =>
							TR_parity <= '0';
					
						when others =>
							null;

					end case;

					TR <= THR & '0';
					sendt <= mod16 + "1111";
				end if;

			elsif (clk16 = '1') and (mod16 = sendt) then

				-- THRE sets one bit time after start
				if tbit = 0 then
					THRE_2 <= THRE_1;
				end if;


				tbit <= tbit + 1;

				-- if at the parity bit, send it out
				if (parity_bits /= 0) and (tbit = data_bits) then
					TR(data_bits) <= TR_parity;
				else
					-- shift bits out
					TR <= '1' & TR(0 to data_bits - 1);
					-- update parity
					case parity is

						when Odd | Even =>
							TR_parity <= TR_parity xor TR(data_bits - 1);
					
						when others =>
							null;

					end case;
				end if;

			end if;
		end if;
	end process;

end RTL;
