ASIC from Scratch – Part 6: Complete UART Module

Veröffentlicht von

In a past post I mentioned how a lot of UART goodness is actually handled at a higher level module. The reason for this was simple: I wanted to keep everything modular and easy to extend. For this UART module in particular this was quite important. I didn’t want to deal with features I might or might not need for now. Two noteworthy examples are easy configuration (baudrate, stopbits, parity, …) and RX/TX FIFO buffers. That made the UART module extraordinarily simple (for now):

`include "./hardware/uart_rx.sv"
`include "./hardware/uart_tx.sv"

module uart(
	input rst, clk,

	input [15:0] baudrate,
	input [1:0] stopbits,
 
	input rx, rxe,
	output [7:0] rx_data,
	output rx_complete,

	input [7:0] tx_data,
	input tx_rdy, txe,
	output tx, tx_complete
);

// RX and TX can be disabled individually (or both through rst signal)
wire rst_tx = rst | ~txe;
wire rst_rx = rst | ~rxe;

uart_tx transmitter(
	.clk(clk),
	.rst(rst_tx),
	.tx(tx),
	.bit_duration(baudrate),
	.stopbits(stopbits),
	.data_ready(tx_rdy),
	.tx_complete(tx_complete),
	.data(tx_data)
);

uart_rx receiver(
	.clk(clk),
	.rst(rst_rx),
	.rx(rx),
	.data(rx_data),
	.bit_duration(baudrate),
	.start_bit_duration(baudrate >> 1),
	.stopbits(stopbits),
	.data_ready(rx_complete)
);

endmodule

As you can see it’s just an instanciation of both the sender and receiver module. Additionally there are two reset signals to selectively disable either of the two modules. You might notice that I’ve secretly added a transmitter module without writing a post about it. That’s because the transmitter is pretty much identical to the receiver in its structure, so there isn’t much to talk about.

The way I tested this was by simply creating a testbench with a wire that loops back from tx to rx. After a couple bug fixes it works as expected with the receiver parroting the transmitted data:

Fig. 1: The result of looping back the transmitter to the receiver

I actually had to fight a couple off-by-one bugs. The first one was fairly simple, if you remember in my receiver I perform some simple filtering. This filtering causes the counter to be out of phase by 4 clock ticks. That’s no problem though, because you can simply cheat your way around that by setting the counter to the correct starting value. Unfortunately in my first implementation I was off by one. Instead of setting the counter to 4, as it should be, I actually set it to 3, causing it to be out of phase by 1 cycle. Here’s the code in question:

STATE_IDLE: begin
	// check for falling edge with some basic filtering
	if((rx_shift_reg == 4'b0001) || (rx_shift_reg == 4'b0010)) begin
		cur_state <= STATE_RECEIVE_START;
	end

	ctr <= 4;
	data_ready <= 0;
	bit_ctr <= 0;
	data <= 0;
end

Much worse than that erronous initial value was a misbehaving counter. For every received bit I actually counted to bit_duration - 1 instead of bit_duration. The reason for this turned out to be both dumb and tricky to find: It was a typo. The incorrect code looked like this ctr = ctr + 1;. See the bug? As it turns out, I missed a < in my assignment, which caused this to synthesize to a latch instead of a flip flop. That in turn caused the value to change immediately (as opposed to the next rising edge), which in turn caused my reset code to trigger immediately. Fortunately I found the bug in the end and was able to fix it, but boy did it cost me a lot of time.

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.