module multicrack (
	input logic clk,
	input logic rst_n,
	input logic en,
	output logic rdy,
	output logic [23:0] key,
	output logic key_valid,
	output logic [7:0] ct_addr,
	input logic [7:0] ct_rddata
);

	localparam int NUM_CORES = 108;
	localparam int WINNER_W = $clog2(NUM_CORES);
	localparam logic [23:0] KEY_INCREMENT = 24'd108;

	logic c_en;

	logic [NUM_CORES-1:0] core_rdy, core_key_valid;
	logic [NUM_CORES-1:0][7:0] core_ct_addr;
	logic [NUM_CORES-1:0][7:0] core_pt_addr;
	logic [NUM_CORES-1:0][7:0] core_pt_rddata;
	logic [NUM_CORES-1:0][23:0] core_key;

	logic [NUM_CORES-1:0] ct_core_wren;
	logic [NUM_CORES-1:0][7:0] ct_core_addr;
	logic [NUM_CORES-1:0][7:0] ct_core_wrdata, ct_core_rddata;

	logic pt_wren;
	logic [7:0] pt_addr;
	logic [7:0] pt_wrdata, pt_rddata;

	logic [7:0] ct_copy_idx_d, ct_copy_idx_q;
	logic [7:0] pt_copy_idx_d, pt_copy_idx_q;
	logic [WINNER_W-1:0] winner_d, winner_q;

	logic all_cores_rdy;
	logic winning_core_valid;
	logic [WINNER_W-1:0] winning_core_idx;
	logic [23:0] winner_key;
	logic [7:0] winner_pt_rddata;

	typedef enum logic [2:0] {
		IDLE,
		CT_FETCH,
		CT_WRITE,
		CRACK_START,
		CRACK,
		PT_FETCH,
		PT_WRITE,
		DONE
	} state_t;

	state_t state_d, state_q;

	pt_mem pt (
		.address(pt_addr),
		.clock(clk),
		.data(pt_wrdata),
		.wren(pt_wren),
		.q(pt_rddata)
	);

	genvar i;
	generate
		for (i = 0; i < NUM_CORES; i++) begin : gen_cores
			ct_core_mem ct (
				.address(ct_core_addr[i]),
				.clock(clk),
				.data(ct_core_wrdata[i]),
				.wren(ct_core_wren[i]),
				.q(ct_core_rddata[i])
			);

			crack #(
				.KeyStart(i),
				.KeyIncrement(KEY_INCREMENT)
			) core (
				.clk,
				.rst_n,
				.en(c_en),
				.rdy(core_rdy[i]),
				.key(core_key[i]),
				.key_valid(core_key_valid[i]),
				.ct_addr(core_ct_addr[i]),
				.ct_rddata(ct_core_rddata[i]),
				.pt_addr_ext(core_pt_addr[i]),
				.pt_rddata_ext(core_pt_rddata[i])
			);
		end
	endgenerate

	always_comb begin
		all_cores_rdy = 1'b1;
		winning_core_valid = 1'b0;
		winning_core_idx = '0;

		for (int j = 0; j < NUM_CORES; j++) begin
			all_cores_rdy &= core_rdy[j];
			if (!winning_core_valid && core_rdy[j] && core_key_valid[j]) begin
				winning_core_valid = 1'b1;
				winning_core_idx = WINNER_W'(j);
			end
		end
	end

	assign winner_key = core_key[winner_q];
	assign winner_pt_rddata = core_pt_rddata[winner_q];

	always_ff @(posedge clk) begin
		if (~rst_n) begin
			state_q <= IDLE;
			ct_copy_idx_q <= '0;
			pt_copy_idx_q <= '0;
			winner_q <= '0;
		end else begin
			state_q <= state_d;
			ct_copy_idx_q <= ct_copy_idx_d;
			pt_copy_idx_q <= pt_copy_idx_d;
			winner_q <= winner_d;
		end
	end

	always_comb begin
		state_d = state_q;
		ct_copy_idx_d = ct_copy_idx_q;
		pt_copy_idx_d = pt_copy_idx_q;
		winner_d = winner_q;

		unique case (state_q)
			IDLE: begin
				if (en) begin
					state_d = CT_FETCH;
					ct_copy_idx_d = '0;
					pt_copy_idx_d = '0;
					winner_d = '0;
				end
			end
			CT_FETCH: state_d = CT_WRITE;
			CT_WRITE: begin
				if (ct_copy_idx_q == 8'hFF) begin
					state_d = CRACK_START;
				end else begin
					state_d = CT_FETCH;
					ct_copy_idx_d = ct_copy_idx_q + 8'd1;
				end
			end
			CRACK_START: begin
				if (all_cores_rdy) state_d = CRACK;
			end
			CRACK: begin
				if (winning_core_valid) begin
					state_d = PT_FETCH;
					pt_copy_idx_d = '0;
					winner_d = winning_core_idx;
				end else if (all_cores_rdy) begin
					state_d = DONE;
				end
			end
			PT_FETCH: state_d = PT_WRITE;
			PT_WRITE: begin
				if (pt_copy_idx_q == 8'hFF) begin
					state_d = DONE;
				end else begin
					state_d = PT_FETCH;
					pt_copy_idx_d = pt_copy_idx_q + 8'd1;
				end
			end
			DONE: state_d = DONE;
			default: state_d = IDLE;
		endcase
	end

	always_comb begin
		rdy = '0;
		key_valid = '0;
		key = '0;

		c_en = '0;

		ct_addr = '0;

		pt_addr = '0;
		pt_wrdata = '0;
		pt_wren = '0;

		for (int j = 0; j < NUM_CORES; j++) begin
			ct_core_addr[j] = core_ct_addr[j];
			ct_core_wrdata[j] = '0;
			ct_core_wren[j] = '0;
			core_pt_addr[j] = pt_copy_idx_q;
		end

		unique case (state_q)
			IDLE: rdy = '1;
			CT_FETCH: begin
				ct_addr = ct_copy_idx_q;
			end
			CT_WRITE: begin
				for (int j = 0; j < NUM_CORES; j++) begin
					ct_core_addr[j] = ct_copy_idx_q;
					ct_core_wrdata[j] = ct_rddata;
					ct_core_wren[j] = '1;
				end
			end
			CRACK_START: begin
				c_en = all_cores_rdy;
			end
			CRACK: begin end
			PT_FETCH: begin end
			PT_WRITE: begin
				pt_addr = pt_copy_idx_q;
				pt_wren = '1;
				pt_wrdata = winner_pt_rddata;
			end
			DONE: begin
				rdy = '1;
				if (core_key_valid[winner_q]) begin
					key_valid = '1;
					key = winner_key;
				end
			end
			default: begin end
		endcase
	end

endmodule: multicrack
