Skip to content

Commit

Permalink
[rtl] More thorough LED clearing on power up
Browse files Browse the repository at this point in the history
It's been observed that the existing LED clearing on power up was not
sufficient as the LEDs were turning on on FPGA reset.

This adds a time deley before we first clear the LEDs as well as a
repeated clear to ensure it takes effect.

Additionally some comments have been added to the existing RTL.
  • Loading branch information
GregAC committed May 30, 2024
1 parent a93f343 commit 405a39c
Showing 1 changed file with 113 additions and 8 deletions.
121 changes: 113 additions & 8 deletions rtl/ip/rgbled_ctrl/rtl/rgbled_ctrl.sv
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// SPDX-License-Identifier: Apache-2.0

module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
parameter int unsigned CycleTime = 31
parameter int unsigned CycleTime = 31,
parameter int unsigned StartupWaitCycles = 5000,
parameter int unsigned StartupRepeats = 2
) (
input clk_i,
input rst_ni,
Expand All @@ -22,12 +24,18 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
logic grb_data_valid, grb_data_last, grb_data_ack;
logic grb_data_sel_q, grb_data_sel_d;

// TODO: This should probably be a generate loop!

// grb0/grb1 are the flops that hold the two LED colour values. They are passed to the ws281x
// driver when a setrgb command is received from software. grb (green/red/blue) is the order the
// ws281x chips expect the colours in.
logic [23:0] grb0_q, grb0_d, grb1_q, grb1_d;
logic grb0_en, grb1_en;

logic reset_turn_off_q, reset_turn_off_d;

// Only write a new value if idle. A write can be triggered by a direct write from software or
// indirectly through an off command.
assign grb0_en = idle & (reg2hw.rgbled0.b.qe | off);
// Write 0 for the off command otherwise write the software supplied values.
assign grb0_d = reg2hw.rgbled0.b.qe ? {reg2hw.rgbled0.g.q, reg2hw.rgbled0.r.q, reg2hw.rgbled0.b.q} :
'0;

Expand All @@ -41,7 +49,10 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
end
end

// Only write a new value if idle. A write can be triggered by a direct write from software or
// indirectly through an off command.
assign grb1_en = idle & (reg2hw.rgbled1.b.qe | off);
// Write 0 for the off command otherwise write the software supplied values.
assign grb1_d = reg2hw.rgbled1.b.qe ? {reg2hw.rgbled1.g.q, reg2hw.rgbled1.r.q, reg2hw.rgbled1.b.q} :
'0;

Expand All @@ -55,25 +66,117 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
end
end

// We always have valid data ready for the ws281x driver.
assign grb_data_valid = 1'b1;
// When issuing an off command force 0 for data in as the off command will start the driver the
// cycle before the grb flops are upated. Otherwise choose grb 0 or 1 depending on which needs to
// be presented to the driver next.
assign grb_data = off ? '0 :
grb_data_sel_q ? grb0_q :
grb1_q;

// Indicate last data when we're pasasing grb1
assign grb_data_last = grb_data_sel_q;

// Flip the GRB flop select when data has been acknowledged by the driver
assign grb_data_sel_d = grb_data_ack ? ~grb_data_sel_q : grb_data_sel_q;

always @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
grb_data_sel_q <= 1'b0;
reset_turn_off_q <= 1'b1;
end else begin
grb_data_sel_q <= grb_data_sel_d;
reset_turn_off_q <= reset_turn_off_d;
end
end

assign reset_turn_off_d = reset_turn_off_q ? ~drv_idle : 1'b0;
// The RGB LEDs have a habit of turning on following reset. The original version of this
// controller cleared the LEDs (write 0 for all RGB values for both LCDs) immediately on reset but
// this was not sufficient. The controller waits for a number of cycles (StartupWaitCycles)
// following reset before it writes the clear and then repeats this (StartupRepeats times) with
// the same wait interval between each repeat. If there is any write to the controller during this
// startup period the startup is aborted (on the assumption the software is about to set the
// LEDs). It would be possible to report the controller is not idle during the startup period
// but this may cause software that immediately begins using the RGB LED to needlessly wait for
// the startup period to end.

localparam int StartupWaitCyclesW = $clog2(StartupWaitCycles + 1);
logic [StartupWaitCyclesW-1:0] wait_counter_q, wait_counter_d;

localparam int StartupRepeatsW = $clog2(StartupRepeats + 1);
logic [StartupRepeatsW-1:0] startup_counter_q, startup_counter_d;
logic startup_go, startup_lockout;

typedef enum logic [1:0] {
STARTUP_WAIT_START = 2'b00,
STARTUP_DO_GO = 2'b01,
STARTUP_WAIT_IDLE = 2'b10,
STARTUP_DONE = 2'b11
} startup_state_e;

startup_state_e startup_state_d, startup_state_q;

always_ff @(posedge clk_i or negedge rst_ni) begin
if (~rst_ni) begin
startup_state_q <= STARTUP_WAIT_START;
wait_counter_q <= '0;
startup_counter_q <= '0;
end else begin
startup_state_q <= startup_state_d;
wait_counter_q <= wait_counter_d;
startup_counter_q <= startup_counter_d;
end
end

always_comb begin
wait_counter_d = wait_counter_q;
startup_state_d = startup_state_q;
startup_counter_d = startup_counter_q;
startup_go = 1'b0;
startup_lockout = 1'b0;

case (startup_state_q)
STARTUP_WAIT_START: begin
// Waiting StartupWaitCycles before writing 0s for all LED values
if (wait_counter_q < StartupWaitCyclesW'(StartupWaitCycles)) begin
wait_counter_d = wait_counter_q + 1'b1;
end else begin
wait_counter_d = '0;
startup_state_d = STARTUP_DO_GO;
end
end
STARTUP_DO_GO: begin
if (drv_idle) begin
// When idle begin writing 0s to the driver.
startup_go = 1'b1;
// Stop softare writes from doing anything this cycle.
startup_lockout = 1'b1;

if (startup_counter_q < StartupRepeatsW'(StartupRepeats)) begin
// Wait for the driver to be idle after the go if we have more startup repeats to do.
startup_state_d = STARTUP_WAIT_IDLE;
startup_counter_d = startup_counter_q + 1'b1;
end else begin
// Otherwise we're done
startup_state_d = STARTUP_DONE;
end
end
end
STARTUP_WAIT_IDLE: begin
if (drv_idle) begin
// Wait for driver to be idle before going back to a new startup wait
startup_state_d = STARTUP_WAIT_START;
end
end
default: ;
endcase

// Any write from software aborts the startup proceedure unless we're stopping software
// commands.
if (!startup_lockout && (reg2hw.ctrl.off.qe | reg2hw.ctrl.setrgb.qe |
reg2hw.rgbled1.b.qe | reg2hw.rgbled0.b.qe)) begin
startup_state_d = STARTUP_DONE;
end
end

rgbled_ctrl_reg_top u_rgbled_ctrl_reg_top (
.clk_i,
Expand All @@ -85,7 +188,7 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
.intg_err_o()
);

assign off = (reg2hw.ctrl.off.qe & reg2hw.ctrl.off.q) | reset_turn_off_q;
assign off = (reg2hw.ctrl.off.qe & reg2hw.ctrl.off.q) | startup_go;
assign go = (reg2hw.ctrl.setrgb.qe & reg2hw.ctrl.setrgb.q) | off;

ws281x_drv #(
Expand All @@ -105,7 +208,9 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
.ws281x_dout_o(rgbled_dout_o)
);

assign idle = drv_idle & ~reset_turn_off_q;
// Indicate not idle when we're just about to do a startup clear to avoid a conflict with
// a software command.
assign idle = drv_idle & ~startup_lockout;

assign hw2reg.status.d = idle;
endmodule

0 comments on commit 405a39c

Please sign in to comment.