task in Verilog

About the task Keyword

A task is a group of statements that would usually appear in an always block. If we need to repeat the same statements multiple times, it makes sense to define them as a task. Common examples include simple data format conversions, such as reversing the bit order in a vector. Say we have an 8-bit vector v. In some specifications, the Least Significant Bit (LSB) is on the “right”, i.e. v[0]. In other specifications, the LSB is on the “left”, i.e. v[7].

For some project, we may need to reverse the bit order of several vectors. This particularly occurs with filesystem interfaces and data communication channels. Do do this, we could use for loops, as in this code snippet:

input      [7:0] a, b;
output reg [7:0] q, w;

integer idx; // Loop Index

always @(a,b) begin
   for (idx=0; idx<8; idx=idx+1) begin
      q[7-idx] = a[idx];
      w[7-idx] = b[idx];
   end   
end

This seems simple enough, but if we need to do this for many different signals, in multiple modules, it would make sense to group these lines into a simpler reusable statement. To accomplish this, we put the loop into a task like this:

task automatic reverse_bits_task;
   input   [7:0]  in;
   output  [7:0]  out;

   integer          idx;
   
   begin
      for (idx=0; idx<8; idx=idx+1) begin
         out[7-idx] = in[idx];
      end
   end
endtask

Then to use the task, we reference it within an always block like this:

always @(a,b) begin
   reverse_bits_task(.in(a),.out(q));
   reverse_bits_task(.in(b),.out(w));
end

The task definition has to appear inside of the module. The automatic keyword is usually required; it instructs the simulator to allocate fresh memory every time the task is evaluated. The default non-automatic behavior has fewer applications and can create subtle simulation bugs.

So almost always your tasks should be automatic.

Assigned Tasks

Tasks in Testbenches

Tasks probably get the most use in testbenches. We’ll start with a simple example that simulates the reverse_bits_task shown above. In a text editor, open the file src/testbench.v. At the bottom of the testbench module, place the definition for reverse_bits_task. Find the always block that assigns q and w, and change it from for loops to task invocations. You should remove or comment out the declaration of idx since it is now defined within the task.

Type make to run the simulation. You should see several lines of output reporting a->q and b->w. Verify that the bits are correctly reversed in each case.

Tasks in Include Files

A task can be reused in a variety of different modules and testbenches. To facilitate this, Verilog has a `include directive that imports lines from external files. You can use this to organize reverse_bits_task in its own file.

Move the reverse_bits_task definition into a file called inc/reverse_bits_task.v (remove or comment the task definition in testbench.v). Then, in the testbench, add the include directive from inside the module:

`include "inc/reverse_bits_task.v"

Run the test again, you should get the same result.

Tasks in Modules

Next, remove or comment the `include line in the testbench.v. Create a new module file named src/reverse_bits_module.v, to act as a “wrapper” for the task:

module reverse_bits_module
  (
   input [7:0]      a,
   input [7:0]      b,
   output reg [7:0] q,
   output reg [7:0] w
   );

`include "inc/reverse_bits_task.v"
   
   always @(a,b) begin
      reverse_bits_task(a,q);
      reverse_bits_task(b,w);
   end
   
endmodule

In the testbench, remove or comment the lines that reference the reverse_bits_task, and make an instance of reverse_bits_module. You will need to change q and w to wire types.

Implement reverse_bits_module

A task is synthesizable if it

  1. Contains only synthesizeable statements, and
  2. Is invoked within a synthesizeable module.

Examples of non-synthesizeable statements include:

To be synthesizeable, a module needs inputs and outputs, and should be fully defined by synthesizeable statements; non-synthesizeable lines can exist for simulation purposes, but they are ignored during synthesis.

Fortunately for our task, a for loop is synthesizeable. With reverse_bits_module serving as a top module, we can synthesize and verify the design on the Basys3 board. Open reverse_bits.xdc and examine the constraints. Input vector a is mapped to switches 0–7, and input vector b is mapped to switches 8–15. Output vector q is mapped to LEDs 0–7, and w is mapped to 8–15. Also notice that there is no clock in this design.

Run make implement to build the design. Since there is no clock or other timing constraints in the design, the timing report is meaningless. The utilization report is more interesting. Open it and notice that there is no logic utilization – zero. The reverse_bits_task is implemented entirely in routing, i.e. switch configurations.

If successful, the build process should create reverse_bits.bit. Program the Basys3 board with this bitstream and verify that the left/right 8-bit groups are reversed. Verify and photograph the following cases:

Place photos of the two cases in your working directory, and name them case1 and case2 (with the appropriate graphics file extension).

Turn in Your Work

To complete this assignment, commit and push your results to the repository, and indicate on Canvas that it is complete:

git add *.bit *.rpt *.txt inc/ src/*
git commit . -m "Complete."
git push origin main