Session 5: RTL Design & Verification¶
Assignment 1 — Write Verilog for the core module¶
The core module is pwm_channel.v — a single PWM channel that takes a rotary encoder input and generates a PWM signal to control LED brightness.
The module has three internal blocks:
- Encoder reader — detects rising edges on
enc_a, increments or decrements the duty cycle register depending onenc_bdirection - 8-bit counter — counts from 0 to 255 continuously
- Comparator — sets
pwm_outHIGH when counter < duty value
The duty cycle starts at 128 (50% brightness) after reset. Turning the encoder clockwise increases brightness, counter-clockwise decreases it.
module pwm_channel (
input clk, rst_n,
input enc_a, enc_b,
output reg pwm_out
);
reg [7:0] counter, duty;
reg enc_a_prev;
// Encoder reader
always @(posedge clk) begin
if (!rst_n) duty <= 8'd128;
else if (enc_a && !enc_a_prev)
duty <= enc_b ? duty + 1 : duty - 1;
enc_a_prev <= enc_a;
end
// 8-bit counter
always @(posedge clk)
counter <= counter + 1;
// PWM comparator
always @(*)
pwm_out = (counter < duty);
endmodule

Assignment 2 — Create a top-level wrapper¶
rgb_top.v instantiates three pwm_channel modules — one for each color (Red, Green, Blue). Each channel receives its own encoder inputs and produces its own PWM output. All three share the same clock and reset.
module rgb_top (
input clk, rst_n,
input enc_r_a, enc_r_b,
input enc_g_a, enc_g_b,
input enc_b_a, enc_b_b,
output pwm_r, pwm_g, pwm_b
);
pwm_channel ch_red (.clk(clk), .rst_n(rst_n), .enc_a(enc_r_a), .enc_b(enc_r_b), .pwm_out(pwm_r));
pwm_channel ch_green (.clk(clk), .rst_n(rst_n), .enc_a(enc_g_a), .enc_b(enc_g_b), .pwm_out(pwm_g));
pwm_channel ch_blue (.clk(clk), .rst_n(rst_n), .enc_a(enc_b_a), .enc_b(enc_b_b), .pwm_out(pwm_b));
endmodule

Assignment 3 — Simulate with testbench and examine waveforms¶
I wrote a testbench rgb_top_tb.v that applies reset, then simulates encoder turns for the red and green channels. Compiled and ran with iverilog and vvp, then opened waveforms in GTKWave.
iverilog -o rgb_sim rgb_top_tb.v rgb_top.v pwm_channel.v && vvp rgb_sim
gtkwave rgb_waves.vcd
The waveforms show:
rst_ngoes HIGH after 40 ns — reset works correctlypwm_r,pwm_g,pwm_ball show PWM signals at 50% duty cycle (duty=128 out of 255)

Assignment 4 — Run linter and fix warnings¶
verilator --lint-only -Wall pwm_channel.v
Result: zero warnings. The code is clean.

What I learned¶
Writing RTL is closer to describing hardware behavior than writing software. There are no loops that execute over time — everything is either combinational (instant) or sequential (triggered by a clock edge).
The 8-bit counter wrapping from 255 back to 0 is not a bug — it is how PWM works. The duty cycle value stays fixed while the counter cycles continuously. The ratio of time spent HIGH vs LOW is the brightness.
Three identical modules sharing a clock is the simplest form of parallelism in hardware. All three channels update simultaneously on every clock edge — something that would require threads or interrupts in software.