-
Notifications
You must be signed in to change notification settings - Fork 1
/
sync_rs232_uart_v1.2.v
181 lines (137 loc) · 10.2 KB
/
sync_rs232_uart_v1.2.v
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
// *****************************************************************
// *** SYNC_RS232_UART.v V1.0, November 22, 2019
// *** New Ver 1.2, April 12, 2021 - Moved parameters to be compatible with ModelSim.
// ***
// *** This transceiver follows slight baud timing errors introduced
// *** by the external host interface's clock making the TDX output
// *** clock timing synchronize to the RXD coming in allowing
// *** high speed synchronous communications. A requirement
// *** for PC RS232 full duplex synchronous communications.
// ***
// *** Written by Brian Guralnick.
// *** Using generic Verilog code which only uses synchronous logic.
// *** Well commented for educational purposes.
// *****************************************************************
module sync_rs232_uart #(
// Setup parameters
parameter CLK_IN_HZ = 50000000, // Set to system input clock frequency
parameter BAUD_RATE = 921600 // Set to desired baud rate
)(
input wire clk, // System clock
input wire rxd, // RS232 serial input data pin
output reg rx_rdy, // Pulsed high for 1 system clock when the received 8 bit data byte is ready
output reg [7:0] rx_data, // Received 8 bit data byte.
input wire ena_tx, // Signals the transmitter to latch the 8 bit data and begin transmitting
input wire [7:0] tx_data, // 8 bit data byte input to be transmitted
output reg txd, // RS232 serial output data pin
output reg tx_busy, // High when transmitter is busy. Low when you may load a byte to transmit
output reg rx_sample_pulse // For debugging RXD errors only. This is an output test pulse
// aligned to when the receiver has sampled the RXD input.
) ;
localparam RX_PERIOD = (CLK_IN_HZ / BAUD_RATE) -1 ; // Set's a reference counter size for each transmitted/received serial data bit
localparam TX_PERIOD = (CLK_IN_HZ / BAUD_RATE) -1 ;
// Receiver regs
reg [15:0] rx_period ;
reg [3:0] rx_position ;
reg [9:0] rx_byte ;
reg rxd_reg, last_rxd ;
reg rx_busy, rx_last_busy ;
// Transmitter regs
reg [15:0] tx_period = 16'h0 ;
reg [3:0] tx_position = 4'h0 ;
reg [9:0] tx_byte = 10'b1111111111 ;
reg [7:0] tx_data_reg = 8'b11111111 ;
reg tx_run = 1'b0 ;
//********************************************************************************************
// make the rx_trigger 'WIRE' equal to any new RXD input High to Low transition (IE start bit)
// when the receiver is not busy receiving a byte
//********************************************************************************************
wire rx_trigger ;
assign rx_trigger = ( ~rxd_reg && last_rxd && ~rx_busy );
always @ (posedge clk) begin
//********************************
// Receiver functions.
//********************************
// register clock the UART RDX input signal.
// This is a personal preference as I prefer FPGA inputs which don't directly feed combinational logic
rxd_reg <= rxd;
last_rxd <= rxd_reg; // create a 1 clock delay register of the rxd_reg serial bit
rx_last_busy <= rx_busy; // create a 1 clock delay of the rx_busy resister.
rx_rdy <= rx_last_busy && ~rx_busy; // create the rx_rdy out pulse for 1 single clock when the rx_busy flag has gone low signifying that rx_data is ready
if ( rx_trigger ) begin // if a 'rx_trigger' event has taken place
rx_period <= ( RX_PERIOD[15:0] >> 1 ) ; // set the period clock to half way inside a serial bit. This makes the best time to sample incoming
// serial bits as the source baud rate may be slightly slow or fast maintaining a good data capture window all the way until the stop bit
rx_busy <= 1'd1 ; // set the rx_busy flag to signify operation of the UART serial receiver
rx_position <= 4'h9 ; // set the serial bit counter to position 9
end else begin
if ( rx_period==0 ) begin // if the receiver period counter has reached it's end
rx_period <= RX_PERIOD[15:0] ; // reset the period counter
rx_sample_pulse <= rx_busy ; // *** This is only a test pulse for debugging purposes
if ( rx_position != 0 ) begin // if the receiver's bit position counter hasn't reached it's end
rx_position <= rx_position - 1'd1 ; // decrement the position counter
rx_byte[9] <= rxd_reg ; // load the receiver's serial shift regitser with the RXD input pin
rx_byte[8:0] <= rx_byte[9:1] ; // shift the input serial shift register.
end else begin // if the receiver's bit position counter reached 0
rx_data <= rx_byte[9:2]; // load the output data register with the correct 8 bit contents of the serial input register
rx_busy <= 1'b0; // turn off the serial receiver busy flag
end
end else begin // if the receiver period counter has not reached it's end
rx_period <= rx_period - 1'b1; // just decrement the receiver period counter
rx_sample_pulse <= 1'b0 ; // *** This is only a test pulse for debugging purposes
end
end // ~rx_trigger
//***********************************************************
// SYNCHRONOUS! Transmitter functions
// This was the most puzzling to get just right
// So that both high and low speed intermittent
// and continuous COM transactions would never
// cause a byte error when communicating with
// a PC as fast as possible.
//***********************************************************
if (ena_tx) begin // If a transmit request comes in
tx_data_reg <= tx_data ; // register a copy of the input data bus
tx_busy <= 1 ; // Set the busy flag
end
// ***********************************************************************************************************************
// This section prepares the data, controls and shift register during the middle of the previous transmission bit.
// ***********************************************************************************************************************
if ( tx_period == (TX_PERIOD[15:0] >> 1) ) begin // ******* at the center of a serial transmitter bit ********
if ( tx_position==1 ) begin // during the transmission of a stop bit
tx_run <= 0 ; // turn off the transmitter running flag. This point is the beginning of when
// a synchronous transmit word alignment to an incomming RXD rx_trigger is permitted
if (tx_busy) begin // before the next start bit, if the busy flag was set,
tx_byte[8:1] <= tx_data_reg[7:0] ; // load the register copy of the tx_data_reg into the serial shift register
tx_byte[9] <= 1'b1 ; // Add a stop bit into the shift register's 10th bit
tx_byte[0] <= 1'b0 ; // Add a start bit into the serial shift register's first bit
tx_busy <= 1'b0 ; // Turn off the busy flag signifying that another transmit byte may be loaded
end // into the tx_data_reg
end else begin
tx_byte[8:0] <= tx_byte[9:1] ; // at any other point than the stop-bit period, shift the serial tx_byte shift register
tx_byte[9] <= 1'b1 ; // load a default stop bit into bit 10 of the serial shift register
if ( tx_position == 0 ) tx_run <= ~txd ; // during the 'center of a serial 'START' transmitter bit'
// if the serial UART TXD output pin has a start bit, turn on the transmitter running flag
// which signifies the point where it is no longer permit-able to align a transmit word
// to an incoming RXD byte potentially corrupting a serial transmission.
end
end
// ***********************************************************************************************************************
// This section takes the above prepared registers and sends them out during the transition edge of the tx_period clock
// and during inactivity, or during the permitted alignment window, it will re-align the transmission period clock to
// a potential incoming rx_trigger event.
// ***********************************************************************************************************************
// if a RXD start bit transition edge is detected and the transmitter is not running,
// IE during the safe synchronous transmit word alignment period
// set halfway between the center of transmitting the stop bit and next start bit
if ( rx_trigger && ~tx_run ) begin
tx_period <= TX_PERIOD[15:0] - 2'h2 ; // reset "SYNCHRONIZE" the transmit period timer to the rx_trigger event, recognizing that the rx_trigger is
// delayed by 2 clocks, so we shave off 2 additional clock cycles for dead perfect parallel TXD output alignment.
tx_position <= 1'b0 ; // force set the transmit reference position to the start bit
txd <= tx_byte[0] ; // immediately set the UART TXD output to the serial out shift register's start bit. IE see above if(tx_busy)
end else if ( tx_period==0 )begin // if the transmitter period counter has reached it's end
tx_period <= TX_PERIOD[15:0] ; // reset the period counter
txd <= tx_byte[0] ; // set the UART TXD output to the serial shift register's output.
if ( tx_position == 0 ) tx_position <= 4'h9 ; // if the transmitter reference bit position counter is at the start bit, set it to bit 1.
else tx_position <= tx_position - 1'b1 ; // otherwise, count down the position counter towards the stop bit
end else tx_period <= tx_period - 1'b1 ; // if the transmit period has not reached it's end, it should count down.
end // always
endmodule