461 lines
9.6 KiB
Verilog
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
|