-- ============================================================================
--
-- Design unit      :   encryption_engine (AES encryption engine)
--
-- File Name        :   encryption_engine.vhd
--
-- Purpose          :   The module provides encryption functionality (ECB-Mode)
--                      based on AES algorithm by using ressource sharing
--                      architecture (32-bit datapath) to reduce required area
--
-- Depends on       :
--
--  - Package(s)    :   aes_definitions, aes_common, aes_column_ops,
--                      aes_row_ops, aes_common, aes_encryption, tech_iface,
--                      engine_iface
--
--  - Module(s)     :   -
--
--
-- Dev. Platform    :   Xilinx ISE 8.1i SP3 / Win32 (W2k SP4)
--
--  - Simulator(s)  :   Modelsim XE III 6.0d
--
--  - Synthesis     :   Xilinx XST
--
--  - Place & Route :   Xilinx PAR
--
--
-- Errors           :   None known
--
--
-- Limitations      :   None known
--
--
-- Notes            :   - requires external S-box stage and subkey memory
--                      - number of rounds is obtained from the subkey memory
--                        (set by key schedule) and should be valid when the
--                        first subkey is read
--
--
-- Author(s)        :   A. Freund (af) <a.freund@dsi-it.de>
--                      Digitale Signalverabeitung & Informationstechnik GmbH,
--                      Bremen, Germany
--
--
-- ============================================================================
--
-- Revision List:
-- Version      Author(s)       Date        Changes
--
-- v0.00        af              2006-11-15  initial version
--
-- see SVN logs
--
-- ============================================================================
--
-- Naming conventions:
--
--  - coding style: two-process method (Gaisler-style)
--  - active-low signals are indicated by appending "_n"
--  - constants are in UPPERCASE
--  - name parts are separated by "_"
--
-- ============================================================================


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;


use work.aes_definitions.all;
use work.aes_common.all;
use work.aes_column_ops.all;
use work.aes_row_ops.all;
use work.aes_common.all;
use work.aes_encryption.all;

use work.tech_iface.all;
use work.engine_iface.all;
use work.aux_funcs.all;


entity encryption_engine is
	port (
		rst		: 	in	std_logic;						-- reset
		clk		:	in	std_logic;						-- clock
		enc_i	:	in	encryption_engine_in_type;		-- see package engine_iface
		enc_o	:	out	encryption_engine_out_type;		-- see package engine_iface
		skm_i	:	in	subkey_memory_ed_out_type;		-- see package tech_iface
		skm_o	:	out	subkey_memory_ed_in_type;		-- see package tech_iface
		sbox_i	:	in	sbox_stage_out_type;			-- see package tech_iface
		sbox_o	:	out	sbox_stage_in_type				-- see package tech_iface
	);
end encryption_engine;


