If you are a beginning FPGA designer, the first example you will be given to learn is that of a counter. It’s sort of a tradition. Class room exercises all illustrate concepts with simple counters. If you ask a question, the instructor will go to the board and start his explanation with a counter. At least, that’s what I would do if I trying to teach an Verilog concept.

But just how useful is a counter in the end anyway?

Let’s try examining a counter all the way from an irrelevant classroom discussion to a vital system component.

How can this be? Well, one peripheral necessary to any multitasking operating system, whether Unix, Linux, Windows, or some other O/S, is an interval timer. An interval timer is little more than a reconfigurable counter. All it does is issue an interrupt to the CPU at a periodic interval.

Embedded systems, such as those found within FPGA’s, have an additional timing need. These systems often need to insert known delays between different operations. Instead of an interval timer, these systems need what are known as “one-shot” timers. Once programmed, they generate an interrupt after the programmed delay takes place and then they return to idle.

Fig 1. The ZipSystem

The ZipCPU wrapper known as the ZipSystem has three such timers within it, shown in Fig 1 as “Generic Timers (x3)”. I call these timers ZipTimers. Each of these ZipTimers supports generating either a regular interrupt or a one-shot delay based interrupt. These ZipTimers have been a part of the ZipSystem since I started. Their simplicity makes them perfect candidates for beginner exercises, and even better candidates for learning formal verification.

The ZipTimer has two capabilities beyond the traditional beginner’s counter exercise. These are first the ability to be programmed over a wishbone bus, and second the ability to interrupt the CPU when the specified delay runs out.

Therefore, let’s examine this timer peripheral as an exercise in learning Verilog, formal verification, and connecting a simple item to a bus using AutoFPGA. Along the way, I’ll do my best to avoid calling this a “counter example”.

The Beginner’s Exercise

Hopefully everyone reading this blog has at one time built a countdown timer in Verilog. Indeed, I use a basic countdown timer as one of the first examples in the Formal Verification course I now teach. Below is the simple example timer that we’ll start with today.

Fig 2. A Countdown Timer
initial	r_value = 0;
always @(posedge i_clk)
	if (i_start)
		r_value <= TIMEOUT;
	else if (r_value != 0)
		r_value <= r_value - 1'b1;

This counter starts at zero. Any time an i_start signal takes place, the counter is set to TIMEOUT and then counts down to zero, as illustrated in Fig 2. Note that setting this counter to TIMEOUT doesn’t guarantee that it will take TIMEOUT clock ticks until it returns to zero–it is possible the i_start signal resets this counter back to TIMEOUT before it hits zero.

We’ll also create an interrupt signal that we will set anytime the counter becomes zero.

initial	o_int <= 1'b0;
always @(posedge i_clk)
	o_int <= (r_value == 1);

That’s not all that hard, right?

Did you notice the subtlety associated with checking r_value==1 here? One of my readers pointed this out. If i_start happens to be true on the same cycle that r_value==1, then o_int might be true on a clock cycle when r_value != 0. Yes, this is a bug. It’ll come back in the next section as well. However, I’m going to leave this bug in place because this was how I originally designed the ZipTimer–with this bug within it. (Oops!) It wasn’t until years later when I attempted to formally verify the timer code presented below that I discovered this subtlety.

For now, let’s just peel this onion back a bit further.

The problem with the above implementation of a counter is that it isn’t very reusable. If you are going to generate a counter that will be programmable, then you’ll want to add a bus interface . If we use the wishbone bus, then anytime (i_wb_stb)&&(i_wb_we) is true, and the address reflects our timer’s address, then we can reload our timer from i_wb_data. This would give us a more adaptable, configurable timer. Such a bus controlled timer could easily become a CPU peripheral.

initial	r_value = 0;
always @(posedge i_clk)
	if ((i_wb_stb)&&(i_wb_we))
		r_value <= i_wb_data;
	else if (r_value != 0)
		r_value <= r_value - 1'b1;

This works fine for FPGA implementations, but what if you want this counter to run in a context where initial statements are ignored? In that case, you need an i_reset input. On a reset, that is when i_reset is high, the counter should return to idle, r_value == 0.

initial	r_value = 0;
always @(posedge i_clk)
	if (i_reset)
		r_value <= 0;
	else if ((i_wb_stb)&&(i_wb_we))
		r_value <= i_wb_data;
	else if (r_value != 0)
		r_value <= r_value - 1'b1;

