Skip to content

Commit 405a39c

Browse files
committed
[rtl] More thorough LED clearing on power up
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.
1 parent a93f343 commit 405a39c

File tree

1 file changed

+113
-8
lines changed

1 file changed

+113
-8
lines changed

rtl/ip/rgbled_ctrl/rtl/rgbled_ctrl.sv

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
6-
parameter int unsigned CycleTime = 31
6+
parameter int unsigned CycleTime = 31,
7+
parameter int unsigned StartupWaitCycles = 5000,
8+
parameter int unsigned StartupRepeats = 2
79
) (
810
input clk_i,
911
input rst_ni,
@@ -22,12 +24,18 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
2224
logic grb_data_valid, grb_data_last, grb_data_ack;
2325
logic grb_data_sel_q, grb_data_sel_d;
2426

27+
// TODO: This should probably be a generate loop!
28+
29+
// grb0/grb1 are the flops that hold the two LED colour values. They are passed to the ws281x
30+
// driver when a setrgb command is received from software. grb (green/red/blue) is the order the
31+
// ws281x chips expect the colours in.
2532
logic [23:0] grb0_q, grb0_d, grb1_q, grb1_d;
2633
logic grb0_en, grb1_en;
2734

28-
logic reset_turn_off_q, reset_turn_off_d;
29-
35+
// Only write a new value if idle. A write can be triggered by a direct write from software or
36+
// indirectly through an off command.
3037
assign grb0_en = idle & (reg2hw.rgbled0.b.qe | off);
38+
// Write 0 for the off command otherwise write the software supplied values.
3139
assign grb0_d = reg2hw.rgbled0.b.qe ? {reg2hw.rgbled0.g.q, reg2hw.rgbled0.r.q, reg2hw.rgbled0.b.q} :
3240
'0;
3341

@@ -41,7 +49,10 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
4149
end
4250
end
4351

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

@@ -55,25 +66,117 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
5566
end
5667
end
5768

69+
// We always have valid data ready for the ws281x driver.
5870
assign grb_data_valid = 1'b1;
71+
// When issuing an off command force 0 for data in as the off command will start the driver the
72+
// cycle before the grb flops are upated. Otherwise choose grb 0 or 1 depending on which needs to
73+
// be presented to the driver next.
5974
assign grb_data = off ? '0 :
6075
grb_data_sel_q ? grb0_q :
6176
grb1_q;
77+
78+
// Indicate last data when we're pasasing grb1
6279
assign grb_data_last = grb_data_sel_q;
6380

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

6684
always @(posedge clk_i or negedge rst_ni) begin
6785
if (!rst_ni) begin
6886
grb_data_sel_q <= 1'b0;
69-
reset_turn_off_q <= 1'b1;
7087
end else begin
7188
grb_data_sel_q <= grb_data_sel_d;
72-
reset_turn_off_q <= reset_turn_off_d;
7389
end
7490
end
7591

76-
assign reset_turn_off_d = reset_turn_off_q ? ~drv_idle : 1'b0;
92+
// The RGB LEDs have a habit of turning on following reset. The original version of this
93+
// controller cleared the LEDs (write 0 for all RGB values for both LCDs) immediately on reset but
94+
// this was not sufficient. The controller waits for a number of cycles (StartupWaitCycles)
95+
// following reset before it writes the clear and then repeats this (StartupRepeats times) with
96+
// the same wait interval between each repeat. If there is any write to the controller during this
97+
// startup period the startup is aborted (on the assumption the software is about to set the
98+
// LEDs). It would be possible to report the controller is not idle during the startup period
99+
// but this may cause software that immediately begins using the RGB LED to needlessly wait for
100+
// the startup period to end.
101+
102+
localparam int StartupWaitCyclesW = $clog2(StartupWaitCycles + 1);
103+
logic [StartupWaitCyclesW-1:0] wait_counter_q, wait_counter_d;
104+
105+
localparam int StartupRepeatsW = $clog2(StartupRepeats + 1);
106+
logic [StartupRepeatsW-1:0] startup_counter_q, startup_counter_d;
107+
logic startup_go, startup_lockout;
108+
109+
typedef enum logic [1:0] {
110+
STARTUP_WAIT_START = 2'b00,
111+
STARTUP_DO_GO = 2'b01,
112+
STARTUP_WAIT_IDLE = 2'b10,
113+
STARTUP_DONE = 2'b11
114+
} startup_state_e;
115+
116+
startup_state_e startup_state_d, startup_state_q;
117+
118+
always_ff @(posedge clk_i or negedge rst_ni) begin
119+
if (~rst_ni) begin
120+
startup_state_q <= STARTUP_WAIT_START;
121+
wait_counter_q <= '0;
122+
startup_counter_q <= '0;
123+
end else begin
124+
startup_state_q <= startup_state_d;
125+
wait_counter_q <= wait_counter_d;
126+
startup_counter_q <= startup_counter_d;
127+
end
128+
end
129+
130+
always_comb begin
131+
wait_counter_d = wait_counter_q;
132+
startup_state_d = startup_state_q;
133+
startup_counter_d = startup_counter_q;
134+
startup_go = 1'b0;
135+
startup_lockout = 1'b0;
136+
137+
case (startup_state_q)
138+
STARTUP_WAIT_START: begin
139+
// Waiting StartupWaitCycles before writing 0s for all LED values
140+
if (wait_counter_q < StartupWaitCyclesW'(StartupWaitCycles)) begin
141+
wait_counter_d = wait_counter_q + 1'b1;
142+
end else begin
143+
wait_counter_d = '0;
144+
startup_state_d = STARTUP_DO_GO;
145+
end
146+
end
147+
STARTUP_DO_GO: begin
148+
if (drv_idle) begin
149+
// When idle begin writing 0s to the driver.
150+
startup_go = 1'b1;
151+
// Stop softare writes from doing anything this cycle.
152+
startup_lockout = 1'b1;
153+
154+
if (startup_counter_q < StartupRepeatsW'(StartupRepeats)) begin
155+
// Wait for the driver to be idle after the go if we have more startup repeats to do.
156+
startup_state_d = STARTUP_WAIT_IDLE;
157+
startup_counter_d = startup_counter_q + 1'b1;
158+
end else begin
159+
// Otherwise we're done
160+
startup_state_d = STARTUP_DONE;
161+
end
162+
end
163+
end
164+
STARTUP_WAIT_IDLE: begin
165+
if (drv_idle) begin
166+
// Wait for driver to be idle before going back to a new startup wait
167+
startup_state_d = STARTUP_WAIT_START;
168+
end
169+
end
170+
default: ;
171+
endcase
172+
173+
// Any write from software aborts the startup proceedure unless we're stopping software
174+
// commands.
175+
if (!startup_lockout && (reg2hw.ctrl.off.qe | reg2hw.ctrl.setrgb.qe |
176+
reg2hw.rgbled1.b.qe | reg2hw.rgbled0.b.qe)) begin
177+
startup_state_d = STARTUP_DONE;
178+
end
179+
end
77180

78181
rgbled_ctrl_reg_top u_rgbled_ctrl_reg_top (
79182
.clk_i,
@@ -85,7 +188,7 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
85188
.intg_err_o()
86189
);
87190

88-
assign off = (reg2hw.ctrl.off.qe & reg2hw.ctrl.off.q) | reset_turn_off_q;
191+
assign off = (reg2hw.ctrl.off.qe & reg2hw.ctrl.off.q) | startup_go;
89192
assign go = (reg2hw.ctrl.setrgb.qe & reg2hw.ctrl.setrgb.q) | off;
90193

91194
ws281x_drv #(
@@ -105,7 +208,9 @@ module rgbled_ctrl import rgbled_ctrl_reg_pkg::*; #(
105208
.ws281x_dout_o(rgbled_dout_o)
106209
);
107210

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

110215
assign hw2reg.status.d = idle;
111216
endmodule

0 commit comments

Comments
 (0)