Verilog Cheatsheet

Verilog Cheatsheet

About

This post is a cheat sheet for Verilog/Sytem Verilog. I wrote this for myself, so if I ever have trouble remembering something(as I don’t write Verilog on a daily basis), I can just look it up on my own site. And maybe someone out there on the internet will find this useful for themselves as well.

But let’s be real, you and I will probably just ask AI for it, but I had this post in draft for a long time, so I thought I’d still post it.

Modules and Ports

//module, name, then the port list with directions and widths
module adder //module name
#(
  parameter WIDTH = 8          //parameters are compile-time constants
)(
  input  wire              clk,   //input port
  input  wire [WIDTH-1:0] a, b, //[MSB:LSB] sets the bus width
  output reg  [WIDTH-1:0] sum   //output driven from an always block -> reg
);
  //... module body goes here ...
endmodule

//Old (non-ANSI) style you'll still see in legacy code:
module adder(a, b, sum);
  input  [7:0] a, b;
  output [7:0] sum;
  //...
endmodule

Data Types (Nets vs Variables)

wire        w;        //net: driven continuously (assign) or by a port
wire [7:0] bus;      //8-bit net
reg         r;        //variable: holds its value, assigned in always/initial
reg  [7:0] count;    //8-bit variable
integer     i;        //32-bit signed, handy for loops in testbenches
real        rl;       //floating point (simulation only, not synthesizable)
time        t;        //64-bit unsigned, for simulation time

//Every bit is really 4-state: 0, 1, x (unknown), z (high impedance)
reg [3:0] a = 4'b01xz;  //x = unknown, z = tri-state / floating

Number Literals

//Format:  <size>'<base><value>   (size is in bits)
8'd255      //decimal, 8 bits
8'h0F       //hexadecimal
4'b1010     //binary
8'o17       //octal
-8'd5       //signed
8'hA_5      //underscores are ignored, use them for readability
'0, '1       //fill all bits with 0 or 1 (SystemVerilog)
16'hZ       //all Z, useful for tri-state buses

Operators

&  |  ^  ~        //bitwise AND, OR, XOR, NOT
&& ||  !          //logical AND, OR, NOT (return a single bit)
== != < > <= >=   //comparison
=== !==          //case equality: also compares x and z bits
<< >>            //logical shift
<<< >>>          //arithmetic shift (keeps the sign bit)
+ - * / %        //arithmetic (/ and % often not synthesizable)

//Reduction operators: collapse a whole vector to one bit
&bus             //AND of all bits
|bus             //OR  of all bits
^bus             //XOR of all bits (parity)

