DESIGN VERIFICATION ENGINEER

SHAIK KHAJA MOHIDDEEN

THE DV HUB

Specializing in RTL verification, protocol compliance, and coverage-driven validation. From silicon bring-up to UVM testbench architecture — building the bridge between design and silicon.

5+
PROTOCOLS
10+
PROJECTS
UVM
METHODOLOGY
SV
UVM
AXI
MIPI
DV
ABOUT ME

Who I Am

I am Shaik Khaja Mohiddeen, a passionate Design Verification Engineer with deep expertise in SystemVerilog, UVM methodology, and protocol-level verification. My focus is on building robust, reusable testbench architectures that ensure silicon correctness from RTL to tape-out.

I work across the full DV spectrum — from writing constrained-random sequences and functional coverage models to implementing SVA assertions and closing coverage goals on complex SoC designs involving multiple bus protocols.

My domain expertise spans AMBA protocols (APB, AHB, AXI), MIPI DSI, QSPI flash interfaces, GDMA controllers, GPIO, PLL, and processor boot sequences — all verified using industry-standard UVM methodology.

🎯
Protocol Expertise: APB, AHB, AXI4, I2C, SPI, UART, MIPI DSI, QSPI, GDMA
Methodology: UVM 1.2, Constrained Random, Functional Coverage, SVA
🔬
Tools: Synopsys VCS, Cadence Xcelium, Questa, EDA Playground
ROLE
Design Verification Engineer
RTL Verification · UVM Testbench · Protocol VIP
DOMAIN
SoC Verification
AMBA · MIPI · QSPI · GDMA · PLL · Boot
LANGUAGE
SystemVerilog · UVM
IEEE 1800-2017 · UVM 1.2
PLATFORM
DV HUB
Personal learning & project showcase portal
EXPERTISE

Technical Skills

WORK

Projects

CODING LAB

Code & Technical Reference

⚡ PROCESSOR BOOT FLOW — PRIMARY + SECONDARY BOOT

BOOT ARCHITECTURE

A complete 3-stage SoC boot flow: Primary Boot (ROM)Secondary Bootloader (SBL)Application. This is the industry-standard approach used in ARM-based SoCs.

STAGE 1
Primary Boot
ROM → SRAM load
STAGE 2
Secondary Boot
SBL → DDR load
STAGE 3
Application
Main firmware runs
INTERFACE
QSPI Flash
Memory-mapped read

PRIMARY BOOT FLOW

Initialize Stack Pointer
Boot Mode Detection (QSPI / eMMC / UART)
PLL / Clock Init (minimal)
QSPI Init (indirect command mode)
Read SBL header from Flash
Validate CRC / Signature
Load SBL → SRAM
Jump to SBL entry point

SECONDARY BOOT FLOW

Full PLL Init + DDR Init (PHY training, calibration)
UART + GDMA + Peripheral Init
Enable caches / MMU
QSPI switch to memory-mapped mode
Read Application image header
Validate Image (CRC + RSA/ECDSA signature)
GDMA transfer → DDR
Cache clean / invalidate
Jump to application entry (from header)
Memory map defined: QSPI_BASE=0x40000000, SRAM_BASE=0x20000000, DDR_BASE=0x80000000
3-stage boot architecture correctly modeled with function pointer jumps
⚠️
Real ARM boot: CPU jumps to Reset Vector first, then boot.s startup assembly, then primary_boot()
⚠️
DDR init is complex — requires PHY training, timing configuration, and calibration before use
No image header structure — industry bootloaders use: magic + size + load_addr + entry_point + crc
No secure boot — real chips require RSA/ECC signature verification + SHA hash
primary_boot.c
C / FIRMWARE
// Memory Map
#define QSPI_BASE        0x40000000
#define SRAM_BASE        0x20000000
#define DDR_BASE         0x80000000
#define FLASH_BOOT_ADDR  0x00000000
#define FLASH_APP_ADDR   0x00100000
#define SBL_SIZE         0x00010000
#define APP_SIZE         0x00020000

// Image Header Structure (Industry Standard)
typedef struct {
    uint32_t magic;        // 0xDEADBEEF
    uint32_t size;
    uint32_t load_addr;
    uint32_t entry_point;
    uint32_t crc;
} image_header_t;

// PRIMARY BOOT (Boot ROM)
void primary_boot() {
    init_stack_pointer();   // MUST be first
    int mode = read_boot_mode();
    if(mode != 1) error_handler();
    pll_init();
    qspi_init();
    uint8_t *sbl_dst = (uint8_t *)SRAM_BASE;
    qspi_read(FLASH_BOOT_ADDR, sbl_dst, SBL_SIZE);
    if(!validate_crc(sbl_dst, SBL_SIZE)) error_handler();
    void (*sbl_entry)() = (void (*)())SRAM_BASE;
    sbl_entry();
}

