fpga4fun where FPGAs are fun

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