aboutsummaryrefslogtreecommitdiffstats
path: root/simple_uart.vhd
blob: dc1441605f1cd846f50ddef345daca0ed426ce73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
-- Simple UART for debugging.  Fixed baud rate, no handshaking or parity
--
-- (C) 2011 Mike Stirling
--

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity simple_uart is
generic (
	f_clock		:	natural	:= 50000000;
	baud_rate	:	natural	:= 115200
	);
port (
	CLOCK		:	in	std_logic;
	nRESET		:	in	std_logic;
	
	ENABLE		:	in	std_logic;
	-- Read not write
	R_nW		:	in	std_logic;
	-- Data not status (address)
	S_nD		:	in	std_logic;
	-- Data bus in
	DI			:	in	std_logic_vector(7 downto 0);
	-- Data bus out
	DO			:	out	std_logic_vector(7 downto 0);
	
	-- Port pins
	RXD			:	in	std_logic;
	TXD			:	out	std_logic
	);
end entity;

-- FIXME: RX really should have some mechanism for syncing with
-- the middle of the bit.  Currently it is just pot luck!

architecture rtl of simple_uart is
constant baud_const	: natural := (f_clock / baud_rate) - 1;
subtype baud_div_t is natural range 0 to baud_const;
subtype bitcount_t is natural range 0 to 7;

type state_t is (Idle, Sync, Start, Data, Stop);

-- Clock enables
signal rx_clken : std_logic;
signal tx_clken : std_logic;
-- Baud rate divider for receive side
signal rx_baud_div : baud_div_t;
-- Baud rate divider for transmit side
signal tx_baud_div: baud_div_t;
-- Receive holding register
signal rx_reg : std_logic_vector(7 downto 0);
-- Receive holding register full flag
signal rx_ready : std_logic;
-- Receive shift register
signal rx_sreg : std_logic_vector(7 downto 0);
-- Receive state
signal rx_state : state_t;
-- Receive bit count
signal rx_bitcount : bitcount_t;
-- Latched rxd for falling edge detection
signal rxd_latch : std_logic;
-- Transmit input register
signal tx_reg : std_logic_vector(7 downto 0);
-- Transmit input register full flag
signal tx_busy : std_logic;
-- Transmit shift register
signal tx_sreg : std_logic_vector(7 downto 0);
-- Transmit state
signal tx_state : state_t;
-- Transmit bit count
signal tx_bitcount : bitcount_t;
-- Internal (async) representation of txd
signal txd_i : std_logic;
-- Latched enable for rising edge detection
signal enable_latch : std_logic;
begin
	rx_clken <= '1' when rx_baud_div = 0 else '0';
	tx_clken <= '1' when tx_baud_div = 0 else '0';
	
	txd_i <=
		'0' when tx_state = Start else
		tx_sreg(0) when tx_state = Data else
		'1';

	process(CLOCK,nRESET)
	begin
		if nRESET = '0' then
			TXD <= '1';
		else
			-- Register TXD to output
			TXD <= txd_i;
		end if;
	end process;
	
	process(CLOCK,nRESET)
	begin
		if nRESET = '0' then
			rx_reg <= (others => '0');
			rx_sreg <= (others => '0');
			rx_ready <= '0';
			rx_state <= Idle;
			rx_bitcount <= 0;
			
			tx_reg <= (others => '0');
			tx_sreg <= (others => '0');
			tx_busy <= '0';
			tx_state <= Idle;
			tx_bitcount <= 0;

			rx_baud_div <= baud_const;
			tx_baud_div <= baud_const;
			enable_latch <= '0';
			DO <= (others => '0');
		elsif rising_edge(CLOCK) then
			-- Latch enable signal for edge detection
			enable_latch <= ENABLE;
			-- Latch rxd for falling edge (start) detection)
			rxd_latch <= RXD;
			
			-- Baud rate generation.  Tx and Rx are done separately so that
			-- the receive counter can be adjusted when the falling edge of a
			-- start bit is detected
			if rx_baud_div = 0 then
				-- Reload
				rx_baud_div <= baud_const;
			else
				rx_baud_div <= rx_baud_div - 1;
			end if;
			if rx_state = Idle and RXD = '0' and rxd_latch = '1' then
				-- Reset rx baud divider to mid point when a start bit occurs
				rx_baud_div <= baud_const / 2;
			end if;
			if tx_baud_div = 0 then
				-- Reload
				tx_baud_div <= baud_const;
			else
				tx_baud_div <= tx_baud_div - 1;
			end if;
			
			-- Handle bus writes - edge triggered to avoid unintentionally
			-- re-transmitting the same character
			if ENABLE = '1' and enable_latch = '0' and R_nW = '0' then
				if S_nD = '0' and tx_busy = '0' then
					-- Write to data register and tx register is empty
					tx_reg <= DI;
					tx_busy <= '1';
				end if;
				-- All other writes are discarded
			end if;
			-- Handle bus reads - edge triggered so DO is held for the
			-- duration of ENABLE, but a subsequent read with !rx_ready
			-- will return 0
			if ENABLE = '1' and enable_latch = '0' and R_nW = '1' then
				if S_nD = '0' then
					-- Receive data register read
					if rx_ready = '1' then
						DO <= rx_reg;
						rx_ready <= '0';
					else
						DO <= (others => '0');
					end if;
				else
					-- Status register read
					-- bit 0 = TX busy
					-- bit 1 = RX ready
					DO <= "000000" & rx_ready & tx_busy;
				end if;
			end if;
			
			if tx_busy = '1' and (tx_state = Idle or tx_state = Stop) then
				-- Start a new transmit cycle.  TXD remains idle until
				-- the start of the next baud period, hence the additional
				-- 'Sync' state.
				tx_sreg <= tx_reg;
				tx_busy <= '0';
				tx_state <= Sync;
				tx_bitcount <= 7;
			end if;
			
			-- Transmitter
			if tx_clken = '1' and not (tx_state = Idle) then
				case tx_state is
				when Sync =>
					tx_state <= Start;
				when Start =>
					tx_state <= Data;				
				when Data =>
					tx_sreg <= '0' & tx_sreg(7 downto 1);
					if tx_bitcount = 0 then
						tx_state <= Stop;
					else
						tx_bitcount <= tx_bitcount - 1;
					end if;				
				when others =>
					-- Return to idle
					tx_state <= Idle;
				end case;
			end if;
			
			-- Receiver
			if rx_clken = '1' then
				case rx_state is
				when Idle =>
					if RXD = '0' then
						-- Start bit detected - next bit is data, so we
						-- skip the start state on the rx side
						rx_state <= Data;
						rx_bitcount <= 7;
					end if;
				when Data =>
					rx_sreg <= RXD & rx_sreg(7 downto 1);
					if rx_bitcount = 0 then
						rx_state <= Stop;
					else
						rx_bitcount <= rx_bitcount - 1;
					end if;
				when others =>
					-- Only latch output if stop bit matches and the
					-- rx register is empty
					if RXD = '1' and rx_ready = '0' then
						rx_reg <= rx_sreg;
						rx_ready <= '1';
					end if;
					-- Return to idle
					rx_state <= Idle;
				end case;
			end if;
		end if;
	end process;

end architecture;