// SECONDARY BOOT (SBL)
void secondary_boot() {
    ddr_init();             // PHY training + calibration
    uart_init();
    gdma_init();
    image_header_t hdr;
    qspi_read(FLASH_APP_ADDR, &hdr, sizeof(hdr));
    if(hdr.magic != 0xDEADBEEF) error_handler();
    uint8_t *app_src = (uint8_t *)(QSPI_BASE + FLASH_APP_ADDR);
    uint8_t *app_dst = (uint8_t *)(hdr.load_addr);
    gdma_config(app_src, app_dst, hdr.size);
    gdma_start();
    wait_for_completion();
    if(!validate_crc(app_dst, hdr.size)) error_handler();
    cache_invalidate_all();
    void (*app_entry)() = (void (*)())(hdr.entry_point);
    app_entry();
}

// APPLICATION (Final Stage)
void application_main() {
    while(1) { /* Main program */ }
}

int main() {
    primary_boot();  // Reset vector entry point
    return 0;
}
💾 QSPI SoC-Level UVM Verification Architecture

SYSTEM ARCHITECTURE

CPU (AHB/AXI Master)

AHB/AXI → QSPI Controller → QSPI Interface → Flash Model

UVM Components:
AHB/AXI Agent: drives transactions to QSPI controller
QSPI Agent: monitors serial protocol (IO0–IO3, CS, CLK)
Scoreboard: compares flash data vs expected
RAL Model: config registers of QSPI controller
CMD
0xEB
Quad I/O Fast Read
DATA MODE
x4 Quad
IO0–IO3 simultaneously
DUMMY
2–10
Clock cycles
SPEED
50–200 MHz
Effective throughput
✓ Page Program 0x02 ✓ Quad Page Prog 0x32 ✓ Fast Read 0x0B ✓ Quad IO Read 0xEB ✓ Sector Erase 0xD8 ✓ Write Enable 0x06
qspi_uvm_tb.sv
SYSTEMVERILOG / UVM
interface qspi_if(input bit clk);
  logic       cs_n;
  logic       sclk;
  logic [3:0] io;
  modport drv (output cs_n, sclk, io);
  modport mon (input  cs_n, sclk, io);
endinterface

class qspi_seq_item extends uvm_sequence_item;
  `uvm_object_utils(qspi_seq_item)
  rand bit [7:0]  cmd;
  rand bit [23:0] addr;
  rand bit [7:0]  data[];
  rand int        dummy_cycles;
  constraint c_cmd {
    cmd inside {8'h06, 8'h02, 8'h32, 8'h0B, 8'hEB, 8'hD8};
  }
  constraint c_dummy { dummy_cycles inside {[2:10]}; }
  constraint c_data_len { data.size() inside {[1:256]}; }
endclass

class qspi_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(qspi_scoreboard)
  uvm_analysis_imp #(qspi_seq_item, qspi_scoreboard) ap;
  bit [7:0] flash_model [bit [23:0]];
  int pass_cnt, fail_cnt;
  function void write(qspi_seq_item item);
    if(item.cmd == 8'h02 || item.cmd == 8'h32) begin
      foreach(item.data[i])
        flash_model[item.addr + i] = item.data[i];
    end else if(item.cmd == 8'h0B || item.cmd == 8'hEB) begin
      foreach(item.data[i]) begin
        if(item.data[i] === flash_model[item.addr + i]) pass_cnt++;
        else begin fail_cnt++; `uvm_error("SB", $sformatf("MISMATCH")) end
      end
    end
  endfunction
endclass
🌀 PLL — Full Operation & C Test Cases

WHAT IS A PLL?

A Phase Locked Loop (PLL) is a control system used to generate a stable high-frequency clock from a reference clock. It is widely used in SoC clock generation, CPUs, GPUs, DDR controllers, and communication systems.

FORMULA
Fout = Fref × M / (N × P)
Where M=multiplier, N=pre-div, P=post-div
EXAMPLE
1200 MHz
Fref=24, N=2, M=100 → VCO=1200MHz
VCO RANGE
800–3200 MHz
Typical SoC VCO operating window
LOCK TIME
50–500 µs
Time to achieve phase lock

PLL BLOCKS

Phase Detector (PD)Charge PumpLoop FilterVCOFeedback Divider → back to PD

INITIALIZATION SEQUENCE

1. Enable reference clock (24/25/26 MHz XTAL)
2. Configure N/M/P dividers
3. Enable PLL (PLL_CTRL bit 0)
4. Wait for PLL_STATUS[LOCK] = 1
5. Switch system clock mux to PLL output
PLL_CTRL — Enable/Disable PLL_DIV — M/N/P Dividers PLL_STATUS — Lock Flag PLL_BYPASS — Bypass Mode

