Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CURRICULUM.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ Flag numbers identify the weak dimension(s): 1=Concept Focus, 2=Starter Calibrat
| `sv/counter` | Up-Counter | ✅ | 23/27 ⚠️1,8,9 | `sv/always-ff` | enable/reset counter, cycle-counted stimulus, `@(posedge clk)` in `initial` |
| `sv/shift-reg` | Shift Register | 📝 | — | `sv/always-ff` | bit shift, concatenation `{}`, serial-in/serial-out |

### Chapter: Clocking Blocks
| Slug | Title | Status | Score | Prereqs | Teaches |
|---|---|---|---|---|---|
| `sv/clocking-sampler-retention` | Clocking Sampler Retention | ✅ | — | `sv/modules-and-ports` | clocking blocks, input skew, sampled values, explicit `#0` timing |

> Introduces `[*]` bus shift and multi-bit `always_ff`; prerequisite
> for pipeline assertions in Part 2.

Expand Down
15 changes: 6 additions & 9 deletions artifacts/tutorial/clocking-sampler-retention/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Xcelium `refdiff`; the starter's default input skew fails in both engines.

## Native example

The commands use CPUs `0-79`, a 30-second wall guard, and the shared landing
build:
The commands use CPUs `0-79`, a 30-second wall guard, and the current native
Mox build:

```text
taskset -c 0-79 timeout --kill-after=3s 30s /var/tmp/thomas-ahle/wt/landing/build-dev-fast/bin/mox-run --single-unit --timescale=1ns/1ns --mode=interpret --max-wall-ms=25000 --top tb src/lessons/sv/clocking-sampler-retention/clocking_sampler.sv
Expand All @@ -17,17 +17,14 @@ taskset -c 0-79 timeout --kill-after=3s 30s /var/tmp/thomas-ahle/wt/landing/buil
```

The starter prints `FAIL: first sample=aa` in both modes. The solution prints
`PASS: clocking sample retained=bb` in both modes. The available binary reports
Mox `bcbd69b0d63800f2e057a58acf0fc0377db9411f`, which predates the landed
MQ93 test lock but reproduces its behavior. The exact current-main landing
receipt for the upstream MQ93 control is recorded at
`/var/tmp/thomas-ahle/fleet/artifacts/landing2/sched-r5-9529d51aacb/`.
`PASS: clocking sample retained=bb` in both modes. The current native Mox
build used for this receipt is `643f9b3d30259a3fbaae4743cb26903e65fdf110`.

The committed source hashes are:

```text
clocking_sampler.sv 75d8287e2293d920c34c1aee73473a7aef293b0b0ddc660bd994e3d8ee06b006
clocking_sampler.sol.sv e4a21ce79f011dcbd439935ecad93d7cfbcd6bf54dc7acbe7432e923670ec117
clocking_sampler.sv 25ca50f72941b026d8c96f65890b439a3cf35df69cde8359eed17cc9b9970a4a
clocking_sampler.sol.sv 7231082a4cd4f46b798b65be466590f27927b22a783f50faf67529e068376d96
```

## Differential receipts
Expand Down
40 changes: 40 additions & 0 deletions src/lessons/clocking-sampler.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it } from 'vitest';
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import path from 'node:path';

const root = path.resolve(process.cwd());
const lessonDir = path.join(root, 'src/lessons/sv/clocking-sampler-retention');
const description = readFileSync(path.join(lessonDir, 'description.html'), 'utf8');
const starter = readFileSync(path.join(lessonDir, 'clocking_sampler.sv'), 'utf8');
const solution = readFileSync(path.join(lessonDir, 'clocking_sampler.sol.sv'), 'utf8');

