diff --git a/src/lessons/content-alignment.test.js b/src/lessons/content-alignment.test.js
index 82265c8..edc7c0b 100644
--- a/src/lessons/content-alignment.test.js
+++ b/src/lessons/content-alignment.test.js
@@ -25,4 +25,25 @@ describe('browser-audit lesson alignment', () => {
expect(read('sva/sequence-basics/grant_check.sol.sv')).not.toContain('llhd.process');
expect(read('sva/sequence-basics/grant_check.sol.sv')).not.toContain('$display("PASS at');
});
+
+ it('keeps the SystemVerilog Basics copy technically accurate', () => {
+ const welcome = read('sv/welcome/description.html');
+ const events = read('sv/events/description.html');
+ const parameters = read('sv/parameters/description.html');
+ const enums = read('sv/enums/description.html');
+
+ expect(welcome).not.toContain('One tight paragraph on printf/display notation');
+ expect(welcome).toContain('Calling $finish explicitly ends the simulation');
+ expect(events).not.toContain('events are not stateful (latching)');
+ expect(events).toContain('wait(event_name.triggered)');
+ expect(events).toContain('IEEE 1800-2023 §15.5');
+ expect(events).toContain('This event is generated automatically');
+ expect(parameters).not.toContain('$bits(16) = 4');
+ expect(parameters).toContain('IEEE 1800-2023 §20.6.2');
+ expect(parameters).toContain('IEEE 1800-2023 §20.8.1');
+ expect(parameters).toContain('replace the hardcoded dimensions');
+ expect(enums).not.toContain('remove state_bits');
+ expect(enums).toContain('do not add a separate state_bits port');
+ expect(enums).toContain('IEEE 1800-2023 §6.19');
+ });
});
diff --git a/src/lessons/sv/enums/description.html b/src/lessons/sv/enums/description.html
index ca6d2d7..618d30c 100644
--- a/src/lessons/sv/enums/description.html
+++ b/src/lessons/sv/enums/description.html
@@ -1,10 +1,10 @@
-
An enum gives meaningful names to a set of integer constants. For a memory controller, the states are naturally expressed as an enum rather than raw bit-vectors:
+An enum gives meaningful names to a set of integer constants. For a memory controller, the states are naturally expressed as an enum rather than raw bit-vectors:
typedef enum logic [1:0] { IDLE, CMD, READ, WRITE } ctrl_state_t;
The compiler assigns IDLE=0, CMD=1, READ=2, WRITE=3 automatically. Once defined, you use the type just like any other port type and use the constant names directly in case branches — no raw numbers needed.
Open status.sv. There are three things to do:
typedef enum declaration above the moduleinput ctrl_state_t state (remove state_bits)state port from logic [1:0] to ctrl_state_t — do not add a separate state_bits portcase branches to match on READ and WRITE by namediff --git a/src/lessons/sv/events/description.html b/src/lessons/sv/events/description.html index 52242fb..63411d2 100644 --- a/src/lessons/sv/events/description.html +++ b/src/lessons/sv/events/description.html @@ -10,8 +10,8 @@In the next lesson we'll build the full memory access controller FSM that transitions between these states. The enum you define here becomes the state register type.
We can coordinate concurrent processes using events.
+The trigger itself is instantaneous, but the event's triggered state persists for the remainder of the current simulation time slot (IEEE 1800-2023 §15.5).
+A process waiting with @ must start waiting before the trigger; use wait(event_name.triggered) when the wait and trigger may occur in either order.">events.
An event is a named synchronization point that processes can wait for or post.
The basic operations on events are:
@(write_done): wait for the event — suspends until posted-
+Note: You could also just write
@ write_done, but using brackets around the event is a universal convention
Note: You can write either
@write_doneor@(write_done). The parenthesized form is common and makes the event control especially easy to spot.
The post is instantaneous: every process suspended at @(write_done) resumes in the same simulation time step. Events carry no data and cannot be synthesised — they live only in testbenches and simulation models.
Add the four TODO lines — one -> and one @ per handshake. Without them, the reader and checker start at t=0, read uninitialised x values, and PASS never prints.
+Warning: If you add
@(write_done)but forget-> write_done, the reader waits forever and the simulation never finishes. Each handshake requires both sides: a sender and at least one receiver.
Warning: If you add
@(write_done)but forget-> write_done, the reader remains blocked. If no other process ends the test or schedules more work, the simulation can then stop with the reader still waiting. Each handshake requires both sides: a sender and at least one receiver.
Hint: You can follow the events status in the simulator waveform viewer, just like any other signal.
@@ -71,6 +71,6 @@
A very important built-in event is posedge clk.
-This even is generated automatically by the simulator whenever the signal clk transitions from 0 to 1.
+This event is generated automatically by the simulator whenever the signal clk transitions from 0 to 1.
You don't post it yourself, but you can wait for it with @(posedge clk).
In the next lesson, always_ff @(posedge clk) 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.
So far we've been writing arrays and vectors with fixed widths, like [7:0].
-Later we will discuss include dynamic arrays, arr[], associative arrays, arr[string], and queues, int q[$],
-which add more flexibility.
-However, these are not synthesizable.
+Later we will discuss dynamic arrays, arr[], associative arrays, arr[string], and queues, int q[$],
+which add more flexibility. These testbench-oriented containers are not the focus of this synthesizable RTL lesson.
Instead we will use compile-time parameters to create flexible, reusable modules:
module sram #(parameter int DEPTH = 16, ...) ( // ... ports ... @@ -14,12 +13,11 @@We can apply some system functions, like -
+$clog2-and$bits-at compile time. -We can use this to compute the minimum number of address bits needed for a given depth.$clog2+and$bits+at compile time. For this lesson, use$clog2(DEPTH)for the minimum number of address bits.Task: Add parametrization to
+Add the parameter block to the module header, then replace the hardcoded dimensions with parameter-derived equivalents: usesram.sv. -Add the parameter block to the module header, then replace the three hardcoded widths with their parameter-derived equivalents: use$clog2(DEPTH)for the address port andWIDTHfor the data ports and memory array.$clog2(DEPTH)for the address port,WIDTHfor both data ports and each memory element, andDEPTHfor the memory range.Always use
$clog2for address widths derived from depth parameters — it automatically adjusts when the depth changes, so the address bus is always exactly the right size.Testbench
The solution testbench instantiates the same module twice with different parameters to demonstrate the power of parameterization: an 8-deep × 4-bit SRAM and a 256-deep × 16-bit SRAM, from a single RTL source.
diff --git a/src/lessons/sv/welcome/description.html b/src/lessons/sv/welcome/description.html index 1b616fc..664d4cd 100644 --- a/src/lessons/sv/welcome/description.html +++ b/src/lessons/sv/welcome/description.html @@ -17,10 +17,10 @@
By the end you'll have a chip that's been hand-coded, formally proved, and exhaustively randomized — the same flow used in industry-grade ASIC design.
+The tutorial introduces RTL design, assertions, and verification environments step by step, using the SRAM as a running example.
Before we build anything, let's make sure the simulator is working. Open top.sv. The initial block runs once at time 0. Use
$display
-to print a message to the log (the syntax is identical to C's printf.)
-
-One tight paragraph on printf/display notation:
-
-
-For example, you can write
+to print a message to the log. For example, you can write
$display("HELLO, %s WORLD", "SRAM");
-+Every SystemVerilog simulation ends when
$finishis called. Without it, the simulator would run forever waiting for events that never come.
Calling
$finishexplicitly ends the simulation. A simulation can also reach its natural end when no process or scheduled event remains; a clock or other recurring process can keep it active, so testbenches commonly call$finishwhen their checks are complete.
Hint: Install Claude Chrome Extension to ask follow up questions on the lessons right in your browser. If any lesson can be improved, please send aSimulation vs. synthesis:
initialblocks,$display, and#ndelay controls exist only in simulation — they have no hardware equivalent and synthesis tools ignore them. Only structural constructs (always_ff,always_comb, module ports,assign) describe real hardware. You will use both worlds throughout this tutorial: RTL for the chip, simulation-only constructs for testing it.