Skip to content

Basic system verilog constructs

  • case sensitive
  • no implicit declaration of variables
  • white space insensitive
  • naming convention: use _ to separate words, e.g., data_in, addr_out, CANNOT start with number
  • block always end with end, endmodule, endfunction, starts with begin or module, function

module

basic_module.sv
module basic_module (
    input logic a,
    input logic b,
    output logic y
); // dont forget semicolon
    initial begin
        // initial block only runs once at time 0
        $display("Hello, SystemVerilog!");
    end
    assign y = a & b; // if we did not use always_comb, we need to use `assign` for combinational logic
endmodule

Behavioral modeling

  • always blocks: used to model sequential and combinational logic
  • initial blocks: used for simulation purposes, runs once at time 0
  • always_comb: used for combinational logic
  • always_ff: used for sequential logic (flip-flops)
  • always_latch: used for latches
  • posedge and negedge: used to trigger on rising or falling edges of a signal
  • = for blocking assignments, <= for non-blocking assignments. we have to use assign for combinational logic outside of always blocks (dataflow modeling)

note: behavioral modeling utilize full language features (that is both synthesizable and non-synthesizable constructs)

-   synthesizable constructs can be converted to hardware
-   non-synthesizable constructs are used for simulation and verification only, for example, `initial` block and
    `$display`
behavioral_modeling.sv
module behavioral_modeling (
    input logic clk,
    input logic rst_n,
    input logic d,
    output logic q
);
    // Sequential logic: D flip-flop
    always_ff @(posedge clk or negedge rst_n) begin // trigger on rising edge of clk or falling edge of rst_n
        if (!rst_n)
            q <= 0; // asynchronous reset
        else
            q <= d; // capture data on rising edge of clk
    end

    // Combinational logic: AND gate
    logic a, b;
    always_comb begin
        a = d; // we dont need the `assign` keyword here inside always block
        b = q;
    end

    assign y = a & b; // combinational output, need assign keyword here
endmodule

Dataflow modeling

  • uses continuous assignments with the assign keyword
  • describes the flow of data through the circuit
dataflow_modeling.sv
1
2
3
4
5
6
7
module dataflow_modeling (
    input logic a,
    input logic b,
    output logic y
);
    assign #20 y = a & b;
endmodule

Gate-level modeling

  • describes the circuit in terms of logic gates and their interconnections
  • uses built-in gate primitives like and, or, not, etc.
gate_level_modeling.sv
1
2
3
4
5
6
7
8
9
module gate_level_modeling (
    input logic a,
    input logic b,
    output logic y
);
    and #1.5 gate1 (y, a, b); // 1.5 time unit delay
    and #(2,3) gate2 (y, a, b); // delay of 2 time units for rise, 3 time units for fall
    buf #(2,3,4) gate3 (y, a); // delay of 2 time units for rise, 3 time units for fall, 4 time units for high to low transition
endmodule

alt text


example: code for a 2-4 decoder using different modeling styles

truth table

A1 A0 Y0 Y1 Y2 Y3
0 0 0 0 0 1
0 1 0 0 1 0
1 0 0 1 0 0
1 1 1 0 0 0
decoder_behavior.sv
module decoder2to4_behavior (
    input logic A1,
    input logic A0,
    output logic [3:0] Y
);
    always_comb begin
        case ({A1, A0})
            2'b00: Y = 4'b0001;
            2'b01: Y = 4'b0010;
            2'b10: Y = 4'b0100;
            2'b11: Y = 4'b1000;
            default: Y = 4'b0000;
        endcase
    end
endmodule
decoder_dataflow.sv
module decoder2to4_dataflow (
    input logic A1,
    input logic A0,
    output logic [3:0] Y
);
    assign Y[0] = ~A1 & ~A0;
    assign Y[1] = ~A1 & A0;
    assign Y[2] = A1 & ~A0;
    assign Y[3] = A1 & A0;
endmodule
decoder_gatelevel.sv
module decoder2to4_gatelevel (
    input logic A1,
    input logic A0,
    output logic [3:0] Y
);
    logic nA1, nA0;

    not (nA1, A1);
    not (nA0, A0);

    and (Y[0], nA1, nA0);
    and (Y[1], nA1, A0);
    and (Y[2], A1, nA0);
    and (Y[3], A1, A0);
endmodule

Parameter declaration

  • use parameter keyword to declare constants
  • parameters can be used to make modules more flexible and reusable
  • localparam can be used to declare local parameters that cannot be overridden during module instantiation
parameter_example.sv
module parameter_example #(
    parameter WIDTH = 8 // default width is 8
)(
    input logic [WIDTH-1:0] data_in,
    output logic [WIDTH-1:0] data_out
);

    localparam DEPTH = 2 ** WIDTH; // local parameter
    assign data_out = data_in; // simple pass-through
endmodule

variables

values

  • 0 : logic low
  • 1 : logic high
  • x : uninitialized / unknown
  • z : high impedance

variable: 4 state (variable that can take on 4 values: 0, 1, x, z)

net: 2 state (variable that can take on 2 values: 0, 1)

data type

  • wire: 4 state net, it can only have 1 source (1 continuous assignment or one module output)
  • wand, wor: wired AND, wired OR (multiple drivers, resolved to AND or OR respectively)
  • tri (logic), tri0, tri1: tri-state net, tri0 defaults to 0 when not driven, tri1 defaults to 1 when not driven
  • reg, logic: 4 state variable (reg is deprecated, logic is not available in verilog)
  • bit: 2 state variable

example:

Module A
module using_wire (A, B, C, D, f);
    input A, B, C, D;
    output f;
    wire f;

    assign f = A & B;
    assign f = C | D;
endmodule

Module B
module using_wand (A, B, C, D, f);
    input A, B, C, D;
    output f;
    wand f;

    assign f = A & B;
    assign f = C | D;
endmodule

Module C
module using_tri (A, B, C, D, f);
    input A, B, C, D;
    output f;
    tri f;

    assign f = A & B;
    assign f = C | D;

in this case, using_wire will cause a multiple driver error, using_tri will cause synthesize error, because tri net only works if the divers can output 1'bz to get off the buz. while using_wand will resolve f to the AND of the two assignments. assignments.

the final value of f in the using_wand module will be (A & B) & (C | D).

alt text