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: WB-UART Overview
Block Diagram of a Simpler Wishbone to UART converter

We’ve now just about got a fully functional wishbone master controlled from a serial stream that can now be used as a debugging bus. We’ve worked out 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, and then how to send the response words back down the serial port. The result is a very simple debugging capability.

While simple, nothing keeps this capability from being very successful within someone’s design. This lesson is about adding interrupt notification to the output stream of our debugging port.

Interrupt Requirements

For our purposes, we’ll define an interrupt as the notification of an event having taken place. Example interrupts might be that the wishbone scope has tripped, or that the flash controller has finished erasing or programming flash. Either way, some sort of notification has taken place, and you’d like to be notified of it.

We’ll require this of our interrupt processing:

  • When an interrupt takes place, we’d like to be informed.

  • We don’t want to be informed twice for the same interrupt.

  • We don’t want to need to control the bus interface, but rather the FPGA. Hence, any internal interrupt state within the bus interface should be self clearing.

  • The interrupt notification should only get inserted into the stream when the stream is idle.

Hence, whenever the interrupt line is raised, we’ll consider that an interrupt condition has taken place. We’ll send an interrupt character (“I”) across the channel. No more interrupt notifications will be sent across the channel until the “I” has been committed to, and the interrupt line has gone low.

Sound simple enough?

The interrupt stream processor

This processor is almost simplicity itself.

We’ll start with the definition of the interrupt command word that we will be inserting into our stream. It is a special word, and so it begins with 2’b11. After that, it is the 2nd special word, so we have a prefix of 5’h11010. The rest of the interrupt word we set to zero. That allows us to extend our protocol later, should we wish to, with 29 bits of information or zeros to indicate no information.

`define	INT_PREFIX	5'b11010
`define	INT_WORD	{ `INT_PREFIX, {(34-5){1'b0}} }

I should mention, at this point, a quick lesson in programming practice. The first time I built this controller, I didn’t define these values at the top of the file. Then, when I needed to change them, I missed changing all of them. By having `define statements at the top, one change to this definition will changes all of its uses later.

Our processing will focus around two primary registers: int_state and pending_interrupt. The idea is basically this: when an interrupt takes place, we’ll go into our interrupt state, and set the interrupt to be pending. In other words, we are waiting to send the value out the port. Once the value has been sent, the pending register will clear, but we’ll stay in our interrupt state until the interrupt itself clears.

Here you can see that logic below. First, for the int_state.

initial	int_state = 1'b0;
always @(posedge i_clk)
	if (i_reset)
		int_state <= 1'b0;
	else if ((i_interrupt)&&(!int_state))
		int_state <= 1'b1;
	else if ((!pending_interrupt)&&(!i_interrupt))
		int_state <= 1'b0;

Note that this state register is only cleared when the interrupt line goes low and the pending line has gone low, indicating that both we have sent the interrupt word forward, and that the interrupt has cleared itself.

Then, here’s the same logic but for the pending_int. Note that this one is cleared whenever the interrupt word is actually sent down stream.

initial	pending_interrupt = 1'b0;
always @(posedge i_clk)
	if (i_reset)
		pending_interrupt <= 1'b0;
	else if ((i_interrupt)&&(!int_state))
		pending_interrupt <= 1'b1;
	else if ((o_int_stb)&&(!i_busy)
			&&(o_int_word[33:29] == `INT_PREFIX))
		pending_interrupt <= 1'b0;

The rest of our logic is mostly stream processing.

For example, we’d like to know when we have a valid word loaded into our output register, so that the interrupt never slows down our communications stream, but only inserts itself when we are idle. If a word is loaded, and waiting to be sent, then we won’t insert an interrupt into the stream.

We’ll consider a response word to be loaded any time the incoming strobe is true, and we’ll clear the condition whenever that word gets written our the output port.

initial	loaded = 1'b0;
always @(posedge i_clk)
	if (i_reset)
		loaded <= 1'b0;
	else if (i_stb)
		loaded <= 1'b1;
	else if ((o_int_stb)&&(!i_busy))
		loaded <= 1'b0;

But when do we have something to write? Obviously we have something to write when something is given to us, as in whenever i_stb is true. But we also want to make certain we have something to write anytime we have a pending_interrupt. We’ll clear our output request upon a write, as long as we don’t have a pending interrupt request.

initial	o_int_stb = 1'b0;
always @(posedge i_clk)
	if (i_reset)
		o_int_stb <= 1'b0;
	else if (i_stb)
		o_int_stb <= 1'b1;
	else if (pending_interrupt)
		o_int_stb <= 1'b1;
	else if ((!loaded)||(!i_busy))
		o_int_stb <= 1'b0;

The consequence of this logic is that once there is an interrupt waiting to be inserted into the output stream, o_int_stb will remain high until it is sent. Likewise, any time there is a word requested, o_int_stb will also remain high. It’s not going to dip low anytime something moves in the downstream direction.

Finally, what word shall be sent? Whenever a request is made, we’ll send the incoming word. So that we wait for the request, we’ll wait until the request is made and we don’t have a valid request loaded into our outgoing register. If there is no incoming request, then we’ll automatically load our interrupt word. This will get ignored, though, unless the o_int_stb line is also high.

always @(posedge i_clk)
	if ((i_stb)&&(!loaded))
		o_int_word <= i_word;
	else if ((pending_interrupt)&&(!loaded))
		// Send an interrupt
		o_int_word <= `INT_WORD;

Future Posts

Adding this one simple capability into our response stream was very easily done. We’ll add idles into this stream next to identify our stream as being used for this purpose. I think you’ll find, when we add the idles in, that the idles are just about as easy as the interrupts are.

After we add idle’s, all of the Verilog will be in place. The next step will be to create a simple wishbone interconnect, and then the Verilator test bench necessary to try this out before deploying it on an actual FPGA.

This means we still have the following topics to come:

Indeed, if you wanted to try this controller out, it’s far enough along that you could probably start working with it today. Just be aware–it doesn’t have any FIFO support. If you try to read from this bus too fast, or write to it too fast, you may find either your commands or their responses getting lost. Perhaps we should add a FIFO to our additional topics list as well, then.

  • Adding in FIFO support

Sound interesting? Write me if you try this, and let me know how it goes.