Now that you have a working serial port which you can use to interact with your CPU, and now again that you know how to respond to simple bus requests, let’s examine whether these two be put together to create a simple means of debugging your FPGA.

In this post, we’ll build a generic means for both reading registers internal to an FPGA using a serial port, and then using that approach to debug your design.

An generic serial port communication interface

For the purpose of this discussion, let’s assume your serial port receiver produces two outputs:

  • rx_stb – a logic value that is true any time a value has been received across the receive port. It is to be true for one clock cycle, and one clock cycle only, any time something is received.

  • rx_data – Eight bits of logic representing the most recently received data value from the received serial port. This value has meaning anytime rx_data is true, and ignored at all other times.

Likewise, we’ll assume that your serial port transmitter has two inputs, tx_stb and tx_data. Their meaning will be analogous to that of the receivers ports.

The advanced student may notice this interface protocol isn’t complete. In particular, the transmitter may be busy some time tx_stb is true, and nothing above allows us to capture that reality. To be complete, then, you will need a third wire, this one coming from your transmit interface: tx_busy. This wire is true any time the transmitter is busy. When put together with the rest of the interface, a character will be accepted into the transmit interface any time tx_stb is true and tx_busy is false. We’ll ignore this wire for now. As long as you don’t press this interface too hard, the approach below will work.

With that out of the way, we can move on to building our basic debug protocol, based upon this interface to a serial port.

Using a Clock Enable as part of a debug process

The first step is a clock enable line. I’ll call this logic_ce for now, but you may see me call it i_ce later. We’ll use this clock enable line to gate all of your logic, such as:

always @(posedge i_clk)
	if (logic_ce)
	begin
		//
		// Your debuggable FPGA logic goes here.
		//

		// You could even stuff a full blown CPU in here, if you
		// wanted to debug it this way.

		// Anything that doesn't fit in this always block, will
		// still need to have it's logic gated by the ce line above.
	end

The goal here is to keep any logic from changing unless the logic_ce line is true.

Why are we doing this? We’re doing this to slow down your logic to the point where it can be inspected.

What … how shall this ce line be controlled?
You can use the serial port to control this clock enable. For this example, we’ll set the serial port up so that any time you send an 8’h00 across the serial port channel, you also step all the logic by one clock.

always @(posedge i_clk)
	logic_ce <= (rx_stb)&&(rx_data == 8'h00);

Sure, this will really slow down your logic, but … you’ll still be running your logic on the FPGA. Where this gets useful is in the next step, reading data back ouf of the FPGA.

Reading data back from within your FPGA

What really makes this idea flow work, is that now, because of the ce line above, we can step any logic within the FPGA. We can then read values back out of the FPGA while everything is stopped. Using that UART to control your bus, you can read out any of your data points with code something like this:

always @(posedge i_clk)
	if (rx_stb)
	begin
		tx_stb <= 1'b1;
		case(rx_data)
		8'h00: tx_data <= 8'h00;
		8'h01: tx_data <= one_of_my_registers;
		8'h02: tx_data <= another_of_my_registers;
		8'h03: tx_data <= a_third_set_of_internal_variables;
		...
		8'hff: tx_data <= the_last_result_i_might_return;
		default: tx_data <= 8'h00;
		endcase
	end else
		tx_stb <= 1'b0;

Using this simple approach, you can now read the results of any logic within your design. You could also do something similar in the other direction to set any values within your design if you so wished, but I’ll leave that last change up to you.

This really needs a software controller

While reading from addresses ‘0’-‘9’ (i.e. 8’d48 to 8’d57) may be pretty simple, and may be done within a simple terminal program, chances are that if you wish to make this work you’ll want to connect a computer program to that terminal program. That computer program will need to send the full set of characters from 8’d0 to 8’d255.

You can see how I drive my serial port in the netuart program I use to bounce the serial port to a TCP/IP port, but there’s no reason why you couldn’t use this as an example of how to interact with a serial port. The commands would be similar, only the network stuff would need to come out.

Multi-Stepping

There’s no reason why you cannot step several steps at a time using this approach. You could also set-up multi-cycle instructions that will start whenever you write a NULL to the port, and whose data may be read after a programmable number of clocks.

reg	counter;
always @(posedge i_clk)
	if ((rx_stb)&&(rx_data == 8'h00))
		counter <= 0;
		logic_ce <= 1'b1;
	else if (counter < NCLOCKS-1)
		counter <= counter + 1'b1;
	else
		logic_ce <= 1'b0;

Indeed, the possibilities of using this approach for debugging are nearly endless.

Output Formats

If you’d like, you can take the outputs you get from this debugging interface and build a VCD file for viewing in GTKwave. If your not sure how to build a VCD file, then just dump the results into a pseudo-human readable text file and you can then wait for a later discussion on how to create VCD files.

Me? Back when I last used this approach (yes, it was some time ago), I just created a file that I could view using octave. Octave allowed me to not only read in a set of binary data, but it aso allowed me to create GTKwave types of graphs of the signals within my design. Then again, that was a personal choice and you might wish to view the results in another fashion..

Am I still using this approach? Not really. I’ve built something better since. Stick around, and you’ll hear about it. For now, …

Go ahead, take this opportunity to surprise me with your ingenuity.