33// SPDX-License-Identifier: Apache-2.0
44
55module 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;
111216endmodule
0 commit comments