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:

  1. Add the typedef enum declaration above the module
  2. -
  3. Change the port to input ctrl_state_t state (remove state_bits)
  4. +
  5. Change the existing state port from logic [1:0] to ctrl_state_t — do not add a separate state_bits port
  6. Update the case branches to match on READ and WRITE by name

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.

diff --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 @@

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:

-

Note: You could also just write @ write_done, but using brackets around the event is a universal convention

+

Note: You can write either @write_done or @(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.

@@ -63,7 +63,7 @@

A two-event pipeline

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 two-event pipeline

Built-in vs named events

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.

diff --git a/src/lessons/sv/parameters/description.html b/src/lessons/sv/parameters/description.html index 1fd8603..79dac29 100644 --- a/src/lessons/sv/parameters/description.html +++ b/src/lessons/sv/parameters/description.html @@ -1,8 +1,7 @@

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 sram.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 and WIDTH for the data ports and memory array.

+Add the parameter block to the module header, then replace the hardcoded dimensions with parameter-derived equivalents: use $clog2(DEPTH) for the address port, WIDTH for both data ports and each memory element, and DEPTH for the memory range.

Always use $clog2 for 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.

Hello World

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 $finish is called. Without it, the simulator would run forever waiting for events that never come.

+

Calling $finish explicitly 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 $finish when their checks are complete.

Simulation vs. synthesis: initial blocks, $display, and #n delay 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.

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 a