In a similar fashion, with only a tiny adjustment, we can use this module to count events. We’ll use an incoming i_ce signal to denote when an event has taken place. Examples of such events include not only clock cycles (i_ce=1), but also incoming or outgoing samples in a DSP system, or lines or frames in a video system. All of these options can be created by appropriately setting an i_ce input to one any time the timer is to step. Put together, our original counter now becomes,

initial	r_value = 0;
always @(posedge i_clk)
	if (i_reset)
		r_value <= 0;
	else if ((i_wb_stb)&&(i_wb_we))
		r_value <= i_wb_data;
	else if ((i_ce)&&(r_value != 0))
		r_value <= r_value - 1'b1;

That’s quite the configurable counter, no?

Let’s now return to our bus interface and properly set the rest of the required bus control values. Since we can respond on every clock cycle, there’s no reason to ever stall the bus.

assign	o_wb_stall = 1'b0;

We’ll also need to create a response to the wishbone bus. Since this operation takes only a single cycle, we’ll acknowledge the bus any time we are selected.

assign	o_wb_ack = (i_wb_stb);

We can do this if the current counter state data is always valid on the bus’s o_wb_data lines.

assign	o_wb_data = r_value;

Voila! A simple, wishbone bus controlled count-down timer!

Interval Timer

The former code works great for a one-shot timer. However, if you want to create a timer that interrupts the CPU every 10ms (as an example), only to be reset by the CPU in an interrupt service routine, then you will find that the interval pseudorandomly walks in phase. The intervals will all be longer then 10ms. How can we fix this?

Fig 3. An Interval Timer

One solution is to use an interval timer. Simply put, an interval timer is one that counts down to zero, and then resets itself to count down again.

initial	r_value = 0;
always @(posedge i_clk)
	if (r_value != 0)
		r_value <= r_value - 1'b1;
	else
		r_value <= interval_count;

As before, we’ll generate an interrupt anytime this timer hits zero,

initial	o_int <= 1'b0;
always @(posedge i_clk)
	o_int <= (r_value == 1);

But … what if we wanted to allow this reload value to be externally set? To create this capability, we’ll attach this interval timer to wishbone bus. Perhaps we want something like,

initial	r_value = 0;
always @(posedge i_clk)
	if ((i_wb_stb)&&(i_wb_we))
		r_interval_count <= i_wb_data;

always @(posedge i_clk)
	if (r_value != 0)
		r_value <= r_value - 1'b1;
	else
		r_value <= r_interval_count;

This is almost identical to our original counter above, save that every time it resets it goes back to r_interval_count instead of the original TIMEOUT parameter. Since r_interval_count is programmable from the bus, we now have a programmable interrupt timer. How hard can this be?

In this case, the devil is in the details.

Look closer. Do you see any of the problems with this implementation? For example, what happens if you want to switch from a 4-second intervals to 10ms intervals? Just how many counts will that first 10ms interval contain? Up to 4 seconds?

If that’s not the response you want, then how should this timer respond?

While we consider this, let’s also consider merging the countdown timer together with the interval timer in a way that both respond to bus requests. Here’s the capability or requirement we’ll build to then:

  • On any reset, the counter will set itself to zero and wait to be configured

    This matches the count-down timer behavior we discussed above.

Fig 4. The ZipTimer Register
  • On any write, the counter will assume the value written to it, as shown in Fig 4 as the New Counter bits, and will then start counting down. If the number written was a zero, then the counter will stay at zero and stop.

    Again, this matches the count-down timer behavior we originally discussed.

  • If the high bit is set upon any write, shown as R in Fig 4 above, then the timer will enter into interval mode. In all other cases, the timer will be started as a one-shot countdown timer.

    This is our first break from the original countdown timer’s functionality, allowing us to run in an interval timer mode.

    Further, if set to interval mode, then the value written to the timer will become the interval definition. Hence, when the timer finishes counting down to zero, we’ll just automatically restart it again with the same New Counter value just written to it.

  • On writing a zero to the counter, all ongoing counts will be ended and the counter will return to idle. Any interval capability will be turned off.

  • We’ll also use a global CE register, i_ce. This will allow you to count down things other than clock cycles. Perhaps you can count incoming samples on an interface. Perhaps you want to count video frames. Perhaps you want to count finished instructions. All of these can be implemented with an appropriate connection to this i_ce wire.