//Concatenation and replication
{a, b}           //glue vectors together
{4{1'b1}}        //replicate: gives 4'b1111

//Ternary (the only "mux" you need)
assign out = sel ? a : b;

Continuous Assignment

assign sum   = a + b;               //an adder
assign y     = sel ? d1 : d0;        //a 2:1 mux
assign {carry, result} = a + b;     //capture the carry-out too
assign #5 z = a & b;                 //with a 5-time-unit delay (simulation)

Procedural Blocks: initial and always

//initial: runs ONCE at time 0. Mostly for testbenches (not synthesizable).
initial begin
  count = 0;
  #10 reset = 1;   //#10 waits 10 time units
end

//always: runs repeatedly whenever its sensitivity list triggers.
always @(a or b or sel) //triggers on any listed signal change
  y = sel ? a : b;

always @(*)            //(*) auto-builds the list -> use this for comb. logic
  y = a & b;

always @(posedge clk) //triggers on the rising clock edge -> sequential logic
  q <= d;

Blocking (=) vs Non-Blocking (<=)

//Blocking: executes immediately, in order, like normal code.
always @(*) begin
  x = a + b;   //x updates now...
  y = x + 1;   //...so y uses the NEW x
end

//Non-blocking: all right-hand sides are sampled first, then assigned together.
always @(posedge clk) begin
  b <= a;      //both use the OLD values -> this is a proper
  c <= b;      //2-stage shift register, not c = a
end

Combinational Logic (if / case)

always @(*) begin
  if (sel == 2'b00)      y = a;
  else if (sel == 2'b01) y = b;
  else                   y = c;   //ALWAYS include else to avoid latches
end

always @(*) begin
  case (sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    default: y = 0;    //default covers the rest -> no latch
  endcase
end

//casex / casez treat x / z bits as don't-cares (use casez for priority logic)

Sequential Logic (Flip-Flops & Reset)

//D flip-flop with asynchronous active-low reset
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)  q <= 0;   //async: reset appears in sensitivity list
  else         q <= d;
end

//Counter with synchronous reset (reset NOT in the sensitivity list)
always @(posedge clk) begin
  if (rst)        count <= 0;
  else if (en)    count <= count + 1;
end

Loops

//for: unrolled into hardware if synthesizable, or used in testbenches
integer i;
always @(*)
  for (i = 0; i < 8; i = i + 1)
    out[i] = in[7-i];   //bit-reverse

repeat (4) @(posedge clk);  //wait 4 clocks (testbench)
while (busy) #1;           //loop until condition clears
forever #5 clk = ~clk;      //run forever -> classic clock generator

Parameters and Macros

module reg_file #(parameter WIDTH = 8, DEPTH = 16) ( /*...*/ );

localparam IDLE = 2'b00, RUN = 2'b01; //local constant, can't be overridden

//Override a parameter at instantiation time:
reg_file #(.WIDTH(16), .DEPTH(32)) u_rf ( /*...*/ );

//`define is a text macro (like C's #define)
`define MAX_ADDR 1023
//used as `MAX_ADDR . Prefer parameter/localparam where you can.

Module Instantiation

//Named port connections (recommended -- order-independent, readable)
adder #(.WIDTH(8)) u_add (
  .clk (clk),      //.port_name (signal_in_parent)
  .a   (x),
  .b   (y),
  .sum (result)
);

//Positional connections (fragile -- must match the port order exactly)
adder u_add2 (clk, x, y, result);

Generate Blocks

genvar g;
generate
  for (g = 0; g < WIDTH; g = g + 1) begin : bit_slice //named block
    full_adder fa (
      .a   (a[g]),
      .b   (b[g]),
      .cin (carry[g]),
      .sum (sum[g]),
      .cout(carry[g+1])
    );
  end
endgenerate

Tasks and Functions

//function: returns one value, no timing/delays, purely combinational
function [7:0] add;
  input [7:0] x, y;
  add = x + y;   //assign to the function name to return
endfunction

//task: can have delays and multiple outputs, used mostly in testbenches
task apply_reset;
  begin
    rst = 1;
    #20 rst = 0;
  end
endtask

//call them:
assign s = add(a, b);
initial apply_reset;

Testbench Basics

module tb;
  reg  clk = 0, rst;
  wire [7:0] out;

  //Instantiate the design under test
  counter dut (.clk(clk), .rst(rst), .out(out));

  always #5 clk = ~clk;      //100 MHz clock (period = 10 time units)

  initial begin
    $dumpfile("wave.vcd");  //waveform file for GTKWave
    $dumpvars(0, tb);         //dump everything in this module

    rst = 1; #12 rst = 0;   //pulse reset

    $display("t=%0t out=%0d", $time, out); //print once
    $monitor("t=%0t out=%0d", $time, out); //print on any change

    #100 $finish;           //end the simulation
  end
endmodule

System Verilog

New Data Types

logic        x;      //4-state; use instead of reg/wire almost everywhere
logic [7:0] bus;    //can be driven by assign OR by always -- no more wire/reg debate
bit          b;      //2-state (0/1 only), faster simulation

//Sized 2-state integer types:
byte   a;   // 8-bit signed
shortint s; //16-bit signed
int    n;   //32-bit signed
longint l;  //64-bit signed

//logic can't be driven by two sources; use a real net type for buses:
wire [7:0] shared_bus; //keep 'wire' where multiple drivers exist

always_comb / always_ff / always_latch

always_comb begin          //combinational; auto sensitivity, warns on latches
  y = sel ? a : b;
end

always_ff @(posedge clk or negedge rst_n) begin //flip-flops only
  if (!rst_n) q <= '0;
  else        q <= d;
end

always_latch begin         //use this only when you truly want a latch
  if (en) q <= d;
end

typedef, enum, struct, union

//typedef gives a name to a type
typedef logic [7:0] byte_t;
byte_t data;

//enum: named states -- perfect for FSMs
typedef enum logic [1:0] {IDLE, LOAD, RUN, DONE} state_t;
state_t state, next;

//struct: bundle related fields together
typedef struct packed {   //packed = maps to a contiguous bit vector
  logic [3:0] opcode;
  logic [7:0] addr;
} instr_t;
instr_t ins;
ins.opcode = 4'h2;          //access fields with a dot

typedef union packed { /*...*/ } u_t; //same bits, viewed different ways

Arrays, Queues & Associative Arrays

logic [7:0] mem [0:255];  //unpacked array: 256 bytes of memory
logic [7:0] mem2 [256];    //same thing, shorthand

int dyn [];                //dynamic array (sized at run time)
dyn = new[10];              //allocate 10 elements

int q [$];                 //queue (grows/shrinks)
q.push_back(5);            //add to the end
q.pop_front();             //remove from the front

int aa [string];           //associative array (sparse, keyed by string)
aa["count"] = 42;

Interfaces

interface bus_if (input logic clk);
  logic [7:0] addr, data;
  logic       valid, ready;

  //modport defines a direction "view" for each side
  modport master (output addr, data, valid, input ready);
  modport slave  (input addr, data, valid, output ready);
endinterface

//Use it as a single port:
module cpu (bus_if.master b);
  //access signals as b.addr, b.valid, ...
endmodule

Packages

//Share typedefs, params and functions across files
package my_pkg;
  typedef enum {RED, GREEN, BLUE} color_t;
  localparam WIDTH = 32;
endpackage

import my_pkg::*;        //import everything
import my_pkg::color_t;   //or just one item

Assertions

//Immediate assertion: checked at the moment it's reached
always_comb
  assert (onehot(sel)) else $error("sel not one-hot!");

//Concurrent assertion: checked over time against the clock
//"req must be followed by ack within 1..3 cycles"
property p_req_ack;
  @(posedge clk) req |-> ##[1:3] ack;
endproperty
assert_req_ack: assert property (p_req_ack);

Classes (Verification / OOP)

//Classes are for testbenches, not synthesizable hardware.
class Packet;
  rand bit [7:0] addr;   //rand = randomize this field
  rand bit [7:0] data;

  //constraint keeps random values legal
  constraint c_addr { addr < 8'h80; }

  function new();          //constructor
  endfunction

  function void print();
    $display("addr=%h data=%h", addr, data);
  endfunction
endclass

//Use it:
Packet p = new();          //create an object (handle)
if (!p.randomize())         //randomize() honours the constraints
  $error("randomization failed");
p.print();

Concurrency: fork/join, mailbox, semaphore

//Run threads in parallel
fork
  drive_stimulus();
  monitor_outputs();
join          //wait for ALL threads (join_any / join_none also exist)

//mailbox: thread-safe queue to pass data between threads
mailbox #(Packet) mbx = new();
mbx.put(p);   //producer
mbx.get(p);   //consumer (blocks until something is available)

//semaphore: guard a shared resource
semaphore sem = new(1);
sem.get(1);  //take a key
//... critical section ...
sem.put(1);  //give it back

Handy System Functions

$clog2(N)        //ceiling log2 -- bits needed to count to N
                 //e.g. localparam AW = $clog2(DEPTH);
$size(arr)       //number of elements
$bits(my_struct) //total width in bits
$signed(x)       //treat as signed / unsigned
$unsigned(x)
$random          //32-bit pseudo-random value
$urandom_range(1,10) //random in an inclusive range
$cast(dest, src) //safe type cast, returns 0 on failure
$fatal / $error / $warning / $info //severity messages

Leave a Reply

Your email address will not be published. Required fields are marked *

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.