ASIC from Scratch – Part 4: UART Testbench

Veröffentlicht von

In order to test a UART RX module, you first need a known good UART TX module. And in order to test a UART TX module, you need a known good UART RX module. The way to avoid this chicken and egg problem is to simply hardcode a UART transmission and then test the receiver with that. A slightly more elegant solution would read the data to send from a file. And that’s exactly what I’m going to show in this blog post.

You might remember this neat little timing diagram from the last installment of this series, where I talked a bit about the theory behind UART. It shows the type of signal I want to generate.

Fig. 1: The UART signal to generate

It’s super easy to hardcode this, all you do is set your ouput low for a bit-duration, then 8x to your databits, then high for one to two bit durations. The hard(er, but still pretty easy) part turned out to be reading a file. Earlier in this blog series I mentioned that I use Icarus Verilog. So far it has served me well, but I ran into a pretty annyoing problem when writing my UART testbench: It’s only partially compatible with SystemVerilog.

As it turns out, Icarus Verilog only supports the old verison of the file-reading command $fgets(target, fileid). SystemVerilog allows you to pass a string as the target, however in Verilog target needs to be a register. I first attempted to read the file into a string and it took me way to long to realize what’s going on.

Anyways, in the end I implemented the read-from-file part using the old Verilog way, which isn’t quite as nice in my opinion, but at least it works. Here’s a rough idea of what it looked like.

reg [7:0] cmdchar;

// more stuff...

intial begin
    testfile = $fopen("uart_rx.txt");
    cmdchar = $fgets(cmdchar, testfile);

    // ... do something with cmdchar
end

Quite easy once you know how to do it. $fgets() simply reads chars until the target is full, in my case that happens after 1 character has been read from the file. Once the bits are in a register, sending them out at the right timing is trivial, too.

// send start bit
uart_tx_test <= 0;
#100

// send data bits
for(int i = 0; i < 8; i = i + 1) begin
	uart_tx_test <= cmdchar[i];
	#100;
end

// send stop bit
uart_tx_test <= 1;
#200

First we send the startbit, i.e. a LOW signal for some time (100 time units in this case). Obviously this must mean that the idle state of a UART transmission line is HIGH, since we detect the falling edge and consider that the start condition. Then we send out the databits one after another. It should be noted that we send the least significant bit first. Once we’re done with that, we set the line HIGH again and wait for at least a bit duration, in my example I’m waiting twice as long.

Once everything is put together, the results look like this.

Fig. 2: The generated UART signal (with incorrect timing)

It’s immediately noticeable that there are red blocks of undefined state in there. They’re not actually part of the signal, they’re only there for validating the testbench. Once I’ve checked the validity of the testbench, I’ll replace these blocks with HIGH, since that what the idle state should be. With them in place it’s much easier to see the bounds of each UART frame in GTKWave. With all your knowledge about UART, can you figure out what I sent here? I’ll give you a tip, it starts with „A“.

Now the only thing left to fix is the timing. Preferably I’d have a way to check several different baudrates. To do that I first calculate the delay_time, which represents a single bit-duration. Then later on in my code I replace hardcoded #100 wait instructions with #(1ns * delay_time) and voila, the code now waits for the correct amount of time.

Fig. 3: The UART transmission with correct timing for a baudrate of 115200.

The result is probably my longest verilog file so far. But don’t worry, it’ll probably get longer and more complex eventually. Next time we’ll start using this testbench with the UART receiver module. Additionally I really want to develop a fully automated testbench for my UART receiver, so I can test a ton of configurations (stop-bit, parity, baudrate, longer transmissions, …) with ease. Without further ado, here you go, the result:

`timescale 1ns/1ps

module uart_rx_tb;

// Change this to set baudrate
int unsigned baudrate = 115200;


reg [7 : 0] cmdchar;
int testfile, c;

reg uart_tx_test;

int unsigned nanosecondsPerSec = 1000000000;
real delay_time_f = real'(nanosecondsPerSec) / real'(baudrate);
int unsigned delay_time = $ceil(delay_time_f);

initial begin
	// Dump uart transmission to file
	$dumpfile("./test/uart1.vcd");
	$dumpvars();

	// Load test data from file
	$display("Loading file");
	testfile = $fopenr("uart_rx.txt");


	$display("nsPs %d", nanosecondsPerSec);
	$display("delay_time_f %f", delay_time_f);
	$display("delay_time %d", delay_time);

	if (testfile == 0) begin
		$display("Error when loading file.");
		$finish;
	end

	$display();


	// Default value for uart tx is X
	uart_tx_test <= 1'dx;
	#(2ns * delay_time);

	// Send each command
	while (!$feof(testfile)) begin
		c = $fgets(cmdchar, testfile);

		if(cmdchar == "\n") begin
			$display("END OF COMMAND");
		end else begin
			$display("cmd output: '%c'", cmdchar);
			
			// send start bit
			uart_tx_test <= 0;
			#(1ns * delay_time);

			// send data bits
			for(int i = 0; i < 8; i = i + 1) begin
				uart_tx_test <= cmdchar[i];
				#(1ns * delay_time);
			end

			// send stop bit
			uart_tx_test <= 1;
			#(2ns * delay_time);

			// set tx to X to make uart frame bounds easily visible in gtkwave
			uart_tx_test <= 1'dx;
			#(3ns * delay_time);

			$display("uart data sent");
		end
	end

	$fclose(testfile);
end

endmodule

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.