VIETNAM NATIONAL UNIVERSITY – HO CHI MINH CITY
UNIVERSITY OF SCIENCE
FACULTY OF ELECTRONICS AND TELECOMMUNICATIONS
SoC Lab
FINAL PROJECT
PULSE WIDTH MODULATION (PWM)
Lecture : MSc. Trần Tuấn Kiệt
Student : 20200017 Nguyễn Thành Đạt
: 20200018 Phan Thị Diễm
: 20200085 Nguyễn Văn Thành
: 20200110 Nguyễn Huy Trường
: 20200295 Tôn Nữ Tâm Nhi
: 20200353 Nguyễn Tiến Thông
Top module
Figure1: Top module of PWM
A Pulse Width Modulation (PWM) module with adjustable parameters for duty
cycle, frequency, alignment, and counter mode. Additionally, it includes a Timer
Prescaler module responsible for counting and generating the PWM signal.
Figure 2:Overview of PWM Design
FSM
Figure 3: State Diagram of PWM
State Descriptions:
- RESET: Initial state or state after a reset.
- IDLE/ENABLE: Waits for the enable signal to transition to the
CONFIGURE state.
- CONFIGURE: Determines whether to configure width or frequency based
on the address signal.
- PROCESS INPUT: Processes input data and sets internal parameters.
- PROCESS OUTPUT: Continuously generates the PWM output based on the
configured parameters.
- DONE: Final state.
Transitions:
- From RESET to IDLE/ENABLE: On a positive edge of i_clk when i_reset is
low.
- From IDLE/ENABLE to CONFIGURE: On a positive edge of i_clk when
i_enable is low and i_wenable is low.
- From CONFIGURE to IDLE/ENABLE: On a positive edge of i_clk when
i_enable is low and i_wenable is high.
- From CONFIGURE to PROCESS INPUT: On a positive edge of i_clk when
i_enable is low and i_wenable is low.
- From PROCESS INPUT to PROCESS OUTPUT: On a positive edge of
i_clk when i_enable is high and i_wenable is low.
- From PROCESS OUTPUT to PROCESS INPUT: On a positive edge of
i_clk when i_enable is low and i_renable is high.
- From PROCESS OUTPUT to DONE: On a positive edge of i_clk when
i_enable is high and i_renable is low.
Code:
module PWM #(
parameter DATA_WIDTH = 32,
parameter DUTY_CYCLE_WIDTH = 7,
parameter SOURCE_CLOCK_VALUE = 50000000,
parameter ADDRESS_WIDTH = 1,
parameter PRES_WIDTH = 16,
parameter PERIOD_WIDTH = 16,
parameter COUNTER_WIDTH = 26
)(
input i_clk,
input i_reset,
input i_enable,
input i_wenable,
input [ADDRESS_WIDTH - 1 : 0] i_address,
input i_renable,
input [DATA_WIDTH - 1 : 0] i_data, //dyty cycle's
unit is %
output logic [DATA_WIDTH - 1 : 0] o_pwm_pulse
);
localparam FRE_CONTROL = 32;
localparam WIDTH_CONTROL = 10;
localparam ALIGNMENT_WIDTH = 2;
localparam CNTMODE_WIDTH = 1;
logic [DUTY_CYCLE_WIDTH - 1 : 0] duty_cycle;
logic [PRES_WIDTH - 1 : 0] prescaler;
logic [PERIOD_WIDTH - 1 : 0] period;
logic [WIDTH_CONTROL - 1 : 0] pwm_widthcontrol;
logic [FRE_CONTROL - 1 : 0] pwm_frecontrol;
logic [DATA_WIDTH - 1 : 0] pwm_out;
logic prescaler_enable;
logic [ALIGNMENT_WIDTH - 1 : 0] alignment_mode;
logic [CNTMODE_WIDTH - 1 : 0] counter_mode;
logic [COUNTER_WIDTH - 1 : 0] o_clk_counter;
logic [COUNTER_WIDTH - 1 : 0] counter_pwm;
assign prescaler = pwm_frecontrol[PRES_WIDTH - 1 : 0];
assign period = pwm_frecontrol[PERIOD_WIDTH +
PRES_WIDTH - 1 : PRES_WIDTH];
assign duty_cycle = pwm_widthcontrol[DUTY_CYCLE_WIDTH - 1 :
0];
assign alignment_mode = pwm_widthcontrol[ALIGNMENT_WIDTH +
DUTY_CYCLE_WIDTH - 1 : DUTY_CYCLE_WIDTH]; // 000 left - alignment, 001 right
- alignment, 010 center - alignment
assign counter_mode = pwm_widthcontrol[CNTMODE_WIDTH +
ALIGNMENT_WIDTH + DUTY_CYCLE_WIDTH - 1 : ALIGNMENT_WIDTH + DUTY_CYCLE_WIDTH];
always_ff @(posedge i_clk, negedge i_reset) begin
if(!i_reset) begin
pwm_widthcontrol <=
{WIDTH_CONTROL{1'b0}};
pwm_frecontrol <= {FRE_CONTROL{1'b0}};
o_pwm_pulse <= {DATA_WIDTH{1'b0}};
prescaler_enable <= 1'b0;
end
else begin
if(!i_enable && !i_wenable) begin
if(!i_address) begin
pwm_widthcontrol <= i_data[WIDTH_CONTROL
- 1 : 0];
prescaler_enable <= prescaler_enable;
end
else begin
pwm_frecontrol <= i_data;
prescaler_enable <= 1'b1;
end
end
else if(!i_enable && !i_renable) begin
o_pwm_pulse <= pwm_out;
pwm_frecontrol <= pwm_frecontrol;
pwm_widthcontrol <= pwm_widthcontrol;
prescaler_enable <= prescaler_enable;
end
else begin
o_pwm_pulse <= o_pwm_pulse;
pwm_frecontrol <= pwm_frecontrol;
pwm_widthcontrol <= pwm_widthcontrol;
prescaler_enable <= prescaler_enable;
end
end
end
Timer_Prescaler #(
.DATA_WIDTH(DATA_WIDTH),
.SOURCE_CLOCK_VALUE(SOURCE_CLOCK_VALUE),
.PRES_WIDTH(PRES_WIDTH),
.PERIOD_WIDTH(PERIOD_WIDTH),
.DUTY_CYCLE_WIDTH(DUTY_CYCLE_WIDTH),
.ALIGNMENT_WIDTH(ALIGNMENT_WIDTH),
.CNTMODE_WIDTH(CNTMODE_WIDTH)
) Pres (
.i_clk_source(i_clk),
.i_reset(i_reset),
.i_CS(prescaler_enable),
.i_alignment(alignment_mode),
.i_countermode(counter_mode),
.i_prescaler(prescaler),
.i_period(period),
.i_dutycycle(duty_cycle),
.o_pwm_clk(pwm_out)
);
endmodule
module Timer_Prescaler #(
parameter DATA_WIDTH = 32,
parameter SOURCE_CLOCK_VALUE = 50000000,
parameter PRES_WIDTH = 16,
parameter PERIOD_WIDTH = 16,
parameter DUTY_CYCLE_WIDTH = 7,
parameter ALIGNMENT_WIDTH = 2,
parameter CNTMODE_WIDTH = 1,
parameter COUNTER_WIDTH = 26
)(
input i_clk_source,
input i_reset,
input i_CS,
input [ALIGNMENT_WIDTH - 1 : 0] i_alignment,
input [CNTMODE_WIDTH - 1 : 0] i_countermode,
input [PRES_WIDTH - 1:0] i_prescaler,
input [PERIOD_WIDTH - 1:0] i_period,
input [DUTY_CYCLE_WIDTH - 1 : 0] i_dutycycle,
output logic [DATA_WIDTH - 1 : 0] o_pwm_clk,
output logic [COUNTER_WIDTH - 1 : 0] o_clk_counter,
output logic [COUNTER_WIDTH - 1 : 0]
counter_pwm
);
//localparam COUNTER_WIDTH =
26;
localparam LEFT_ALIGNMENT =
2'b00;
localparam RIGHT_ALIGNMENT =
2'b01;
localparam CENTER_ALIGNMENT =
2'b10;
localparam UP_COUNTING =
1'b1;
localparam DOWN_COUNTING =
1'b0;
logic [COUNTER_WIDTH - 1 : 0] clk_counter;
logic [PRES_WIDTH + PERIOD_WIDTH + 2 - 1 : 0]
counter_divider;
logic [COUNTER_WIDTH - 1 : 0] counter_max;
logic [COUNTER_WIDTH - 1 : 0] left_edge;
logic [COUNTER_WIDTH - 1 : 0] right_edge;
assign counter_divider = (i_CS == 1'b1) ? (i_prescaler + 1) *
(i_period + 1) : (PRES_WIDTH + PERIOD_WIDTH + 2 - 1)'('d1);
assign counter_max = (i_CS == 1'b1) ? SOURCE_CLOCK_VALUE /
counter_divider : {COUNTER_WIDTH{1'b0}};
assign counter_pwm = (i_CS == 1'b0) ?
{COUNTER_WIDTH{1'b0}} : ((((i_countermode == UP_COUNTING) && (i_alignment ==
LEFT_ALIGNMENT)) || ((i_countermode == DOWN_COUNTING) && (i_alignment ==
RIGHT_ALIGNMENT)))) ? i_dutycycle * counter_max / 100 : (((i_countermode ==
DOWN_COUNTING) && (i_alignment == LEFT_ALIGNMENT)) || ((i_countermode ==
UP_COUNTING) && (i_alignment == RIGHT_ALIGNMENT))) ? ((100 - i_dutycycle) *
counter_max / 100) : {COUNTER_WIDTH{1'b0}};
assign left_edge = (i_CS == 1'b0) ?
{COUNTER_WIDTH{1'b0}} : (i_alignment == CENTER_ALIGNMENT) ? ((100 -
i_dutycycle) * counter_max / 200) : {COUNTER_WIDTH{1'b0}};
assign right_edge = (i_CS == 1'b0) ?
{COUNTER_WIDTH{1'b0}} : (i_alignment == CENTER_ALIGNMENT) ? ((100 +
i_dutycycle) * counter_max / 200) : {COUNTER_WIDTH{1'b0}};
assign o_clk_counter = clk_counter;
always_ff@(posedge i_clk_source, negedge i_reset) begin
if(!i_reset) begin
clk_counter <= {COUNTER_WIDTH{1'b0}};
end
else begin
if(i_CS) begin
if(i_countermode == DOWN_COUNTING) begin
if(clk_counter == 0) begin
clk_counter <= counter_max;
end
else begin
clk_counter <= clk_counter - 1;
end
end
else begin
if(clk_counter == counter_max) begin
clk_counter <= {COUNTER_WIDTH'('d1)};
end
else begin
clk_counter <= clk_counter + 1;
end
end
end
else begin
clk_counter <= clk_counter;
end
end
end
always_comb begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
if (!i_reset) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
end
else begin
if (i_CS) begin
if (i_countermode == DOWN_COUNTING) begin
if (i_alignment == LEFT_ALIGNMENT) begin
o_pwm_clk = (clk_counter > counter_pwm) ?
{(DATA_WIDTH-1)'('b0), 1'b1} : {(DATA_WIDTH-1)'('b0), 1'b0};
end
else if (i_alignment == RIGHT_ALIGNMENT) begin
o_pwm_clk = (clk_counter > counter_pwm) ?
{(DATA_WIDTH-1)'('b0), 1'b0} : {(DATA_WIDTH-1)'('b0), 1'b1};
end
else if (i_alignment == CENTER_ALIGNMENT) begin
if (clk_counter < left_edge) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
end
else if (clk_counter >= left_edge && clk_counter <=
right_edge) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b1};
end
else if (clk_counter > right_edge) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
end
else begin
o_pwm_clk = o_pwm_clk; // This case seems
redundant
end
end
else begin
o_pwm_clk = o_pwm_clk; // Default case
end
end
else begin
if (i_alignment == LEFT_ALIGNMENT) begin
o_pwm_clk = (clk_counter <= counter_pwm) ?
{(DATA_WIDTH-1)'('b0), 1'b1} : {(DATA_WIDTH-1)'('b0), 1'b0};
end
else if (i_alignment == RIGHT_ALIGNMENT) begin
o_pwm_clk = (clk_counter <= counter_pwm) ?
{(DATA_WIDTH-1)'('b0), 1'b0} : {(DATA_WIDTH-1)'('b0), 1'b1};
end
else if (i_alignment == CENTER_ALIGNMENT) begin
if (clk_counter < left_edge) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
end
else if (clk_counter >= left_edge && clk_counter <=
right_edge) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b1};
end
else if (clk_counter > right_edge) begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
end
else begin
o_pwm_clk = o_pwm_clk; // This case seems
redundant
end
end
else begin
o_pwm_clk = o_pwm_clk; // Default case
end
end
end
else begin
o_pwm_clk = {(DATA_WIDTH-1)'('b0), 1'b0};
end
end
end
endmodule
Explain code:
Module Overview:
• Inputs:
o i_clk: System clock
o i_reset: Reset signal
o i_enable: Enables PWM functionality
o i_wenable: Enables writing to control registers
o i_address: Selects which control register to write to
o i_renable: Enables reading the PWM output
o i_data: Data to be written to control registers
• Outputs:
o o_pwm_pulse: The generated PWM signal
Key Components:
• Parameters: Define various constants such as data width, duty cycle width,
source clock value, etc.
• Signals: Declare internal signals used for control and data flow.
• Timer_Prescaler Instance: A module instance responsible for dividing the
clock frequency and generating the PWM waveform.
• Combinational Logic: Assigns values to signals based on inputs and
parameters.
• Sequential Logic: Implements state-based behavior using an always_ff
block, controlled by the clock and reset signals.
Code Structure:
• Parameter Declarations: Sets fixed values for various parameters.
• Signal Declarations: Declares internal signals.
• Assignment Statements: Assigns values to signals based on calculations and
parameter values.
• Always_ff Block: Implements the main state machine logic, handling clock
events and reset.
• Timer_Prescaler Instance: Instantiates the Timer_Prescaler module with
necessary connections.
Functionality:
• Initialization: Upon reset, signals are set to default values.
• Data Writing: When i_wenable is active, writes data to control registers for
frequency and duty cycle.
• PWM Generation: When i_enable is active, enables the prescaler and starts
PWM generation based on configured parameters.
• Output Reading: When i_renable is active, outputs the generated PWM
signal.
Key Points:
• The code uses a state machine to control the PWM generation process.
• The Timer_Prescaler module handles clock division and PWM waveform
creation.
• Control registers allow configuring frequency and duty cycle.
• The code supports different alignment modes (left, right, center) for the
PWM pulse.