module prga (
	input logic clk,
	input logic rst_n,
	input logic en,
	output logic rdy,
	input logic [23:0] key,

	output logic [7:0] s_addr,
	input logic [7:0] s_rddata,
	output logic [7:0] s_wrdata,
	output logic s_wren,

	output logic [7:0] ct_addr,
	input logic [7:0] ct_rddata,

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

	logic [7:0] i_d, i_q, j_d, j_q, k_d, k_q, si_d, si_q, sj_d, sj_q;
	logic [7:0] length_d, length_q;

	enum logic [3:0] {
		INIT,
		SET_LENGTH,
		FETCH_I,
		FETCH_J,
		WRITE_I,
		WRITE_J,
		FETCH_PAD,
		WRITE_PT,
		DONE
	} state_d, state_q;

	always_comb begin: fsm_output_logic
		rdy = '0;

		i_d = i_q;
		j_d = j_q;
		k_d = k_q;
		si_d = si_q;
		sj_d = sj_q;
		length_d = length_q;

		unique case (state_q)
			INIT: begin
				rdy = '1;

				s_addr = '0;
				s_wrdata = '0;
				s_wren = '0;

				/* Fetch message length. */
				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			SET_LENGTH: begin
				length_d = ct_rddata;

				rdy = '0;

				s_addr = '0;
				s_wrdata = '0;
				s_wren = '0;

				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = ct_rddata;
				pt_wren = '1;
			end
			FETCH_I: begin
				i_d = i_q + 1'b1;

				rdy = '0;

				s_addr = i_d;
				s_wrdata = '0;
				s_wren = '0;

				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			/* s_rddata now contains s[i]. */
			FETCH_J: begin
				/* Calculate j. */
				j_d = j_q + s_rddata;

				/* Save s[i]. */
				si_d = s_rddata;

				rdy = '0;

				s_addr = j_d;
				s_wrdata = '0;
				s_wren = '0;

				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			/* s_rddata now contains s[j]. */
			WRITE_I: begin
				/* Save s[j]. */
				sj_d = s_rddata;

				rdy = '0;

				s_addr = j_d;
				s_wrdata = si_q;
				s_wren = '1;

				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			WRITE_J: begin
				rdy = '0;

				s_addr = i_d;
				s_wrdata = sj_q;
				s_wren = '1;

				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			FETCH_PAD: begin
				rdy = '0;

				s_addr = si_q + sj_q;
				s_wrdata = '0;
				s_wren = '0;

				ct_addr = k_q;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			WRITE_PT: begin
				k_d = k_q + 1'b1;

				rdy = '0;

				s_addr = '0;
				s_wrdata = '0;
				s_wren = '0;

				ct_addr = '0;

				pt_addr = k_q;
				pt_wrdata = s_rddata ^ ct_rddata;
				pt_wren = '1;
			end
			DONE: begin
				rdy = '1;

				s_addr = '0;
				s_wrdata = '0;
				s_wren = '0;

				ct_addr = '0;

				pt_addr = '0;
				pt_wrdata = '0;
				pt_wren = '0;
			end
			default: begin end
		endcase
	end: fsm_output_logic

	always_comb begin: fsm_next_state_logic
		unique case (state_q)
			INIT: begin
				if (rdy && en) state_d = SET_LENGTH;
				else state_d = INIT;
			end
			SET_LENGTH: state_d = FETCH_I;
			FETCH_I: state_d = FETCH_J;
			FETCH_J: state_d = WRITE_I;
			WRITE_I: state_d = WRITE_J;
			WRITE_J: state_d = FETCH_PAD;
			FETCH_PAD: state_d = WRITE_PT;
			WRITE_PT: begin
				if (k_q == length_q) state_d = DONE;
				else state_d = FETCH_I;
			end
			DONE: state_d = DONE;
			default: state_d = INIT;
		endcase
	end: fsm_next_state_logic

	always_ff @(posedge clk) begin: fsm_reg
		if (~rst_n) begin
			state_q <= INIT;
			i_q <= '0;
			j_q <= '0;
			k_q <= 1'b1;
			si_q <= '0;
			sj_q <= '0;
			length_q <= '0;
		end else begin
			state_q <= state_d;
			i_q <= i_d;
			j_q <= j_d;
			k_q <= k_d;
			si_q <= si_d;
			sj_q <= sj_d;
			length_q <= length_d;
		end
	end: fsm_reg

endmodule: prga
