From 5c8ace13304ab59a42565ab9f7badd468d6dda34 Mon Sep 17 00:00:00 2001 From: Samir Date: Fri, 26 Jun 2026 20:33:34 -0700 Subject: [PATCH] Add introductory Verilog lessons --- .../verilog/always-blocks/counter.sol.sv | 12 +++ src/lessons/verilog/always-blocks/counter.sv | 7 ++ .../verilog/always-blocks/description.html | 62 ++++++++++++++ src/lessons/verilog/gates/description.html | 81 +++++++++++++++++++ src/lessons/verilog/gates/mux2.sol.sv | 12 +++ src/lessons/verilog/gates/mux2.sv | 10 +++ src/lessons/verilog/if-case/alu.sol.sv | 16 ++++ src/lessons/verilog/if-case/alu.sv | 11 +++ src/lessons/verilog/if-case/description.html | 65 +++++++++++++++ src/lessons/verilog/intro/description.html | 34 ++++++++ src/lessons/verilog/intro/top.sol.sv | 6 ++ src/lessons/verilog/intro/top.sv | 5 ++ src/lessons/verilog/modules/adder.sol.sv | 7 ++ src/lessons/verilog/modules/adder.sv | 7 ++ src/lessons/verilog/modules/description.html | 56 +++++++++++++ .../verilog/operators/description.html | 45 +++++++++++ src/lessons/verilog/operators/ops.sol.sv | 12 +++ src/lessons/verilog/operators/ops.sv | 10 +++ .../verilog/testbench/description.html | 66 +++++++++++++++ src/lessons/verilog/testbench/tb.sol.sv | 33 ++++++++ src/lessons/verilog/testbench/tb.sv | 20 +++++ .../verilog/wires-regs/description.html | 48 +++++++++++ src/lessons/verilog/wires-regs/signals.sol.sv | 12 +++ src/lessons/verilog/wires-regs/signals.sv | 10 +++ 24 files changed, 647 insertions(+) create mode 100644 src/lessons/verilog/always-blocks/counter.sol.sv create mode 100644 src/lessons/verilog/always-blocks/counter.sv create mode 100644 src/lessons/verilog/always-blocks/description.html create mode 100644 src/lessons/verilog/gates/description.html create mode 100644 src/lessons/verilog/gates/mux2.sol.sv create mode 100644 src/lessons/verilog/gates/mux2.sv create mode 100644 src/lessons/verilog/if-case/alu.sol.sv create mode 100644 src/lessons/verilog/if-case/alu.sv create mode 100644 src/lessons/verilog/if-case/description.html create mode 100644 src/lessons/verilog/intro/description.html create mode 100644 src/lessons/verilog/intro/top.sol.sv create mode 100644 src/lessons/verilog/intro/top.sv create mode 100644 src/lessons/verilog/modules/adder.sol.sv create mode 100644 src/lessons/verilog/modules/adder.sv create mode 100644 src/lessons/verilog/modules/description.html create mode 100644 src/lessons/verilog/operators/description.html create mode 100644 src/lessons/verilog/operators/ops.sol.sv create mode 100644 src/lessons/verilog/operators/ops.sv create mode 100644 src/lessons/verilog/testbench/description.html create mode 100644 src/lessons/verilog/testbench/tb.sol.sv create mode 100644 src/lessons/verilog/testbench/tb.sv create mode 100644 src/lessons/verilog/wires-regs/description.html create mode 100644 src/lessons/verilog/wires-regs/signals.sol.sv create mode 100644 src/lessons/verilog/wires-regs/signals.sv diff --git a/src/lessons/verilog/always-blocks/counter.sol.sv b/src/lessons/verilog/always-blocks/counter.sol.sv new file mode 100644 index 0000000..be7d71a --- /dev/null +++ b/src/lessons/verilog/always-blocks/counter.sol.sv @@ -0,0 +1,12 @@ +module counter( + input clk, + input reset, + output reg [3:0] count +); + always @(posedge clk) begin + if (reset) + count <= 4'b0; + else + count <= count + 1; + end +endmodule diff --git a/src/lessons/verilog/always-blocks/counter.sv b/src/lessons/verilog/always-blocks/counter.sv new file mode 100644 index 0000000..9710e92 --- /dev/null +++ b/src/lessons/verilog/always-blocks/counter.sv @@ -0,0 +1,7 @@ +module counter( + input clk, + input reset, + output reg [3:0] count +); + // TODO: increment count on posedge clk, reset to 0 when reset is high +endmodule diff --git a/src/lessons/verilog/always-blocks/description.html b/src/lessons/verilog/always-blocks/description.html new file mode 100644 index 0000000..e37bb97 --- /dev/null +++ b/src/lessons/verilog/always-blocks/description.html @@ -0,0 +1,62 @@ +

