Serial interface 4 - RS-232 receiver
We are building an "async receiver" module:

Our implementation works like this:
- The module assembles data from the RxD line as it comes.
- As the bits are being received, they appears on the "data" bus, but the data is not ready yet. Only once a complete byte has been received, "data_ready" is asserted for one clock cycle.
Oversampling
An asynchronous receiver has to somehow get in-sync with the incoming signal (it doesn't have access to the clock used by the transmitter).
- To determine when a new data byte is coming, we look for the "start" bit by oversampling the signal at a multiple of the baud rate frequency.
- Once the "start" bit is detected, we sample the line at the known baud rate to acquire the data bits.
Receivers typically oversample the incoming signal at 16 times the baud rate. We use 8 times here... For 115200 baud, that gives a sampling rate of 921600Hz.
Let's assume that we have a "Baud8Tick" signal available, asserted 921600 times a second.
The design
First, the incoming "RxD" signal has no relationship with our clock.
We use two D flip-flops to sample it at 921600Hz, and synchronize it to our clock domain.
|
reg [1:0] RxD_sync; always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD}; |
We filter the data, so that short spikes on the RxD line aren't mistaken for start bits.
|
reg [1:0] RxD_cnt; reg RxD_bit; always @(posedge clk) if(Baud8Tick) begin if(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1; else if(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1; if(RxD_cnt==2'b00) RxD_bit <= 0; else if(RxD_cnt==2'b11) RxD_bit <= 1; end |
A state machine allows us to go through each bit received, once a "start" is detected.
|
reg [3:0] state; always @(posedge clk) if(Baud8Tick) case(state) 4'b0000: if(~RxD_bit) state <= 4'b1000; // start bit found? 4'b1000: if(next_bit) state <= 4'b1001; // bit 0 4'b1001: if(next_bit) state <= 4'b1010; // bit 1 4'b1010: if(next_bit) state <= 4'b1011; // bit 2 4'b1011: if(next_bit) state <= 4'b1100; // bit 3 4'b1100: if(next_bit) state <= 4'b1101; // bit 4 4'b1101: if(next_bit) state <= 4'b1110; // bit 5 4'b1110: if(next_bit) state <= 4'b1111; // bit 6 4'b1111: if(next_bit) state <= 4'b0001; // bit 7 4'b0001: if(next_bit) state <= 4'b0000; // stop bit default: state <= 4'b0000; endcase |
Notice that we used a "next_bit" signal, to go from bit to bit.
|
reg [2:0] bit_spacing; always @(posedge clk) if(state==0) bit_spacing <= 0; else if(Baud8Tick) bit_spacing <= bit_spacing + 1; wire next_bit = (bit_spacing==7); |
Finally a shift register collects the data bits as they come.
|
reg [7:0] RxD_data; always @(posedge clk) if(Baud8Tick & next_bit & state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]}; |
The complete code can be found here.
It has a few improvements; follow the comments in the code.
Links
- More details on Asynchronous Communication