11/**
22 * Full-tutorial QA: every lesson → solve → run/verify → no errors.
3- * Lessons are visited by URL index so navigation is fast and reliable.
3+ * Lessons are visited by URL index (?lesson=N, 1-based) for reliable navigation .
44 */
55import { test , expect } from '@playwright/test' ;
66
7- // One entry per lesson in the order they appear in the app.
7+ // One entry per lesson in the exact order they appear in the app (matches index.js) .
88// runner: 'sim' | 'bmc' | 'lec' | 'both' | 'cocotb'
99const LESSONS = [
10- { title : 'Welcome' , runner : 'sim' } ,
11- { title : 'Modules and Ports' , runner : 'sim' } ,
12- { title : 'always_comb and case' , runner : 'sim' } ,
13- { title : 'Priority Encoder' , runner : 'sim' } ,
14- { title : 'Flip-Flops with always_ff' , runner : 'sim' } ,
15- { title : 'Up-Counter' , runner : 'sim' } ,
16- { title : 'Parameters and localparam' , runner : 'sim' } ,
17- { title : 'Interfaces and modport' , runner : 'sim' } ,
18- { title : 'Tasks and Functions' , runner : 'sim' } ,
19- { title : 'typedef enum' , runner : 'sim' } ,
20- { title : 'Two-Always Moore FSM' , runner : 'sim' } ,
21- { title : 'covergroup and coverpoint' , runner : 'sim' } ,
22- { title : 'Bins and ignore_bins' , runner : 'sim' } ,
23- { title : 'Cross coverage' , runner : 'sim' } ,
24- { title : 'Your First cocotb Test' , runner : 'cocotb' } ,
25- { title : 'Clock and Timing' , runner : 'cocotb' } ,
26- { title : 'Immediate Assertions' , runner : 'bmc' } ,
27- { title : 'Concurrent Assertions in Simulation' , runner : 'sim' } ,
28- { title : 'Vacuous Pass' , runner : 'sim' } ,
29- { title : '$isunknown — Detecting X and Z' , runner : 'sim' } ,
30- { title : 'Sequences and Properties' , runner : 'bmc' } ,
31- { title : 'Implication: |-> and |=>' , runner : 'bmc' } ,
32- { title : 'Bounded Model Checking' , runner : 'bmc' } ,
33- { title : 'Clock Delay ##m and ##[m:n]' , runner : 'bmc' } ,
34- { title : '$rose and $fell' , runner : 'bmc' } ,
35- { title : 'Request / Acknowledge' , runner : 'bmc' } ,
36- { title : 'Consecutive Repetition [*m]' , runner : 'bmc' } ,
37- { title : 'Goto Repetition [->m]' , runner : 'bmc' } ,
38- { title : 'Non-Consecutive Equal Repetition [=m]' , runner : 'bmc' } ,
39- { title : 'throughout — Stability During a Sequence' , runner : 'bmc' } ,
40- { title : 'Sequence Composition: intersect, within, and, or' , runner : 'bmc' } ,
41- { title : '$stable and $past' , runner : 'bmc' } ,
42- { title : '$changed and $sampled' , runner : 'bmc' } ,
43- { title : 'disable iff — Reset Handling' , runner : 'bmc' } ,
44- { title : 'Aborting Properties: reject_on and accept_on' , runner : 'bmc' } ,
45- { title : 'cover property' , runner : 'bmc' } ,
46- { title : 'Local Variables in Sequences' , runner : 'bmc' } ,
47- { title : '$onehot, $onehot0, $countones' , runner : 'bmc' } ,
48- { title : '.triggered — Sequence Endpoint Detection' , runner : 'bmc' } ,
49- { title : 'the checker Construct' , runner : 'bmc' } ,
50- { title : 'Recursive Properties' , runner : 'bmc' } ,
51- { title : 'always and s_eventually' , runner : 'bmc' } ,
52- { title : 'until and s_until' , runner : 'bmc' } ,
53- { title : 'assume property' , runner : 'both' } ,
54- { title : 'Logical Equivalence Checking' , runner : 'lec' } ,
55- { title : 'The First UVM Test' , runner : 'sim' } ,
56- { title : 'Sequence Items' , runner : 'sim' } ,
57- { title : 'Sequences' , runner : 'sim' } ,
58- { title : 'The Driver' , runner : 'sim' } ,
59- { title : 'Monitor and Scoreboard' , runner : 'sim' } ,
60- { title : 'Environment and Test' , runner : 'sim' } ,
61- { title : 'Functional Coverage' , runner : 'sim' } ,
62- { title : 'Cross Coverage' , runner : 'sim' } ,
63- { title : 'Coverage-Driven Verification' , runner : 'sim' } ,
10+ // ── SystemVerilog Basics ──────────────────────────────────────────────────────
11+ { title : 'Welcome' , runner : 'sim' } ,
12+ { title : 'Modules and Ports' , runner : 'sim' } ,
13+ { title : 'always_comb and case' , runner : 'sim' } ,
14+ { title : 'Priority Encoder' , runner : 'sim' } ,
15+ { title : 'Flip-Flops with always_ff' , runner : 'sim' } ,
16+ { title : 'Up-Counter' , runner : 'sim' } ,
17+ { title : 'Parameters and localparam' , runner : 'sim' } ,
18+ { title : 'Interfaces and modport' , runner : 'sim' } ,
19+ { title : 'Tasks and Functions' , runner : 'sim' } ,
20+ { title : 'typedef enum' , runner : 'sim' } ,
21+ { title : 'Two-Always Moore FSM' , runner : 'sim' } ,
22+ { title : 'covergroup and coverpoint' , runner : 'sim' } ,
23+ { title : 'Bins and ignore_bins' , runner : 'sim' } ,
24+ { title : 'Cross coverage' , runner : 'sim' } ,
25+ // ── SystemVerilog Assertions ──────────────────────────────────────────────────
26+ { title : 'Concurrent Assertions in Simulation' , runner : 'sim' } ,
27+ { title : 'Vacuous Pass' , runner : 'sim' } ,
28+ { title : '$isunknown — Detecting X and Z' , runner : 'sim' } ,
29+ { title : 'Immediate Assertions' , runner : 'bmc' } ,
30+ { title : 'Sequences and Properties' , runner : 'bmc' } ,
31+ { title : 'Implication: |-> and |=>' , runner : 'bmc' } ,
32+ { title : 'Bounded Model Checking' , runner : 'bmc' } ,
33+ { title : 'Clock Delay ##m and ##[m:n]' , runner : 'bmc' } ,
34+ { title : '$rose and $fell' , runner : 'bmc' } ,
35+ { title : 'Request / Acknowledge' , runner : 'bmc' } ,
36+ { title : 'Consecutive Repetition [*m]' , runner : 'bmc' } ,
37+ { title : 'Goto Repetition [->m]' , runner : 'bmc' } ,
38+ { title : 'Non-Consecutive Equal Repetition [=m]' , runner : 'bmc' } ,
39+ { title : 'throughout — Stability During a Sequence' , runner : 'bmc' } ,
40+ { title : 'Sequence Composition: intersect, within, and, or' , runner : 'bmc' } ,
41+ { title : '$stable and $past' , runner : 'bmc' } ,
42+ { title : '$changed and $sampled' , runner : 'bmc' } ,
43+ { title : 'disable iff — Reset Handling' , runner : 'bmc' } ,
44+ { title : 'Aborting Properties: reject_on and accept_on' , runner : 'bmc' } ,
45+ { title : 'cover property' , runner : 'bmc' } ,
46+ { title : 'Local Variables in Sequences' , runner : 'bmc' } ,
47+ { title : '$onehot, $onehot0, $countones' , runner : 'bmc' } ,
48+ { title : '.triggered — Sequence Endpoint Detection' , runner : 'bmc' } ,
49+ { title : 'The checker Construct' , runner : 'bmc' } ,
50+ { title : 'Recursive Properties' , runner : 'bmc' } ,
51+ { title : 'assume property' , runner : 'both' } ,
52+ { title : 'always and s_eventually' , runner : 'bmc' } ,
53+ { title : 'until and s_until' , runner : 'bmc' } ,
54+ { title : 'Logical Equivalence Checking' , runner : 'lec' } ,
55+ // ── UVM ──────────────────────────────────────────────────────────────────────
56+ { title : 'The First UVM Test' , runner : 'sim' } ,
57+ { title : 'Sequence Items' , runner : 'sim' } ,
58+ { title : 'Sequences' , runner : 'sim' } ,
59+ { title : 'The Driver' , runner : 'sim' } ,
60+ { title : 'Monitor and Scoreboard' , runner : 'sim' } ,
61+ { title : 'Environment and Test' , runner : 'sim' } ,
62+ { title : 'Functional Coverage' , runner : 'sim' } ,
63+ { title : 'Cross Coverage' , runner : 'sim' } ,
64+ { title : 'Coverage-Driven Verification' , runner : 'sim' } ,
65+ // ── cocotb ───────────────────────────────────────────────────────────────────
66+ { title : 'Your First cocotb Test' , runner : 'cocotb' } ,
67+ { title : 'Clock and Timing' , runner : 'cocotb' } ,
6468] ;
6569
6670for ( const [ index , lesson ] of LESSONS . entries ( ) ) {
67- test ( `[${ index } ] ${ lesson . title } ` , async ( { page } ) => {
68- await page . goto ( `/?lesson=${ index } ` ) ;
69-
70- // Verify we landed on the right lesson
71+ test ( `[${ String ( index + 1 ) . padStart ( 2 ) } ] ${ lesson . title } ` , async ( { page } ) => {
72+ // URL uses 1-based lesson index
73+ await page . goto ( `/?lesson=${ index + 1 } ` ) ;
7174 await expect ( page . getByTestId ( 'lesson-title' ) ) . toHaveText ( lesson . title , { timeout : 10_000 } ) ;
7275
73- // Apply solution
76+ // Apply the solution
7477 const solveBtn = page . getByTestId ( 'solve-button' ) ;
7578 if ( await solveBtn . count ( ) > 0 ) {
7679 await solveBtn . click ( ) ;
@@ -79,27 +82,19 @@ for (const [index, lesson] of LESSONS.entries()) {
7982
8083 const logs = page . getByTestId ( 'runtime-logs' ) ;
8184
85+ // Run simulation (sim / cocotb / both)
8286 if ( lesson . runner === 'sim' || lesson . runner === 'cocotb' || lesson . runner === 'both' ) {
8387 await page . getByTestId ( 'run-button' ) . click ( ) ;
8488 await expect ( logs ) . not . toContainText ( 'exit code: 1' , { timeout : 120_000 } ) ;
85- // Confirm a tool actually ran
86- const toolRan = await Promise . race ( [
87- expect ( logs ) . toContainText ( '$ circt-sim' , { timeout : 120_000 } ) . then ( ( ) => true ) . catch ( ( ) => false ) ,
88- expect ( logs ) . toContainText ( '$ cocotb' , { timeout : 120_000 } ) . then ( ( ) => true ) . catch ( ( ) => false ) ,
89- ] ) ;
90- // At least one of circt-sim or cocotb must appear
91- const logText = await logs . innerText ( ) ;
92- const ran = logText . includes ( '$ circt-sim' ) || logText . includes ( '$ cocotb' ) || logText . includes ( 'cocotb' ) ;
93- expect ( ran , `No sim tool ran for lesson "${ lesson . title } ": ${ logText . slice ( 0 , 300 ) } ` ) . toBe ( true ) ;
89+ // Confirm a simulation tool actually executed
90+ await expect ( logs ) . toContainText ( '$ circt' , { timeout : 120_000 } ) ;
9491 }
9592
93+ // Run formal / LEC (bmc / lec / both)
9694 if ( lesson . runner === 'bmc' || lesson . runner === 'lec' || lesson . runner === 'both' ) {
97- const verifyBtn = page . getByTestId ( 'verify-button' ) ;
98- await verifyBtn . click ( ) ;
95+ await page . getByTestId ( 'verify-button' ) . click ( ) ;
9996 await expect ( logs ) . not . toContainText ( 'exit code: 1' , { timeout : 120_000 } ) ;
100- const logText = await logs . innerText ( ) ;
101- const ran = logText . includes ( '$ circt-bmc' ) || logText . includes ( '$ circt-lec' ) ;
102- expect ( ran , `No formal tool ran for lesson "${ lesson . title } ": ${ logText . slice ( 0 , 300 ) } ` ) . toBe ( true ) ;
97+ await expect ( logs ) . toContainText ( '$ circt' , { timeout : 120_000 } ) ;
10398 }
10499 } ) ;
105100}
0 commit comments