SystemVerilog Assertions & Functional Coverage
Master SVA, functional coverage, and coverage-driven verification for comprehensive design validation.
π‘οΈ Verification Fundamentals
SystemVerilog Assertions (SVA)
AdvancedCreate powerful temporal assertions to verify protocol compliance and design behavior.
Key Topics:
- β’Immediate vs concurrent assertions
- β’Sequence definitions and operators
- β’Property declarations and binding
- β’Temporal operators (##, [*], [->])
- β’System tasks ($assert, $cover, $assume)
Basic Syntax:
// Basic SVA syntax
// Immediate assertion
always_comb begin
assert (reset_n || !valid)
else $error("Valid should be low during reset");
end
// Concurrent assertion
property req_ack_handshake;
@(posedge clk) disable iff (!reset_n)
req |-> ##[1:3] ack;
endproperty
assert property (req_ack_handshake)
else $error("Request not acknowledged in time");
// Sequence definition
sequence addr_valid_seq;
@(posedge clk) addr_valid ##1 !addr_valid;
endsequenceAdvanced Implementation:
// Comprehensive SVA example for AXI protocol
interface axi_assertions_if (
input logic aclk,
input logic aresetn,
// AXI signals
input logic awvalid, awready,
input logic wvalid, wready,
input logic bvalid, bready,
input logic arvalid, arready,
input logic rvalid, rready
);
// Sequence definitions
sequence write_addr_handshake;
@(posedge aclk) awvalid && awready;
endsequence
sequence write_data_handshake;
@(posedge aclk) wvalid && wready;
endsequence
sequence write_resp_handshake;
@(posedge aclk) bvalid && bready;
endsequence
sequence read_addr_handshake;
@(posedge aclk) arvalid && arready;
endsequence
sequence read_data_handshake;
@(posedge aclk) rvalid && rready;
endsequence
// Property definitions
property write_sequence_order;
@(posedge aclk) disable iff (!aresetn)
write_addr_handshake ##0 write_data_handshake |->
##[1:16] write_resp_handshake;
endproperty
property read_sequence;
@(posedge aclk) disable iff (!aresetn)
read_addr_handshake |-> ##[1:16] read_data_handshake;
endproperty
property valid_stable;
@(posedge aclk) disable iff (!aresetn)
(awvalid && !awready) |=> $stable(awvalid);
endproperty
property no_valid_during_reset;
@(posedge aclk)
!aresetn |-> (!awvalid && !wvalid && !arvalid);
endproperty
property exclusive_write_channels;
@(posedge aclk) disable iff (!aresetn)
!(write_addr_handshake && write_data_handshake && write_resp_handshake);
endproperty
// Assertion bindings
assert_write_order: assert property(write_sequence_order)
else $error("[%0t] Write sequence violation", $time);
assert_read_sequence: assert property(read_sequence)
else $error("[%0t] Read sequence violation", $time);
assert_valid_stable: assert property(valid_stable)
else $error("[%0t] Valid signal not stable", $time);
assert_reset_behavior: assert property(no_valid_during_reset)
else $error("[%0t] Valid signals active during reset", $time);
// Coverage assertions
cover_write_sequence: cover property(write_sequence_order);
cover_read_sequence: cover property(read_sequence);
cover_back_to_back_writes: cover property(
@(posedge aclk) write_addr_handshake ##1 write_addr_handshake
);
// Assumptions for formal verification
assume_reset_duration: assume property(
@(posedge aclk) $fell(aresetn) |-> ##[1:10] $rose(aresetn)
);
endinterfaceFunctional Coverage
IntermediateImplement comprehensive functional coverage to measure verification completeness and identify gaps.
Key Topics:
- β’Covergroups and coverpoints
- β’Cross coverage and bins
- β’Coverage options and sampling
- β’Temporal coverage with transitions
- β’Coverage-driven verification
Basic Syntax:
// Basic functional coverage
covergroup cg_packet @(posedge clk);
// Simple coverpoint
cp_length: coverpoint packet.length {
bins small = {[1:64]};
bins medium = {[65:256]};
bins large = {[257:1500]};
}
// Cross coverage
cp_type: coverpoint packet.pkt_type;
cross_type_length: cross cp_type, cp_length;
// Transition coverage
cp_state: coverpoint fsm.state {
bins transitions = (IDLE => ACTIVE),
(ACTIVE => WAIT),
(WAIT => DONE);
}
endgroupAdvanced Implementation:
// Advanced functional coverage example
class network_coverage;
// Packet properties
typedef enum {TCP, UDP, ICMP, ARP} protocol_e;
typedef enum {LOW, MED, HIGH, URGENT} priority_e;
// Coverage group for protocol analysis
covergroup cg_protocol_coverage @(posedge clk);
option.per_instance = 1;
option.name = "protocol_analysis";
// Protocol distribution
cp_protocol: coverpoint protocol {
bins tcp_traffic = {TCP};
bins udp_traffic = {UDP};
bins icmp_traffic = {ICMP};
bins arp_traffic = {ARP};
// Illegal bins
illegal_bins reserved = {4,5,6,7};
}
// Packet size coverage with custom bins
cp_size: coverpoint size {
bins tiny = {[1:63]};
bins small = {[64:127]};
bins medium = {[128:511]};
bins large = {[512:1023]};
bins jumbo = {[1024:1500]};
bins huge = {[1501:$]};
// Ignore specific values
ignore_bins zero_size = {0};
}
// Priority with weighted bins
cp_priority: coverpoint priority {
bins low = {LOW} with (item inside {[0:25]});
bins medium = {MED} with (item inside {[26:75]});
bins high = {HIGH} with (item inside {[76:90]});
bins urgent = {URGENT} with (item inside {[91:100]});
}
// Cross coverage for protocol combinations
cross_protocol_size: cross cp_protocol, cp_size {
// Focus on specific combinations
bins tcp_large = binsof(cp_protocol.tcp_traffic) &&
binsof(cp_size.large);
bins udp_small = binsof(cp_protocol.udp_traffic) &&
binsof(cp_size.small);
// Ignore unrealistic combinations
ignore_bins arp_jumbo = binsof(cp_protocol.arp_traffic) &&
binsof(cp_size.jumbo);
}
// Complex cross with conditions
cross_prio_proto_size: cross cp_priority, cp_protocol, cp_size {
option.cross_num_print_missing = 10;
// Define interesting scenarios
bins urgent_tcp_any = binsof(cp_priority.urgent) &&
binsof(cp_protocol.tcp_traffic);
bins low_udp_small = binsof(cp_priority.low) &&
binsof(cp_protocol.udp_traffic) &&
binsof(cp_size.small);
}
endgroup
// State machine coverage
covergroup cg_fsm_coverage @(posedge clk);
option.per_instance = 1;
cp_current_state: coverpoint current_state {
bins idle_state = {IDLE};
bins active_state = {ACTIVE};
bins wait_state = {WAIT};
bins done_state = {DONE};
bins error_state = {ERROR};
}
cp_next_state: coverpoint next_state;
// State transitions
cp_state_transitions: coverpoint current_state {
bins valid_transitions =
(IDLE => ACTIVE),
(ACTIVE => WAIT),
(ACTIVE => DONE),
(WAIT => ACTIVE),
(WAIT => DONE),
(DONE => IDLE),
(ERROR => IDLE);
bins error_transitions =
(IDLE => ERROR),
(ACTIVE => ERROR),
(WAIT => ERROR);
// Illegal transitions
illegal_bins invalid =
(DONE => ACTIVE),
(DONE => WAIT),
(ERROR => ACTIVE);
}
// Cross current and next states
cross_state_machine: cross cp_current_state, cp_next_state;
endgroup
// Temporal coverage with sequences
covergroup cg_temporal_coverage @(posedge clk);
// Back-to-back transactions
cp_back_to_back: coverpoint {
sequence back_to_back_seq;
valid && ready ##1 valid && ready;
endsequence
cover property(@(posedge clk) back_to_back_seq);
}
// Burst transactions
cp_burst_length: coverpoint burst_count iff (burst_active) {
bins single = {1};
bins short = {[2:4]};
bins medium = {[5:8]};
bins long = {[9:16]};
}
endgroup
// Coverage instantiation and control
function new();
cg_protocol_coverage = new();
cg_fsm_coverage = new();
cg_temporal_coverage = new();
// Set coverage options
cg_protocol_coverage.option.goal = 95;
cg_protocol_coverage.option.comment = "Network protocol coverage";
endfunction
function void sample_packet(input packet_t pkt);
this.protocol = pkt.protocol;
this.size = pkt.size;
this.priority = pkt.priority;
cg_protocol_coverage.sample();
endfunction
function void sample_fsm(input state_e curr, state_e next);
this.current_state = curr;
this.next_state = next;
cg_fsm_coverage.sample();
endfunction
function real get_coverage();
real protocol_cov = cg_protocol_coverage.get_inst_coverage();
real fsm_cov = cg_fsm_coverage.get_inst_coverage();
return (protocol_cov + fsm_cov) / 2.0;
endfunction
function void print_coverage();
$display("=== Coverage Report ===");
$display("Protocol Coverage: %.2f%%",
cg_protocol_coverage.get_inst_coverage());
$display("FSM Coverage: %.2f%%",
cg_fsm_coverage.get_inst_coverage());
$display("Overall Coverage: %.2f%%", get_coverage());
// Print missing coverage
cg_protocol_coverage.option.print_missing = 1;
endfunction
endclassCoverage-Driven Verification
ExpertImplement feedback-driven verification flows that automatically adjust stimulus based on coverage holes.
Key Topics:
- β’Coverage feedback loops
- β’Adaptive test generation
- β’Coverage exclusion and waivers
- β’Metrics and reporting
- β’Integration with formal verification
Basic Syntax:
// Coverage-driven test generation
class adaptive_test_generator;
coverage_database cov_db;
constraint_engine engine;
function void generate_stimulus();
// Analyze coverage holes
hole_analysis_t holes = cov_db.find_holes();
// Adjust constraints based on holes
foreach(holes[i]) begin
engine.boost_probability(holes[i].scenario, 2.0);
end
// Generate targeted stimulus
stimulus_t stim = engine.generate();
endfunction
endclassAdvanced Implementation:
// Complete coverage-driven verification framework
class coverage_driven_testbench;
// Coverage database and analysis
typedef struct {
string scenario_name;
real current_coverage;
real target_coverage;
int hit_count;
int total_attempts;
} coverage_scenario_t;
coverage_scenario_t coverage_scenarios[];
real overall_coverage_target = 95.0;
int max_iterations = 10000;
// Test generation engine
class adaptive_generator;
rand bit [31:0] data;
rand bit [7:0] cmd;
rand bit [3:0] priority;
rand bit [15:0] addr;
// Dynamic constraint weights
real data_weights[4] = '{25.0, 25.0, 25.0, 25.0}; // Equal initially
real cmd_weights[16]; // Command weights
real addr_weights[4]; // Address region weights
constraint adaptive_data {
data dist {
[32'h0000_0000:32'h3FFF_FFFF] := data_weights[0],
[32'h4000_0000:32'h7FFF_FFFF] := data_weights[1],
[32'h8000_0000:32'hBFFF_FFFF] := data_weights[2],
[32'hC000_0000:32'hFFFF_FFFF] := data_weights[3]
};
}
constraint adaptive_cmd {
cmd dist {
[8'h00:8'h03] := cmd_weights[0],
[8'h04:8'h07] := cmd_weights[1],
[8'h08:8'h0B] := cmd_weights[2],
[8'h0C:8'h0F] := cmd_weights[3]
};
}
function void update_weights(string scenario, real boost_factor);
case (scenario)
"high_data": data_weights[3] *= boost_factor;
"low_addr": addr_weights[0] *= boost_factor;
"rare_cmd": cmd_weights[3] *= boost_factor;
default: $warning("Unknown scenario: %s", scenario);
endcase
endfunction
function new();
// Initialize uniform weights
foreach(cmd_weights[i]) cmd_weights[i] = 6.25; // 16 commands
foreach(addr_weights[i]) addr_weights[i] = 25.0;
endfunction
endclass
// Coverage analysis and feedback
class coverage_analyzer;
network_coverage net_cov;
function coverage_scenario_t[] analyze_holes();
coverage_scenario_t holes[];
real protocol_cov = net_cov.cg_protocol_coverage.get_coverage();
real fsm_cov = net_cov.cg_fsm_coverage.get_coverage();
// Identify specific coverage holes
if (net_cov.cg_protocol_coverage.cp_protocol.get_coverage() < 90.0) begin
coverage_scenario_t hole;
hole.scenario_name = "rare_protocols";
hole.current_coverage = net_cov.cg_protocol_coverage.cp_protocol.get_coverage();
hole.target_coverage = 95.0;
holes = {holes, hole};
end
if (net_cov.cg_protocol_coverage.cross_protocol_size.get_coverage() < 85.0) begin
coverage_scenario_t hole;
hole.scenario_name = "protocol_size_cross";
hole.current_coverage = net_cov.cg_protocol_coverage.cross_protocol_size.get_coverage();
hole.target_coverage = 90.0;
holes = {holes, hole};
end
return holes;
endfunction
function real calculate_progress_rate(coverage_scenario_t scenario);
if (scenario.total_attempts == 0) return 0.0;
return real'(scenario.hit_count) / real'(scenario.total_attempts) * 100.0;
endfunction
function bit is_coverage_sufficient();
real overall = (net_cov.cg_protocol_coverage.get_coverage() +
net_cov.cg_fsm_coverage.get_coverage()) / 2.0;
return overall >= overall_coverage_target;
endfunction
endclass
// Main verification loop
task run_coverage_driven_verification();
adaptive_generator gen = new();
coverage_analyzer analyzer = new();
int iteration = 0;
$display("Starting coverage-driven verification...");
$display("Target coverage: %.1f%%", overall_coverage_target);
while (iteration < max_iterations && !analyzer.is_coverage_sufficient()) begin
// Analyze current coverage state
coverage_scenario_t holes[] = analyzer.analyze_holes();
// Report progress every 100 iterations
if (iteration % 100 == 0) begin
real current_cov = (net_cov.cg_protocol_coverage.get_coverage() +
net_cov.cg_fsm_coverage.get_coverage()) / 2.0;
$display("[%0d] Current coverage: %.2f%%, Holes: %0d",
iteration, current_cov, holes.size());
end
// Adjust generation strategy based on holes
foreach(holes[i]) begin
real boost_factor = calculate_boost_factor(holes[i]);
gen.update_weights(holes[i].scenario_name, boost_factor);
$display(" Boosting scenario '%s' by %.1fx (current: %.1f%%)",
holes[i].scenario_name, boost_factor, holes[i].current_coverage);
end
// Generate and apply stimulus
if (gen.randomize()) begin
apply_stimulus(gen.data, gen.cmd, gen.priority, gen.addr);
net_cov.sample_packet(create_packet(gen));
// Update hole statistics
foreach(holes[i]) begin
holes[i].total_attempts++;
if (hit_target_scenario(holes[i].scenario_name, gen)) begin
holes[i].hit_count++;
end
end
end else begin
$error("Failed to randomize stimulus at iteration %0d", iteration);
end
iteration++;
end
// Final coverage report
$display("
=== Final Coverage Report ===");
$display("Iterations completed: %0d", iteration);
$display("Protocol coverage: %.2f%%", net_cov.cg_protocol_coverage.get_coverage());
$display("FSM coverage: %.2f%%", net_cov.cg_fsm_coverage.get_coverage());
real final_coverage = (net_cov.cg_protocol_coverage.get_coverage() +
net_cov.cg_fsm_coverage.get_coverage()) / 2.0;
$display("Overall coverage: %.2f%%", final_coverage);
if (final_coverage >= overall_coverage_target) begin
$display("β Coverage target achieved!");
end else begin
$display("β Coverage target not reached. Consider:");
$display(" - Increasing max_iterations");
$display(" - Adding more targeted constraints");
$display(" - Reviewing coverage model");
end
endtask
function real calculate_boost_factor(coverage_scenario_t hole);
real gap = hole.target_coverage - hole.current_coverage;
if (gap > 20.0) return 4.0; // Large gap - high boost
else if (gap > 10.0) return 2.5; // Medium gap
else if (gap > 5.0) return 1.5; // Small gap
else return 1.1; // Minimal boost
endfunction
function bit hit_target_scenario(string scenario, adaptive_generator gen);
case (scenario)
"rare_protocols": return (gen.cmd inside {8'h0C, 8'h0D, 8'h0E, 8'h0F});
"high_data": return (gen.data >= 32'hC000_0000);
"protocol_size_cross": return (gen.cmd == 8'h01 && gen.data > 32'h8000_0000);
default: return 0;
endcase
endfunction
endclassπ― Assertion Categories
Safety Properties
Something bad never happens
Example:
Bus collision never occurs
SVA Syntax:
assert property (@(posedge clk) !(req1 && req2));
Liveness Properties
Something good eventually happens
Example:
Every request gets acknowledged
SVA Syntax:
assert property (@(posedge clk) req |-> ##[1:$] ack);
Fairness Properties
Resources are allocated fairly
Example:
All masters get bus access
SVA Syntax:
assert property (@(posedge clk) req |-> ##[1:10] grant);
Functional Properties
Correct functional behavior
Example:
Data integrity maintained
SVA Syntax:
assert property (@(posedge clk) write |-> ##1 (mem[addr] == data));
π Coverage Best Practices
Layered Coverage Model
Build coverage in logical layers from basic to complex
Cross Coverage
Combine multiple dimensions for comprehensive analysis
Temporal Coverage
Cover sequences and state transitions
Exclude Unreachable Scenarios
Use ignore_bins for impossible combinations
π Key Verification Metrics
Functional Coverage
Percentage of design features exercised
Code Coverage
Percentage of RTL code executed
Assertion Coverage
Percentage of assertions triggered
Bug Discovery Rate
Rate of finding new bugs over time
Ready to Build Comprehensive Verification Environments?
Master SVA and functional coverage to create robust, metrics-driven verification flows.