`define STATE_RESET	3'b000
`define STATE_HALT	3'b001
`define STATE_IF	3'b010
`define STATE_DECODE	3'b011
`define STATE_EXEC	3'b100
`define STATE_MEM	3'b101
`define STATE_WRITEBACK	3'b110

module statemachine(clk, reset, opcode, op, pc_reset, pc_load, pc_sel, ir_load,
		addr_sel, mem_cmd, reg_w_sel, reg_a_sel, reg_b_sel, write,
		loada, loadb, loadc, loads, loadm, asel, bsel, csel, vsel,
		halt);
	input clk, reset;
	input [1:0] op;
	input [2:0] opcode;
	output reg pc_reset, pc_load, ir_load, addr_sel, write, loada, loadb,
		loadc, loads, loadm, asel, bsel, csel, halt;
	output reg [1:0] pc_sel, mem_cmd;
	output reg [2:0] reg_w_sel, reg_a_sel, reg_b_sel;
	output reg [3:0] vsel;

	reg [3:0] state;

	always_comb begin
		{pc_reset, pc_load, pc_sel, ir_load, addr_sel, mem_cmd,
			reg_w_sel, reg_a_sel, reg_b_sel, write, loada, loadb,
			loadc, loads, loadm, asel, bsel, csel, vsel, halt}
			= 31'b0;

		casex ({state, opcode, op})
			/* Reset state. */
			{`STATE_RESET, 5'bxxx_xx}:
				{pc_reset, pc_load, addr_sel, mem_cmd}
					= 5'b111_10;

			/* Instruction fetch. */
			{`STATE_IF, 5'bxxx_xx}:
				{pc_load, ir_load, addr_sel, mem_cmd} = 5'b111_10;

			/* MOV immediate to register. */
			{`STATE_DECODE, 5'b110_10}:
				{addr_sel, mem_cmd, reg_w_sel, write, vsel}
					= 11'b1_10_100_1_0100;

			/* MOV register to register. */
			{`STATE_DECODE, 5'b110_00}:
				{reg_b_sel, loadb} = 4'b001_1;
			{`STATE_EXEC, 5'b110_00}:
				{loadc, asel} = 2'b11;
			{`STATE_WRITEBACK, 5'b110_00}:
				{addr_sel, mem_cmd, reg_w_sel, write, vsel}
					= 11'b1_10_010_1_0001;

			/* General ALU operations. */
			{`STATE_DECODE, 5'b101_xx}:
				{reg_a_sel, reg_b_sel, loada, loadb} = 8'b100_001_11;

			/* ADD. */
			{`STATE_EXEC, 5'b101_00}:
				loadc = 1'b1;
			{`STATE_WRITEBACK, 5'b101_00}:
				{addr_sel, mem_cmd, reg_w_sel, write, vsel}
					= 11'b1_10_010_1_0001;

			/* CMP. */
			{`STATE_EXEC, 5'b101_01}:
				{addr_sel, mem_cmd, loads} = 4'b1_10_1;

			/* AND. */
			{`STATE_EXEC, 5'b101_10}:
				loadc = 1'b1;
			{`STATE_WRITEBACK, 5'b101_10}:
				{addr_sel, mem_cmd, reg_w_sel, write, vsel}
					= 11'b1_10_010_1_0001;

			/* MVN. */
			{`STATE_EXEC, 5'b101_11}:
				loadc = 1'b1;
			{`STATE_WRITEBACK, 5'b101_11}:
				{addr_sel, mem_cmd, reg_w_sel, write, vsel}
					= 11'b1_10_010_1_0001;

			/* LDR. */
			{`STATE_DECODE, 5'b011_xx}:
				{reg_a_sel, loada} = 4'b100_1;
			{`STATE_EXEC, 5'b011_xx}:
				{loadm, bsel} = 2'b11;
			{`STATE_MEM, 5'b011_xx}:
				mem_cmd = 2'b10;
			{`STATE_WRITEBACK, 5'b011_xx}:
				{addr_sel, mem_cmd, reg_w_sel, write, vsel}
					= 11'b1_10_010_1_0010;

			/* STR. */
			{`STATE_DECODE, 5'b100_xx}:
				{reg_a_sel, reg_b_sel, csel, loada, loadc}
					= 9'b100_010_111;
			{`STATE_EXEC, 5'b100_xx}:
				{loadm, bsel} = 2'b11;
			{`STATE_MEM, 5'b100_xx}:
				mem_cmd = 2'b01;
			{`STATE_WRITEBACK, 5'b100_xx}:
				{addr_sel, mem_cmd} = 3'b1_10;

			/* B, BEQ, BNE, BLT, BLE. */
			{`STATE_DECODE, 5'b001_xx}:
				{pc_load, pc_sel} = 3'b1_01;
			{`STATE_EXEC, 5'b001_xx}:
				{addr_sel, mem_cmd} = 3'b1_10;

			/* BL. */
			{`STATE_DECODE, 5'b010_11}:
				{pc_load, pc_sel, reg_w_sel, write, vsel}
					= 11'b1_11_100_1_1000;
			{`STATE_EXEC, 5'b010_11}:
				{addr_sel, mem_cmd} = 3'b1_10;

			/* BX, BLX. */
			{`STATE_DECODE, 5'b010_xx}:
				{reg_b_sel, loadc, csel} = 5'b010_11;

			/* BX. */
			{`STATE_EXEC, 5'b010_00}:
				{pc_load, pc_sel} = 3'b1_10;
			{`STATE_WRITEBACK, 5'b010_00}:
				{addr_sel, mem_cmd} = 3'b1_10;

			/* BLX. */
			{`STATE_EXEC, 5'b010_10}:
				{pc_load, pc_sel, reg_w_sel, write, vsel}
					= 11'b1_10_100_1_1000;
			{`STATE_WRITEBACK, 5'b010_10}:
				{addr_sel, mem_cmd}
					= 3'b1_10;

			{`STATE_HALT, 5'bxxx_xx}:
				halt = 1'b1;

			default: begin end
		endcase
	end

	always_ff @(posedge clk) casex ({reset, state, opcode, op})
		/* Reset. */
		10'b1_xxxx_xxx_xx:
			state <= `STATE_RESET;
		{1'b0, `STATE_RESET, 5'bxxx_xx}:
			state <= `STATE_IF;

		/* Beginning of instruction cycle. */
		{1'b0, `STATE_IF, 5'bxxx_xx}:
			state <= `STATE_DECODE;

		/* HALT. */
		{1'b0, `STATE_DECODE, 5'b111_xx}:
			state <= `STATE_HALT;
		{1'b0, `STATE_HALT, 5'bxxx_xx}:
			state <= `STATE_HALT;

		/* MOV immediate to register. */
		{1'b0, `STATE_DECODE, 5'b110_10}:
			state <= `STATE_IF;

		/* MOV register to register. */
		{1'b0, `STATE_DECODE, 5'b110_00}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b110_00}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b110_00}:
			state <= `STATE_IF;

		/* ADD. */
		{1'b0, `STATE_DECODE, 5'b101_00}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b101_00}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b101_00}:
			state <= `STATE_IF;

		/* CMP. */
		{1'b0, `STATE_DECODE, 5'b101_01}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b101_01}:
			state <= `STATE_IF;

		/* AND. */
		{1'b0, `STATE_DECODE, 5'b101_10}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b101_10}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b101_10}:
			state <= `STATE_IF;

		/* MVN. */
		{1'b0, `STATE_DECODE, 5'b101_11}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b101_11}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b101_11}:
			state <= `STATE_IF;

		/* LDR. */
		{1'b0, `STATE_DECODE, 5'b011_xx}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b011_xx}:
			state <= `STATE_MEM;
		{1'b0, `STATE_MEM, 5'b011_xx}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b011_xx}:
			state <= `STATE_IF;

		/* STR. */
		{1'b0, `STATE_DECODE, 5'b100_xx}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b100_xx}:
			state <= `STATE_MEM;
		{1'b0, `STATE_MEM, 5'b100_xx}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b100_xx}:
			state <= `STATE_IF;

		/* B, BEQ, BNE, BLT, BLE. */
		{1'b0, `STATE_DECODE, 5'b001_xx}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b001_xx}:
			state <= `STATE_IF;

		/* BL. */
		{1'b0, `STATE_DECODE, 5'b010_11}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b010_11}:
			state <= `STATE_IF;

		/* BX, BLX. */
		{1'b0, `STATE_DECODE, 5'b010_xx}:
			state <= `STATE_EXEC;
		{1'b0, `STATE_EXEC, 5'b010_xx}:
			state <= `STATE_WRITEBACK;
		{1'b0, `STATE_WRITEBACK, 5'b010_xx}:
			state <= `STATE_IF;

		/* Should not happen. Otherwise, an error occurred. */
		default:
			state <= `STATE_RESET;
	endcase
endmodule: statemachine
