% 6.111 Final Project - Fall 1996 - "PIC-Cam" %
% Kenneth B. Russell <kbrussel@mit.edu> %

INCLUDE "lpm_counter.inc";

% State machine which controls UART and its bus.           %
% Main controller interacts with this state machine        %
%    via the INIT_UART and SEND_BYTE handshaking signals.  %
%    The state machine responds with a "BUSY" signal       %
%    which goes high when the state machine is processing  %
%    a request.                                            %
% When the SEND_BYTE input is driven high, the data to be  %
%    sent must be visible on the 16-bit data bus (the high %
%    byte of which will be transmitted when HI_SEL is      %
%    high). The state machine will then latch the data and %
%    pull the BUSY signal high. At that point the          %
%    controller may stop driving the data bus with the     %
%    data. Transmission of the data byte is complete when  %
%    the BUSY signal goes low.                             %

SUBDESIGN uart_ss
(
	% Inputs %
	/clk			: INPUT;
	reset			: INPUT;
	init_uart		: INPUT;
	send_byte		: INPUT;
	hi_sel			: INPUT;
	db_in[8..1]		: INPUT;
	/txrdy			: INPUT;
	/rts			: INPUT;
	
	% Outputs %
	% To UART: %
	busy			: OUTPUT;
	db_out[8..1]	: OUTPUT;
	oedb			: OUTPUT;
	addr[3..1]		: OUTPUT;
	reset_uart		: OUTPUT;
	/wr				: OUTPUT;
	/cs				: OUTPUT;
	% To bus transceivers: %
	clkab			: OUTPUT;
	/oelo			: OUTPUT;
	/oehi			: OUTPUT;
)
VARIABLE
	ss				: MACHINE WITH STATES (
		ResetState,
		InitUart_0, InitUart_1, InitUart_2, InitUart_3, InitUart_4,
		SendByte_0, SendByte_1, SendByte_2, SendByte_3, SendByte_3_5, SendByte_4, SendByte_5
	);
	addr_ctr		: lpm_counter WITH (
		LPM_WIDTH = 4,
		LPM_DIRECTION = "UP"
	);
	delay_ctr		: lpm_counter WITH (
		LPM_WIDTH = 7,  % Large enough to time reset pulse (5000ns) %
		LPM_DIRECTION = "UP"
	);
	load_ff			: NODE;
	hi_sel_ff		: DFFE;
	/rts_sync		: DFF;
