Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
    • Senior Debug Playbook
    • Reviewing Verification Code
    • Verification Strategy: Spec to Sign-Off
    • SystemVerilog Interview Q&A Bank
      • Q&A: Language Core
      • Q&A: Testbench Plumbing
      • Q&A: Classes and Interfaces
      • Q&A: Constraint and Assertion Fire-Drill
      • Q&A: Scenario and Design Questions
    • Capstone: Verify a Parameterized FIFO
  • Principal-Level Verification28
Loading lesson

‹SystemVerilog Interview Q&A Bank
  1. Topics
  2. SystemVerilog
  3. Senior & Interview Preparation
  4. SystemVerilog Interview Q&A Bank
  5. Q&A: Language Core

Part 8 · Senior & Interview Prep · Intermediate

Q&A: Language Core

Blocking versus nonblocking, four-state discipline, combinational intent, tasks and storage.

On this page

    Every answer in this bank follows one formula: direct answer first, evidence second, the edge third, and the testbench payoff last.

    • Direct answer: one sentence that answers exactly what was asked.

    • Evidence: the smallest code that proves it.

    • The edge: the follow-up, raised before the interviewer does.

    • So what: where it bites in a real testbench.

    One driver and four states

    Q: What is the difference between logic, wire and reg

    logic is a 4-state data type. As a variable it may have one continuous driver or any number of procedural writers, but not both kinds; always_ff and always_comb additionally forbid other writers to their outputs. wire is a net with resolution across many drivers, for tristate buses. reg is the legacy name that logic replaces; it never implied a register.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    // one continuous driver is legal on logic
    logic b;
    assign b = x & y;
    // a second continuous driver on b would be a compile error;
    // a many-driver bus stays a net:
    wire w;
    assign w = en1 ? d1 : 1'bz;
    assign w = en2 ? d2 : 1'bz;

    A reg becomes a flop only when assigned in a clocked block. In always_comb it is plain combinational logic.

    Q: Four-state versus two-state, when is each right

    Four-state (logic, integer) belongs everywhere a signal touches the DUT. X propagation is a debugging signal, not noise.

    Two-state (bit, int) is smaller and faster and belongs in testbench-internal bookkeeping. Assigning X to a two-state variable silently converts it to 0.

    The rule is 4-state at the DUT boundary and 2-state inside the testbench, with $isunknown guarding the crossing.

    The listing below is not run in this course's checks. The course runner tries Verilator first, and Verilator is 2-state, so the X constant becomes a plain value and the checks fail. It passes under Icarus (iverilog -g2012), which models four states.

    x_guard.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // not-run: needs 4-state X; Verilator 5.048 (tried first by the runner) is 2-state and fails the X checks; passes under Icarus
    module x_guard (
      input  logic [3:0] sampled,
      output logic       flagged,
      output bit   [3:0] safe
    );
      always_comb begin
        flagged = $isunknown(sampled);
        if (flagged) safe = 4'b0;
        else safe = sampled;
      end
    endmodule
    
    module tb_x_guard;
      timeunit 1ns; timeprecision 1ps;
      logic [3:0] sampled;
      logic flagged;
      bit [3:0] safe;
      x_guard dut (.*);
      int errors = 0;
      initial begin
        sampled = 4'b10x1; #1;
        if (flagged !== 1'b1) errors++;
        if (safe !== 4'b0000) errors++;       // guarded: flagged sample replaced by 0
        sampled = 4'b1011; #1;
        if (flagged !== 1'b0) errors++;
        if (safe !== 4'b1011) errors++;
        if (errors == 0) $display("PASS: X flagged before conversion and clean values pass through");
        else $fatal(1, "FAIL: x-guard output wrong");
        $finish;
      end
    endmodule

    The same boundary discipline runs here with a parity check. The testbench proves corrupt samples are flagged and replaced while clean samples pass through.

    parity_guard.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    module parity_guard (
      input  logic [3:0] sampled,
      input  logic       sampled_parity,
      output logic       flagged,
      output logic [3:0] safe
    );
      always_comb begin
        flagged = (^sampled !== sampled_parity);
        if (flagged) safe = 4'b0;
        else safe = sampled;
      end
    endmodule
    
    module tb_parity_guard;
      timeunit 1ns; timeprecision 1ps;
      logic [3:0] sampled;
      logic sampled_parity;
      logic flagged;
      logic [3:0] safe;
      parity_guard dut (.*);
      int errors = 0;
      initial begin
        sampled = 4'b1011; sampled_parity = 1'b1; #1;   // reduction xor is 1: clean
        if (flagged !== 1'b0) errors++;
        if (safe !== 4'b1011) errors++;
        sampled = 4'b1010; sampled_parity = 1'b1; #1;   // reduction xor is 0: corrupted
        if (flagged !== 1'b1) errors++;
        if (safe !== 4'b0000) errors++;
        sampled = 4'b0000; sampled_parity = 1'b0; #1;   // reduction xor is 0: clean zero
        if (flagged !== 1'b0) errors++;
        if (safe !== 4'b0000) errors++;
        if (errors == 0) $display("PASS: parity guard flags corruption and passes clean data");
        else $fatal(1, "FAIL: parity guard output wrong");
        $finish;
      end
    endmodule

    The swap and the scheduler

    Q: Blocking versus nonblocking, why does the swap matter

    Blocking (=) executes in statement order. Nonblocking (<=) samples its right-hand side now and assigns in the NBA region, after all blocking code in the step.

    The rule is nonblocking in clocked logic and blocking in combinational logic. Cross-block blocking races are scheduler-undefined; NBA deferral makes every block read pre-edge values, so results are deterministic.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module tb_swap;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      logic a_nb = 1'b0, b_nb = 1'b1, a_b = 1'b0, b_b = 1'b1;
      int errors = 0;
      always_ff @(posedge clk) begin a_nb <= b_nb; b_nb <= a_nb; end
      always @(posedge clk) begin a_b = b_b; b_b = a_b; end
      initial begin
        @(posedge clk); #1;
        if (a_nb !== 1'b1 || b_nb !== 1'b0) errors++;   // clean swap
        if (a_b !== 1'b1 || b_b !== 1'b1) errors++;     // blocking kept itself
        @(posedge clk); #1;
        if (a_nb !== 1'b0 || b_nb !== 1'b1) errors++;   // swapped back
        if (errors == 0) $display("PASS: nonblocking swap exchanges values and blocking version collapses");
        else $fatal(1, "FAIL: swap behavior wrong");
        $finish;
      end
    endmodule

    The waveform shows the exchange repeating on every clock edge.

    • Slot 0: the first rising edge exchanges the seeds, so a becomes 1 and b becomes 0.

    • Slot 1: the second edge swaps them back, a 0 and b 1.

    • Slot 2: the third edge, a 1 and b 0 again.

    • Slot 3: the fourth edge, a 0 and b 1 again.

    • Slot 4: the fifth edge, a 1 and b 0. Every edge exchanges the two bits.

    Combinational intent

    Q: Two combinational styles, are they the same

    No. always_comb runs once at time zero, sees signals read inside called functions, and forbids other processes from writing its outputs. always @* waits for an input change and can miss function-internal reads.

    A latch inferred inside always_comb is flagged as a warning. It becomes an error only where the lint flow escalates it.

    design.sv
    1
    2
    3
    4
    5
    6
    // snippet
    function logic f(input logic x);
      return x ^ mode;        // hidden input, not an argument
    endfunction
    // always @* misses mode changes; always_comb stays sensitive:
    // always_comb begin y2 = f(a); end
    design.sv
    1
    2
    3
    4
    // bad-example
    always_comb begin
      if (sel) z = a;         // no else: z holds state, a latch is inferred
    end

    The testbench below proves the positive form: defaults assigned before the branch leave no path unassigned. Every select gives a defined one-hot output, and disable gives all-zero.

    dec24.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    module dec24 (
      input  logic [1:0] sel,
      input  logic       en,
      output logic [3:0] y
    );
      always_comb begin
        y = 4'b0;
        if (en) y[sel] = 1'b1;
      end
    endmodule
    
    module tb_dec24;
      timeunit 1ns; timeprecision 1ps;
      logic [1:0] sel;
      logic en;
      logic [3:0] y;
      dec24 dut (.*);
      int errors = 0;
      initial begin
        en = 1;
        for (int s = 0; s < 4; s++) begin
          sel = s[1:0]; #1;
          if (y !== (4'b0001 << s)) errors++;
        end
        en = 0; sel = 2'd2; #1;
        if (y !== 4'b0) errors++;
        if (errors == 0) $display("PASS: decoder is one-hot for every select and silent when disabled");
        else $fatal(1, "FAIL: decoder output wrong");
        $finish;
      end
    endmodule

    Tasks, functions and storage

    Q: Task versus function, what is the real split

    A function runs in zero time and returns a value. A task may consume time and returns results through output arguments.

    Anything touching the clock is a task; pure computation is a function. Both are automatic in testbenches, since static locals corrupt each other across concurrent calls.

    Q: Packed versus unpacked, what maps to what

    Packed dimensions sit before the name and form one contiguous vector. Unpacked dimensions sit after the name and form separate elements, the natural model for memories.

    Whole-vector operations work on packed data only. The testbench below drives a byte memory through a task, computes expectations in a function, and checks all 16 locations.

    byte_mem.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    module byte_mem (
      input  logic       clk,
      input  logic       wr,
      input  logic [3:0] addr,
      input  logic [7:0] wdata,
      output logic [7:0] rdata
    );
      logic [7:0] mem [16];
      always_ff @(posedge clk) begin
        if (wr) mem[addr] <= wdata;
        rdata <= mem[addr];
      end
    endmodule
    

    Construct

    Use it for

    Never for

    task with clock waits

    Driving pins and waiting for responses

    Pure computation

    function, zero time

    CRC, compare, format, expected-value math

    Anything with #, @ or wait

    Packed vector

    Buses, slices, whole-word arithmetic

    Sparse storage

    Unpacked array

    Memories and lookup tables

    Whole-array assignment from a scalar

    Common pitfall

    Sampling DUT outputs into bit or int turns X into 0 and hides the bug. Guard with $isunknown before converting, and keep blocking assignments out of clocked blocks.

    Interview

    The swap pair is the classic whiteboard proof. Write both versions, predict a == b for the blocking one, and name NBA deferral as the reason cross-block reads stay deterministic.

    Check yourself Why does the blocking swap end with a == b?

    The first line updates a immediately, so the second line reads the new a instead of the old one. Both end up holding the old b.

    Check yourself A called function reads a signal that is not an argument. Which construct stays correct, and why?

    always_comb stays correct because its sensitivity includes function-internal reads. always @* misses them and goes stale.

    Check yourself When must a testbench helper be a task rather than a function?

    When it consumes time: driving pins across clock edges or waiting for a response. Computation with no time advance belongs in a function.

    Related topics

    • Foundations: data types

    • Foundations: procedural blocks

    Previous

    223 / 259

    Next: Q&A: Testbench Plumbing

    Up next

    Up nextQ&A: Testbench Plumbing

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Q&A: Classes and Interfaces
    • Q&A: Constraint and Assertion Fire-Drill
    • Q&A: Scenario and Design Questions
    clkab
    A nonblocking swap exchanging two bits on every edge