Skip to content

Commit 9a9a524

Browse files
thomasahleclaude
andcommitted
Fix always-ff simulation hang; add Escape key close; add QA e2e tests
- sv/always-ff/tb.sv: replace 'always #5 clk=~clk' with finite initial repeat(200) — LLHD interpreter does not terminate on $finish when an always block is present; finite repeat ensures both initial blocks exit naturally so simulation completes - +page.svelte: close options menu on Escape key; refactor completed derivation to use filesEqual; reset log buffer on starter reset - e2e/qa-ghpages.spec.js: comprehensive QA suite against live GH Pages - e2e/ghpages.config.js: widen testMatch glob for qa-ghpages.spec.js - Remove sv/priority-enc lesson (not yet ready) - sva/formal-assume and sva/vacuous-pass: add PASS marker to testbenches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2247ac5 commit 9a9a524

15 files changed

Lines changed: 511 additions & 206 deletions

File tree

e2e/ghpages.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { defineConfig } from '@playwright/test';
66
*/
77
export default defineConfig({
88
testDir: '../e2e',
9-
testMatch: 'ghpages.spec.js',
9+
testMatch: '*ghpages*.spec.js',
1010
timeout: 180_000,
1111
fullyParallel: false,
1212
retries: 1,

e2e/mlir-run.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
test('MLIR intro: run executes via circt-sim without SV-source error', async ({ page }) => {
4+
await page.goto('/lesson/mlir/intro');
5+
await expect(page.getByTestId('lesson-title')).toHaveText('What is MLIR?');
6+
7+
await page.getByTestId('run-button').click();
8+
9+
const logs = page.getByTestId('runtime-logs');
10+
await expect(logs).toContainText('$ circt-sim', { timeout: 120_000 });
11+
await expect(logs).toContainText('# using MLIR source: /src/adder.mlir', { timeout: 120_000 });
12+
await expect(logs).not.toContainText('# no SystemVerilog source files found in workspace');
13+
await expect(logs).not.toContainText('# no SystemVerilog or MLIR source files found in workspace');
14+
await expect(logs).not.toContainText('exit code: 1');
15+
});

e2e/qa-ghpages.spec.js

Lines changed: 373 additions & 0 deletions
Large diffs are not rendered by default.

src/lessons/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const parts = [
7676
{
7777
title: 'SystemVerilog Basics',
7878
chapters: [
79-
{ title: 'Introduction', lessons: [L('sv/welcome'), L('sv/modules-and-ports'), L('sv/data-types'), L('sv/always-comb'), L('sv/priority-enc')] },
79+
{ title: 'Introduction', lessons: [L('sv/welcome'), L('sv/modules-and-ports'), L('sv/data-types'), L('sv/always-comb')] },
8080
{ title: 'Sequential Logic', lessons: [L('sv/events'), L('sv/always-ff'), L('sv/counter')] },
8181
{ title: 'Parameterized Modules', lessons: [L('sv/parameters')] },
8282
{ title: 'Data Types', lessons: [L('sv/packed-structs')] },

src/lessons/meta.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export default {
77
'sv/modules-and-ports': { title: 'Modules and Ports', focus: '/src/adder.sv' },
88
'sv/data-types': { title: 'Data Types', focus: '/src/data_types.sv' },
99
'sv/always-comb': { title: 'always_comb and case', focus: '/src/mux.sv' },
10-
'sv/priority-enc': { title: 'Priority Encoder', focus: '/src/priority_enc.sv' },
1110
'sv/events': { title: 'Events', focus: '/src/event_sync.sv' },
1211
'sv/always-ff': { title: 'Flip-Flops with always_ff', focus: '/src/sram_core.sv' },
1312
'sv/counter': { title: 'Up-Counter', focus: '/src/counter.sv' },

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

Lines changed: 78 additions & 93 deletions
Large diffs are not rendered by default.

src/lessons/sv/always-ff/tb.sv

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
// Registered reads have 1-cycle latency: drive addr on cycle N,
44
// rdata reflects that address on cycle N+1.
55
module tb;
6-
logic clk = 0; // driven by always block below
6+
logic clk = 0; // driven by clock generator below
77
logic we = 0; // write-enable
88
logic [3:0] addr = 0; // byte address (0..15)
99
logic [7:0] wdata = 0; // data to write
1010
logic [7:0] rdata; // data read out — valid 1 cycle after addr
1111

1212
int fail = 0;
1313

14-
// Clock generator: period = 10 time units
15-
always #5 clk = ~clk;
14+
// Clock generator: period = 10 time units (finite repeat for simulator compatibility)
15+
initial repeat(200) #5 clk = ~clk;
1616

1717
// Connect all ports by name (shorthand: .port matches variable of same name)
1818
sram_core dut(.*);

src/lessons/sv/events/description.html

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1-
<p>An SRAM testbench has three operations that must happen in strict order: <em>write</em> values into memory, then <em>read</em> them back, then <em>check</em> the results. Every <code>initial</code> block starts at t=0, so without coordination the reader races ahead of the writer and sees uninitialised <dfn data-card="x (unknown) is one of the four logic values in SystemVerilog alongside 0, 1, and z. x appears in uninitialised signals, unresolved bus conflicts, and certain simulation artefacts. Unlike 0 or 1, x propagates: AND(x, 1) = x, not 0. The === (case equality) operator distinguishes x from 0/1, while == treats x as a mismatch.">x</dfn> values. A named <dfn data-card="An event in SystemVerilog is a synchronisation primitive — not a value (0 or 1), just a named instant in simulation time. Any process can trigger it with ->; any number of processes can wait for it with @(event_name). When a process hits @(event_name) it suspends immediately. When any other process executes -> event_name, every suspended waiter resumes in the same simulation time step.">event</dfn> fixes this: one process announces <em>I'm done — your turn</em>, and all waiters wake up at once.</p>
1+
<p>
2+
Recall that <code>initial</code> blocks start at t=0.
3+
This happens even if there are multiple <code>initial</code> blocks in the same module, and even if they are in different modules. They all start at the same time, and run concurrently.
4+
This is a powerful feature: it lets you write separate processes that run in parallel, without needing to manually interleave their code.
5+
But it also means you need to coordinate them, so they don't step on each other's toes.
6+
</p>
7+
<p>
8+
For example, an SRAM testbench may want to write some values into memory, and then read them back.
9+
We can do this with two <code>initial</code> blocks, but we need to make sure the reader waits until the writer is done.
10+
</p>
11+
<p>
12+
We can coordinate concurrent processes using <dfn data-card="Events are similar to constructions in many other programming languages. Such as threading.Event in Python, std::condition_variable in C++, sync.Cond in Go, and so on.
13+
The main gotcha is that System Verilog events are not stateful (latching).
14+
If you wait for an event that has already been posted, you will wait forever.">events</dfn>.
15+
An event is a named synchronization point that processes can wait for or post.
16+
The basic operations on events are: </p>
17+
<ul>
18+
<li><code>event write_done</code>: declare a named event</li>
19+
<li><code>-&gt; write_done</code>: "post" the event — wakes every process</li>
20+
<li><code>@(write_done)</code>: wait for the event — suspends until posted</li>
21+
</ul>
22+
<p>
23+
<blockquote><p>Note: You could also just write <code>@ write_done</code>, but using brackets around the event is a universal convention</p></blockquote>
224

3-
<h2>Three keywords, one idea</h2>
4-
<pre>
5-
event write_done; // declare a named event
6-
7-
-> write_done; // post it — wakes every process waiting on it
8-
9-
@(write_done); // wait for it — suspends until posted
10-
</pre>
1125
<p>The post is instantaneous: every process suspended at <code>@(write_done)</code> resumes in the same simulation time step. Events carry no data and cannot be synthesised — they live only in testbenches and simulation models.</p>
1226

1327
<h2>A two-event pipeline</h2>
1428
<p><code>event_sync.sv</code> chains writer, reader, and checker using two events:</p>
15-
<pre>
16-
writer ──→ write_done ──→ reader ──→ read_done ──→ checker
17-
</pre>
18-
1929
<!-- Three-process timing diagram -->
2030
<svg width="430" height="120" viewBox="0 0 430 120" xmlns="http://www.w3.org/2000/svg" style="display:block;max-width:100%;font-family:'IBM Plex Mono',monospace;font-size:12px;margin:10px auto">
2131
<rect width="430" height="120" rx="4" fill="#fffdf7"/>
@@ -51,14 +61,16 @@ <h2>A two-event pipeline</h2>
5161
<text x="400" y="98" text-anchor="middle" fill="#0a5558" font-size="10">report</text>
5262
</svg>
5363

54-
<p>Add the four TODO lines — one <code>-></code> and one <code>@</code> per handshake. Without them, the reader and checker start at t=0, read uninitialised <code>x</code> values, and <code>PASS</code> never prints.</p>
64+
<p>Add the four TODO lines — one <code>-&gt;</code> and one <code>@</code> per handshake. Without them, the reader and checker start at t=0, read uninitialised <code>x</code> values, and <code>PASS</code> never prints.</p>
5565

56-
<blockquote><p>If you add <code>@(write_done)</code> but forget <code>-> write_done</code>, the reader waits forever and the simulation never finishes. Each handshake requires both sides: a sender and at least one receiver.</p></blockquote>
66+
<blockquote><p>Warning: If you add <code>@(write_done)</code> but forget <code>-> write_done</code>, the reader waits forever and the simulation never finishes. Each handshake requires both sides: a sender and at least one receiver.</p></blockquote>
67+
<p>
68+
Hint: You can follow the events status in the simulator waveform viewer, just like any other signal.
69+
</p>
5770

5871
<h2>Built-in vs named events</h2>
59-
<p>You already used <code>@(posedge clk)</code> in the previous testbench. That is the same wait-for-event operator: <code>posedge clk</code> is a <em>built-in</em> event generated automatically whenever <code>clk</code> transitions 0→1. A named event is the same mechanism with a name you choose:</p>
60-
<pre>
61-
@(posedge clk) // built-in — posted by the simulator on each 0→1 transition
62-
@(write_done) // named — posted by your code with ->
63-
</pre>
72+
<p>
73+
A very important built-in event is <code>posedge clk</code>.
74+
This even is generated automatically by the simulator whenever the signal <code>clk</code> transitions from 0 to 1.
75+
You don't post it yourself, but you can wait for it with <code>@(posedge clk)</code>.
6476
<p>In the next lesson, <code>always_ff @(posedge clk)</code> uses exactly this: the block suspends at the rising-edge event and latches new values into the flip-flop array. Every register in our SRAM waits for that one recurring event.</p>

src/lessons/sv/priority-enc/description.html

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/lessons/sv/priority-enc/priority_enc.sol.sv

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)