PLL FAILURE CASES (FROM REAL SoC VALIDATION)

PLL not locking Incorrect divider config Clock instability Power supply noise Loop filter issues
pll_test_cases.c
C / FIRMWARE VALIDATION
#define PLL_CTRL    (*(volatile uint32_t*)0x4000A000)
#define PLL_DIV     (*(volatile uint32_t*)0x4000A004)
#define PLL_STATUS  (*(volatile uint32_t*)0x4000A008)
#define PLL_BYPASS  (*(volatile uint32_t*)0x4000A00C)

// TC1: PLL Enable
void test_pll_enable() {
    PLL_CTRL |= (1 << 0);
    if(PLL_CTRL & (1 << 0)) printf("[PASS] PLL Enable\n");
    else                     printf("[FAIL] PLL Enable\n");
}

// TC2: PLL Lock Detection
void test_pll_lock() {
    int timeout = 10000;
    while(!(PLL_STATUS & (1 << 0)) && timeout--);
    if(timeout > 0) printf("[PASS] PLL LOCK achieved\n");
    else             printf("[FAIL] PLL LOCK timeout\n");
}

// TC3: Divider Configuration (M=100, N=2, P=1 → Fout=1200MHz)
void test_pll_divider() {
    PLL_DIV = (100 << 16) | (2 << 8) | (1 << 0);
    if(PLL_DIV == ((100 << 16) | (2 << 8) | 1))
        printf("[PASS] Divider Config: Fout=1200MHz\n");
    else
        printf("[FAIL] Divider Config\n");
}

// TC4: Bypass Mode (Fout = Fref = 24MHz)
void test_pll_bypass() {
    PLL_BYPASS = 1;
    if(PLL_BYPASS == 1) printf("[PASS] PLL Bypass: Fout=Fref=24MHz\n");
    else                 printf("[FAIL] PLL Bypass\n");
}

// TC5: Disable
void test_pll_disable() {
    PLL_CTRL &= ~(1 << 0);
    if(!(PLL_CTRL & (1 << 0))) printf("[PASS] PLL Disabled\n");
    else                        printf("[FAIL] PLL Disable\n");
}

// TC6: Lock Time Measurement
void test_pll_lock_time() {
    uint32_t start = get_timer_us();
    PLL_CTRL |= (1 << 0);
    while(!(PLL_STATUS & (1 << 0)));
    uint32_t lock_time = get_timer_us() - start;
    printf("PLL Lock Time: %u us\n", lock_time);
    if(lock_time < 500) printf("[PASS] Lock time within spec\n");
    else                 printf("[FAIL] Lock time exceeded\n");
}

// TC7: Stress Test — 100 enable/disable cycles
void test_pll_stress() {
    for(int i = 0; i < 100; i++) {
        PLL_CTRL |= (1 << 0);
        while(!(PLL_STATUS & (1 << 0)));
        PLL_CTRL &= ~(1 << 0);
    }
    printf("[PASS] Stress: 100 enable/disable cycles\n");
}
🔄 GDMA — Generic DMA Controller

GDMA OVERVIEW

GDMA (Generic Direct Memory Access) is a hardware engine that transfers data between memory and peripherals without CPU intervention. It reduces processor bottleneck and enables high-speed data movement in SoC designs.

CHANNELS
8–16
Independent DMA channels
MODES
M2M / M2P / P2M
Memory & peripheral transfers
BURST
1/4/8/16
Beats per transaction
IRQ
TC / TE / HT
Transfer complete / error / half

WORKING FLOW

1. CPU configures GDMA (src, dst, size, burst, channel)
2. CPU enables GDMA channel
3. GDMA reads source address via AHB/AXI
4. GDMA writes to destination address
5. Transfer complete → IRQ sent to CPU
6. CPU reads TC status, clears interrupt
Memory → Memory Memory → Peripheral (TX) Peripheral → Memory (RX) Scatter-Gather (LLI)
gdma_seq_item.sv
SYSTEMVERILOG / UVM
typedef enum logic [1:0] {
  DMA_M2M=2'b00, DMA_M2P=2'b01, DMA_P2M=2'b10
} dma_dir_e;

