Outputs a simple waveform which is high or low for a set number of cycles, then takes on the opposite state for a set number of cycles. If not immediately re-started, the output remains steady in its last state.
One-shot by default: Pulse start
high for one cycle to begin. Then
finish
pulses high for one cycle during the last clock cycle of the last
phase of the duty_cycle_out
output. However, finish
can be looped back
to start
, or start
can be tied high, to generate a continuous waveform
with a duty cycle and frequency determined by the high_cycles
and
low_cycles
counts. The cycle count for one phase is loaded during the
last cycle of the previous phase.
Which phase comes first is set by the first_phase
input. A value of 1
means the high phase comes first. Note that first_phase
is only read at
start
.
Asserting clear
immediately sets duty_cycle_out
to INITIAL_OUTPUT
and
the state
to IDLE
.
NOTE: The limit case of a cycle count of zero is equal to a cycle count of
one. It takes one cycle to transition from phase to phase, so a zero length
phase would result in a constant output, and while adding state transitions
to support that is possible, it's pointless. If you need to stop the
output, either assert clear
or stop pulsing start
.
start
, to first_phase
will
produce a Manchester-encoded version of the bit stream.high_cycles
and low_cycles
values, while keeping their
sum constant, produces a variable duty cycle signal of constant frequency,
useful for driving servos, Pulse-Width Modulation, or to filter into a DC
analog value.high_cycles
and low_cycles
ratio constant,
but scaling their values up and down, preserves the duty cycle but alters
the frequency of the output.start
of a frame.`default_nettype none module Duty_Cycle_Generator #( parameter COUNT_WIDTH = 0, parameter INITIAL_OUTPUT = 1'b0 ) ( input wire clock, input wire clear, input wire start, output reg finish, input wire first_phase, // 0/1 -> low/high input wire [COUNT_WIDTH-1:0] high_cycles, input wire [COUNT_WIDTH-1:0] low_cycles, output wire duty_cycle_out ); localparam COUNT_ZERO = {COUNT_WIDTH{1'b0}}; initial begin finish = 1'b0; end
Define the states. We always output a low and a high phase, starting in
either order, then we can either immediately start a new sequence (e.g.:
going from LOW_SECOND
to LOW_FIRST
), or wait in IDLE
.
localparam STATE_WIDTH = 3; localparam [STATE_WIDTH-1:0] IDLE = 'd0; localparam [STATE_WIDTH-1:0] LOW_FIRST = 'd1; localparam [STATE_WIDTH-1:0] HIGH_SECOND = 'd2; localparam [STATE_WIDTH-1:0] HIGH_FIRST = 'd3; localparam [STATE_WIDTH-1:0] LOW_SECOND = 'd4; // States 5 through 7 are unreachable. Could be checked as an error. wire [STATE_WIDTH-1:0] state; reg [STATE_WIDTH-1:0] state_next = IDLE;
As the state changes, we load the necessary output value, otherwise the output remains steady.
reg load_output_value = 1'b0; reg output_value = 1'b0; Register #( .WORD_WIDTH (1), .RESET_VALUE (INITIAL_OUTPUT) ) output_signal ( .clock (clock), .clock_enable (load_output_value), .clear (clear), .data_in (output_value), .data_out (duty_cycle_out) );
We count the number of clock cycles in each phase, signalling on the last
cycle of each phase. (We can halt the count by loading a phase_cycles
of
zero, which we do in the IDLE state, which raises div_by_zero
.)
reg [COUNT_WIDTH-1:0] phase_cycles = COUNT_ZERO; wire end_of_phase; wire no_phase; reg phase_done = 1'b0; Pulse_Divider #( .WORD_WIDTH (COUNT_WIDTH), .INITIAL_DIVISOR (COUNT_ZERO) ) phase_duration ( .clock (clock), .restart (1'b0), .divisor (phase_cycles), .pulses_in (1'b1), .pulse_out (end_of_phase), .div_by_zero (no_phase) );
We need to signal the end of phase continuously in the IDLE
state so we
can load a new cycle count right away when start
is eventually asserted.
In effect, it remembers that a phase has ended in the past, while we wait
for an indeterminate time in IDLE
. All other phase endings are handled
immediately.
always @(*) begin phase_done = (end_of_phase == 1'b1) || (no_phase == 1'b1); end
Define the possible actions on the system, independent of state, based on internal control signals and inputs. Here, that's the action to take at the end of a phase: we either do nothing or start a low or high phase.
reg start_no_phase = 1'b0; reg start_low_phase = 1'b0; reg start_high_phase = 1'b0; always @(*) begin start_no_phase = (phase_done == 1'b1) && (start == 1'b0); start_low_phase = (phase_done == 1'b1) && (start == 1'b1) && (first_phase == 1'b0); start_high_phase = (phase_done == 1'b1) && (start == 1'b1) && (first_phase == 1'b1); end
Define the system transformations as a function of state and action. These are the edges on the state diagram.
reg start_low_first = 1'b0; // Start with the low phase first, reg low_done_to_high = 1'b0; // then move to the high phase second. reg high_done = 1'b0; // When high phase is done and we don't start a new phase reg high_done_start_low = 1'b0; // When high phase is done and we start a low phase reg high_done_start_high = 1'b0; // When high phase is done and we start another high phase reg start_high_first = 1'b0; // Start with the high phase first, reg high_done_to_low = 1'b0; // the move to the low phase second. reg low_done = 1'b0; // When low phase is done and we don't start a new phase reg low_done_start_low = 1'b0; // When low phase is done and we start another low phase reg low_done_start_high = 1'b0; // When low phase is done and we start a high phase always @(*) begin start_low_first = (state == IDLE) && (start_low_phase == 1'b1); low_done_to_high = (state == LOW_FIRST) && (phase_done == 1'b1); high_done = (state == HIGH_SECOND) && (start_no_phase == 1'b1); high_done_start_low = (state == HIGH_SECOND) && (start_low_phase == 1'b1); high_done_start_high = (state == HIGH_SECOND) && (start_high_phase == 1'b1); start_high_first = (state == IDLE) && (start_high_phase == 1'b1); high_done_to_low = (state == HIGH_FIRST) && (phase_done == 1'b1); low_done = (state == LOW_SECOND) && (start_no_phase == 1'b1); low_done_start_low = (state == LOW_SECOND) && (start_low_phase == 1'b1); low_done_start_high = (state == LOW_SECOND) && (start_high_phase == 1'b1); end
Define the next state function: the state each transformation leads to.
always @(*) begin state_next = (start_low_first == 1'b1) ? LOW_FIRST : state; state_next = (low_done_to_high == 1'b1) ? HIGH_SECOND : state_next; state_next = (high_done == 1'b1) ? IDLE : state_next; state_next = (high_done_start_low == 1'b1) ? LOW_FIRST : state_next; state_next = (high_done_start_high == 1'b1) ? HIGH_FIRST : state_next; state_next = (start_high_first == 1'b1) ? HIGH_FIRST : state_next; state_next = (high_done_to_low == 1'b1) ? LOW_SECOND : state_next; state_next = (low_done == 1'b1) ? IDLE : state_next; state_next = (low_done_start_low == 1'b1) ? LOW_FIRST : state_next; state_next = (low_done_start_high == 1'b1) ? HIGH_FIRST : state_next; end Register #( .WORD_WIDTH (STATE_WIDTH), .RESET_VALUE (IDLE) ) state_storage ( .clock (clock), .clock_enable (1'b1), .clear (clear), .data_in (state_next), .data_out (state) );
From the states and transformations, compute the control logic.
always @(*) begin // Signal when we've completed the high/low pair of phases. finish = (phase_done == 1'b1) && ((state == LOW_SECOND) || (state == HIGH_SECOND)); // When the current phase is done, load the next value for the next phase. // Checking the `state_next` is an optimization to prevent the // `output_signal` register from constantly re-loading while `IDLE`. // This isn't necessary, but having useless loads obscures function // and may waste power (when more than just a single bit like here). load_output_value = (phase_done == 1'b1) && (state_next != IDLE); // As we change states, what value should the output change to, or // stay steady with the current output (by omitting those transitions // here). output_value = (start_low_first == 1'b1) ? 1'b0 : duty_cycle_out; output_value = (low_done_to_high == 1'b1) ? 1'b1 : output_value; output_value = (high_done_start_low == 1'b1) ? 1'b0 : output_value; output_value = (start_high_first == 1'b1) ? 1'b1 : output_value; output_value = (high_done_to_low == 1'b1) ? 1'b0 : output_value; output_value = (low_done_start_high == 1'b1) ? 1'b1 : output_value; // As we change states, how long should the next phase last? // Otherwise, the phase remains steady by disabling the count with // a value of zero. phase_cycles = (start_low_first == 1'b1) ? low_cycles : COUNT_ZERO; phase_cycles = (low_done_to_high == 1'b1) ? high_cycles : phase_cycles; phase_cycles = (high_done_start_low == 1'b1) ? low_cycles : phase_cycles; phase_cycles = (high_done_start_high == 1'b1) ? high_cycles : phase_cycles; phase_cycles = (start_high_first == 1'b1) ? high_cycles : phase_cycles; phase_cycles = (high_done_to_low == 1'b1) ? low_cycles : phase_cycles; phase_cycles = (low_done_start_low == 1'b1) ? low_cycles : phase_cycles; phase_cycles = (low_done_start_high == 1'b1) ? high_cycles : phase_cycles; end endmodule