Skip to content

Commit 288e617

Browse files
thomasahleclaude
andcommitted
Improve 11 low-scoring lessons + add 4 Testbench Essentials lessons
Lesson quality improvements (rubric scores): - sv/data-types: 21→25 — remove signed-arithmetic section, tighten to 2 concepts - sv/packed-structs: 21→26 — drop unions, add bit-layout SVG, rename title - sva/isunknown: 20→25 — expand 1→3 properties, new X-propagation SVG - sva/immediate-assert: 21→25 — expand 2→4 assertions (add count/simultaneous) - sva/checker: 21→23 — add data_stable_check (2nd property), reuse SVG - sva/recursive: 20→23 — expand 1→3 properties, SRAM lock narrative, state-machine SVG - sva/lec: 20→24 — expand 1-char fix to 3 full expressions, LEC flow SVG New Testbench Essentials chapter (Part 1): - sv/classes: mem_item class with fields, constructor, convert2string + handle SVG - sv/queues-arrays: dynamic array + queue with push/pop + layout SVG - sv/fork-join: fork...join/join_any/join_none with timing diagram SVG - sv/randomization: rand fields, constraints, inline with, constraint_mode(0) + SVG Also: sidebar part-progress fraction, sva/seq-args lesson, UVM scoreboard fixes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0905b28 commit 288e617

46 files changed

Lines changed: 1285 additions & 330 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CURRICULUM.md

Lines changed: 70 additions & 60 deletions
Large diffs are not rendered by default.

