A verification course can cover SystemVerilog syntax, UVM components and functional coverage yet still leave learners unprepared for project work.
The missing element is usually not another language feature. It is the engineering loop around the language:
- Interpret a specification
- Identify uncertainty
- Create a verification strategy
- Build observable checks
- Run a repeatable flow
- Debug failures
- Assess coverage
- Explain residual risk
A small FIFO is ideal for teaching this loop because the data path is easy to understand, while the corner cases are rich enough to expose weak verification.
The important design decision is to make the FIFO deliberately imperfect.
Start with an Incomplete Specification
Do not give learners a perfectly bounded problem.
Provide a short specification:
- Configurable depth
- Synchronous write and read operations
- Full and empty indicators
- Support for simultaneous read and write
- Reset clears the FIFO
Then leave several questions open:
- What happens when a read is requested while the FIFO is empty?
- Is a write accepted on the cycle in which full deasserts?
- What is the output value after reset?
- Can read and write occur simultaneously while the FIFO is full?
- Are the status flags registered or combinational?
- How soon after reset may traffic begin?
The first exercise is not coding.
It is producing a clarification table.
| Question | Assumption | Verification consequence |
|---|---|---|
| Read while empty | Request is ignored | Assert that occupancy remains zero |
| Write while full | Request is rejected | Assert that memory, pointers and occupancy remain unchanged |
| Simultaneous read and write | Occupancy remains unchanged | Check ordering and data continuity |
| Reset | Pointers and occupancy are cleared | Assert that empty is high and full is low |
This teaches a habit that scales beyond FIFOs:
Verification begins by defining the claim.
Seed Controlled Defects
A training design should contain known defects.
Keep a private fault list so that the verification environment itself can be qualified.
Useful FIFO defects include:
- Occupancy incrementing during simultaneous read and write
- A write pointer wrapping one cycle late
- Full asserting at DEPTH - 1
- A read while empty corrupting the output
- Reset clearing pointers but not occupancy
- Incorrect wrapping for a non-power-of-two depth
- A scoreboard failing to detect duplicated data
- An assertion being disabled during the exact cycle in which it should fire
Introduce defects gradually.
Early learners may receive one obvious bug. More advanced learners can receive interacting faults or a defect in the verification environment rather than in the design.
Build the Minimum Testbench First
A useful learning progression begins below full UVM.
The first environment needs:
- Clock and reset generation
- A transaction representation
- A driver
- An observer or monitor
- A reference queue
- A scoreboard
- A small assertion set
- A repeatable test command
The important learning question is not whether the queue code is clever.
It is whether the learner has correctly defined write_accepted and read_accepted from the protocol and the FIFO state sampled before the clock edge.
Reference-Model Pseudocode
The model should determine accepted operations from the same pre-clock state used by the DUT.
on each clock:
if reset:
model_queue.clear()
else:
write_accepted = write_enable and write_is_permitted
read_accepted = read_enable and read_is_permitted
if read_accepted:
expected_read_data = model_queue.front()
update_model_queue(
write_accepted,
write_data,
read_accepted
)
if read_accepted:
compare(expected_read_data, observed_read_data)
The exact implementation of update_model_queue() depends on the agreed semantics for simultaneous read and write, especially at empty and full boundaries.
That behaviour must come from the clarified specification rather than from the order of statements in the model.
Add Assertions for Invariants
Assertions should capture behaviour that must always hold.
Illustrative SystemVerilog-style properties might include:
property p_reset_clears_state;
@(posedge clk)
!rst_n |=> empty && !full;
endproperty
assert property (p_reset_clears_state);
property p_no_underflow;
@(posedge clk) disable iff (!rst_n)
empty && rd_en |=> $stable(dut.count);
endproperty
assert property (p_no_underflow);
property p_count_in_range;
@(posedge clk) disable iff (!rst_n)
dut.count <= DEPTH;
endproperty
assert property (p_count_in_range);
These are examples, not a complete solution.
The learner should review:
- Whether the properties match the agreed acceptance rules
- Whether the temporal relationships are correct
- Whether internal DUT signals are appropriate for the selected verification boundary
- Whether the properties could pass vacuously
- Whether equivalent behaviour can be checked through the external interface
A useful advanced exercise is to provide an assertion that passes vacuously because its antecedent never occurs.
Design Stimulus Around Decisions
Randomisation is useful only when it explores meaningful behaviour.
Create scenarios around:
- Filling the FIFO to capacity
- Draining the FIFO to empty
- Simultaneous read and write
- Reset during active traffic
- Repeated transitions into and out of full
- Repeated transitions into and out of empty
- Non-power-of-two pointer wrapping
- Back-to-back rejected requests
- Data patterns that reveal duplication or ordering mistakes
Then add constrained-random traffic to combine these conditions.
The learner should be able to explain which risk each scenario addresses.
“More random tests” is not a verification strategy".
Make Coverage Answer a Question
Coverage should trace back to the specification and risk model.
Possible coverpoints include:
- Occupancy level
- Transition into and out of full
- Transition into and out of empty
- Simultaneous read and write by occupancy
- Reset during active traffic
- Accepted and rejected operations
- Read-pointer and write-pointer wrap events
- Boundary behaviour for non-power-of-two depths
Do not reward coverage percentage alone.
Ask:
- Which legal behaviours remain unobserved?
- Is an uncovered bin reachable?
- Is a constraint preventing it?
- Does hitting the bin demonstrate that the behaviour was checked?
- Is an exclusion justified?
- Does the coverage model represent the delivered configuration?
Coverage demonstrates observation, not correctness.
Qualify the Testbench
A verification environment should demonstrate that it can detect relevant faults.
Run the known buggy variants and record which mechanism detects each defect.
| Fault | Assertion | Scoreboard | Coverage | Expected result |
|---|---|---|---|---|
| Incorrect full threshold | Yes | Possibly | Boundary bin | Failure detected |
| Data reordering | Not necessarily | Yes | Insufficient alone | Failure detected |
| Missed wrap condition | Possibly | Yes | Wrap bin | Failure detected |
| Vacuous property | No functional failure | No | Property coverage or review | Verification weakness identified |
This exercise teaches that no single checking mechanism is sufficient.
It also exposes defects in the testbench itself.
For example, if a seeded data-ordering bug is not detected, the learner must investigate whether the stimulus failed to create the condition, the monitor sampled incorrectly or the scoreboard implemented the same mistake as the DUT.
Put the Lab in a Repeatable Workflow
A learner should be able to run the environment with one documented command.
The specific tools can vary. The important outcomes are:
- Deterministic reproduction from a recorded seed
- Stored logs and results
- Clear pass-or-fail status
- Version-controlled source
- Automated regression
- A concise failure summary
A basic CI pipeline can run:
- Lint
- Compile or elaborate
- Smoke tests
- Assertions
- A short regression
Longer random, formal or fault-injection runs can execute on a schedule.
Require a Structured Debug Report
When a test fails, require the learner to record:
- Failing command
- Random seed
- Product or parameter configuration
- First visible symptom
- Expected behaviour
- Suspected failure boundary
- Relevant waveform or log markers
- Root cause
- Proposed fix
- Evidence that the fix works
- Regression impact
- Remaining uncertainty
This is more valuable than a screenshot of a passing waveform.
It teaches reproducibility, debugging discipline and technical communication.
Assess Sign-Off Reasoning
The final exercise should not ask only:
Did all the tests pass?
Ask the learner to recommend one of three outcomes:
- Ready for sign-off
- Conditionally ready, with stated limitations
- Not ready for sign-off
The recommendation should refer to:
- Requirements addressed
- Assertions and checking mechanisms
- Regression results
- Coverage analysis
- Detection of injected faults
- Known exclusions
- Testbench limitations
- Unresolved risks
This is the point at which a laboratory exercise begins to resemble engineering work.
Capability Checklist
A learner is progressing when they can:
- Challenge an ambiguous requirement
- Derive acceptance conditions
- Choose complementary checking mechanisms
- Reproduce a failure
- Distinguish design defects from testbench defects
- Interpret coverage rather than quote a percentage
- Identify an unsafe or vacuous property
- Respond constructively to review
- Explain why the evidence is or is not sufficient
- Recommend an appropriate sign-off decision
A buggy FIFO will not reproduce the complexity of a commercial SoC.
It does not need to.
Its purpose is to teach a complete and repeatable verification loop in a bounded environment, then provide enough evidence to decide what technical responsibility the learner is ready to own next.
For a wider analysis of practical semiconductor training, mentoring, tool access and evidence-based capability development, read:
Semiconductor Skills Gap 2026: Building Capability Before Certificates
[https://alpinumconsulting.com/blogs/general/semiconductor-skills-gap-2026/]
Top comments (0)