xmas-snoopy/gateware/rtl/led_ctrl.v

461 lines
9.6 KiB
Verilog

/*
* led_ctrl.v
*
* vim: ts=4 sw=4
*
* LED controller
*
* Copyright (C) 2023 Sylvain Munaut <tnt@246tNt.com>
* 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