/*
 * 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
