This post starts to complete the design of a very elementary debug port that can be used to command an internal, on-board, wishbone bus. Other posts in this series include:

Fig 1: DBG Port Overview
Block Diagram of a Simpler Wishbone to UART converter

We’ve slowly been building up to the implementation of a WB-UART component, such as that in Fig 1.

A this point, we’ve now described how to get a serial port up and running, how to create words from the serial stream, and how to control a simple wishbone bus master from the command words running through this stream. We are very close to having a working debug bus–since we can add the FIFOs and interrupts in later if we have to.

This lesson is about what it takes to turn the response words from the bus master into a characters that will be sent out the output transport. The output transport, as you will recall, has a functionality similar to a serial port. Indeed, it might be a serial port, although it doesn’t need to be.

Stream timing

Before we start, though, let’s examine how to handle stream timing. Our problem is that we will be generating words to be sent out of our serial port at a rate that is much faster than the port can handle. We’re going to want to make certain that these response words are not lost–in spite of the speed differential.

Earlier, we described how to build a strobe type of signal. For our purposes here, the strobe signal was one that was valid for a single clock only. Here, we are also going to use a busy signal. The two signals will work together in the following way: the strobe will indicate a desire to send a word downstream, while the busy response from the next component will be used to indicate that the downstream component is not ready to receive. To deal with this, once we set our strobe we will continue to hold it high (request data be sent) until the downstream components busy but is cleared.

Conditions for moving forward will therefore look something like …

if ((i_stb)&&(!o_dw_busy))
	....

This condition will be true any time we accept a request to send a word.

Likewise, there is a similar condition on the downstream side of any stream component:

if ((o_dw_stb)&&(!i_tx_busy))
	....

Prefixes will change depending upon which side of the link we are on, or which components we are using, but the logic is going to be the same every time.

Now, with that out of the way, we can disassemble our words into a serial port output channel.

Breaking Words into nibbles

The trick to making any stream processing work is to only do a little bit of processing at each step. Hence for our first step, we turn our response word into bytes.

There are a couple primary variables to make this work. The first one is the word buffer, r_word. This buffer holds what is left of our response word that has not yet been sent. The next variable is the r_len word that holds how many valid nibbles remain in our r_word register.

Hence, on any new incoming word, as long as we are not busy, we send a nibble noting the top two bits (Read data, write acknowledgement, etc.), and we note that the register now has eight nibbles within it. In the case of any special words, we instead send the top five bits of the command word and nothing more. Other protocols would work as well, this is just what we are going to choose.

Here’s the big always blog for what we want to do.