The always block is the core procedural construct in Verilog. It describes behavior that is re-evaluated + whenever signals in its sensitivity + list change.

+ +

Combinational Logic: always @(*)

+
always @(*) begin
+  if (sel)
+    y = a;
+  else
+    y = b;
+end
+

The @(*) (or @*) is shorthand for "re-evaluate when any input changes." Use + blocking assignment (=) for combinational logic.

+ +

Sequential Logic: always @(posedge clk)

+
always @(posedge clk) begin
+  if (reset)
+    q <= 0;
+  else
+    q <= d;
+end
+

This creates a flip-flop. + Use non-blocking assignment (<=) for sequential logic to avoid race conditions.

+ + + + Combinational + always @(*) + Blocking = + No clock needed + → Gates & muxes + + + Sequential + always @(posedge clk) + Non-blocking <=< /text> + Clock-triggered + → Flip-flops + + +

Critical Rules

+ + +
+

Latch warning: If you don't assign a reg in every branch of an + always @(*) block, synthesis creates a latch (unintentional memory). Always cover all + cases!

+
+ +
+

Exercise: Complete counter.sv. Implement a 4-bit counter that increments on each + rising clock edge and resets to 0 when reset is high.

+
\ No newline at end of file diff --git a/src/lessons/verilog/gates/description.html b/src/lessons/verilog/gates/description.html new file mode 100644 index 0000000..4aa8cfa --- /dev/null +++ b/src/lessons/verilog/gates/description.html @@ -0,0 +1,81 @@ +

Verilog provides built-in gate-level + primitives that model fundamental logic gates. This is the lowest level of abstraction before transistors. +

+ +

Basic Gates

+ + + AND + + + + + + Y = A · B + + + OR + + + + + Y = A + B + + + NOT + + + + + Y = ~A + + + XOR + + + + + + Y = A ⊕ B + + + NAND + + + + Y = ~(A·B) + + + NOR + + + Y = ~(A+B) + + +

Verilog Gate Syntax

+
and  u1 (y, a, b);   // y = a & b
+or   u2 (y, a, b);   // y = a | b
+not  u3 (y, a);      // y = ~a
+xor  u4 (y, a, b);   // y = a ^ b
+nand u5 (y, a, b);   // y = ~(a & b)
+nor  u6 (y, a, b);   // y = ~(a | b)
+ +

The first argument is always the output, followed by inputs. The instance name (u1, + u2, ...) is optional but recommended.

+ +
+

Industry note: Gate-level modeling is rarely written by hand today — synthesis tools generate it + from behavioral RTL. However, understanding gates is essential for reading netlists and debugging timing issues. +

+
+ +
+

Exercise: Build a 2-to-1 multiplexer using only gate primitives (and, + or, not). When sel=0, output a; when sel=1, + output b.

