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.
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.
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.
= in always @(*) — blocking, evaluates immediately<= in always @(posedge clk) — non-blocking, all assignments happen simultaneously
+ at the clock edgealways block must be of type regalways block++ +Latch warning: If you don't assign a
+regin every branch of an +always @(*)block, synthesis creates a latch (unintentional memory). Always cover all + cases!
+\ 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 @@ +Exercise: Complete
+counter.sv. Implement a 4-bit counter that increments on each + rising clock edge and resets to 0 whenresetis high.
Verilog provides built-in gate-level + primitives that model fundamental logic gates. This is the lowest level of abstraction before transistors. +
+ +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. +
+
+\ 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 @@ +Exercise: Build a 2-to-1 multiplexer using only gate primitives (
+and, +or,not). Whensel=0, outputa; whensel=1, + outputb.
Verilog provides if/else and case statements for decision-making inside procedural blocks.
+ These map directly to hardware multiplexers and priority encoders.
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.
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.
casez — Treats ? and z as don't-care in the pattern (useful for priority
+ encoders)casex — Treats x and z as don't-care (dangerous in simulation, avoid in
+ RTL)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+ +
+\ 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 @@ +Exercise: Complete the ALU in
+alu.svusing acasestatement. Implement + ADD, SUB, AND, OR operations based on a 2-bit opcode.
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.
+ +While Verilog looks syntactically similar to C, there's a fundamental difference:
+ + + +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.
Verilog code serves two purposes:
+initial, $display, and #delay exist only in simulation.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.
+ +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 @@ +Your first task: Open
top.svand use$displayto print "Hello, Verilog!" followed by$finish. This confirms the simulation environment is working.
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.
+ +input — Data flows into the module (like a chip receiving a clock or data)output — Data flows out of the module (like a chip driving a result)inout — Bidirectional port (used for things like I²C data lines)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).
The assign statement creates a continuous connection — like a physical wire. Whenever a or
+ b changes, sum updates immediately.
+\ 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 @@ +Exercise: Complete the
+addermodule inadder.sv. It should take two + 4-bit inputsaandb, and produce a 5-bitsumoutput equal to +a + b. The testbench will verify your design.
Verilog provides a rich set of operators for manipulating bits and values. These are the workhorses of RTL design. +
+ +Operate on each bit independently:
+~a // NOT (invert every bit) +a & b // AND +a | b // OR +a ^ b // XOR +a ~^ b // XNOR+ +
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).
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)+ +
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)+ +
{a, b} // concatenate: join bits
+{4{1'b1}} // replicate: 4'b1111
+{a[3], b[2:0]} // mix and match bits
+
+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.
+ ++\ 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 @@ +Exercise: Complete
+ops.svto compute: (1) the parity of inputausing + the XOR reduction operator, (2) the concatenation ofaandbinto a wider output, and + (3) a mux using the ternary operator.
A testbench + is where you verify your design works correctly. It wraps around your module, drives inputs, and checks outputs.
+ +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+ +
$display("msg", args) — Print once (like printf with newline)$monitor("msg", args) — Print every time a listed signal changes$time — Current simulation time$dumpfile("name.vcd") — Create waveform dump file$dumpvars — Record all signals for waveform viewing#N — Delay N time units$finish — End simulationAlways 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);+ +
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 !=.
+\ 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 @@ +Exercise: Complete the testbench in
+tb.sv. Instantiate theadder+ module, drive test vectors, and check that the output matches expected values using$display.
Verilog has two fundamental signal types: wire and reg. Understanding the difference is
+ critical — it determines how and where you can use a signal.
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+ +
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 is the default type for ports — no declaration needed for input portsoutput reg declares an output that is assigned procedurallywire inside an always blockassign to drive a reg++ +Note: SystemVerilog simplifies this with the
+logictype that can be used anywhere. + But understandingwirevsregis essential for reading legacy Verilog code.
+\ 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 +endmoduleExercise: Complete the module in
+signals.sv. Useassignto drive the +wireoutput and analwaysblock to drive theregoutput.