Skip to content

Commit 2ac498a

Browse files
thomasahleclaude
andcommitted
Curriculum polish: lesson descriptions and structure
- Rename 'Parameters and localparam' to 'Parameters' - Move Parameters into Data Types chapter (remove Parameterized Modules chapter) - Fix always_ff description: typo, 2-stage pipeline context, SRAM transition - Update counter description: reframe around always_ff, add burst-access dfn - Minor alignment fix in packed-structs description Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ebf888b commit 2ac498a

5 files changed

Lines changed: 24 additions & 17 deletions

File tree

src/lessons/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ export const parts = [
7878
chapters: [
7979
{ 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')] },
81-
{ title: 'Parameterized Modules', lessons: [L('sv/parameters')] },
82-
{ title: 'Data Types', lessons: [L('sv/packed-structs')] },
81+
{ title: 'Data Types', lessons: [L('sv/parameters'), L('sv/packed-structs')] },
8382
{ title: 'Interfaces & Procedures', lessons: [L('sv/interfaces'), L('sv/tasks-functions')] },
8483
{ title: 'State Machines', lessons: [L('sv/enums'), L('sv/fsm')] },
8584
{ title: 'Covergroups', lessons: [L('sv/covergroup-basics'), L('sv/coverpoint-bins'), L('sv/cross-coverage')] },

src/lessons/meta.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default {
1010
'sv/events': { title: 'Events', focus: '/src/event_sync.sv' },
1111
'sv/always-ff': { title: 'Flip-Flops with always_ff', focus: '/src/sram_core.sv' },
1212
'sv/counter': { title: 'Up-Counter', focus: '/src/counter.sv' },
13-
'sv/parameters': { title: 'Parameters and localparam', focus: '/src/sram.sv' },
13+
'sv/parameters': { title: 'Parameters', focus: '/src/sram.sv' },
1414
'sv/packed-structs': { title: 'Packed Structs and Unions', focus: '/src/mem_cmd.sv' },
1515
'sv/interfaces': { title: 'Interfaces and modport', focus: '/src/mem_if.sv' },
1616
'sv/tasks-functions': { title: 'Tasks and Functions', focus: '/src/tb.sv' },

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<p><code>always @(event) begin ... end</code> is block that runs every time a specified event fires.
1+
<p><code>always @(event) begin ... end</code> is a block that runs every time a specified event fires.
22
If the event is a clock edge (e.g. <code>posedge clk</code>) we typically use <code>always_ff</code> instead, where "ff" stands for "flip-flop".
33
</p>
44
<svg width="300" height="68" viewBox="0 0 300 68" xmlns="http://www.w3.org/2000/svg" style="display:block;max-width:100%;font-family:'IBM Plex Mono',monospace;font-size:13px;margin:10px auto">
@@ -15,18 +15,20 @@
1515
</svg>
1616
<p>
1717
A flip-flop is a 1-bit memory element that captures its input (d) at a clock edge and holds it until the next edge.
18-
It's easy to model a flip-flop in SystemVerilog using an <code>always_ff</code> block:
18+
It's easy to model a flip-flop in SystemVerilog using an <code>always_ff</code> block.
19+
Here, two flip-flops form a 2-stage pipeline — <code>out</code> always lags <code>mem</code> by one cycle:
1920
<pre>
2021
always_ff @(posedge clk) begin
21-
mem &lt;= d; // on each rising edge, capture d into mem
22-
out &lt;= mem; // on the same edge, capture mem into out (the old value of mem)
22+
mem &lt;= d; // on each rising edge, capture d into mem
23+
out &lt;= mem; // capture the old value of mem into out
2324
end
2425
</pre>
2526
<blockquote><p>
2627
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>. All right-hand sides are evaluated first, then all assignments happen simultaneously — this prevents races and models real flip-flop behaviour.</p></blockquote>
2728
<p>
28-
SRAM consists entirely of flip-flops.
29-
However we'll need a slightly more advanced pattern to model memory addresses:
29+
An SRAM is an array of flip-flops — one per bit — indexed by address.
30+
We'll need a slightly more advanced pattern to model that array.
31+
We also use a port <code>we</code> (write enable) to control when writes happen, and a separate port <code>rdata</code> for the read result.
3032
</p>
3133
<!-- Timing diagram: write on cycle 1, read request cycle 2, result cycle 3 -->
3234
<svg width="400" height="210" viewBox="0 0 400 210" xmlns="http://www.w3.org/2000/svg" style="display:block;max-width:100%;font-family:'IBM Plex Mono',monospace;font-size:13px;margin:10px auto">
@@ -69,7 +71,7 @@
6971
<line x1="108" y1="196" x2="324" y2="196" stroke="#586469" stroke-width="1.5" marker-start="url(#sff-larr)" marker-end="url(#sff-rarr)" stroke-dasharray="4,3"/>
7072
<text x="216" y="208" text-anchor="middle" fill="#586469" font-size="12">1-cycle read latency</text>
7173
</svg>
72-
<p>Open <code>sram_core.sv</code> and fill in the <code>always_ff</code> body with two statements:</p>
74+
<p>In <code>sram_core.sv</code> fill in the <code>always_ff</code> body with two statements:</p>
7375
<ul>
7476
<li>When <code>we</code> is high, write <code>wdata</code> into <code>mem[addr]</code></li>
7577
<li>Always register the read: capture <code>mem[addr]</code> into <code>rdata</code></li>
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
<p>Our SRAM testbench uses a counter like this to step through addresses in burst-write and burst-read sequences — incrementing the address each cycle to fill or verify the entire memory array.</p>
2-
<p>A counter is the canonical example of sequential logic with state. It uses the same <code>always_ff</code> pattern as a flip-flop, but adds an enable signal and an incrementing expression.</p>
3-
<p>Open <code>counter.sv</code> and implement an 8-bit up-counter:</p>
1+
<p>Another thing we can build with <code>always_ff</code> is a counter.</p>
2+
<p>Our SRAM will use a counter to step through addresses in
3+
<dfn data-card="Burst access is a sequence of reads or writes to consecutive addresses, often used in memory interfaces to improve efficiency. A burst-write sequence means writing multiple data words to consecutive addresses in one operation, while a burst-read sequence means reading multiple data words from consecutive addresses in one operation.">
4+
burst-write</dfn> and burst-read sequences.
5+
We will also have two signals <code>en</code> and <code>rst_n</code> to control the counter.
6+
The counter should increment when <code>en</code> is high, and reset to zero when <code>rst_n</code> is low.
7+
</p>
8+
<p>In <code>counter.sv</code> implement an 8-bit up-counter:</p>
49
<ul>
5-
<li><dfn data-card="Synchronous reset clears the register only on a clock edge (when rst_n is low at posedge clk). This keeps the flip-flop's timing clean — the reset path goes through normal combinational logic before the flip-flop. Asynchronous reset, by contrast, clears the register immediately regardless of the clock, which requires extra care in timing analysis and FPGA implementation.">Synchronous reset</dfn>: when <code>!<dfn data-card="The _n suffix is an RTL naming convention for active-low signals: the signal is asserted (active) when it is 0, not 1. rst_n means reset-active-low — the chip is held in reset while rst_n = 0 and runs normally when rst_n = 1. Active-low resets are common because power-on-reset circuits naturally pull a line low until the supply stabilizes, and because CMOS NOR gates are faster than NAND gates (active-low logic is slightly cheaper in some process nodes).">rst_n</dfn></code>, set <code>count</code> to zero</li>
6-
<li>Count up: when <code>en</code> is high, add 1 each cycle</li>
10+
<li><dfn data-card="Synchronous reset clears the register only on a clock edge (when rst_n is low at posedge clk). This keeps the flip-flop's timing clean — the reset path goes through normal combinational logic before the flip-flop. Asynchronous reset, by contrast, clears the register immediately regardless of the clock, which requires extra care in timing analysis and FPGA implementation.">Synchronous reset</dfn>: when <code>!<dfn data-card="The _n suffix is an RTL naming convention for active-low signals: the signal is asserted (active) when it is 0, not 1. rst_n means reset-active-low — the chip is held in reset while rst_n = 0 and runs normally when rst_n = 1. Active-low resets are common because power-on-reset circuits naturally pull a line low until the supply stabilizes, and because CMOS NOR gates are faster than NAND gates (active-low logic is slightly cheaper in some process nodes).">rst_n</dfn></code>, set <code>count</code> to zero</li>
11+
<li>Count up: when <code>en</code> is high, add 1 each cycle</li>
712
</ul>
813
<blockquote><p>Notice that <code>else if</code> naturally gives reset higher priority than enable — a common and intentional idiom in RTL.</p></blockquote>
914
<h2>Testbench</h2>
1015
<p>The testbench verifies three behaviors in sequence: counting, holding (enable de-asserted), and resetting. After releasing reset with enable high it uses <code>repeat(5) @(posedge clk);</code> to advance exactly five cycles, then samples <code>count</code>:</p>
1116
<pre>repeat(5) @(posedge clk);
1217
#1 $display("after 5 cycles: %0d (expect 5)", count);</pre>
18+
<p>Here <code>repeat (n) begin ... end</code> is a convenient way to repeat a block of code n times.</p>
1319
<p>Open the <strong>Waves</strong> tab to see <code>count</code> increment each cycle, freeze when <code>en</code> goes low, and snap back to zero when <code>rst_n</code> asserts.</p>

src/lessons/sv/packed-structs/description.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
<p>Fields are laid out <strong>MSB-first</strong> in declaration order. You can assign the struct as a whole or access individual fields:</p>
33
<pre>mem_cmd_t cmd;
44
cmd.we = 1'b1;
5-
cmd.addr = 4'd3; // field write
5+
cmd.addr = 4'd3; // field write
66
cmd = '{we: 1'b1, addr: 4'd3, wdata: 8'd55}; // struct literal
7-
cmd = 13'b1_0011_00110111; // raw bit assignment</pre>
7+
cmd = 13'b1_0011_00110111; // raw bit assignment</pre>
88
<p>Open <code>mem_cmd.sv</code>. Inside the <code><dfn data-card="typedef creates a type alias — a new name for an existing type. In SystemVerilog it is most commonly used with enum and struct to give a meaningful name to a bit-vector layout: typedef struct packed {...} mem_cmd_t creates a type called mem_cmd_t that you can use in port declarations and variable definitions. The _t suffix is a convention for type names, borrowed from C. typedef'd types can be exported from packages and imported by any module in the design.">typedef</dfn> struct packed { } mem_cmd_t;</code> skeleton, add the three fields of the SRAM command bus:</p>
99
<ul>
1010
<li>A single-bit write-enable flag (<code>we</code>)</li>

0 commit comments

Comments
 (0)