Simple enough? Almost.

If low logic is a priority, and it has always been a priority for me, then you’ll also want to be able to configure this peripheral for just the amount of logic necessary. We’ll use the parameter VW to control how many bits are in our counter. We’ll also use BW to be the width of the data bus–nominally 32 bits. Finally we’ll use the one bit parameter, RELOADABLE to control whether or not this timer offers an interval timer mode or not. For example, if you know you are only ever going to measure 20ms intervals from a 100MHz clock, then you won’t ever need any more than VW=21 bits.

Simple enough now? I thought so. Let’s dive into a walk through of the code.

The Code

We’ll walk through the code of the ZipTimer in two separate sections. First, we’ll discuss the traditional Verilog code. Then we’ll move from that to the formal properties section. Once we’ve finished discussing the formal properties within the code, I show how to connect a peripheral like this to an AutoFPGA based design.

Normally I skip the front matter of a Verilog file when blogging, so as to only focus on the relevant portions. In this case, I’ll show the three parameters: BW, containing the size of the bus, VW, containing the number of bits in our counter, and RELOADABLE–set to one if we want to support an interval timer capability in addition to the one-shot timer capability.

	parameter	BW = 32;
	parameter	VW = (BW-1);
	parameter [0:0]	RELOADABLE=1;

I’ll also simplify the write command below to a wb_write flag. Since you’ll see this often below, here’s the declaration.

	assign	wb_write = (i_wb_stb)&&(i_wb_we);

As a result, any time you read wb_write below you now know that it is nothing more than a predicate that will be true any time the bus is writing to our only timer register.

We’ll also use a flag r_running to keep track of whether or not the timer is running.

	initial	r_running = 1'b0;
	always @(posedge i_clk)
		if (i_reset)
			r_running <= 1'b0;
		else if (wb_write)
			r_running <= (|i_wb_data[(VW-1):0]);
		else if ((r_zero)&&(!auto_reload))
			r_running <= 1'b0;

Basically the timer is r_running any time it is non-zero, or any time it is zero and about to reload for the next interval. If we wanted to, it would make sense to scribble in our notes at this point that,

always @(*)
if ((r_value != 0)||(auto_reload))
	assert(r_running);

The contrary case, where r_running is not true, will be a little more difficult to specify so we’ll save it until we have to think our way through it in the next section. Either way, when we get to building our formal properties section, we’ll then copy our scribbled notes over there so we can place all of our formal properties in one place.

Moving on, if you recall from above, we used a RELOADABLE parameter option to select whether or not this timer included the interval timer ability, or just a one-shot capability. Hence, if RELOADABLE is true then we’ll include this interval timer capability.

	generate
	if (RELOADABLE != 0)
	begin

The interval timer capability itself centers around two registers. The first, r_auto_reload, is a single bit flag telling us whether or not the timer needs to be restarted once it hits zero. The second register will tell us what our interval is should r_auto_reload be set–but we’ll get to that in a moment.

Initially, I cleared this r_auto_reload value upon any reset and set it on any write where the most significant bit is set.

		initial	r_auto_reload = 1'b0;

		always @(posedge i_clk)
			if (i_reset)
				r_auto_reload <= 1'b0;
			else if (wb_write)
				r_auto_reload <= (i_wb_data[(BW-1)]);

This approach failed when I tried to formally verify the ZipTimer. When I dug a bit deeper, I realized that the timer interval could never be allowed to be zero. Were it zero, this would break the r_running assertion we placed into our notes above. Hence, I rewrote the r_auto_reload logic above into,

		initial	r_auto_reload = 1'b0;

		always @(posedge i_clk)
			if (i_reset)
				r_auto_reload <= 1'b0;
			else if (wb_write)
				r_auto_reload <= (i_wb_data[(BW-1)])
					&&(|i_wb_data[(VW-1):0]);

The big difference is that in order to create an interval timer, you need to not only set the high order bit but you must also provide a non-zero interval length.

The second item worth commenting on here is the assign statement.

		assign	auto_reload = r_auto_reload;

By assigning to a global wire value within a generate block, I can then use this wire value throughout the rest of my design without needing to waste regs when RELOADABLE is false and I don’t need them. Optimizations within the synthesizer will then remove any extra logic dependent upon these values.