class dma_seq_item extends uvm_sequence_item;
  `uvm_object_utils_begin(dma_seq_item)
    `uvm_field_int(src_addr,   UVM_ALL_ON)
    `uvm_field_int(dst_addr,   UVM_ALL_ON)
    `uvm_field_int(length,     UVM_ALL_ON)
    `uvm_field_int(burst_len,  UVM_ALL_ON)
    `uvm_field_int(channel_id, UVM_ALL_ON)
  `uvm_object_utils_end

  rand logic [31:0] src_addr;
  rand logic [31:0] dst_addr;
  rand logic [15:0] length;
  rand dma_dir_e    direction;
  rand logic [3:0]  burst_len;
  rand int unsigned channel_id;
  rand logic        irq_en;

  constraint c_align {
    src_addr[1:0] == 2'b00;
    dst_addr[1:0] == 2'b00;
  }
  constraint c_burst  { burst_len inside {1, 4, 8, 16}; }
  constraint c_channel { channel_id inside {[0:7]}; }
  constraint c_no_overlap {
    (src_addr + (length << 2)) <= dst_addr ||
    (dst_addr + (length << 2)) <= src_addr;
  }
  constraint c_sram {
    src_addr inside {[32'h2000_0000:32'h200F_FFFF]};
    dst_addr inside {[32'h2010_0000:32'h201F_FFFF]};
  }
endclass
🔌 APB UVM Testbench — Complete Architecture

APB PROTOCOL

APB (Advanced Peripheral Bus) is an AMBA protocol for low-bandwidth peripheral access. 2-cycle non-pipelined transfer: SETUP phase + ACCESS phase.

APB Phases:
IDLE → PSEL=0, PENABLE=0
SETUP → PSEL=1, PENABLE=0, PADDR/PWRITE valid
ACCESS → PSEL=1, PENABLE=1, wait PREADY
DONE → PREADY=1, transfer complete
PCLK PSEL PENABLE PADDR PWRITE PWDATA PRDATA PREADY PSLVERR PPROT PSTRB
apb_uvm_tb.sv
SYSTEMVERILOG / UVM
class apb_seq_item extends uvm_sequence_item;
  `uvm_object_utils_begin(apb_seq_item)
    `uvm_field_int(addr,  UVM_ALL_ON)
    `uvm_field_int(data,  UVM_ALL_ON)
    `uvm_field_int(wr,    UVM_ALL_ON)
    `uvm_field_int(delay, UVM_ALL_ON)
  `uvm_object_utils_end
  rand logic [31:0] addr;
  rand logic [31:0] data;
  rand logic         wr;
  rand int unsigned   delay;
  constraint c_align { addr[1:0] == 2'b00; }
  constraint c_delay { delay inside {[0:5]}; }
endclass

class apb_driver extends uvm_driver #(apb_seq_item);
  `uvm_component_utils(apb_driver)
  virtual apb_if vif;
  task drive_item(apb_seq_item item);
    @(posedge vif.pclk);
    vif.paddr  <= item.addr;
    vif.pwrite <= item.wr;
    vif.pwdata <= item.wr ? item.data : 'x;
    vif.psel   <= 1;
    vif.penable <= 0;
    @(posedge vif.pclk);
    vif.penable <= 1;
    do @(posedge vif.pclk);
    while(!vif.pready);
    vif.psel <= 0; vif.penable <= 0;
  endtask
endclass

// SVA Assertions
property apb_setup_phase;
  @(posedge PCLK) disable iff(!PRESETn)
  $rose(PSEL) |-> !PENABLE;
endproperty
a_setup: assert property(apb_setup_phase)
  else $error("APB: PENABLE high on PSEL assertion");

property apb_addr_stable;
  @(posedge PCLK) disable iff(!PRESETn)
  (PSEL && PENABLE) |-> $stable(PADDR) && $stable(PWRITE);
endproperty
a_stable: assert property(apb_addr_stable);
📌 GPIO Interrupt Verification

GPIO REGISTER MAP

0x00
GPIO_DIR
1=output, 0=input per pin
0x04
GPIO_DATA_OUT
Output data register
0x08
GPIO_DATA_IN
Input data (read-only)
0x0C
GPIO_INT_EN
Interrupt enable per pin
0x10
GPIO_INT_TYPE
Edge/Level select
0x14
GPIO_INT_STATUS
W1C — interrupt flag
💡
W1C (Write-1-to-Clear): Writing a 1 to INT_STATUS clears the interrupt flag. Writing 0 has no effect.
gpio_interrupt_seq.sv
SYSTEMVERILOG / UVM
`define GPIO_DIR        8'h00
`define GPIO_DATA_OUT   8'h04
`define GPIO_INT_EN     8'h0C
`define GPIO_INT_TYPE   8'h10
`define GPIO_INT_STATUS 8'h14

class gpio_rise_int_seq extends uvm_sequence #(gpio_seq_item);
  `uvm_object_utils(gpio_rise_int_seq)
  rand int pin_num;
  constraint c_pin { pin_num inside {[0:31]}; }

  task body();
    gpio_seq_item item;
    // 1. Set direction as INPUT
    item = gpio_seq_item::type_id::create("dir");
    start_item(item);
    void'(item.randomize() with {
      is_write==1; reg_addr==`GPIO_DIR;
      reg_data[pin_num]==0;
    }); finish_item(item);

    // 2. Set rising edge interrupt type
    item = gpio_seq_item::type_id::create("type");
    start_item(item);
    void'(item.randomize() with {
      is_write==1; reg_addr==`GPIO_INT_TYPE;
    }); finish_item(item);

    // 3. Enable interrupt on pin
    item = gpio_seq_item::type_id::create("en");
    start_item(item);
    void'(item.randomize() with {
      is_write==1; reg_addr==`GPIO_INT_EN;
      reg_data[pin_num]==1;
    }); finish_item(item);

    // 4. Clear INT_STATUS (W1C)
    item = gpio_seq_item::type_id::create("clr");
    start_item(item);
    void'(item.randomize() with {
      is_write==1; reg_addr==`GPIO_INT_STATUS;
      reg_data[pin_num]==1;
    }); finish_item(item);
  endtask