BEGIN
	DEFAULTS
		busy = VCC;
		/wr = VCC;
		/cs = VCC;
		/oelo = VCC;
		/oehi = VCC;
		addr_ctr.cnt_en = GND;
		delay_ctr.cnt_en = GND;
	END DEFAULTS;
	
	ss.clk = /clk;
	ss.reset = reset;

	addr_ctr.clock = /clk;
	delay_ctr.clock = /clk;
	
	hi_sel_ff.d = hi_sel;
	hi_sel_ff.clk = /clk;
	hi_sel_ff.clrn = VCC;
	hi_sel_ff.prn = VCC;
	hi_sel_ff.ena = load_ff;
	
	/rts_sync.d = /rts;
	/rts_sync.clk = /clk;
	
	TABLE
		addr_ctr.q[]	=>	addr[],		db_out[];
		
		% Write decimal divisor. %
		% Baud rate = 56000, clock = 14.75MHz; decimal divisor = 16. Percent error = 2.88. %
		% Baud rate = 38400, clock = 14.75MHz; decimal divisor = 24. Percent error = .02. % 
		% Baud rate = 56000, clock = 12.2727MHz; divisor = 14. Percent error = 2.16. %
		% Baud rate = 38400, clock = 12.2727MHz; divisor = 20. Percent error = .1. %
		% Mechanism: enable DLAB (register 3), write low byte %
		% (14) to address 0, write high byte (0) %
		% to address 1, disable DLAB (and init line control %
		% register while we're at it). %
		H"0"			=>	H"3",		H"80";
		H"1"			=>	H"0",		24;  % Decimal divisor %
		H"2"			=>	H"1",		H"00";
		H"3"			=>	H"3",		B"00010011";  % 8-N-1 %

		% It turns out we don't have to initialize any more of %
		% the UART's registers. It'll start up in 16450 mode, %
		% which is fine for us, since it'll give us a pin (/TXRDY) %
		% to look at to see whether the UART is busy. %
	END TABLE;
	
	CASE ss IS
		WHEN ResetState =>
			busy = GND;
			addr_ctr.aclr = VCC;
			delay_ctr.aclr = VCC;
			IF (init_uart) THEN
				ss = InitUart_0;
			ELSIF (send_byte) THEN
				ss = SendByte_0;
			ELSE
				ss = ResetState;
			END IF;

		%                                                     %
		% UART initialization: master reset and register init %
		%                                                     %
		WHEN InitUart_0 =>
			% Perform master reset first %
			reset_uart = VCC;
			delay_ctr.cnt_en = VCC;
			% Must be a minimum of 5000ns long. %
			% We'll take all of the clock cycles we can count %
			% anyway, though. %
			IF (delay_ctr.q[] == H"7F") THEN
				delay_ctr.sclr = VCC;
				ss = InitUart_1;
			ELSE
				ss = InitUart_0;
			END IF;
		WHEN InitUart_1 =>
			% Pause for a few clock cycles to let the UART get ready %
			delay_ctr.cnt_en = VCC;
			IF (delay_ctr.q[] == H"08") THEN
				delay_ctr.sclr = VCC;
				ss = InitUart_2;
			ELSE
				ss = InitUart_1;
			END IF;			
		WHEN InitUart_2 =>
			% Now initialize registers %		
			oedb = VCC;
			/cs = GND;
			% Stay in this state for two clock cycles (to make sure %
			% we don't violate the setup time for the chip select) %
			delay_ctr.cnt_en = VCC;
			IF (delay_ctr.q[] == H"01") THEN
				delay_ctr.sclr = VCC;
				ss = InitUart_3;
			ELSE
				ss = InitUart_2;
			END IF;
		WHEN InitUart_3 =>
			oedb = VCC;
			/cs = GND;
			/wr = GND;
			% Stay in this state for three clock cycles (to make %
			% the write pulse at least 100ns wide) %
			% Note that we're keeping a good margin of error now %
			delay_ctr.cnt_en = VCC;
			IF (delay_ctr.q[] == H"02") THEN
				delay_ctr.sclr = VCC;
				ss = InitUart_4;
			ELSE
				ss = InitUart_3;
			END IF;
		WHEN InitUart_4 =>
			% Maintain data on the bus %
			oedb = VCC;
			% Increment address counter %
			addr_ctr.cnt_en = VCC;
			% Hold chip select for a second %
			/cs = GND;
			IF (addr_ctr.q[] == H"3") THEN
				ss = ResetState;
			ELSE
				delay_ctr.sclr = VCC;
				ss = InitUart_1;
			END IF;
		
		%                               %
		% Subroutine for sending a byte %
		%                               %
		WHEN SendByte_0 =>
			% Don't let BUSY go high before we're ready! %
			busy = GND;
			% Latch the data on the bus into the transceivers %
			clkab = VCC;
			% Increment the address counter so we get an address %
			% of zero going out to the UART (setting us up for writing %
			% to register zero) %
			addr_ctr.cnt_en = VCC;
			% Latch the HI_SEL signal into the D flip flop HI_SEL_FF %
			load_ff = VCC;
			ss = SendByte_1;
		WHEN SendByte_1 =>
			delay_ctr.sclr = VCC;
			% Flow control. Don't write the data to the UART %
			% until the host says we can. %
			IF (/rts_sync.q == VCC) THEN
				ss = SendByte_1;
			ELSE
				% Need to pause to let the address stabilize %
				ss = SendByte_2;
			END IF;
		WHEN SendByte_2 =>
			/cs = GND;
			% Pause for two clock cycles so we don't violate /CS setup time %
			delay_ctr.cnt_en = VCC;
			IF (delay_ctr.q[] == H"01") THEN
				IF (hi_sel_ff) THEN
					/oehi = GND;
				ELSE
					/oelo = GND;
				END IF;
				delay_ctr.sclr = VCC;
				ss = SendByte_3;
			ELSE
				ss = SendByte_2;
			END IF;
		WHEN SendByte_3 =>
			% Drive the stored value onto the UART's data bus %
			IF (hi_sel_ff) THEN
				/oehi = GND;
			ELSE
				/oelo = GND;
			END IF;
			% Maintain /CS, assert /WR %
			/cs = GND;
			/wr = GND;
			% Pause for three clock cycles to make sure we don't violate %
			% the min write pulse width %
			delay_ctr.cnt_en = VCC;
			IF (delay_ctr.q[] == H"02") THEN
				delay_ctr.sclr = VCC;
				ss = SendByte_3_5;
			ELSE
				ss = SendByte_3;
			END IF;
		WHEN SendByte_3_5 =>
			% Hold /CS for one more clock cycle after /WR goes high %
			/cs = GND;
			ss = SendByte_4;
		WHEN SendByte_4 =>
			% Wait for /TXRDY to go inactive. %
			% This will happen a maximum of 195ns after /WR goes inactive. %
			% Therefore we wait 5 clock cycles in this state. %
			delay_ctr.cnt_en = VCC;
			IF (delay_ctr.q[] == H"04") THEN
				delay_ctr.sclr = VCC;
				ss = SendByte_5;
			ELSE
				ss = SendByte_4;
			END IF;
		WHEN SendByte_5 =>
			% When /TXRDY goes back to logic zero, the byte is done being sent %
			% and we can jump back to the reset state. %
			IF (/txrdy) THEN
				ss = SendByte_5;
			ELSE
				ss = ResetState;
			END IF;
	END CASE;
END;