+
\ No newline at end of file diff --git a/src/lessons/verilog/gates/mux2.sol.sv b/src/lessons/verilog/gates/mux2.sol.sv new file mode 100644 index 0000000..5e80f32 --- /dev/null +++ b/src/lessons/verilog/gates/mux2.sol.sv @@ -0,0 +1,12 @@ +module mux2( + input a, + input b, + input sel, + output y +); + wire sel_n, w1, w2; + not u1 (sel_n, sel); + and u2 (w1, a, sel_n); + and u3 (w2, b, sel); + or u4 (y, w1, w2); +endmodule diff --git a/src/lessons/verilog/gates/mux2.sv b/src/lessons/verilog/gates/mux2.sv new file mode 100644 index 0000000..9ce6e24 --- /dev/null +++ b/src/lessons/verilog/gates/mux2.sv @@ -0,0 +1,10 @@ +module mux2( + input a, + input b, + input sel, + output y +); + // TODO: build a 2:1 mux using and, or, not gates + // y = (a & ~sel) | (b & sel) + wire sel_n, w1, w2; +endmodule diff --git a/src/lessons/verilog/if-case/alu.sol.sv b/src/lessons/verilog/if-case/alu.sol.sv new file mode 100644 index 0000000..ce53f47 --- /dev/null +++ b/src/lessons/verilog/if-case/alu.sol.sv @@ -0,0 +1,16 @@ +module alu( + input [7:0] a, + input [7:0] b, + input [1:0] op, + output reg [7:0] result +); + always @(*) begin + case (op) + 2'b00: result = a + b; + 2'b01: result = a - b; + 2'b10: result = a & b; + 2'b11: result = a | b; + default: result = 8'b0; + endcase + end +endmodule diff --git a/src/lessons/verilog/if-case/alu.sv b/src/lessons/verilog/if-case/alu.sv new file mode 100644 index 0000000..4db5e34 --- /dev/null +++ b/src/lessons/verilog/if-case/alu.sv @@ -0,0 +1,11 @@ +module alu( + input [7:0] a, + input [7:0] b, + input [1:0] op, // 00=ADD, 01=SUB, 10=AND, 11=OR + output reg [7:0] result +); + // TODO: use a case statement on 'op' to compute result + always @(*) begin + result = 8'b0; + end +endmodule diff --git a/src/lessons/verilog/if-case/description.html b/src/lessons/verilog/if-case/description.html new file mode 100644 index 0000000..e428786 --- /dev/null +++ b/src/lessons/verilog/if-case/description.html @@ -0,0 +1,65 @@ +

Verilog provides if/else and case statements for decision-making inside procedural blocks. + These map directly to hardware multiplexers and priority encoders.

+ +

if / else

