/* * led_ctrl.v * * vim: ts=4 sw=4 * * LED controller * * Copyright (C) 2023 Sylvain Munaut * SPDX-License-Identifier: CERN-OHL-P-2.0 */ `default_nettype none module led_ctrl ( // LED matrix output wire [13:0] led_a, output wire [2:0] led_c, input wire led_clk, input wire led_rst, // Status output output reg trig_out, // Wishbone input wire [15:0] wb_addr, output reg [31:0] wb_rdata, input wire [31:0] wb_wdata, input wire wb_we, input wire wb_cyc, output reg wb_ack, input wire wb_clk, input wire wb_rst ); // // 6 MHz LED clock // // 16 time slots of 129 cycles -> ~ 2900 Hz PWM rate // // For each time slot : // - Anode sel : 4b // - Start cycle : 7b \__ For each Cathode // - Stop cycle : 7b / x3 // // Both the 'Start' and 'Stop' cycle are counted as active // so start=0 and stop=127 means active for 128 cycles. // To be completely off jus set stop < start // // 256 entries in EBR = 16 frames of 16 time slots // Each frame is played 96 times, leading to about ~ 30 fps // // Signals // ------- // Bus access wire bus_clr_rd; wire bus_clr_wr; reg bus_we_csr; reg bus_we_mem; // CSR reg ctrl_scan_ena; reg ctrl_drv_rgbleden; reg ctrl_drv_curren; reg [3:0] ctrl_trig_frame; reg ctrl_trig_ena; reg ctrl_trig_clr; wire [3:0] stat_cur_frame; // Frame memory wire [7:0] fmw_addr; wire [47:0] fmw_data; wire [47:0] fmw_mask; wire fmw_ena; wire [7:0] fmr_addr; wire [47:0] fmr_data; wire fmr_ena; // Control reg [4:0] led_off_sync; wire led_off_0; wire led_off_1; wire led_off_2; // Trigger wire trig_upd; wire trig_clr; reg [3:0] trig_frame; reg trig_ena; wire trig_match; // Cycle counter wire tick_0; reg tick_1; reg tick_2; reg [7:0] cycle_cnt_0; wire [6:0] cycle_0; reg [6:0] cycle_1; // Subframe counter reg [3:0] subframe_0; wire subframe_last_0; wire subframe_ce_0; // Play counter reg [7:0] play_cnt_0; wire play_last_0; wire play_ce_0; // Frame reg [3:0] frame_0; wire frame_ce_0; // PWM wire [6:0] cyc_start_1[0:2]; wire [6:0] cyc_stop_1[0:2]; reg [2:0] sig_start_1; reg [2:0] sig_stop_1; reg [2:0] started_2; reg [2:0] stopped_2; // Anode/Cathode wire [3:0] led_a_1; reg [13:0] led_a_2; reg [2:0] led_c_3; // Bus interface // ------------- // Ack always @(posedge wb_clk) wb_ack <= wb_cyc & ~wb_ack; // Clear signals assign bus_clr_rd = wb_ack | ~wb_cyc; assign bus_clr_wr = wb_ack | ~wb_cyc | ~wb_we; // Reads always @(posedge wb_clk) if (bus_clr_rd) wb_rdata <= 32'h00000000; else wb_rdata <= { trig_out, /* [31] */ ctrl_trig_ena, /* [30] */ 2'b00, /* [29:28] */ ctrl_trig_frame, /* [27:24] */ 4'b0000, /* [23:20] */ stat_cur_frame, /* [19:16] */ 13'h0000, /* [15: 3] */ ctrl_drv_curren, /* [ 2] */ ctrl_drv_rgbleden, /* [ 1] */ ctrl_scan_ena /* [ 0] */ }; xclk_cnt #( .WIDTH(4) ) xclk_frame_I ( .i_val (frame_0), .i_clk (led_clk), .o_val (stat_cur_frame), .o_clk (wb_clk), .rst (led_clk | wb_clk) ); // Write strobes always @(posedge wb_clk) if (bus_clr_wr) begin bus_we_csr <= 1'b0; bus_we_mem <= 1'b0; end else begin bus_we_csr <= ~wb_addr[9]; bus_we_mem <= wb_addr[9]; end // Writes to CSR always @(posedge wb_clk or posedge wb_rst) if (wb_rst) begin ctrl_scan_ena <= 1'b0; ctrl_drv_rgbleden <= 1'b0; ctrl_drv_curren <= 1'b0; ctrl_trig_frame <= 4'h0; ctrl_trig_ena <= 1'b0; end else if (bus_we_csr) begin ctrl_scan_ena <= wb_wdata[0]; ctrl_drv_rgbleden <= wb_wdata[1]; ctrl_drv_curren <= wb_wdata[2]; ctrl_trig_frame <= wb_wdata[27:24]; ctrl_trig_ena <= wb_wdata[30]; end always @(posedge wb_clk) ctrl_trig_clr <= bus_we_csr & wb_wdata[31]; // Writes to Frame Memory assign fmw_addr = wb_addr[8:1]; assign fmw_ena = bus_we_mem; assign fmw_data = { 2'b00, /* [47:46] n/a */ wb_wdata[22:16], /* [45:39] cyc_start[2] */ wb_wdata[30:24], /* [38:32] cyc_stop [2] */ wb_wdata[ 6: 0], /* [31:25] cyc_start[1] */ wb_wdata[14: 8], /* [24:18] cyc_stop [1] */ wb_wdata[22:16], /* [17:11] cyc_start[0] */ wb_wdata[30:24], /* [10: 4] cyc_stop [0] */ wb_wdata[ 3: 0] /* [ 3: 0] anode_sel */ }; assign fmw_mask = { 2'b11, /* [47:46] n/a */ {7{~wb_addr[0]}}, /* [45:39] cyc_start[2] */ {7{~wb_addr[0]}}, /* [38:32] cyc_stop [2] */ {7{~wb_addr[0]}}, /* [31:25] cyc_start[1] */ {7{~wb_addr[0]}}, /* [24:18] cyc_stop [1] */ {7{ wb_addr[0]}}, /* [17:11] cyc_start[0] */ {7{ wb_addr[0]}}, /* [10: 4] cyc_stop [0] */ {4{ wb_addr[0]}} /* [ 3: 0] anode_sel */ }; // Frame memory // ------------ ice40_ebr #( .READ_MODE (0), .WRITE_MODE (0) ) frame_mem_I[2:0] ( .wr_addr (fmw_addr), .wr_data (fmw_data), .wr_mask (fmw_mask), .wr_ena (fmw_ena), .wr_clk (wb_clk), .rd_addr (fmr_addr), .rd_data (fmr_data), .rd_ena (fmr_ena), .rd_clk (led_clk) ); // LED clock domain control // ------------------------ // Enable always @(posedge led_clk or posedge led_rst) if (led_rst) led_off_sync <= 5'b11111; else led_off_sync <= { led_off_sync[3:0], ~ctrl_scan_ena }; assign led_off_0 = led_off_sync[2]; assign led_off_1 = led_off_sync[3]; assign led_off_2 = led_off_sync[4]; // Trigger xclk_pulse xclk_trig_upd_I ( .i_stb (bus_we_csr), .i_clk (wb_clk), .o_stb (trig_upd), .o_clk (led_clk), .rst (led_rst | wb_rst) ); xclk_pulse xclk_trig_clr_I ( .i_stb (ctrl_trig_clr), .i_clk (wb_clk), .o_stb (trig_clr), .o_clk (led_clk), .rst (led_rst | wb_rst) ); always @(posedge led_clk or posedge led_rst) if (led_rst) begin trig_frame <= 4'h0; trig_ena <= 1'b0; end else if (trig_upd) begin trig_frame <= ctrl_trig_frame; trig_ena <= ctrl_trig_ena; end assign trig_match = trig_ena & (trig_frame == frame_0) & frame_ce_0; always @(posedge led_clk or posedge led_rst) if (led_rst) trig_out <= 1'b0; else trig_out <= (trig_out | trig_match) & ~trig_clr; // Cycle counter // ------------- // Counter always @(posedge led_clk or posedge led_off_0) if (led_off_0) cycle_cnt_0 <= 8'h80; else cycle_cnt_0 <= tick_0 ? 8'h00 : (cycle_cnt_0 + 1); // Cycle assign cycle_0 = cycle_cnt_0[6:0]; always @(posedge led_clk) cycle_1 <= cycle_0; // Ticks assign tick_0 = cycle_cnt_0[7] & ~led_off_0; always @(posedge led_clk) { tick_2, tick_1 } <= { tick_1, tick_0 }; // Subframe counter // ---------------- always @(posedge led_clk or posedge led_off_0) if (led_off_0) subframe_0 <= 4'h0; else if (subframe_ce_0) subframe_0 <= subframe_0 + 1; assign subframe_last_0 = (subframe_0 == 4'hf); assign subframe_ce_0 = tick_0; // Play counter // ------------ always @(posedge led_clk or posedge led_off_0) if (led_off_0) play_cnt_0 <= 0; else if (play_ce_0) play_cnt_0 <= play_last_0 ? 8'd94 : (play_cnt_0 - 1); assign play_last_0 = play_cnt_0[7]; assign play_ce_0 = tick_0 & subframe_last_0; // Frame counter // ------------- always @(posedge led_clk or posedge led_off_0) if (led_off_0) frame_0 <= 0; else if (frame_ce_0) frame_0 <= frame_0 + 1; assign frame_ce_0 = tick_0 & subframe_last_0 & play_last_0; // Frame Memory Lookup // ------------------- assign fmr_addr = { frame_0, subframe_0 }; assign fmr_ena = tick_0; assign cyc_start_1[2] = fmr_data[45:39]; assign cyc_stop_1[2] = fmr_data[38:32]; assign cyc_start_1[1] = fmr_data[31:25]; assign cyc_stop_1[1] = fmr_data[24:18]; assign cyc_start_1[0] = fmr_data[17:11]; assign cyc_stop_1[0] = fmr_data[10: 4]; assign led_a_1 = fmr_data[3:0]; // PWM // --- genvar i; generate for (i=0; i<3; i=i+1) begin // Comparators (stop is offset !) always @(*) sig_start_1[i] = cyc_start_1[i] == cycle_1; always @(posedge led_clk) sig_stop_1[i] <= (cyc_stop_1[i] == cycle_1) & ~tick_1; // State register always @(posedge led_clk or posedge led_off_1) begin if (led_off_1) begin started_2[i] <= 1'b0; stopped_2[i] <= 1'b0; end else begin started_2[i] <= (started_2[i] | sig_start_1[i]) & ~tick_1; stopped_2[i] <= (stopped_2[i] | sig_stop_1[i]) & ~tick_1; end end // Final decision always @(posedge led_clk or posedge led_off_2) if (led_off_2) led_c_3[i] <= 1'b0; else led_c_3[i] <= started_2[i] & ~stopped_2[i]; end endgenerate // Anode driver // ------------ // Decode and gating always @(posedge led_clk or posedge led_off_1) if (led_off_1) led_a_2 <= 0; else begin led_a_2 <= 0; led_a_2[led_a_1] <= ~tick_1; end // IOBs SB_IO #( // .PIN_TYPE(6'b0101_01), // PIN_OUTPUT_REGISTERED, PIN_INPUT .PIN_TYPE(6'b1101_01), // PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED, PIN_INPUT .PULLUP(1'b0) ) led_anode_drv_I[13:0] ( .PACKAGE_PIN (led_a), .OUTPUT_CLK (led_clk), .OUTPUT_ENABLE (led_a_2), .D_OUT_0 (1'b1) ); // Cathode driver // -------------- SB_RGBA_DRV #( .CURRENT_MODE("0b1"), .RGB0_CURRENT("0b000011"), /* Green : 4 mA */ .RGB1_CURRENT("0b000111"), /* Pink : 6 mA */ .RGB2_CURRENT("0b001111") /* Blue : 8 mA */ ) led_cathode_drv_I ( .RGBLEDEN (ctrl_drv_rgbleden), .RGB0PWM (led_c_3[0]), .RGB1PWM (led_c_3[1]), .RGB2PWM (led_c_3[2]), .CURREN (ctrl_drv_curren), .RGB0 (led_c[0]), .RGB1 (led_c[1]), .RGB2 (led_c[2]) ); endmodule // led_ctrl