blob: 2ebe1fc5a5568fb8f72a29c71dfc311c2e795912 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
module pwm_chan (
input clk_i,
input rst_ni,
input pwm_en_i,
input invert_i,
input blink_en_i,
input htbt_en_i,
input [15:0] phase_delay_i,
input [15:0] duty_cycle_a_i,
input [15:0] duty_cycle_b_i,
input [15:0] blink_param_x_i,
input [15:0] blink_param_y_i,
input [15:0] phase_ctr_i,
input cycle_end_i,
input clr_blink_cntr_i,
input [3:0] dc_resn_i,
output logic pwm_o
);
logic [15:0] duty_cycle_actual;
logic [15:0] on_phase;
logic [15:0] off_phase;
logic phase_wrap;
logic pwm_int;
// Standard blink mode
logic [15:0] blink_ctr_q;
logic [15:0] blink_ctr_d;
logic [15:0] duty_cycle_blink;
assign blink_ctr_d = (!(blink_en_i && !htbt_en_i) || clr_blink_cntr_i) ? 16'h0 :
((blink_ctr_q == blink_param_x_i + blink_param_y_i + 16'h1) && cycle_end_i)
? 16'h0 : (cycle_end_i) ? blink_ctr_q + 16'h1 : blink_ctr_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
blink_ctr_q <= 16'h0;
end else begin
if (clr_blink_cntr_i) begin
blink_ctr_q <= 16'h0;
end else begin
blink_ctr_q <= (blink_en_i && !htbt_en_i) ? blink_ctr_d : blink_ctr_q;
end
end
end
assign duty_cycle_blink = (blink_en_i && !htbt_en_i && (blink_ctr_q > blink_param_x_i)) ?
duty_cycle_b_i : duty_cycle_a_i;
// Heartbeat mode
logic [15:0] htbt_ctr_q;
logic [15:0] htbt_ctr_d;
logic [15:0] duty_cycle_htbt;
logic [15:0] dc_htbt_d;
logic [15:0] dc_htbt_q;
logic dc_htbt_end;
assign htbt_ctr_d = (!(blink_en_i && htbt_en_i) || clr_blink_cntr_i) ? 16'h0 :
((htbt_ctr_q == blink_param_x_i) && cycle_end_i) ? 16'h0 :
(cycle_end_i) ? htbt_ctr_q + 16'h1 : htbt_ctr_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
htbt_ctr_q <= 16'h0;
end else begin
if (clr_blink_cntr_i) begin
htbt_ctr_q <= 16'h0;
end else begin
htbt_ctr_q <= (blink_en_i && htbt_en_i) ? htbt_ctr_d : htbt_ctr_q;
end
end
end
assign dc_htbt_end = cycle_end_i & (htbt_ctr_q == blink_param_x_i);
logic htbt_direction;
logic dc_wrap;
logic pos_htbt;
logic neg_htbt;
assign pos_htbt = (duty_cycle_a_i < duty_cycle_b_i);
assign neg_htbt = (duty_cycle_a_i > duty_cycle_b_i);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
htbt_direction <= neg_htbt;
end else if (pos_htbt && ((dc_htbt_q >= duty_cycle_b_i) || (dc_wrap && dc_htbt_end))) begin
htbt_direction <= 1'b1; // duty cycle counts down
end else if (pos_htbt && (dc_htbt_q == duty_cycle_a_i) && dc_htbt_end) begin
htbt_direction <= 1'b0; // duty cycle counts up
end else if (neg_htbt && ((dc_htbt_q <= duty_cycle_b_i) || (dc_wrap && dc_htbt_end))) begin
htbt_direction <= 1'b0; // duty cycle counts up
end else if (neg_htbt && (dc_htbt_q == duty_cycle_a_i) && dc_htbt_end) begin
htbt_direction <= 1'b1; // duty cycle counts down
end else begin
htbt_direction <= htbt_direction;
end
end
logic pattern_repeat;
assign pattern_repeat = (pos_htbt & htbt_direction) | (neg_htbt & ~htbt_direction) |
(~pos_htbt & ~neg_htbt);
assign {dc_wrap, dc_htbt_d} = !(htbt_ctr_q == blink_param_x_i) ? {1'b0, dc_htbt_q} :
((dc_htbt_q == duty_cycle_a_i) && pattern_repeat) ?
{1'b0, duty_cycle_a_i} : (htbt_direction) ?
{1'b0, dc_htbt_q} - {1'b0, blink_param_y_i} - 1'b1 :
{1'b0, dc_htbt_q} + {1'b0, blink_param_y_i} + 1'b1;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
dc_htbt_q <= duty_cycle_a_i;
end else begin
dc_htbt_q <= ((htbt_ctr_q == blink_param_x_i) && cycle_end_i) ? dc_htbt_d : dc_htbt_q;
end
end
assign duty_cycle_htbt = dc_htbt_q;
assign duty_cycle_actual = (blink_en_i && !htbt_en_i) ? duty_cycle_blink :
(blink_en_i && htbt_en_i) ? duty_cycle_htbt : duty_cycle_a_i;
logic [15:0] phase_delay_scaled;
logic [15:0] duty_cycle_scaled;
assign phase_delay_scaled = phase_delay_i << (4'd15 - dc_resn_i);
assign duty_cycle_scaled = duty_cycle_actual << (4'd15 - dc_resn_i);
assign on_phase = phase_delay_scaled;
assign {phase_wrap, off_phase} = {1'b0, phase_delay_scaled} + {1'b0, duty_cycle_scaled};
logic on_phase_exceeded;
logic off_phase_exceeded;
assign on_phase_exceeded = (phase_ctr_i >= on_phase);
assign off_phase_exceeded = (phase_ctr_i >= off_phase);
assign pwm_int = !pwm_en_i ? 1'b0 :
phase_wrap ? on_phase_exceeded | ~off_phase_exceeded :
on_phase_exceeded & ~off_phase_exceeded;
assign pwm_o = invert_i ? ~pwm_int : pwm_int;
endmodule : pwm_chan