Basic PWM Design
Pulse width modulation is used in a variety of applications including sophisticated control circuitry. A common way we use them is to control dimming ofΒ RGB LEDsΒ or to control the direction of aΒ servo. We can accomplish a range of results in both applications because pulse width modulation allows us to vary how much time the signal is high in an analog fashion. While the signal can only be high (usually 5V) or low (ground) at any time, we can change the proportion of time the signal is high compared to when it is low over a consistent time interval.
When the signal is high, we call this “on time”. To describe the amount of “on time” , we use the concept of duty cycle. Duty cycle is measured in percentage. The percentage duty cycle specifically describes the percentage of time a digital signal is on over an interval or period of time. This period is the inverse of the frequency of the waveform.
If a digital signal spends half of the time on and the other half off, we would say the digital signal has a duty cycle of 50% and resembles an ideal square wave. If the percentage is higher than 50%, the digital signal spends more time in the high state than the low state and vice versa if the duty cycle is less than 50%. Here is a graph that illustrates these three scenarios:
To learn more https://learn.sparkfun.com/tutorials/pulse-width-modulation/all
The circuit
- Components: The design comprises two main components: an up counter and a comparator. The up counter is typically an r-bit counter, where r represents the resolution of the system. In this example, we’ll assume an 8-bit counter (range from 0 to 255), but the concept applies to counters of any bit width. The comparator is a simple circuit that compares the current count value from the counter with a predefined duty cycle value.
- Counter Operation: The up counter starts at 0 and increments by one with each clock cycle. When it reaches its maximum value (255 for an 8-bit counter), it rolls over to 0 and continues counting. This counting action forms the basis for generating the PWM signal.
- Duty Cycle: The duty cycle represents the percentage of time that the PWM signal is in the high state compared to the total time of one count cycle. In this design, the duty cycle is defined by a specific count value, let’s say 128.
- Comparator Operation: The comparator continuously compares the current count value from the counter with the duty cycle value. If the count value is less than the duty cycle value, the comparator asserts an output signal. Otherwise, it de-asserts the output signal.
- PWM Signal Generation: As the counter counts up, the comparator output remains asserted until the counter reaches the duty cycle value. Once the counter equals or exceeds the duty cycle value, the comparator output drops to zero for the remainder of the count cycle (up to 255). This behavior repeats with each count cycle, generating a PWM signal where the duty cycle determines the proportion of time the signal is high.
- Duty Cycle Calculation: To calculate the duty cycle, you divide the duty cycle count value (in this case, 127, as the count starts from 0) by the total count range (256 for an 8-bit counter). This gives you the percentage of time the PWM signal will be high during each count cycle. In this example, 127/256β represents the duty cycle of the PWM signal on the output.
Now Let us Design
It is really very simple
module timer_input
#(parameter BITS = 4)
(
input clk,
input reset_n,
input enable,
// output [BITS - 1:0] Q,
output done
);
reg [BITS - 1:0] Q_reg, Q_next;
always @(posedge clk or negedge reset_n) begin
if (~reset_n)
Q_reg <= 'b0;
else if (enable)
Q_reg <= Q_next;
else
Q_reg <= Q_reg;
end
endmodule
So, above is a design of a up counter
- The counter is asynchronously reset to zero when the
reset_n
signal is active low. - If the
enable
signal is asserted and the reset is inactive, the counter operates. It loads the next count value into the register on the rising edge of the clock. - If the
enable
signal is not asserted, the counter remains in its current state without any changes.
Next state logic
assign done = (Q_reg == FINAL_VALUE);
always @(*) begin
Q_next = done ? 'b0 : Q_reg + 1;
end
Now how do we calculate Duty cycle
The circuit generates a PWM signal based on the duty cycle value. To compute the duty cycle, you can follow this approach: the PWM signal remains ‘on’ from 0 to (duty – 1), where duty is the desired duty cycle. For example, if the duty cycle is set to 128, the PWM signal stays ‘on’ from 0 to 127.
The duty cycle of the PWM signal can be calculated as (duty – 1) divided by 2 to the power of r, where r is the bit width of the counter. For instance, if you have an 8-bit counter (r = 8), the formula becomes (duty – 1) divided by 256.
You can adjust the duty cycle according to your requirements by computing the appropriate duty value based on the desired percentage. For example, if you need a duty cycle of 50%, you can calculate the duty value as 0.5 * 256 = 128.
Testbench Design
The test bench for the pulse width modulation (PWM) signal involves defining signals, instantiating the unit under test (UUT), generating a clock signal, and manipulating the duty cycle to observe the PWM output. It follows these steps:
- Signal Definition: Define signals such as clock, reset, duty cycle, and PWM output.
- UUT Instantiation: Instantiate the PWM signal generator module within the test bench.
- Clock Generation: Generate a clock signal to drive the UUT.
- Reset Initialization: Initially assert the reset signal to initialize the UUT.
- Duty Cycle Variation: Change the duty cycle multiple times during simulation to observe different PWM output patterns, such as 25%, 50%, and 75%.
- Observation: Monitor the PWM output signal and verify its behavior for each duty cycle value.
module pwm_basic_tb (
);
localparam R = 8;
reg clk, reset_n;
reg [R - 1:0] duty;
wire pwm_out;
// Instantiate module under test
pwm_basic #(.R(R)) uut (
.clk(clk),
.reset_n(reset_n),
.duty(duty),
.pwm_out(pwm_out)
);
// Timer
initial
#(7 * 2**R * T) $stop;
// Generate stimuli
// Generating a clk signal
localparam T = 10;
always begin
clk = 1'b0;
#(T / 2);
clk = 1'b1;
#(T / 2);
end
initial begin
// Issue a quick reset for 2 ns
reset_n = 1'b0;
#2;
reset_n = 1'b1;
duty = 0.25 * (2**R);
repeat(2 * 2**R) @(negedge clk);
duty = 0.50 * (2**R);
repeat(2 * 2**R) @(negedge clk);
duty = 0.75 * (2**R);
end
endmodule
Design Issues
The counter in the design is an up counter that counts from 0 to 2^rβ1, where r represents the number of bits in the counter. For example, in an 8-bit counter, r=8, and the counter counts from 0 to 255.
If the duty cycle value is set to the maximum value allowed by the counter, which is 2^rβ1, the counter will count from 0 to the maximum value and then wrap around to 0 again. This results in an incorrect duty cycle calculation because the comparison with the maximum value is not exact due to the wrap-around behavior.
For instance, if the duty cycle is set to 255 (in an 8-bit counter), the counter will count from 0 to 254, and then when it reaches 255, it wraps around to 0 instead of staying at 255. This discrepancy affects the accuracy of the duty cycle calculation.
So to solve these issues we will see the improved design on Day 2
Thanks for sharing, Great Workπ