The next register associated with the interval timer capability is the r_interval_count register–containing the the interval length expressed as value to reset our register to after it reaches zero. On any write, we’ll set this interval count to the information found on the data bus in i_wb_data. The r_auto_reload we just dealt with above will determine whether or not this r_interval_count is relevant or not.

		initial	r_interval_count = 0;
		always @(posedge i_clk)
			if (i_reset)
				r_interval_count <= 0;
			else if (wb_write)
				r_interval_count <= i_wb_data[(VW-1):0];
		assign	interval_count = r_interval_count;

Finally, if we are building without the interval timer capability, we’ll set both of these values, auto_reload and interval_count, to zero. The synthesizer will then remove any of the relevant interval timer logic below.

	end else begin
		assign	auto_reload = 1'b0;
		assign	interval_count = 0;
	end endgenerate

Let’s now dig into the core of this count-down timer: the counter’s value, r_value. This follows primarily from the counter we started with, corrected by our discussion above, but now with the changes necessary to handle both an interval timer capability as well as a one-shot countdown timer capability.

In both cases, the counter is initialized to zero, cleared on reset, and set on any bus write.

	initial	r_value = 0;
	always @(posedge i_clk)
		if (i_reset)
			r_value <= 0;
		else if (wb_write)
			r_value <= i_wb_data[(VW-1):0];

Otherwise, we’ll adjust the timer any time i_ce is true and the timer is currently running. As you may remember from above, the timer is r_running any time r_value it is nonzero, or we are in interval timer mode (r_auto_reload is true).

		else if ((i_ce)&&(r_running))
		begin

If the counter is not zero, we’ll count down.

			if (!r_zero)
				r_value <= r_value - 1'b1;

Once it reaches zero, we’ll restart it if we are in interval timer mode. In this mode, auto_reload will be true.

			else if (auto_reload)
				r_value <= interval_count;
		end

If auto_reload is not set then once the counter reaches zero, the timer will stop.