architecture behavioural of encryption_engine is


	-- control fsm, states: idle / load input data / shift-rows / sub-bytes + mix-column + add-key
	type ctrl_fsm_t is (cf_idle, cf_load, cf_sr, cf_sb_mc_ak);


	type registers is record
		busy		:	std_logic;			-- busy, encryption in progress
		valid		:	std_logic;			-- ciphertext is valid
		ctrl_fsm	:	ctrl_fsm_t;			-- control fsm
		key_id		:	key_id_t;			-- stored key id
		subkey_no	:	aes_round_no_t;		-- subkey number (round number)
		part_no		:	part_no_t;			-- partinal number of subkey (# of 32-bit slice within subkey)
		inc_part_no	:	std_logic;			-- increment partial number
		state		:	aes_state_t;		-- state matrix
		rounds		:	aes_round_no_t;		-- stored number of rounds
		din_sel		:	std_logic;			-- input data select (0 : process data, 1 : load data)
		sb_mx_sel	:	std_logic;			-- select SubBytes() + MixColumn() path
		act_sbox	:	std_logic;			-- activate S-box stage (to suppress simulation warnings on Actel)
		shift_rows	:	std_logic;			-- perform ShiftRows() on state matrix
	end record;


	type variables is record
		key_id		:	key_id_t;
		sbox_dout	:	part_data_t;
	end record;


	signal	r, rin : registers;


begin


	-- combinatorial logic
	comb: process (rst, r, enc_i, skm_i, sbox_i)
		variable v : registers;
		variable t : variables;
	begin

		-- assign default values
		v := r;
--
--
--
		-- strobes
		v.shift_rows := '0';



		if (r.inc_part_no = '1')
			then
				v.part_no := r.part_no + 1;
		end if;

		-- count-up subkey # (round #)
		if (r.part_no = "11")
			then
				v.subkey_no := r.subkey_no + 1;
		end if;


		t.sbox_dout := (others => '-');

		-- transform state matrix
		if (r.shift_rows = '1')
			then
				-- perform ShiftRows() (followed by one column-rotation step)
				v.state := shift_rows(r.state);
				v.state := v.state(95 downto 0) & (31 downto 0 => '-');	-- avoid unnecessary logic
				t.sbox_dout := col2slv(select_column(shift_rows(r.state), 0));
			else
				-- rotate column-weise
				if (r.sb_mx_sel = '0')
					then
						-- bypass SubBytes() and MixColumns() during loading
						v.state(31 downto 0) := r.state(127 downto 96) xor skm_i.subkey;
					else
						v.state(31 downto 0) := col2slv(mix_column(slv2col(sbox_i.dout))) xor skm_i.subkey;
				end if;
				v.state(127 downto 32) := r.state(95 downto 0);
				t.sbox_dout := col2slv(select_column(r.state, 0));
		end if;

		-- shift-in input data
		if (r.din_sel = '1')
			then
				v.state(127 downto 96) := enc_i.plain;
		end if;


		-- control fsm
		case r.ctrl_fsm is
			-- wait for start
			when cf_idle =>
				v.sb_mx_sel := '0';		-- bypass SubBytes() + MixColumn()

				-- handle loading
				if (enc_i.start = '1')
					then
						v.busy := '1';
						v.key_id := enc_i.key_id;	-- store key-id
						v.part_no := "01";			-- consider synchronous subkey memory
						v.inc_part_no := '1';		-- allow part_no to increment

						v.ctrl_fsm := cf_load;
				end if;

			-- load input data
			when cf_load =>
				-- prepare to handle last input data word
				if (r.part_no = "11")
					then
						v.din_sel := '0';
						v.inc_part_no := '0';		-- hold partial number at "00"
						v.rounds := skm_i.rounds;
				end if;

				-- finish loading
				if (r.din_sel = '0')
					then
						v.act_sbox := '1';				-- activate S-box stage
						v.sb_mx_sel := '1';				-- enable SubBytes() +  MixColumn() path
						-- r.part_no is automatically set to "00" (wrap-around)
						v.shift_rows := '1';
						v.inc_part_no := '1';			-- re-activate partial number counting

						v.ctrl_fsm := cf_sr;
				end if;

			-- perform ShiftRows()
			when cf_sr =>
				-- check for last round
				if (r.subkey_no = r.rounds)
					then
						v.sb_mx_sel := '0';				-- bypass SubBytes() + MixColumn()
						v.valid := '1';					-- start output of encrypted data
				end if;

				v.ctrl_fsm := cf_sb_mc_ak;

			-- perform SubBytes(), MixColumn() and AddRoundKey()
			when cf_sb_mc_ak =>
				-- one clock cycle before completion
				if (r.part_no = "11")
					then
						v.inc_part_no := '0';			-- hold partial number at "00"
						v.act_sbox := '0';				-- deactivate S-box stage
				end if;

				-- column-wise rotation completed
				if (r.act_sbox = '0')
					then
						-- check for last round
						if (r.sb_mx_sel = '0')
							then
								v.subkey_no := (others => '0');		-- consider synchronous subkey memory
								-- r.part_no is held at "00"
								v.din_sel := '1';					-- activate input data load mode
								v.busy := '0';
								v.valid := '0';						-- stop output of encrypted data

								v.ctrl_fsm := cf_idle;
							else
								v.act_sbox := '1';					-- re-activate S-box stage
								v.inc_part_no := '1';				-- re-activate partial number counting
								v.shift_rows := '1';

								v.ctrl_fsm := cf_sr;
							end if;
				end if;

			when others =>
				v.ctrl_fsm := cf_idle;

		end case;


		-- handle start signal (synchronous subkey memory)
		if (enc_i.start = '1')
			then
				t.key_id := enc_i.key_id;
			else
				t.key_id := r.key_id;
		end if;

--
--
--
		-- reset handling
		if (rst = '1')
			then
				v.ctrl_fsm := cf_idle;
				v.rounds := (others => '0');
				v.key_id := (others => '0');
				v.subkey_no := (others => '0');
				v.part_no := (others => '0');
				v.busy := '0';
				v.valid := '0';
				v.inc_part_no := '0';
				v.din_sel := '1';
				v.act_sbox := '0';
		end if;
--
--
--
		-- update registers
		rin <= v;
--
--
--
		-- drive outputs
		enc_o.valid	<= r.valid;
		enc_o.busy <= r.busy or (not sbox_i.ready);
		enc_o.cipher <= sbox_i.dout xor skm_i.subkey;

		skm_o.rd_en <= enc_i.start or r.busy;
		skm_o.subkey_no <= r.subkey_no;
		skm_o.part_no <= r.part_no;
		skm_o.key_id <= t.key_id;

		sbox_o.act <= r.act_sbox;
		sbox_o.din <= t.sbox_dout;
		sbox_o.ed_mode <= '0';


	end process;


	-- registers
	regs: process (clk, rst)
	begin
		if rising_edge(clk)
			then
				r <= rin;
		end if;

		-- async. reset
		if (rst = '1')
			then
		end if;
	end process;


end behavioural;
