Skip to content

Commit dd5dc21

Browse files
authored
Merge pull request #3 from PaoloRondot/main
Update documentation and fix the waveforms crashing
2 parents 288e617 + 48da860 commit dd5dc21

4 files changed

Lines changed: 36 additions & 8 deletions

File tree

scripts/toolchain.lock.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ readonly CIRCT_REF_LOCKED="8bcd565479190e5cc512a254d10cde513b2c123c"
1111
readonly CIRCT_LLVM_SUBMODULE_REF_LOCKED="aa3d6b37c7945bfb4c261dd994689de2a2de25bf"
1212

1313
readonly SURFER_ARTIFACT_URL_LOCKED="https://gitlab.com/surfer-project/surfer/-/jobs/artifacts/main/download?job=pages_build"
14-
readonly SURFER_ARTIFACT_SHA256_LOCKED="2a684122436e7a7729cc4e57062fdc2ce8ec5fa096d84ca383dd59011012b873"
14+
readonly SURFER_ARTIFACT_SHA256_LOCKED="abf8d4c3415d445bf86edb39dda9ec9f37d20ccddf4069ec925acb608dcb661b"

src/lessons/sv/always-ff/description.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@
2525
</pre>
2626
<blockquote><p>
2727
We use <strong><dfn data-card="Non-blocking assignment (<=) schedules the update to happen after all right-hand sides in the current time step are evaluated. This means two flip-flops can swap values correctly: a <= b; b <= a; works as expected. Blocking assignment (=) takes effect immediately, like a variable assignment in C — correct for combinational logic but causes races in sequential logic.">non-blocking assignment</dfn></strong> (<code>&lt;=</code>) inside <code>always_ff</code>. It works in two steps: first, all right-hand sides are sampled using current values; then all left-hand sides update simultaneously. So <code>out</code> always captures the value <code>mem</code> held <em>before</em> this edge — creating a true one-cycle delay, not a zero-delay pass-through. The same rule is why <code>a &lt;= b; b &lt;= a;</code> correctly swaps two flip-flops.</p></blockquote>
28+
29+
<p>The names describe how each operator behaves in the flow of your procedural code — whether the assignment <strong>blocks</strong> (pauses) execution until it completes.</p>
30+
<p><strong>Blocking <code>=</code></strong> — execution stops and waits. The assignment completes immediately, in place, before the next line runs. Think of it like hand-delivering a letter: the recipient has it before you walk away.</p>
31+
<pre>
32+
a = b; // a gets b's value RIGHT NOW
33+
c = a; // c sees the new value of a
34+
</pre>
35+
<p><strong>Non-blocking <code>&lt;=</code></strong> — execution continues without waiting. The assignment schedules a write for later and immediately moves on. Think of it like dropping a letter in a mailbox: you keep walking and it gets delivered later, when the NBA update region runs.</p>
36+
<pre>
37+
a &lt;= b; // schedules a write to a, but doesn't apply it yet
38+
c &lt;= a; // c gets a's OLD value — the write above hasn't happened yet
39+
</pre>
40+
<p>All right-hand sides are evaluated first, then all writes happen together at the end of the time step. This is what makes <code>always_ff</code> correctly model real hardware, where all flip-flops in a clocked stage sample their inputs and update simultaneously.</p>
41+
2842
<p>
2943
An SRAM is an array of flip-flops — one per bit — indexed by address.
3044
We'll need a slightly more advanced pattern to model that array.
@@ -78,4 +92,4 @@
7892
</ul>
7993
<blockquote><p>The read is <em>registered</em>: drive <code>addr</code> on cycle N and <code>rdata</code> reflects that address on cycle N+1. This is the standard synchronous-read SRAM model.</p></blockquote>
8094
<h2>Testbench</h2>
81-
<p><code>tb.sv</code> writes three values to addresses 2, 7, and 0, then reads them back one cycle later. Each read prints <code>PASS</code> or <code>FAIL</code> — run it before solving to see all three fail, then again after to confirm they all pass. Open the <strong>Waves</strong> tab to see <code>clk</code>, <code>we</code>, <code>addr</code>, <code>wdata</code>, and <code>rdata</code> over time.</p>
95+
<p><code>tb.sv</code> writes three values to addresses 2, 7, and 0, then reads them back one cycle later. Each read prints <code>PASS</code> or <code>FAIL</code> — run it before solving to see all three fail, then again after to confirm they all pass. Open the <strong>Waves</strong> tab to see <code>clk</code>, <code>we</code>, <code>addr</code>, <code>wdata</code>, and <code>rdata</code> over time.</p>