endclass
🔔 UVM SYNCHRONIZATION — Barrier & Event

uvm_barrier — MANY-TO-MANY SYNC

A uvm_barrier blocks multiple processes until a specified count of participants reach a common synchronization point, after which all are released together. Think of it as a checkpoint gate — nobody passes until everyone arrives.

SYNC TYPE
Many-to-Many
Count-based release
USE CASE
Group Sync
Multi-agent startup
KEY METHOD
wait_for()
Blocks until count met
RISK
Deadlock
Count mismatch hangs sim

uvm_event — ONE-TO-MANY NOTIFICATION

A uvm_event is a one-to-many communication mechanism where one process triggers an event and any number of processes waiting on it are released simultaneously. Like ringing a bell — one rings, many hear.

SYNC TYPE
One-to-Many
Trigger-based notification
KEY METHODS
trigger() / wait_trigger()
Fire and wait pattern
SAFE WAIT
wait_ptrigger()
Works if already fired
USE CASE
IRQ / Notification
Monitor→Scoreboard sync

BARRIER vs EVENT — KEY DIFFERENCE

uvm_barrier
Multiple processes wait at a count-based gate
All released when COUNT threshold is met
Use for: Phase sync, multi-agent simultaneous start

uvm_event
One process triggers, many listeners respond
All waiters released by a single TRIGGER signal
Use for: IRQ notification, monitor→scoreboard handoff
💡
Pro Tip: Use wait_ptrigger() instead of wait_trigger() when the event may fire before the waiter arrives — avoids missed-trigger bugs that are very hard to debug.
⚠️
Deadlock risk: If b = new("barrier", 4) but only 3 processes call wait_for(), simulation hangs forever. Always match count to actual process count.
🎯
Interview Answer: "uvm_barrier synchronizes multiple processes by count; uvm_event triggers one-to-many notification. Use barrier for group sync, event for notifications like IRQ or monitor handoffs."
uvm_barrier_example.sv
SYSTEMVERILOG / UVM
`include "uvm_macros.svh"
import uvm_pkg::*;

// 3 parallel processes synchronize at a barrier
class barrier_test extends uvm_test;
  `uvm_component_utils(barrier_test)
  uvm_barrier b;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    b = new("barrier", 3);  // ⚠ Must match process count!
  endfunction

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    fork
      process1();  // reaches at time 10
      process2();  // reaches at time 20
      process3();  // reaches at time 30 — slowest
    join           // ALL cross together at time 30
    phase.drop_objection(this);
  endtask

  task process1();
    `uvm_info("P1", "Started", UVM_LOW)
    #10;
    `uvm_info("P1", "Reached barrier", UVM_LOW)
    b.wait_for();
    `uvm_info("P1", "Crossed barrier", UVM_LOW)
  endtask

  task process2();
    `uvm_info("P2", "Started", UVM_LOW)
    #20;
    `uvm_info("P2", "Reached barrier", UVM_LOW)
    b.wait_for();
    `uvm_info("P2", "Crossed barrier", UVM_LOW)
  endtask

  task process3();
    `uvm_info("P3", "Started", UVM_LOW)
    #30;  // Slowest — all other processes wait for this
    `uvm_info("P3", "Reached barrier", UVM_LOW)
    b.wait_for();
    `uvm_info("P3", "Crossed barrier", UVM_LOW)
  endtask
endclass

module top;
  initial run_test("barrier_test");
endmodule
uvm_event_example.sv
SYSTEMVERILOG / UVM
// uvm_event: One producer triggers, many consumers react
class event_test extends uvm_test;
  `uvm_component_utils(event_test)
  uvm_event ev;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    ev = new("ev");
  endfunction

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    fork
      producer();
      consumer1();
      consumer2();
    join
    phase.drop_objection(this);
  endtask

  task producer();
    #20;
    `uvm_info("PROD", "Triggering event", UVM_LOW)
    ev.trigger();  // Single trigger unblocks ALL waiters
  endtask

  task consumer1();
    `uvm_info("CONS1", "Waiting...", UVM_LOW)
    ev.wait_trigger();
    `uvm_info("CONS1", "Event received!", UVM_LOW)
  endtask

  task consumer2();
    `uvm_info("CONS2", "Waiting...", UVM_LOW)
    ev.wait_ptrigger();  // Safe: works even if already triggered
    `uvm_info("CONS2", "Event received!", UVM_LOW)
  endtask
endclass

module top;
  initial run_test("event_test");
endmodule

// Key uvm_event API:
// ev.trigger()         → fires the event
// ev.wait_trigger()    → blocks until triggered (may miss if early)
// ev.wait_ptrigger()   → safe: works even if triggered before wait
// ev.is_on()           → returns 1 if event has been triggered
// ev.reset()           → clears event state for reuse
⚡ CLOCK GLITCH DETECTION — Runtime Assertions

WHAT IS A CLOCK GLITCH?

A clock glitch is an unexpected short pulse, extra edge, or duty-cycle distortion in a clock signal. Glitches cause flip-flop metastability, incorrect data capture, and hard-to-debug functional failures.

TYPE 1
Narrow Pulse
HIGH/LOW period too short
TYPE 2
Extra Edge
Unexpected transition
TYPE 3
Period Drift
Unstable clock period
TYPE 4
Duty Cycle Error
Not 50% as expected

WHEN DO GLITCHES OCCUR?

During clock gating enable/disable transitions
Reset release at asynchronous time boundary
PLL switchover (clock mux change)
Clock domain crossing (CDC) paths
Power-on / power-down sequences

GLITCH DETECTION CHECKLIST

✓ Min Pulse Width ✓ Period Stability ✓ No Extra Edges ✓ Duty Cycle 50% ✓ Clock Gating Safety ✓ Reset Transition ✓ CDC Glitch Check ✓ Random Stress
⚠️
Debug Tip: A "HIGH time violation" assertion typically indicates glitch, duty-cycle issue, or clock gating problem. Add $display("CLK transition at %0t", $time) and inspect waveform in DVE/Verdi.
clk_glitch_assertions.sv
SYSTEMVERILOG / SVA
`define MIN_PULSE_WIDTH  4   // ns — adjust to your clock spec
`define CLK_PERIOD       10  // ns — expected clock period
`define JITTER_DELTA     1   // ns — allowed jitter tolerance
`define MIN_HIGH_TIME    4   // ns — minimum HIGH duration

// ─── TC1: Minimum Pulse Width Check ──────────────
time prev_edge;

always @(posedge clk or negedge clk) begin
  if (prev_edge != 0) begin
    assert (($time - prev_edge) >= `MIN_PULSE_WIDTH)
      else $error("GLITCH: Pulse width %0t ns at %0t",
                  ($time - prev_edge), $time);
  end
  prev_edge = $time;