e2e/qa-all-lessons.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const LESSONS = [
1616
{ title: 'Flip-Flops with always_ff', slug: 'sv/always-ff', runner: 'sim' },
1717
{ title: 'Up-Counter', slug: 'sv/counter', runner: 'sim' },
1818
{ title: 'Parameters', slug: 'sv/parameters', runner: 'sim' },
19-
{ title: 'Packed Structs and Unions', slug: 'sv/packed-structs', runner: 'sim' },
19+
{ title: 'Packed Structs', slug: 'sv/packed-structs', runner: 'sim' },
2020
{ title: 'Interfaces', slug: 'sv/interfaces', runner: 'sim' },
2121
{ title: 'Modports', slug: 'sv/modports', runner: 'sim' },
2222
{ title: 'Tasks and Functions', slug: 'sv/tasks-functions', runner: 'sim' },
@@ -25,6 +25,10 @@ const LESSONS = [
2525
{ title: 'covergroup and coverpoint', slug: 'sv/covergroup-basics', runner: 'sim' },
2626
{ title: 'Bins and ignore_bins', slug: 'sv/coverpoint-bins', runner: 'sim' },
2727
{ title: 'Cross coverage', slug: 'sv/cross-coverage', runner: 'sim' },
28+
{ title: 'Classes and Objects', slug: 'sv/classes', runner: 'sim' },
29+
{ title: 'Dynamic Arrays and Queues', slug: 'sv/queues-arrays', runner: 'sim' },
30+
{ title: 'Concurrent Processes: fork...join', slug: 'sv/fork-join', runner: 'sim' },
31+
{ title: 'Constrained Randomization', slug: 'sv/randomization', runner: 'sim' },
2832
// ── SystemVerilog Assertions ──────────────────────────────────────────────────
2933
{ title: 'Concurrent Assertions in Simulation', slug: 'sva/concurrent-sim', runner: 'sim' },
3034
{ title: 'Vacuous Pass', slug: 'sva/vacuous-pass', runner: 'sim' },

scripts/test-all-lessons.mjs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,14 @@ async function runLesson({ verilog, bmc, work, category, slug, lessonDir, result
538538
// The new circt-sim.js includes VPI support; use it for all lessons.
539539
const simTool = 'circt-sim';
540540
const topName = metaTop ?? (isUvm ? 'tb_top' : 'tb');
541-
// UVM phase cleanup internally advances ~1 ns; give 10 ns to complete.
542-
// sv/sva simulations use a 10 µs (10^10 fs) ceiling to guard against hangs
543-
// in XFAIL lessons where $finish is never reached (e.g. tasks-functions).
544-
// Normal tests finish in ≪ 1 µs of simulation time.
545-
const simExtra = isUvm ? ['--max-time', '10000000'] : ['--max-time', '10000000000'];
541+
// Default UVM ceiling: 10 ns (enough for delta-cycle-only tests like
542+
// factory-override/ral/sequence/seq-item that complete at t=0).
543+
// Clock-driven lessons (driver/env/monitor/…) set meta.simMaxTime (fs)
544+
// to get more time without inflating the whole suite.
545+
// sv/sva simulations use a 10 µs ceiling to guard against hangs.
546+
const uvmDefault = '10000000';
547+
const simMaxTime = lessonMeta.simMaxTime ? String(lessonMeta.simMaxTime) : (isUvm ? uvmDefault : '10000000000');
548+
const simExtra = ['--max-time', simMaxTime];
546549

547550
let compileSolFiles, compileStartFiles, extraFlags;
548551

@@ -610,6 +613,9 @@ async function runLesson({ verilog, bmc, work, category, slug, lessonDir, result
610613
} else if (xfailReason) {
611614
process.stdout.write(` ${Y}sol=XFAIL${X}`);
612615
results.xfail++;
616+
if (process.env.VERBOSE_XFAIL) {
617+
console.log('\n' + solSim.output.split('\n').map(l => ' ' + l).join('\n'));
618+
}
613619
} else {
614620
process.stdout.write(` ${R}sol=NO_PASS${X}\n`);
615621
results.failures.push({ label, mode: 'solution', reason: 'no PASS in output', output: solSim.output });
@@ -655,6 +661,10 @@ async function main() {
655661
}
656662
}
657663

664+
// Optional lesson filter: node test-all-lessons.mjs uvm/env sv/fsm ...
665+
const FILTER = process.argv.slice(2).filter(a => !a.startsWith('--'));
666+
const shouldRun = (label) => FILTER.length === 0 || FILTER.includes(label);
667+
658668
async function run(work) {
659669
const meta = await loadMeta();
660670

@@ -672,6 +682,7 @@ async function run(work) {
672682

673683
// ── sv/ ─────────────────────────────────────────────────────────────────────
674684
for (const slug of listDir('sv')) {
685+
if (!shouldRun(`sv/${slug}`)) continue;
675686
await runLesson({ verilog, bmc, work, category: 'sv', slug, lessonDir: path.join(LESSONS_DIR, 'sv', slug), results, meta });
676687
}
677688

@@ -680,13 +691,13 @@ async function run(work) {
680691
const runner = meta[`sva/${slug}`]?.runner;
681692

682693
if (runner === 'lec') {
683-
console.log(` ${D}SKIP sva/${slug} (lec runner)${X}`);
684-
results.skip++;
694+
if (shouldRun(`sva/${slug}`)) { console.log(` ${D}SKIP sva/${slug} (lec runner)${X}`); results.skip++; }
685695
continue;
686696
}
687697

688698
// 'bmc' or 'both' → BMC path; null/undefined → sim
689699
const category = (runner === 'bmc' || runner === 'both') ? 'sva-bmc' : 'sva-sim';
700+
if (!shouldRun(`${category}/${slug}`)) continue;
690701
await runLesson({ verilog, bmc, work, category, slug, lessonDir: path.join(LESSONS_DIR, 'sva', slug), results, meta });
691702
}
692703

@@ -696,6 +707,7 @@ async function run(work) {
696707
console.log(`${D}Run: npm run sync:circt${X}\n`);
697708
} else {
698709
for (const slug of listDir('uvm')) {
710+
if (!shouldRun(`uvm/${slug}`)) continue;
699711
await runLesson({ verilog, bmc, work, category: 'uvm', slug, lessonDir: path.join(LESSONS_DIR, 'uvm', slug), results, meta });
700712
}
701713
}
@@ -706,17 +718,21 @@ async function run(work) {
706718
{
707719
const mlirSim = await loadTool('circt-sim');
708720
for (const slug of listDir('mlir')) {
721+
if (!shouldRun(`mlir/${slug}`)) continue;
709722
await runMlirLesson({ sim: mlirSim, slug, lessonDir: path.join(LESSONS_DIR, 'mlir', slug), results });
710723
}
711724
}
712725

713726
// ── cocotb/ ─────────────────────────────────────────────────────────────────
714727
const cocotbDeps = checkCocotbDeps();
715728
if (!cocotbDeps.ok) {
716-
console.log(`\n${D}Skipping cocotb/ — ${cocotbDeps.reason}${X}\n`);
717-
for (const slug of listDir('cocotb')) results.skip++;
729+
if (FILTER.length === 0) {
730+
console.log(`\n${D}Skipping cocotb/ — ${cocotbDeps.reason}${X}\n`);
731+
for (const slug of listDir('cocotb')) results.skip++;
732+
}
718733
} else {
719734
for (const slug of listDir('cocotb')) {
735+
if (!shouldRun(`cocotb/${slug}`)) continue;
720736
await runCocotbLesson({ slug, lessonDir: path.join(LESSONS_DIR, 'cocotb', slug), results, work });
721737
}
722738
}

src/lessons/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export const parts = [
8282
{ title: 'Interfaces & Procedures', lessons: [L('sv/interfaces'), L('sv/modports'), L('sv/tasks-functions')] },
8383
{ title: 'State Machines', lessons: [L('sv/enums'), L('sv/fsm')] },
8484
{ title: 'Covergroups', lessons: [L('sv/covergroup-basics'), L('sv/coverpoint-bins'), L('sv/cross-coverage')] },
85+
{ title: 'Testbench Essentials', lessons: [L('sv/classes'), L('sv/queues-arrays'), L('sv/fork-join'), L('sv/randomization')] },
8586
],
8687
},
8788
{

src/lessons/meta.js

Lines changed: 12 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<p>A <strong><dfn data-card="A class is a blueprint that bundles data fields and the methods that operate on them. Unlike structs, class variables hold handles (pointers) to objects on the heap, not the objects themselves.">class</dfn></strong> bundles data and methods into a single unit:</p>
2+
<pre>class &lt;name&gt;;
3+
&lt;field declarations&gt;
4+
function new(&lt;args&gt;); // constructor
5+
&lt;initialization&gt;
6+
endfunction
7+
function string convert2string(); ... endfunction
8+
endclass</pre>
9+
<p>Classes are the foundation of UVM — every sequence item, driver, and monitor extends <code>uvm_object</code> or <code>uvm_component</code>, which are themselves classes.</p>
10+
11+
<h3>Handle semantics</h3>
12+
<p>A class variable is a <strong><dfn data-card="A handle is a reference (pointer) to an object. Assigning one handle to another makes both variables point to the same object — they do not copy the data.">handle</dfn></strong>, not a value. Assigning one handle to another makes both variables point to the <em>same</em> object:</p>
13+
14+
<svg width="430" height="110" viewBox="0 0 430 110" xmlns="http://www.w3.org/2000/svg" style="display:block;max-width:100%;font-family:'IBM Plex Mono',monospace;font-size:11px;margin:8px auto">
15+
<!-- Left panel: t2 = t1 — shared -->
16+
<text x="90" y="14" text-anchor="middle" fill="#0d6f72" font-weight="bold" font-size="12">t2 = t1</text>
17+
<text x="20" y="42" fill="#1e2a2c">t1</text>
18+
<line x1="36" y1="38" x2="70" y2="38" stroke="#1e2a2c" stroke-width="1.5" marker-end="url(#cls-a)"/>
19+
<text x="20" y="80" fill="#1e2a2c">t2</text>
20+
<line x1="36" y1="76" x2="70" y2="55" stroke="#1e2a2c" stroke-width="1.5" marker-end="url(#cls-a)"/>
21+
<rect x="72" y="20" width="110" height="50" rx="4" fill="#e6f5f5" stroke="#0d6f72" stroke-width="1.5"/>
22+
<text x="127" y="40" text-anchor="middle" fill="#0d6f72" font-weight="bold">object A</text>
23+
<text x="127" y="58" text-anchor="middle" fill="#0d6f72" font-size="10">wdata = A5</text>
24+
<text x="90" y="100" text-anchor="middle" fill="#c17d2a" font-size="10">both share the same object</text>
25+
<!-- Divider -->
26+
<line x1="215" y1="8" x2="215" y2="102" stroke="#ccc" stroke-width="1" stroke-dasharray="4,2"/>
27+
<!-- Right panel: t2 = new() — independent -->
28+
<text x="320" y="14" text-anchor="middle" fill="#c17d2a" font-weight="bold" font-size="12">t2 = new(...)</text>
29+
<text x="224" y="42" fill="#1e2a2c">t1</text>
30+
<line x1="240" y1="38" x2="270" y2="38" stroke="#1e2a2c" stroke-width="1.5" marker-end="url(#cls-a)"/>
31+
<rect x="272" y="20" width="80" height="30" rx="4" fill="#e6f5f5" stroke="#0d6f72" stroke-width="1.5"/>
32+
<text x="312" y="39" text-anchor="middle" fill="#0d6f72" font-size="10">object A</text>
33+
<text x="224" y="80" fill="#1e2a2c">t2</text>
34+
<line x1="240" y1="76" x2="270" y2="76" stroke="#1e2a2c" stroke-width="1.5" marker-end="url(#cls-a)"/>
35+
<rect x="272" y="60" width="80" height="30" rx="4" fill="#fff8ec" stroke="#c17d2a" stroke-width="1.5"/>
36+
<text x="312" y="79" text-anchor="middle" fill="#c17d2a" font-size="10">object B</text>
37+
<text x="320" y="100" text-anchor="middle" fill="#0d6f72" font-size="10">independent objects</text>
38+
<defs>
39+
<marker id="cls-a" markerWidth="7" markerHeight="6" refX="7" refY="3" orient="auto">
40+
<path d="M0,0 L7,3 L0,6 Z" fill="#1e2a2c"/>
41+
</marker>
42+
</defs>
43+
</svg>
44+
<pre>mem_item t1 = new(1, 4'd5, 8'hA5);
45+
mem_item t2 = t1; // t2 and t1 share the object
46+
t2.wdata = 8'hFF;
47+
$display(t1.wdata); // prints FF — same object!</pre>
48+
<p>To get an independent copy, call <code>new(...)</code> again:</p>
49+
<pre>t2 = new(0, 4'd3, 8'h00); // t2 now points to a different object
50+
// t1.wdata is still 8'hFF</pre>
51+
52+
<h3>The <code>this</code> keyword</h3>
53+
<p>Inside a constructor, argument names often shadow field names. Use <code>this.field</code> to refer to the field unambiguously:</p>
54+
<pre>function new(bit we, logic [3:0] addr, ...);
55+
this.we = we; // 'this.we' is the field; 'we' is the argument
56+
this.addr = addr;
57+
endfunction</pre>
58+
59+
<p>Open <code>mem_item.sv</code> and complete three tasks:</p>
60+
<ol>
61+
<li>Declare four fields: <code>bit we</code>, <code>logic [3:0] addr</code>, <code>logic [7:0] wdata</code>, <code>logic [7:0] rdata</code>.</li>
62+
<li>Fill in the constructor body using <code>this.field = arg</code>; leave <code>rdata</code> at its default (0).</li>
63+
<li>Implement <code>convert2string</code>: return <code>"WR[&lt;addr&gt;]=&lt;wdata&gt;"</code> for writes, <code>"RD[&lt;addr&gt;]"</code> for reads.</li>
64+
</ol>
65+
<p>The testbench (already in the file) demonstrates handle sharing: assigning <code>t2 = t1</code> and then modifying <code>t2.wdata</code> changes <code>t1.wdata</code> too. Then <code>t2 = new(...)</code> creates a fresh object, leaving <code>t1</code> unchanged.</p>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
class mem_item;
2+
bit we;
3+
logic [3:0] addr;
4+
logic [7:0] wdata;
5+
logic [7:0] rdata;
6+
7+
function new(bit we, logic [3:0] addr, logic [7:0] wdata);
8+
this.we = we;
9+
this.addr = addr;
10+
this.wdata = wdata;
11+
this.rdata = 8'h00;
12+
endfunction
13+
14+
function string convert2string();
15+
if (we)
16+
return $sformatf("WR[%0d]=%0h", addr, wdata);
17+
else
18+
return $sformatf("RD[%0d]", addr);
19+
endfunction
20+
endclass
21+
22+
module mem_item_tb;
23+
initial begin
24+
mem_item t1, t2;
25+
26+
t1 = new(1, 4'd5, 8'hA5);
27+
$display("t1: %s", t1.convert2string());
28+
29+
t2 = t1;
30+
t2.wdata = 8'hFF;
31+
assert (t1.wdata == 8'hFF) else $fatal(1, "FAIL: handle assignment should share the object");
32+
33+
t2 = new(0, 4'd3, 8'h00);
34+
$display("t2: %s", t2.convert2string());
35+
assert (t1.wdata == 8'hFF) else $fatal(1, "FAIL: t1 should be unchanged after t2 = new(...)");
36+
37+
$display("PASS");
38+
end
39+
endmodule

src/lessons/sv/classes/mem_item.sv

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// A class bundles data and methods that operate on it.
2+
// Unlike structs, classes are reference types: a variable holds a *handle*
3+
// (pointer) to an object, not the object itself.
4+
5+
class mem_item;
6+
// TODO: declare four fields
7+
// bit we
8+
// logic [3:0] addr
9+
// logic [7:0] wdata
10+
// logic [7:0] rdata
11+
12+
// Constructor — called with 'new(we, addr, wdata)'
13+
function new(bit we, logic [3:0] addr, logic [7:0] wdata);
14+
// TODO: store each argument into the matching field using this.field = arg
15+
// leave rdata at its default value (0)
16+
endfunction
17+
18+
// Pretty-print for $display
19+
function string convert2string();
20+
// TODO: return a formatted string:
21+
// if we: "WR[<addr>]=<wdata>"
22+
// else: "RD[<addr>]"
23+
// Hint: use $sformatf("WR[%0d]=%0h", addr, wdata)
24+
endfunction
25+
endclass
26+
27+
module mem_item_tb;
28+
initial begin
29+
mem_item t1, t2;
30+
31+
// Create the first object and verify convert2string
32+
t1 = new(1, 4'd5, 8'hA5);
33+
$display("t1: %s", t1.convert2string());
34+
35+
// Assign handle — t2 and t1 point to the SAME object
36+
t2 = t1;
37+
t2.wdata = 8'hFF;
38+
assert (t1.wdata == 8'hFF) else $fatal(1, "FAIL: handle assignment should share the object");
39+
40+
// Create a new independent object
41+
t2 = new(0, 4'd3, 8'h00);
42+
$display("t2: %s", t2.convert2string());
43+
assert (t1.wdata == 8'hFF) else $fatal(1, "FAIL: t1 should be unchanged after t2 = new(...)");
44+
45+
$display("PASS");
46+
end
47+
endmodule

0 commit comments

Comments
 (0)