describe('clocking sampler retention lesson', () => {
it('teaches standard clocking-block sampling without internal status text', () => {
expect(description).toContain('IEEE 1800-2023 §14.3');
expect(description).toContain('§14.4');
expect(description).toContain('§14.13');
expect(description).toContain('explicit <code>#0</code>');
expect(description).toContain('sampled values');
expect(description).not.toMatch(/Mox|WASM|Mox's|commit|receipt|\/var\/tmp|fleet|census/i);
});

it('keeps the starter and solution distinct and binds both refdiff receipts', () => {
expect(starter).toContain('input sig;');
expect(starter).not.toContain('input #0 sig;');
expect(solution).toContain('input #0 sig;');

for (const [file, expectedCategory] of [
['starter-refdiff.json', 'both_fail'],
['solution-refdiff.json', 'both_pass'],
]) {
const sourceName = file.startsWith('starter') ? 'clocking_sampler.sv' : 'clocking_sampler.sol.sv';
const source = readFileSync(path.join(lessonDir, sourceName));
const receipt = JSON.parse(
readFileSync(path.join(root, 'artifacts/tutorial/clocking-sampler-retention', file), 'utf8')
);
expect(receipt.category).toBe(expectedCategory);
expect(receipt.sha256).toBe(createHash('sha256').update(source).digest('hex'));
}
});
});
1 change: 0 additions & 1 deletion src/lessons/curriculum-integrity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const removedStatusLessons = [
'sv/nested-child-input',
'sv/sequential-udp-init',
'sv/compile-mode-status',
'sv/clocking-sampler-retention',
'sv/protected-envelope-boundary',
'sv/virtual-provider-closure',
'sv/wide-readmem',
Expand Down
1 change: 1 addition & 0 deletions src/lessons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const parts = [
chapters: [
{ title: 'Introduction', lessons: [L('sv/welcome'), L('sv/modules-and-ports'), L('sv/data-types'), L('sv/always-comb')] },
{ title: 'Sequential Logic', lessons: [L('sv/events'), L('sv/always-ff'), L('sv/counter')] },
{ title: 'Clocking Blocks', lessons: [L('sv/clocking-sampler-retention')] },
{ title: 'Data Types', lessons: [L('sv/parameters'), L('sv/packed-structs')] },
{ title: 'State Machines', lessons: [L('sv/enums')] },
{ title: 'Covergroups', lessons: [L('sv/covergroup-basics'), L('sv/coverpoint-bins'), L('sv/cross-coverage')] },
Expand Down
1 change: 1 addition & 0 deletions src/lessons/meta.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/lessons/receipt-matrix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import meta from './meta.js';
const root = path.resolve(process.cwd());
const receiptDir = path.join(root, 'artifacts/tutorial/receipts');
const missingDir = path.join(receiptDir, '20261001-missing');
const capabilityReceiptSlugs = ['sv/clocking-sampler-retention'];

function genericReceiptSlugs() {
return readdirSync(receiptDir)
Expand All @@ -19,6 +20,9 @@ describe('tutorial receipt coverage', () => {
const matrix = JSON.parse(readFileSync(path.join(missingDir, 'matrix.json'), 'utf8'));
const covered = new Set([
...genericReceiptSlugs(),
...capabilityReceiptSlugs.filter((slug) =>
existsSync(path.join(root, 'artifacts/tutorial/clocking-sampler-retention/solution-refdiff.json'))
),
...matrix.entries.map((entry) => entry.slug)
]);
expect([...covered].sort()).toEqual(Object.keys(meta).sort());
Expand Down
53 changes: 53 additions & 0 deletions src/lessons/sv/clocking-sampler-retention/clocking_sampler.sol.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module tb;
logic clk = 0;
logic [7:0] sig = 8'hAA;
bit reactive_done = 0;

clocking cb @(posedge clk);
input #0 sig;
endclocking

always #5 clk = ~clk;
always @(posedge clk) sig = 8'hBB;
reactive_writer writer(.clk(clk));

initial begin
@(cb);
if (cb.sig !== 8'hBB) begin
$display("FAIL: first sample=%h", cb.sig);
$finish;
end
wait (reactive_done);
if (cb.sig !== 8'hBB) begin
$display("FAIL: same-slot sample=%h", cb.sig);
$finish;
end
#1;
if (cb.sig !== 8'hBB) begin
$display("FAIL: retained sample=%h", cb.sig);
$finish;
end
#5;
if (cb.sig !== 8'hBB) begin
$display("FAIL: negedge sample=%h", cb.sig);
$finish;
end
@(cb);
if (cb.sig !== 8'hBB) begin
$display("FAIL: resample=%h", cb.sig);
$finish;
end
$display("PASS: clocking sample retained=%h", cb.sig);
$finish;
end
endmodule

program reactive_writer(input logic clk);
initial begin
@(posedge clk);
tb.sig = 8'hCC;
#0;
tb.reactive_done = 1;
#100;
end
endprogram
53 changes: 53 additions & 0 deletions src/lessons/sv/clocking-sampler-retention/clocking_sampler.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module tb;
logic clk = 0;
logic [7:0] sig = 8'hAA;
bit reactive_done = 0;

clocking cb @(posedge clk);
input sig;
endclocking

always #5 clk = ~clk;
always @(posedge clk) sig = 8'hBB;
reactive_writer writer(.clk(clk));

initial begin
@(cb);
if (cb.sig !== 8'hBB) begin
$display("FAIL: first sample=%h", cb.sig);
$finish;
end
wait (reactive_done);
if (cb.sig !== 8'hBB) begin
$display("FAIL: same-slot sample=%h", cb.sig);
$finish;
end
#1;
if (cb.sig !== 8'hBB) begin
$display("FAIL: retained sample=%h", cb.sig);
$finish;
end
#5;
if (cb.sig !== 8'hBB) begin
$display("FAIL: negedge sample=%h", cb.sig);
$finish;
end
@(cb);
if (cb.sig !== 8'hBB) begin
$display("FAIL: resample=%h", cb.sig);
$finish;
end
$display("PASS: clocking sample retained=%h", cb.sig);
$finish;
end
endmodule

program reactive_writer(input logic clk);
initial begin
@(posedge clk);
tb.sig = 8'hCC;
#0;
tb.reactive_done = 1;
#100;
end
endprogram
14 changes: 14 additions & 0 deletions src/lessons/sv/clocking-sampler-retention/description.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<p>A clocking block gives a testbench a named clock event and a sampled view of its input signals. The input skew controls when each signal is sampled relative to that event.</p>

<h2>Exercise</h2>
<p>Add an explicit <code>#0</code> input skew to <code>cb.sig</code>. The testbench writes <code>BB</code> in an Active-region process, then writes <code>CC</code> after the clocking event has published its sample. The correct solution observes <code>BB</code> at the first event, keeps that sampled value through the same time slot and the following negedge, and samples <code>BB</code> again at the next positive edge.</p>

<pre>clocking cb @(posedge clk);
input #0 sig;
endclocking</pre>

<p>Without an explicit input skew, the default is <code>1step</code>, so the input is sampled immediately before the corresponding clock edge. With an explicit <code>#0</code>, the input is sampled in the Observed region. The clocking-block event is triggered after the sampled values are updated, and a clocking input keeps the value from its most recent clocking event.</p>

<blockquote><p>IEEE 1800-2023 §14.3 defines the default <code>1step</code> input skew. §14.4 defines sampling relative to the clock event and specifies that an explicit <code>#0</code> input skew samples in the Observed region. §14.13 specifies that the updated sampled values are available before the clocking-block event triggers and that a <code>#0</code> input is not resampled during same-slot Active re-entry unless a new clocking event occurs.</p></blockquote>

<p>Open <code>clocking_sampler.sv</code> and add the skew. The passing solution prints <code>PASS: clocking sample retained=bb</code>.</p>
Loading