end

// ─── TC2: Clock Period Stability Check ───────────
time last_posedge;

always @(posedge clk) begin
  if (last_posedge != 0) begin
    assert (($time - last_posedge) inside
            {[(`CLK_PERIOD-`JITTER_DELTA):
              (`CLK_PERIOD+`JITTER_DELTA)]})
      else $error("PERIOD GLITCH: %0t ns at %0t",
                  ($time - last_posedge), $time);
  end
  last_posedge = $time;
end

// ─── TC3: No Extra Edge Detection ────────────────
bit expected_clk;
always @(posedge ref_clk) expected_clk <= ~expected_clk;

always @(clk) begin
  assert (clk === expected_clk)
    else $error("EXTRA EDGE: Unexpected transition at %0t", $time);
end

// ─── TC4: Duty Cycle Check (50% expected) ────────
time rise_time, fall_time;
always @(posedge clk) rise_time = $time;

always @(negedge clk) begin
  fall_time = $time;
  assert ((fall_time - rise_time) >= `MIN_HIGH_TIME)
    else $error("DUTY CYCLE: HIGH=%0t ns at %0t",
                (fall_time - rise_time), $time);
end

// ─── TC5: Clock Gating Glitch (SVA Property) ─────
property no_glitch_on_gating;
  @(posedge clk) disable iff (!reset)
  $stable(gated_clk) || $rose(gated_clk) || $fell(gated_clk);
endproperty
a_no_gating_glitch: assert property(no_glitch_on_gating)
  else $error("GATING GLITCH detected on gated_clk");

// ─── TC6: Random Stress Test ─────────────────────
task run_clk_stress_test();
  repeat(1000) begin
    clk_enable = $urandom_range(0, 1);
    @(posedge clk);
  end
  $display("[PASS] Stress: 1000 random enable cycles");
endtask
🔌 AHB — SPLIT vs RETRY vs HSPLIT

WHY SPLIT EXISTS IN AHB

In AMBA AHB, when a slave needs a long time to prepare data (slow memory, busy peripheral), it must release the bus so other masters can use it. This is the purpose of SPLIT — true bus sharing for long-latency transactions.

SPLIT
Response Type
Slave: "come back later" — bus freed
RETRY
Response Type
Bus held, try again next cycle
HSPLIT
Control Signal
Slave: "master X can now retry"
HRESP
2-bit Signal
OKAY/ERROR/RETRY/SPLIT

SPLIT RESPONSE — STEP-BY-STEP FLOW

1. Master sends transaction to Slave
2. Slave is busy → responds HRESP = SPLIT
3. Arbiter sees SPLIT → removes master from priority queue
4. Bus is freed — other masters can now use it
5. Slave becomes ready → asserts HSPLIT[master_id] bit
6. Arbiter sees HSPLIT → re-grants bus to that specific master
7. Master retries the original transaction → gets OKAY response

SPLIT vs RETRY — THE CRITICAL DIFFERENCE

RETRY:
Slave says "try again immediately"
Master KEEPS the bus — bus is NOT released
Arbiter re-grants to same master next cycle
Use for: short delays only

SPLIT:
Slave says "come back later"
Master RELEASES the bus completely
Other masters can use bus while slave prepares data
Use for: long delays — true multi-master bus sharing

HSPLIT SIGNAL EXPLAINED

HSPLIT is a multi-bit signal (one bit per master) sent from the slave to the arbiter. When bit N is asserted, it means "Master N is now permitted to retry its transaction." Without HSPLIT assertion, the arbiter will NOT re-grant the bus to that master.

SPLIT = response action by slave HSPLIT = re-enable signal to arbiter RETRY = hold bus, immediate retry
🎯
Interview Answer: "SPLIT and RETRY are both 2-cycle AHB responses for delayed transfers. SPLIT releases the bus and uses HSPLIT to re-grant specific masters later. RETRY holds the bus and forces the same master to try again immediately. HSPLIT is a separate signal — not the same as SPLIT."
ahb_split_assertions.sv
SYSTEMVERILOG / SVA
// AHB Response Encoding (HRESP[1:0])
typedef enum logic [1:0] {
  OKAY  = 2'b00,
  ERROR = 2'b01,
  RETRY = 2'b10,
  SPLIT = 2'b11
} hresp_e;

// AHB Interface (relevant signals)
interface ahb_if(input bit HCLK);
  logic [31:0] HADDR;
  logic [1:0]  HTRANS;   // IDLE/BUSY/NONSEQ/SEQ
  logic         HWRITE;
  logic [31:0] HWDATA, HRDATA;
  logic [1:0]  HRESP;    // OKAY/ERROR/RETRY/SPLIT
  logic         HREADY;
  logic [15:0] HSPLIT;   // Bit per master — re-grant signal
endinterface

// SVA: Both SPLIT and RETRY are 2-cycle responses
// Cycle 1: HRESP asserted, HREADY=0
// Cycle 2: HRESP still asserted, HREADY=1
property ahb_split_two_cycle;
  @(posedge HCLK) disable iff(!HRESETn)
  (HRESP == SPLIT && !HREADY) |=> (HRESP == SPLIT && HREADY);
endproperty
a_split_two_cycle: assert property(ahb_split_two_cycle)
  else $error("AHB: SPLIT must be a 2-cycle response");

property ahb_retry_two_cycle;
  @(posedge HCLK) disable iff(!HRESETn)
  (HRESP == RETRY && !HREADY) |=> (HRESP == RETRY && HREADY);
endproperty
a_retry_two_cycle: assert property(ahb_retry_two_cycle)
  else $error("AHB: RETRY must be a 2-cycle response");

// SVA: After SPLIT, master must NOT get bus until HSPLIT asserted
property ahb_hsplit_required(master_id);
  @(posedge HCLK) disable iff(!HRESETn)
  $rose(HGRANT[master_id]) |->
    $past(HSPLIT[master_id], 1);
endproperty
generate
  for(genvar i=0; i<16; i++) begin : g_hsplit
    a_hsplit: assert property(ahb_hsplit_required(i))
      else $error("AHB: Bus granted to M%0d before HSPLIT", i);
  end
endgenerate
◉ COVERAGE CLOSURE — Sign-off & EXCLUDE

THE SCENARIO: 100% FUNC + 95% CODE COVERAGE

If functional coverage is 100% and code coverage is 95%, you do not blindly proceed to sign-off. The critical question is: what does that missing 5% represent? Not all uncovered code is equally important.

FUNC COV 100%
Testplan Done
All planned scenarios verified
CODE COV 95%
RTL Lines Unexercised
Some branches/lines not hit
KEY QUESTION
What is that 5%?
Dead code or real gap?
INDUSTRY NORM
90–95% Code OK
With justified waivers

THE "EXCLUDE" KEYWORD — WHAT IT MEANS

The exclude mechanism intentionally removes certain RTL code from the coverage calculation. When RTL is confirmed unreachable by the design team, excluding it prevents falsely low coverage numbers — but exclusions require proper justification and documentation.

Correct Coverage Closure Flow:
1. Analyze coverage report (line/branch/FSM/toggle coverage)
2. Identify uncovered code — categorize each item
3. Confirm with RTL designer: "Is this code reachable?"
4. If unreachable → apply EXCLUDE with written justification
5. Re-run coverage tool → verify target is now met
6. Document as coverage waivers → proceed to sign-off

VALID EXCLUSION CASES (CAN EXCLUDE)

Default FSM case that can never occur due to binary encoding
Safety check: if (addr > MAX_ADDR) — constraints ensure this is never reached
Debug output signals not synthesized in production build
Power-down paths not modeled in simulation environment

INVALID EXCLUSION CASES (DO NOT EXCLUDE)

Valid functional paths you simply haven't written tests for yet
Reset / error / corner cases that ARE architecturally reachable
FSM states or transitions defined in the design spec
⚠️
Golden Rule: Never exclude just to reach 100%. Every exclusion must be analyzed, confirmed by the design team, and documented as a coverage waiver before sign-off.
🎯
Interview Answer: "I won't directly proceed at 95% code coverage. I'll analyze the uncovered code. If it's unreachable RTL confirmed by the designer, I'll exclude it using tool mechanisms and document the waiver. If it's a valid path, I'll add tests before sign-off."
coverage_model_and_exclusions.sv
SYSTEMVERILOG / COVERAGE
// ─── Coverage Exclusion Pragmas (Tool-Specific) ──

// VCS / Questa / Xcelium style pragma:
// pragma coverage off
if (state == 4'hF) begin
  // Unreachable — only states 0,1,2 are valid
  // RTL designer confirmed: never reachable
  // Waiver ID: WAV-PLL-001
  error_out <= 1;
end
// pragma coverage on

// ─── Functional Coverage Model — QSPI Example ────
covergroup qspi_cg @(posedge clk);

  // All QSPI commands
  cp_cmd: coverpoint cmd {
    bins write_en   = {8'h06};  // Write Enable
    bins page_prog  = {8'h02};  // Page Program
    bins quad_prog  = {8'h32};  // Quad Page Program
    bins fast_read  = {8'h0B};  // Fast Read
    bins quad_read  = {8'hEB};  // Quad I/O Read
    bins sect_erase = {8'hD8};  // Sector Erase
    // cmd[7:6]=2'b10 → EXCLUDED (reserved, unreachable)
    illegal_bins reserved = {[8'h80:8'hBF]};
  }

  // Address coverage
  cp_addr: coverpoint addr {
    bins low  = {[0:24'h0FFFFF]};
    bins mid  = {[24'h100000:24'hEFFFFF]};
    bins high = {[24'hF00000:24'hFFFFFF]};
  }

  // Dummy cycles
  cp_dummy: coverpoint dummy_cycles {
    bins min_d = {2};
    bins mid_d = {[3:9]};
    bins max_d = {10};
  }

  // Cross coverage: command × address region
  cx_cmd_addr: cross cp_cmd, cp_addr;

endgroup

// ─── Coverage Waiver Log (Documentation) ─────────
// Item                 | Coverage | Action  | Waiver ID
// ─────────────────────┼──────────┼─────────┼──────────
// FSM default state    |   0%     | EXCLUDE | WAV-001
// cmd[7:6]=2b10        |   0%     | EXCLUDE | WAV-002
// Error inject path    |   0%     | ADD TEST| N/A
// Interrupt handler    |   0%     | ADD TEST| N/A
// Debug SFR access     |   0%     | EXCLUDE | WAV-003
GET IN TOUCH

Contact

👤
NAME
Shaik Khaja Mohiddeen
🎯
ROLE
Design Verification Engineer
📍
LOCATION
Hyderabad, Telangana, India
💻
PLATFORM
DV_HUB — Design Verification Hub
🔬
SPECIALIZATION
UVM · SV · Protocol Verification