+
always @(*) begin
+  if (sel == 2'b00)
+    y = a;
+  else if (sel == 2'b01)
+    y = b;
+  else if (sel == 2'b10)
+    y = c;
+  else
+    y = d;
+end
+

if/else creates priority logic — the first matching condition wins. This synthesizes to + a chain of muxes.

+ +

case

+
always @(*) begin
+  case (sel)
+    2'b00: y = a;
+    2'b01: y = b;
+    2'b10: y = c;
+    2'b11: y = d;
+    default: y = 0;
+  endcase
+end
+

case checks for an exact match. All values should be covered — use default to catch + remaining cases.

+ +

Variants

+ +
always @(*) begin
+  casez (req)
+    4'b1???: grant = 4'b1000;  // bit 3 highest priority
+    4'b01??: grant = 4'b0100;
+    4'b001?: grant = 4'b0010;
+    4'b0001: grant = 4'b0001;
+    default: grant = 4'b0000;
+  endcase
+end
+ +

Synthesis Implications

+ + + if/else + Priority chain + Slower, cascaded muxes + + + case + Parallel selection + Faster, one-hot mux + + +
+

Exercise: Complete the ALU in alu.sv using a case statement. Implement + ADD, SUB, AND, OR operations based on a 2-bit opcode.

+
\ No newline at end of file diff --git a/src/lessons/verilog/intro/description.html b/src/lessons/verilog/intro/description.html new file mode 100644 index 0000000..f6eb0d4 --- /dev/null +++ b/src/lessons/verilog/intro/description.html @@ -0,0 +1,34 @@ +

Verilog is a Hardware Description Language (HDL) used to model and design digital circuits. Created in 1984 by Phil Moorby and Prabhu Goel at Gateway Design Automation, it became an IEEE standard (IEEE 1364) in 1995.

+ +

HDL vs Software Languages

+

While Verilog looks syntactically similar to C, there's a fundamental difference:

+ + + + Software (C/Python) + Sequential execution + One thing at a time + Runs on a processor + Instructions → CPU + + + Hardware (Verilog) + Parallel execution + Everything at once + Becomes the circuit + Description → Gates + + +

In software, statements execute one after another. In Verilog, all assign statements and always blocks execute concurrently — just like real wires and gates in a circuit all operate simultaneously.

+ +

Synthesis vs Simulation

+

Verilog code serves two purposes:

+ + +

Verilog vs SystemVerilog

+

SystemVerilog is a superset of Verilog. Think of Verilog as the foundation for describing hardware, and SystemVerilog as the extended version with added verification and design features. This tutorial starts with core Verilog concepts, then builds into SystemVerilog.

+ +

Your first task: Open top.sv and use $display to print "Hello, Verilog!" followed by $finish. This confirms the simulation environment is working.

diff --git a/src/lessons/verilog/intro/top.sol.sv b/src/lessons/verilog/intro/top.sol.sv new file mode 100644 index 0000000..0b18b6f --- /dev/null +++ b/src/lessons/verilog/intro/top.sol.sv @@ -0,0 +1,6 @@ +module top; + initial begin + $display("Hello, Verilog!"); + $finish; + end +endmodule diff --git a/src/lessons/verilog/intro/top.sv b/src/lessons/verilog/intro/top.sv new file mode 100644 index 0000000..23efc7f --- /dev/null +++ b/src/lessons/verilog/intro/top.sv @@ -0,0 +1,5 @@ +module top; + initial begin + // TODO: print "Hello, Verilog!" using $display, then call $finish + end +endmodule diff --git a/src/lessons/verilog/modules/adder.sol.sv b/src/lessons/verilog/modules/adder.sol.sv new file mode 100644 index 0000000..f76b853 --- /dev/null +++ b/src/lessons/verilog/modules/adder.sol.sv @@ -0,0 +1,7 @@ +module adder( + input [3:0] a, + input [3:0] b, + output [4:0] sum +); + assign sum = a + b; +endmodule diff --git a/src/lessons/verilog/modules/adder.sv b/src/lessons/verilog/modules/adder.sv new file mode 100644 index 0000000..6cc0b74 --- /dev/null +++ b/src/lessons/verilog/modules/adder.sv @@ -0,0 +1,7 @@ +module adder( + input [3:0] a, + input [3:0] b, + output [4:0] sum +); + // TODO: assign sum = a + b +endmodule diff --git a/src/lessons/verilog/modules/description.html b/src/lessons/verilog/modules/description.html new file mode 100644 index 0000000..ad86113 --- /dev/null +++ b/src/lessons/verilog/modules/description.html @@ -0,0 +1,56 @@ +

Every Verilog design starts with a module — the basic building block that represents a hardware + component. A module defines its interface through ports + and describes its internal behavior.

+ +

Port Directions

+ + + + + + + + my_module + behavior inside + input + + input + + + output + inout + + + + + + +

Module Syntax

+

A module declaration looks like this:

+
module adder(
+  input  [3:0] a,    // 4-bit input
+  input  [3:0] b,    // 4-bit input
+  output [4:0] sum   // 5-bit output (extra bit for carry)
+);
+  assign sum = a + b;
+endmodule
+ +

The [3:0] notation defines a vector + — a multi-bit signal. [3:0] means 4 bits (indices 3, 2, 1, 0).

+ +

Continuous Assignment

+

The assign statement creates a continuous connection — like a physical wire. Whenever a or + b changes, sum updates immediately.

+ +
+

Exercise: Complete the adder module in adder.sv. It should take two + 4-bit inputs a and b, and produce a 5-bit sum output equal to + a + b. The testbench will verify your design.

+
\ No newline at end of file diff --git a/src/lessons/verilog/operators/description.html b/src/lessons/verilog/operators/description.html new file mode 100644 index 0000000..7d8e66e --- /dev/null +++ b/src/lessons/verilog/operators/description.html @@ -0,0 +1,45 @@ +

Verilog provides a rich set of operators for manipulating bits and values. These are the workhorses of RTL design. +

+ +

Bitwise Operators

+

Operate on each bit independently:

+
~a      // NOT (invert every bit)
+a & b   // AND
+a | b   // OR
+a ^ b   // XOR
+a ~^ b  // XNOR
+ +

Logical Operators

+

Evaluate the entire expression as true (1) or false (0):

+
!a      // logical NOT
+a && b  // logical AND
+a || b  // logical OR
+

Key difference: 4'b1010 & 4'b1100 = 4'b1000 (bitwise), but + 4'b1010 && 4'b1100 = 1 (logical — both are nonzero).

+ +

Reduction Operators

+

Collapse a vector into a single bit:

+
&a   // AND all bits: a[3]&a[2]&a[1]&a[0]
+|a   // OR all bits
+^a   // XOR all bits (parity)
+ +

Shift Operators

+
a << 2   // shift left by 2 (fills with 0)
+a >> 1   // shift right by 1
+a <<< 2  // arithmetic shift left
+a >>> 1  // arithmetic shift right (preserves sign)
+ +

Concatenation & Replication

+
{a, b}       // concatenate: join bits
+{4{1'b1}}    // replicate: 4'b1111
+{a[3], b[2:0]}  // mix and match bits
+ +

Ternary Operator

+
assign y = sel ? a : b;  // if sel=1, y=a; else y=b
+

This is the most common way to write a mux in behavioral Verilog.

+ +
+

Exercise: Complete ops.sv to compute: (1) the parity of input a using + the XOR reduction operator, (2) the concatenation of a and b into a wider output, and + (3) a mux using the ternary operator.

+
\ No newline at end of file diff --git a/src/lessons/verilog/operators/ops.sol.sv b/src/lessons/verilog/operators/ops.sol.sv new file mode 100644 index 0000000..136e4de --- /dev/null +++ b/src/lessons/verilog/operators/ops.sol.sv @@ -0,0 +1,12 @@ +module ops( + input [3:0] a, + input [3:0] b, + input sel, + output parity, + output [7:0] combined, + output [3:0] mux_out +); + assign parity = ^a; + assign combined = {a, b}; + assign mux_out = sel ? a : b; +endmodule diff --git a/src/lessons/verilog/operators/ops.sv b/src/lessons/verilog/operators/ops.sv new file mode 100644 index 0000000..832f64a --- /dev/null +++ b/src/lessons/verilog/operators/ops.sv @@ -0,0 +1,10 @@ +module ops( + input [3:0] a, + input [3:0] b, + input sel, + output parity, // XOR reduction of a + output [7:0] combined, // {a, b} concatenated + output [3:0] mux_out // sel ? a : b +); + // TODO: implement using reduction, concatenation, and ternary operators +endmodule diff --git a/src/lessons/verilog/testbench/description.html b/src/lessons/verilog/testbench/description.html new file mode 100644 index 0000000..733b8de --- /dev/null +++ b/src/lessons/verilog/testbench/description.html @@ -0,0 +1,66 @@ +

A testbench + is where you verify your design works correctly. It wraps around your module, drives inputs, and checks outputs.

+ +

Testbench Anatomy

+
module tb;              // no ports!
+  reg  clk, reset;      // drive inputs with reg
+  wire [3:0] count;     // capture outputs with wire
+
+  // Instantiate the DUT (Design Under Test)
+  counter dut (
+    .clk(clk), .reset(reset), .count(count)
+  );
+
+  // Clock generation
+  initial clk = 0;
+  always #5 clk = ~clk; // 10ns period
+
+  // Stimulus
+  initial begin
+    reset = 1;           // assert reset
+    #20;                 // wait 20ns
+    reset = 0;           // release reset
+    #100;                // let it run
+    $finish;
+  end
+endmodule
+ +

Key Simulation Constructs

+ + +

Named Port Connections

+

Always use named connections (.port(signal)) instead of positional connections. It's self-documenting + and avoids bugs when port order changes:

+
// Good: named connection
+counter dut (.clk(clk), .reset(rst), .count(cnt));
+
+// Bad: positional (fragile)
+counter dut (clk, rst, cnt);
+ +

Self-Checking Testbenches

+
initial begin
+  a = 4'd3; b = 4'd5;
+  #10;
+  if (sum !== 5'd8) begin
+    $display("FAIL: expected 8, got %0d", sum);
+    $finish;
+  end
+  $display("PASS");
+  $finish;
+end
+

Use !== instead of != to catch unknown (x) values that would silently pass + with !=.

+ +
+

Exercise: Complete the testbench in tb.sv. Instantiate the adder + module, drive test vectors, and check that the output matches expected values using $display.

+
\ No newline at end of file diff --git a/src/lessons/verilog/testbench/tb.sol.sv b/src/lessons/verilog/testbench/tb.sol.sv new file mode 100644 index 0000000..953d2e4 --- /dev/null +++ b/src/lessons/verilog/testbench/tb.sol.sv @@ -0,0 +1,33 @@ +module adder( + input [3:0] a, + input [3:0] b, + output [4:0] sum +); + assign sum = a + b; +endmodule + +module top; + reg [3:0] a, b; + wire [4:0] sum; + + adder dut (.a(a), .b(b), .sum(sum)); + + initial begin + a = 4'd3; b = 4'd5; + #10; + $display("a=%0d b=%0d sum=%0d", a, b, sum); + if (sum !== 5'd8) begin + $display("FAIL: expected 8"); $finish; + end + + a = 4'd15; b = 4'd1; + #10; + $display("a=%0d b=%0d sum=%0d", a, b, sum); + if (sum !== 5'd16) begin + $display("FAIL: expected 16"); $finish; + end + + $display("ALL TESTS PASSED"); + $finish; + end +endmodule diff --git a/src/lessons/verilog/testbench/tb.sv b/src/lessons/verilog/testbench/tb.sv new file mode 100644 index 0000000..ae0820e --- /dev/null +++ b/src/lessons/verilog/testbench/tb.sv @@ -0,0 +1,20 @@ +module adder( + input [3:0] a, + input [3:0] b, + output [4:0] sum +); + assign sum = a + b; +endmodule + +module top; + reg [3:0] a, b; + wire [4:0] sum; + + // TODO: instantiate the adder module as 'dut' + + initial begin + // TODO: test a=3, b=5, check sum==8 + // TODO: test a=15, b=1, check sum==16 + // TODO: print "ALL TESTS PASSED" and call $finish + end +endmodule diff --git a/src/lessons/verilog/wires-regs/description.html b/src/lessons/verilog/wires-regs/description.html new file mode 100644 index 0000000..4a98f06 --- /dev/null +++ b/src/lessons/verilog/wires-regs/description.html @@ -0,0 +1,48 @@ +

Verilog has two fundamental signal types: wire and reg. Understanding the difference is + critical — it determines how and where you can use a signal.

+ +

wire — Continuous Connections

+

A wire represents a physical connection between components. It must be driven by a continuous assignment + (assign) or a module output. It cannot hold a value on its own.

+
wire [7:0] data_bus;       // 8-bit wire
+assign data_bus = a & b;   // continuously driven
+ +

reg — Procedural Storage

+

A reg holds a value assigned inside procedural blocks (always, initial). + Despite the name, a reg does not always become a hardware register — it depends on how + you use it.

+
reg [7:0] counter;
+always @(posedge clk)
+  counter <= counter + 1;  // THIS becomes a register
+ + + + wire + Driven by assign + or module output + Like a physical wire + + reg + Assigned in always + or initial blocks + Holds value until changed + + +

Key Rules

+ + +
+

Note: SystemVerilog simplifies this with the logic type that can be used anywhere. + But understanding wire vs reg is essential for reading legacy Verilog code.

+
+ +
+

Exercise: Complete the module in signals.sv. Use assign to drive the + wire output and an always block to drive the reg output.

+
\ No newline at end of file diff --git a/src/lessons/verilog/wires-regs/signals.sol.sv b/src/lessons/verilog/wires-regs/signals.sol.sv new file mode 100644 index 0000000..9947825 --- /dev/null +++ b/src/lessons/verilog/wires-regs/signals.sol.sv @@ -0,0 +1,12 @@ +module signals( + input clk, + input [3:0] a, + input [3:0] b, + output [3:0] and_out, + output reg [3:0] reg_out +); + assign and_out = a & b; + + always @(posedge clk) + reg_out <= a; +endmodule diff --git a/src/lessons/verilog/wires-regs/signals.sv b/src/lessons/verilog/wires-regs/signals.sv new file mode 100644 index 0000000..9707d54 --- /dev/null +++ b/src/lessons/verilog/wires-regs/signals.sv @@ -0,0 +1,10 @@ +module signals( + input clk, + input [3:0] a, + input [3:0] b, + output [3:0] and_out, // wire output: a & b + output reg [3:0] reg_out // reg output: latched a on posedge clk +); + // TODO: use assign for and_out + // TODO: use always @(posedge clk) to latch 'a' into reg_out +endmodule