always @(posedge i_clk)
	if (i_reset)
	begin
		// On reset, clear our variables
		r_word <= 0;
		r_len  <= 0;
		o_dw_stb  <= 1'b0;
		o_dw_bits <= 5'h0;
	end else if ((i_stb)&&(!o_dw_busy))
	begin			
		// If we are not busy, then load a new command
		// into our buffer to be sent
		r_word <= i_word[31:0];
		r_len  <= 4'h8;

		o_dw_stb <= 1'b1;
		o_dw_bits <= { 3'b100, i_word[33:32] };
		if (i_word[33:32]==2'b11)
		begin
			// If this is a special command,
			// grab all top 5-bits to output
			// them
			o_dw_bits <= i_word[33:29];
			r_len <= 4'h0;
		end else if (i_word[33:32] == 2'b01)
		begin // On an ACK, print only the 'K'
			// and nothing more
			r_len <= 4'h0;
		end
	end else if ((o_dw_stb)&&(!i_tx_busy))
	begin
		// Our last byte was accepted, move to the next one
		if (r_len != 4'h0)
		begin
			o_dw_stb  <= 1'b1;
			o_dw_bits <= { 1'b0, r_word[31:28] };
			r_word <= { r_word[27:0], 4'h0 };
			r_len  <= r_len - 1'b1;
		end else
			o_dw_stb  <= 1'b0;
	end

We’ll simplify this giant always block by first removing the reset logic from it. Indeed, the only things that need reset logic are the r_len and o_dw_stb registers.

always @(posedge i_clk)
if (i_reset)
begin
	r_len <= 0;
	o_dw_stb <= 0;
end else if ((i_stb)&&(!o_dw_busy))
begin
	o_dw_stb <= 1'b1;
	if (i_word[33:32]==2'b11)
		r_len <= 4'h0;
	else
		r_len <= 4'h8;
end else if (!i_tx_busy)
begin
	o_dw_stb <= (r_len != 4'h0);
	if (r_len != 4'h0)
		r_len <= r_len - 1'b1;
end

You should find this logic identical to the big always block above.

The rest of our logic, to include all the various wires associated with the word register r_word can then be drastically simplified.

always @(posedge i_clk)
// No reset logic needed
if ((i_stb)&&(!o_dw_busy))
begin
	// On any new word, set the result
	r_word <= i_word;
end else if (!i_tx_busy)
	// Whenever we aren't busy, a new nibble is accepted
	// and the word shifts.  If we never set our output
	// strobe, this will never become busy, but if the
	// register isn't in use, there's no penalty to clearing
	// it repeatedly.
	r_word <= { r_word[27:0], 4'h0 };

Even the outputs may be set quite simply.

always @(posedge i_clk)
if ((i_stb)&&(!o_dw_busy))
begin
	if (i_word[33:32] == 2'b11)
		o_dw_bits <= i_word[33:29];
	else
		o_dw_bits <= { 3'b100, i_word[33:32] };
end else if (i_tx_busy)
	o_dw_bits <= { 1'b0, r_word[31:28] };

If you look over the difference between what we started with and where we ended up, there’s a lot of differences. All of these differences translate into fewer resources in our output. They can basically be summarized by this: if you don’t care the what the value of a register is under certain conditions, then don’t make that register depend upon those conditions. Remember, the resources used by a piece of logic can be (roughly) calculated by the number of inputs that piece of logic requires.

Turning nibbles into characters

The next step on the output process is to turn this stream of nibbles into output characters. This is almost a pure lookup table, but the stream logic keeps it from being quite that.

The stream logic consists of paying attention to the busy and strobe flags to determine when we have an output to send. In particular, if we are not busy and a request is made of us, we’ll forward it down the line.

always @(posedge i_clk)
	if ((i_stb)&&(!o_gx_busy))
		// On a new request, send it forward
		o_gx_stb <= 1'b1;
	else if (!i_busy)
		o_gx_stb <= 1'b0;

Since we don’t have more than a single clock’s worth of processing to do here, we can just set the busy bit to be the same as our output strobe bit.

assign	o_gx_busy = o_gx_stb;

The real work is in the lookup table. When given a value between 5’h0 and 5’hf, we just send the hexadecimal output. Values with the 5’h10 bit set indicate we need to send a special character. You can see the special characters we support below.

always @(posedge i_clk)
	if ((i_stb)&&(!o_gx_busy))
	begin
		case(i_bits)
		5'h00: o_gx_char <= "0";
		5'h01: o_gx_char <= "1";
		5'h02: o_gx_char <= "2";
		5'h03: o_gx_char <= "3";
		5'h04: o_gx_char <= "4";
		5'h05: o_gx_char <= "5";
		5'h06: o_gx_char <= "6";
		5'h07: o_gx_char <= "7";
		5'h08: o_gx_char <= "8";
		5'h09: o_gx_char <= "9";
		5'h0a: o_gx_char <= "a";
		5'h0b: o_gx_char <= "b";
		5'h0c: o_gx_char <= "c";
		5'h0d: o_gx_char <= "d";
		5'h0e: o_gx_char <= "e";
		5'h0f: o_gx_char <= "f";
		//
		5'h10: o_gx_char <= "R";	// Read response w/data
		5'h11: o_gx_char <= "K";	// Write ACK
		5'h12: o_gx_char <= "A";	// Address was set
		5'h13: o_gx_char <= "S";	// Special
		//
		5'h18: o_gx_char <= "T";	// reseT
		5'h19: o_gx_char <= "E";	// BUS Error
		5'h1a: o_gx_char <= "I";	// Interrupt
		5'h1b: o_gx_char <= "Z";	// I'm here, but slping
		default: o_gx_char <= 8'hd;	// Carriage return
		endcase
	end

Unknown characters just get quietly turned into carriage returns, which the next module turns into carriage return and newline pairs (CR/NL).

Adding Carriage Returns and Newlines to the Stream

The final step is to add a carriage return/newline pair to our output stream any time it becomes idle. This will assist any buffered software following that might depend upon the end of a line before forwarding the data received.

To do this, we’ll create a new stream component in our processing chain. Any characters given to this stream will just send their characters back out the port. But, if the port is idle we’ll insert a carriage return into the stream. Then, any time a carriage return is sent out, we’ll set a register last_cr to indicate that we’ve sent one carriage return, and don’t need to send it again. This value will clear upon any new and valid data from the port.

The next state variable we’ll use is cr_state. This variable will be set at anytime last_cr is set, and we’ll clear it upon sending a newline.

Put together, any time we enter our newline state, we’ll set last_cr and set cr_state. After the carriage return is sent, we’ll send a newline and clear cr_state. We’ll then stay in that state until anything else is sent through the port.

always @(posedge i_clk)
	if (i_reset)
	begin
		// On reset, act as though we've already sent
		// a CR/NL pair
		cr_state <= 1'b0;
		last_cr  <= 1'b0;
		o_nl_stb <= 1'b0;
	end else if ((i_stb)&&(!o_nl_busy))
	begin
		// On any input, set the last_cr bit if it was
		// a carriage return, and clear it otherwise. 
		// Clear the cr_state bit either way.
		o_nl_stb  <= i_stb;
		o_nl_byte <= i_byte;
		cr_state <= 1'b0;
		last_cr <= (i_byte[7:0] == 8'hd);
	end else if ((o_nl_busy)&&(!i_busy))
	begin
		// If we've just sent something, let's check
		// if we need to send a cr/newline pair
		if (!last_cr)
		begin
			// We haven't sent a carriage
			// return.  So, send it if the
			// input is idle.
			cr_state  <= (!i_stb);
			o_nl_byte <= 8'hd;
			last_cr   <= (!i_stb);
			o_nl_stb  <= (!i_stb);
		end else if (cr_state)
		begin
			// We've sent the carriage
			// return, but not the newline.
			// Send the newline now.
			cr_state  <= 1'b0;
			o_nl_byte <= 8'ha;
			o_nl_stb  <= 1'b1;
		end else
			// Idle the channel
			o_nl_stb  <= 1'b0;
	end

Future Posts

There, that’s all there is to it! You now know how to take a response word from the wishbone bus master, and how to send that result back out a serial port.

While we’ve just about presented all of the components, and while you almost have a usable bus at this point, we’re not there yet. Some particular functionality remains missing. This includes:

Now that our controller is nearly complete, though, these will be much simpler topics. We’ll come back to them in future posts.