The next register in our implementation is r_zero. r_zero is a helper register. It needs to be equivalent to r_value == 0. (We’ll prove that these two expressions evaluate to the same value in a moment.) By setting r_zero on the clock before r_value reaches zero, we relieve some of the timing stress within this module. Hence, instead of an always block that depends upon whether or not r_value == 0, such as the original designs we started out with, they can instead depend upon a single pre-calculated single-bit value r_zero.

	reg	r_zero  = 1'b1;
	always @(posedge i_clk)
		if (i_reset)
			r_zero <= 1'b1;
		else if (wb_write)
			r_zero <= (i_wb_data[(VW-1):0] == 0);
		else if ((r_running)&&(i_ce))
		begin
			if (r_value == { {(VW-1){1'b0}}, 1'b1 })
				r_zero <= 1'b1;
			else if ((r_zero)&&(auto_reload))
				r_zero <= 1'b0;
		end

Were I writing this code from scratch, I’d scribble into the margin that I’ll want to come back and prove the formal property that

always @(*) assert(r_zero == (r_value == 0));

The final required piece of logic is the interrupt register, o_int. While we might consider setting the interrupt line to r_zero, we’d then get lots of interrupts every time the counter was idle. We’d also get lots of interrupts between any pair of i_ce strobes while the counter was waiting to reload. Hence, we’ll only set the interrupt any time r_value transitions to zero, or more explicitly any time it is equal to one and the i_ce register is high.

	initial	o_int   = 1'b0;
	always @(posedge i_clk)
		if ((i_reset)||(wb_write)||(!i_ce))
			o_int <= 1'b0;
		else // if (i_ce)
			o_int <= (r_value == { {(VW-1){1'b0}}, 1'b1 });

As our last step, we’ll set the return data on the bus to be the indication of whether we are in interval timer mode (auto_reload is high), followed by the current state of the counter.

	assign	o_wb_data = { auto_reload, r_value };

Other wishbone return return values follow from our prior discussion:

	assign	o_wb_stall = 1'b0;
	assign	o_wb_ack = (i_wb_stb);

That’s all it takes to generate a timer peripheral for a CPU. It’s a bit more than the simple counter we started out with. In the next section, we’ll discuss how we might go about formally verifying this timer.

The formal proof

If you are not familiar with using yosys for formal verification, then I’ll recommend you go back and read about my first experiences with formal methods.

If you just want a quick reminder, there are two basic operators we’ll be using below. The assume() operator restricts the size of the possible state space that the formal methods will examine. The assert() operator defines which states within this group are illegal. The formal engine will try all possible logic threads to find one where the predicate expression inside the assert() statement can be made to be false.

Fig 5. The Golden Rule of Formal Verification

To know which of assume() or assert() to use for any particular property, I follow the rule shown in Fig 5. Hence, we’ll assume() any properties about our inputs, and we’ll assert() any properties about our own internal state or any outputs we might produce.

There is a third operator we’ll be using as well. This is the $past() operator. As we use it below, this operator returns the value of the item within it one clock ago. The problem with the $past operator is that it tends to misbehave prior to the beginning of time. Hence, any time you see me using this you’ll also see f_past_valid in the condition list.

As with most of my formal property sections, they start with the definition of the f_past_valid variable I just mentioned. We discussed this above, and in more detail before. Basically, any assertion regarding something one clock in the $past(), before any initial settings, will fail. By checking for f_past_valid being true as part of a formal logic test, I can then use $past() expressions in any assert() statements below without worrying about whether or not the logic being referenced occurred before time began.

`ifdef	FORMAL
	reg	f_past_valid;
	initial	f_past_valid = 1'b0;
	always @(posedge i_clk)
		f_past_valid <= 1'b1;

The next order of business is bounding the i_reset signal. This signal needs to be true initially. We’ll also insist that it’s true any time f_past_valid is false. Aren’t these two the same condition? Not quite. While they are very similar, they are separate conditions. This second condition specifies that any time the induction engine tries to set f_past_valid to false, then the i_reset line must also be true which will then force the design into its initial state.

	initial	assume(i_reset);
	always @(*)
		if (!f_past_valid)
			assume(i_reset);

If we do this properly, we can use f_past_valid being false as an indication that our design should be in its reset state. In a similar fashion, on the clock following any i_reset, the design should also be in its reset state.

We’ll pull any value from above that has an initial statement or responds to an i_reset signal, and insist on either condition that the registers have the same value. We’ll also desk check our design to make certain that registers set via an initial statement are also responsive to an i_reset and vice-versa. That is, an initial statement should set the register to the same value that an i_reset would set them to.

	always @(posedge i_clk)
	if ((!f_past_valid)||($past(i_reset)))
	begin
		assert(r_value     == 0);
		assert(r_running   == 0);
		assert(auto_reload == 0);
		assert(r_zero      == 1'b1);
	end

Let’s now move on to some internal consistency checks. For example, we stated above that we wanted r_zero to be equivalent to r_value==0. Let’s now assert that this relationship holds.

	always @(*)
		assert(r_zero == (r_value == 0));

Likewise, anytime our value is non-zero the timer should be running.

	always @(*)
		if (r_value != 0)
			assert(r_running);

In a similar fashion, any time we are in interval mode we should be running.

	always @(*)
		if (auto_reload)
			assert(r_running);

Perhaps you may recall these from our scribbled notes above as well?

Further, if our parameter RELOADABLE is false, then auto_reload should also be false.

	always @(*)
		if (!RELOADABLE)
			assert(auto_reload == 0);

Finally, anytime auto_reload is true, we should have a non-zero interval time.

	always @(*)
		if (auto_reload)
			assert(interval_count != 0);

Those are the simple properties. The next several are more complex.

Our next step will be to work through the properties associated with r_value.

For the first of these more complex properties, we’ll say that any time r_value==0, i.e. any time the timer has stopped counting down, then it should stay that way. However, if you try to express this simply,

	always @(posedge i_clk)
	if ((f_past_valid)&&($past(r_value)==0))
		assert(r_value == 0);

you might be surprised that your assertion doesn’t hold. You’ll first find that following a wishbone write, r_value might be something other than zero. You’ll then discover that, by design, following an automatic reload it won’t be zero either. Hence, the actual property is a touch more complex.

	always @(posedge i_clk)
	if ((f_past_valid)&&($past(r_value)==0)
			&&(!$past(wb_write))&&(!$past(auto_reload)))
		assert(r_value == 0);

Next, let’s consider the case where r_value was equal to zero on the last clock, but isn’t equal to zero any more. Specifically, we want to test whether the interval timer started over on a reload as desired.

In this case, the simple property has several exceptions to it. The counter won’t move to its reload value following a reset, nor will it necessarily move to its reload value following a bus write. Finally, it should only restart if i_ce is true, and in all other cases remain where it was.

	always @(posedge i_clk)
	if ((f_past_valid)&&(!$past(i_reset))&&(!$past(wb_write))
			&&($past(r_value)==0)&&($past(auto_reload)))
	begin
		if ($past(i_ce))
			assert(r_value == interval_count);
		else
			assert(r_value == $past(r_value));
	end

Now let’s consider the case where the r_value is not equal to zero on the last clock. While I’d like to write the assertion that,

	always @(posedge i_clk)
	if ((f_past_valid)&&($past(r_value)!=0))
		assert(r_value == $past(r_value)-1'b1);

the formal engine again corrects me with several traces showing why this isn’t the case. The first trace reminds me that, following a reset, r_value will be zero. Once I fix that and try again, the second trace reminds me that r_value can be anything following a bus write. Rather than running the formal tools again, I go back and desk check this time to discover that if i_ce isn’t true on the last clock then r_value shouldn’t change.

This brings us to the following property,

	always @(posedge i_clk)
	if ((f_past_valid)&&(!$past(i_reset))
			&&(!$past(wb_write))&&($past(r_value)!=0))
	begin
		if ($past(i_ce))
			assert(r_value == $past(r_value)-1'b1);
		else
			assert(r_value == $past(r_value));
	end

How about a bus write? Following a bus write, we want our counter to have the data written to it in our value. The exception is a reset. If a bus write and a reset occur on the same clock, we’d rather reset.

	always @(posedge i_clk)
	if ((f_past_valid)&&(!$past(i_reset))&&($past(wb_write)))
		assert(r_value == $past(i_wb_data[(VW-1):0]));

This also applies to the interval length. Following a bus write, if the value is non-zero, and if this module is built with the interval timer capability, then the high bit will determine whether or not we enter into interval timer mode.

	always @(posedge i_clk)
	if ((f_past_valid)&&(!$past(i_reset))&&($past(wb_write))
			&&(RELOADABLE)&&(|$past(i_wb_data[(VW-1):0])))
		assert(auto_reload == $past(i_wb_data[(BW-1)]));

We’re almost done. Before leaving, we need to double check our output interrupt. This wire, o_int, should be set any time r_value transitions from 1 to 0. Or, at least, that was my original thought. Then as I worked through this logic using formal methods, I realized there were some exceptions.

If the timer is reset in the last cycle, then the interrupt should be inactive.

	always @(posedge i_clk)
	if (!(f_past_valid)||($past(i_reset)))
		assert(!o_int);

Following a bus write, the interrupt wire should be zero as well. This will allow us to turn the timer off by simply writing a zero to it. Likewise, if i_ce wasn’t true on the last clock, then we didn’t just hit zero and the interrupt should be clear again.

	else if (($past(wb_write))||(!$past(i_ce)))
		assert(!o_int);

So when should the output interrupt be set? Any time we transition to zero. Hence, it should be set following the clock where i_ce was high and r_value was a one.

	else
		assert(o_int == ((r_running)&&(r_value == 0)));

Our last formal properties are associated with the wishbone bus. At first glance, these properties below may just appear like restatements of the logic above. In many ways they are. However, by placing these here I know I won’t carelessly adjust this interface logic while trying to optimize things.

	always @(*)
		assert(o_wb_ack == i_wb_stb);

	always @(*)
		assert(!o_wb_stall);
	always @(*)
		assert(o_wb_data[BW-1] == auto_reload);
	always @(*)
		assert(o_wb_data[VW-1:0] == r_value);
`endif
endmodule

That’s the last of our formal properties. Did you notice along the way how the formal engine helped us find the right properties for our code? That it found subtleties like the reset condition that needed to be checked for? Not only that, did you notice how the formal engine helped us flesh out the final details in our timer implementation?

These are all reasons why I have now started using formal methods before ever writing a test bench or running a simulation. Using formal methods helps me discover details I might otherwise not think about.

AutoFPGA

We have one more task before we are done: connecting this timer to the rest of our design. If you are using AutoFPGA, that’s just as easy as adding the configuration file for this timer to the AutoFPGA command line. Alternatively, we could connect this to the bus interconnect by hand, but I think you’ll find it simpler to use AutoFPGA.

Normally this isn’t necessary with the ZipCPU since the ZipTimer is already connected manually within the ZipSystem module, shown above in Fig 1. Two recent AutoFPGA based designs, one for the MAX-1000 and another for the TinyFPGA (neither quite complete), however, don’t use the ZipSystem but rather the bare ZipBones ZipCPU wrapper, shown in Fig 6.

Fig 6. The ZipBones

In each of these designs, you can find a AutoFPGA bustimer configuration within the ZipBones configuration file, since having the timer is so useful to the ZipCPU–as it would be to any CPU.

You may recall from the general format of an AutoFPGA configuration file that the entries consist of @KEY=VALUE pairs. They are primarily used to tell AutoFPGA what text to copy and paste into a set of various project files. If the VALUE takes less than a line, the @KEY=VALUE definition can be placed on a single line. Otherwise, all of the lines following @KEY= will consist of the VALUE for that key. Likewise, if the VALUE is numeric, you can have AutoFPGA calculate the value by placing a $ between the @ and the KEY and so use the @$KEY=EXPRESSION form. Finally, to reference one value, @KEYONE=VALONE, from within another, you would reference it as @$(KEYONE) within the value portion of the second @KEYTWO=VALTWO construct.

Perhaps this would make more sense if we walked through an example.

Every AutoFPGA component description begins with a @PREFIX tag. This defines the beginning of the component, as well as providing a name for the component.

@PREFIX=bustimer

I chose to call this device a bustimer. Unlike other timers that might be internal to other portions of the design, this one can be accessed from the main wishbone bus–hence the name.

If you are following along from the ZipBones configuration file, you can skip to the @PREFIX=bustimer line. The information prior defines how to connect the ZipBones ZipCPU wrapper to the bus. A watchdog timer definition follows this one, all within the same configuration file.

While AutoFPGA doesn’t require it, I often define a @DEVID tag. I primarily use this tag for contexts that don’t like lower case. AutoFPGA doesn’t do anything fancy with this tag, other then paste it into other tags as I tell it to below.

@DEVID=BUSTIMER

The main.v file of an AutoFPGA based project starts with a series of ifdef’s just before the module declaration. This allows a user to select some items and not others, as well as capturing a set of dependencies of what items depend upon others. In this case, we have no dependencies, but we’ll still create an define line in case something else depends upon this bus timer.

@ACCESS=@$(DEVID)_ACCESS

Now let’s connect our timer to a wishbone bus. Specifically, we want to connect this component to the system bus, by default named wb. We’ll also declare that this bus timer is a bus slave whose result is always available and that never stalls, @SLAVE.TYPE=SINGLE, and one that has only a single address, @NADDR=1.

@SLAVE.BUS=wb
@SLAVE.TYPE=SINGLE
@NADDR=1

Actually connecting this to a bus depends upon the code we want to place into our main.v file. This is the purpose of the @MAIN.INSERT tag. Code within this tag will get copied directly (after variable name substitution) into the main.v file.

@MAIN.INSERT=
	ziptimer @$(PREFIX)i(i_clk, i_reset, 1'b1,
			wb_cyc, (wb_stb)&&(@$(PREFIX)_sel), wb_we, wb_data,
				@$(PREFIX)_ack, @$(PREFIX)_stall,
				@$(PREFIX)_data, @$(PREFIX)_int);

While you don’t need to reference the @$(PREFIX) tag at all, I often use this to help keep the names unique within any given design. Once the pattern matching takes place, these lines will just turn into:

@MAIN.INSERT=
	ziptimer bustimeri(i_clk, i_reset, 1'b1,
			wb_cyc, (wb_stb)&&(bustimer_sel), wb_we, wb_data,
				bustimer_ack, bustimer_stall,
				bustimer_data, bustimer_int);

You don’t need to define the bustimer_ack, bustimer_stall, or bustimer_data wires. AutoFPGA will define these for you once you tell it that this item is a bus slave. AutoFPGA will also create a bustimer_sel wire. This wire will be true any time this bus component’s address is selected. You do need to connect these wires to your component, as we’ve done above.

After running AutoFPGA, you should then be able to find this code snipped in the main.v, file. AutoFPGA will also declare the rest of the wires necessary to set this up as well.

The last wire, bustimer_int, is also defined automatically by AutoFPGA as part of generating and connecting up the interrupt wires. In our case, we have a programmable interrupt controller (PIC) defined elsewhere in the design with the name of buspic. Hence, all we need to tell AutoFPGA is that we have an interrupt named BUSTIMER, whose interrupt wire is bustimer_int, that needs to be connected to the buspics inputs.

@INT.BUSTIMER.WIRE= @$(PREFIX)_int
@INT.BUSTIMER.PIC= buspic

This will add our bustimer_int to the list of components within the bus_int_vector. Hence, if you check the main.v file, you’ll see the bustimer_int listed long with the other interrupt lines.

assign	bus_int_vector = {
	// ...
	bustimer_int,
	// ...
};

Internal to the board.h file that AutoFPGA creates for the internal processor that might use this design, this will also create a line,

#define	BUSPIC_BUSTIMER	BUSPIC(1)

after assigning this peripheral to interrupt control wire number one.

The last two parts deal with non-Verilog parts of the design. The first of these deals with connecting this device to the external debugging interface. Specifically, we’ll want to create a register, R_BUSTIMER, with the human name, BUSTIMER. These next three lines adjust the files regdefs.h and regdefs.cpp.

@REGS.NOTE= // The bus timer
@REGS.N=1
@REGS.0= 0 R_@$(DEVID) @$(DEVID)

First, upon reading these lines, AutoFPGA will place a definition into the regdefs.h output file, defining an R_BUSTIMER identifier to be equivalent to the address of this bus timer register. This will tell external host components where in the memory space to access this register when using readio() or writeio().

AutoFPGA will also place references to this register into regdefs.cpp. This file contains a mapping between the computer regiser name R_BUSTIMER and the user name for this register, BUSTIMER. As a result, you can then use the wbregs program, a variant of the old fashioned peek and poke hardware interface, to read from the bus timer with the command wbregs bustimer, or to write to it via wbregs bustimer newvalue. (I have another version of this program called avregs for use with the Avalon bus on a Cyclone-V …)

The last item places a reference to this bus timer into the board.h file. This file would be used by the ZipCPU to know if the bus timer was built into the design and, if so, what address that timer was placed into.

@BDEF.IONAME=_bustimer
@BDEF.IOTYPE=unsigned
@BDEF.OSDEF=_BOARD_HAS_@$(DEVID)
@BDEF.OSVAL=static volatile @$BDEF.IOTYPE *const @$(BDEF.IONAME) = ((@$BDEF.IOTYPE *)@$[0x%08x](REGBASE));

AutoFPGA will use this to create a line, similar to the following one, inside the board.h file:

#ifdef	BUSTIMER_ACCESS
#define	_BOARD_HAS_BUSTIMER
static volatile unsigned *const _bustimer = ((unsigned *)0x00100000);
#endif	// BUSTIMER_ACCESS

Remember, the goal of AutoFPGA is to make the inclusion of bus components into a design easier. Hence, by placing these few lines into an AutoFPGA configuration file, this bus timer will be included into the design. Removing these lines from the AutoFPGA configuration will seemlessly remove this bus timer from the design.

Conclusions

We’ve now gone over everything it takes to create a useful countdown timer within an FPGA design–whether a “one-shot” timer, or a fully programmable interval timer. Once put together, the final Verilog code isn’t all that much more difficult than the original counter we started out from. What may surprise you, though, is how much work we went through to place such a simple counter into a design. Not only did we formally verify our timer, but then we also had to connect it to the wishbone bus within the design. We also dealt with several subtle issues associated with making a timer useful along the way.

What can you use such a timer for?

We’ve already discussed how this can be the centerpiece of the multitasking implementation within an Operating System. Upon any timer interrupt, the Operating System can then swap user tasks or processes. Should the Lord will, I’ll come back and share how one ZipCPU implementation uses this interrupt for exactly that purpose.

There’s another valuable use of a timer such as this–one which may not be as obvious. In the presentation above, we discussed wiring the interrupt wire to the CPU’s interrupt controller. If you instead connected the interrupt output to the reset wire for the CPU, you would have created a watchdog timer.

So you see, a basic counter isn’t nearly as irrelevant as you might have thought it would be, neither is it as simple. It’s also provides a very good example of the full FPGA design process.