src/lessons/sv/tasks-functions/description.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
// task body
99
endtask</code></pre>
1010
<p>
11-
Here <code>automatic</code> means the task is re-entrant: it can be called recursively or from multiple places without interference.
12-
Non-automatic tasks share state across calls.
11+
Here <code>automatic</code> means the task is re-entrant: it can be called recursively or from multiple places without interference — just like a normal C/C++ function whose local variables live on the stack, with a fresh copy created for each call.
12+
A non-<code>automatic</code> (static) task behaves like a C function where every local variable is declared <code>static</code>: all calls share the same memory, so concurrent calls will overwrite each other's state.
1313
</p>
1414
<p>
1515
Calling a task <code>write_word(addr, data)</code> is blocking.
@@ -21,6 +21,9 @@
2121
<li><code>write_word(vif, addr, data)</code> — a task that drives one write transaction: assert <code>we</code>, set <code>addr</code> and <code>wdata</code>, wait one clock edge, then de-assert <code>we</code>.</li>
2222
<li><code>read_word(vif, addr, data)</code> — a task that drives one read transaction: set <code>addr</code>, wait one clock edge, then capture <code>rdata</code>.</li>
2323
</ul>
24+
<blockquote><p>
25+
Waiting for <code>@(posedge clk)</code> is not enough on its own. The testbench and the DUT are both sensitive to the same edge, so driving or sampling signals <em>at</em> the edge puts you in a race against the simulator's scheduler. The safe pattern is to wait for the edge and then advance a small delta — <code>@(posedge clk); #1;</code> — so that your assignments land in a quiet moment after the DUT has already reacted to the clock.
26+
</p></blockquote>
2427
<blockquote><p>These are the exact helper routines a UVM driver uses internally. In Part 3 the driver wraps them in a class method that pulls transactions from a sequencer — but the core protocol logic is the same.</p></blockquote>
2528
<h2>Testbench structure</h2>
2629
<p>The <code>initial</code> block calls <code>write_word</code> and <code>read_word</code> using the shared <code>mem_if</code> virtual interface, then checks the parity of the returned data with <code>parity_check</code>.</p>

src/runtime/circt-adapter.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,21 @@ function needsUvmLibrary(files) {
170170
function removeInlinedPortsFromVcd(vcd) {
171171
if (typeof vcd !== 'string') return vcd;
172172
const lines = vcd.split('\n');
173-
const skipIds = new Set();
173+
const topLevelIds = new Set(); // ids used by non-dotted (top-level) signals
174+
const dottedIds = new Map(); // id → true for dotted signal names
174175

175176
for (const line of lines) {
176177
const m = line.match(/\$var\s+\S+\s+\d+\s+(\S+)\s+(\S+)(?:\s+\[\S+\])?\s+\$end/);
177-
if (m && m[2].includes('.')) skipIds.add(m[1]);
178+
if (!m) continue;
179+
if (m[2].includes('.')) dottedIds.set(m[1], true);
180+
else topLevelIds.add(m[1]);
181+
}
182+
// Only skip dotted signals whose VCD id is already claimed by a top-level
183+
// signal (i.e. inlined port duplicates). Keep interface member signals that
184+
// have their own unique id.
185+
const skipIds = new Set();
186+
for (const id of dottedIds.keys()) {
187+
if (topLevelIds.has(id)) skipIds.add(id);
178188
}
179189
if (skipIds.size === 0) return vcd;
180190

@@ -243,8 +253,9 @@ function fixLlhdVcdEncoding(vcd) {
243253
for (let i = 0; i < svWidth; i++) {
244254
decoded += flagBits[i] === '1' ? 'x' : valBits[i];
245255
}
246-
// 1-bit results use the compact scalar form.
247-
return (svWidth === 1 ? decoded : 'b' + decoded) + ' ' + id;
256+
// 1-bit results use the compact scalar form (no space before id).
257+
if (svWidth === 1) return decoded + id;
258+
return 'b' + decoded + ' ' + id;
248259
}
249260
}
250261

0 commit comments

Comments
 (0)