Verilog States and truth tables

Verilog represents circuits digitally.


Tasks/Functions

A task is a procedure/method/subroutine/… structure in Verilog. They are local to modules (but could be ‘included’). Tasks may substitute for repetitive operations and may be useful in simplifying and clarifying a test bench. They may contain combinatorial or sequential logic and occupy (simulation) time in execution. [Syntax may vary; an alternative example is given in the lab. manual.]

task something;
input …
output …
begin
<behavioural code>
end
endtask

Functions are like simple tasks returning a value. They are typically used to represent some combinatorial function. For example:

function adder;         // Function name is also result
input x, y;
begin
adder = x + y;          // Hardly worthwhile!
end
endfunction
 

 
assign sum = adder(a, b);


Compiler directives

Verilog complier directives are keywords preceded by the backtick character `

Some of the more common ones are:

`timescale

Set the timestep in simulation, i.e. how long #1 really represents.

`include

Import another file into the source; useful for including common definitions amongst several files in a project.

`define

Verilog allows test substitution in a similar manner to C's “#define”.

`define THING 99         // Define text substitution
...
x <= `THING;             // In use (note `)

It is recommended to be used in the same way, and for the same reasons.

`undef

Remove a compile-time definition.

`ifdef, `else, `elsif, `endif, `ifndef

Used to control conditional compilation by ‘bracketing’ parts of the code. The application should be obvious.


Generate

module big_memory (input wire CE, input wire WE, input wire OE,
                   input wire A[11:0], input wire [7:0] Din,
                   output wire [7:0] Dout);
reg  [3:0] En;
wire [7:0] Data [0:3]           // Array of four data buses
 
always @ (CE, A[11:10])         // Address decoder with enable
  begin
  En = 4'b0000;
  if (CE) En[A[11:10]] = 1'b1;
  end
 
generate
  genvar i;                     // Variable used for compile-time iteration
  for (i=0; i<4; i=i+1)
    begin: block
    memory mem((.CE(En[i]), .WE(WE), .OE(OE),
                .A(A[9:0]), .Din(Din), .Dout(Data[i]));
    end
endgenerate
 
assign Dout = Data[A[11:10]];   // Output multiplexer
 
end

Generate allows multiple instances to be placed iteratively. The example here shows how a 4 KiB memory could be constructed from four, 1 KiB blocks.

This example instantiation iterates a structural Verilog block: it is possible to contain a variety of statements within the block.

Structure built by 'generate'

The control for the generate needs to be determined when the blocks are to be instantiated. Hardware cannot be created dynamically ‘at run time’. Obviously!

The real value of such constructs is apparent when the number of instantiations is parameterised. For example:

...
 
always @ (CE, A[9+`BITS:10])
  begin
  En = 0;
  if (CE) En[A[9+`BITS:10]] = 1`b1;
  end
 
generate
  genvar i;
  for (i = 0; i < (1<<(`BITS-1)); i = i + 1)
  ...

This allows the iterations to be specified with a single input definition.

Here `BITS specifies the log2 of the number of blocks and thus 1<<`BITS regenerates this number. It should be constrained to 1 because “A[10:10]” is legal but “A[9:10]” will cause problems.

The reverse approach is more difficult as taking a log is more tedious. However here is an appropriate function from the Verilog Standard document.

function integer clogb2;
  input [31:0] value;
  for (clogb2 = 0; value > 0; clogb2 = clogb2 + 1;)
    value = value>>1;
endfunction

If the log2 is non-integral, this finds the next largest integer, which is generally what is wanted.


Parameters

module adder (a, b, s);
parameter n = 16;
input  wire [n-1:0] a, b;
output wire [n-1:0] s;
 
assign s = a + b;
 
endmodule
 
module example;
wire [15:0] pp, qq, rr;
wire [31:0] ss, tt, uu;
wire  [7:0] vv, ww, xx;
 
adder add16(pp, qq, rr);
adder #(32) add32 (ss, tt, uu);                 // Verilog 95
adder #(.n(8)) add8 (.a(vv), .b(ww), .c(xx));   // Verilog 2001
 
endmodule

There are several ways to define and modify parameters in Verilog. Only a selection is elucidated here but there should be enough to illustrate the concept.

The example above specifies a (somewhat redundant) module and instantiates it three times. The module has a single parameter (n) which, in this case, specifies its bus widths. There is a ‘default’ value of 16 assigned to this parameter.

The first instantiation simply uses this default value. The other instantiations override the default. For clarity, in the first instantiation it is suggested that a ‘16’ should be passed anyway.

The add32 uses the older Verilog-95 syntax both the parameter list and the connections. This syntax works much like many programming languages in that associations are determined by the order of the lists. This syntax is also usable in later versions of Verilog.

add8 is instantiated using Verilog 2001 syntax. This specifies each parameter/connection explicitly.

.<name_inside_instance>(<local_name>)

In this syntax the elements can be specified in any order.

It is recommended that the Verilog 2001 syntax is used. It is more verbose but less prone to errors, especially if lists are edited after creation. In the case of parameters it also allows an arbitrary set of overrides to be used, not just those from the start of the list.

defparam

A parameter can be passed explicitly to a module using ‘defparam’.

The following could be added to the example above:

defparam add16.n = 16;

where a value is assigned using its hierarchical name.

localparam

A ‘local’ parameter is a parameter which cannot be overridden by a ‘ defparam’; it can be defined in terms of a passed parameter though, e.g. ‘localparam my_param = 2 * your_param;

localparam’s can sometimes substitute for `define and may be used more flexibly.


Up to Verilog

Back to more Verilog notes

Diversion to SystemVerilog

Forward to next session (Simulation)