Notiz
Present Cipher Implementation
In this exercise, the top module of the PRESENT cipher shall be implemented. It combines all previously implemented submodules and provides the functionality of the encryption. The interface is given as follows:

The PRESENT top module take a $64-bit$ plaintext at the input port i_plaintext and a $80-$ or $128-bit$ key at the input port i_key and produces a $64-bit$ ciphertext at the output port o_ciphertext. The two fixed bit widths of the key input i_key shall be configurable by the parameter key_size The module is clocked with a clock signal at the input port i_clk finishing one round of encryption in every clock cycle. Thus, 32 clock cycles are necessary to complete one encryption of the PRESENT block cipher.
For the first 31 rounds, one round of encryption is done as follows:

At first, the current data state is XORed with the upper 64-bits of the current key register, which correspond to the round key. After adding the round key, every nibble of the data is substituted by the substitution layer and then permuted by the permutation layer. In parallel, the key state is updated based on the current key register and round counter using the key schedule. The output of the round function updates both the data and key state for the next round. In the last round of PRESENT, a key post-whitening is performed using the last round key:

The behavior of the PRESENT top module is described with the following finite state machine realizing the update of the states and the output of the final ciphertext.

On a clock synchronous active high reset at port i_reset, the provided plaintext at port i_plaintext and the provided key at port i_key are stored as data_state and key_state respectively. They are updated every round by the output of the round function and the key schedule. Furthermore, on a reset, the round_counter is set to 1 and incremented every clock cycle. The ciphertext output o_ciphertext and the output signal o_ciphertext_ready are set to $’0’$ during the encryption rounds. When the round counter wraps around to $0$, the encryption is in its 32nd round and only the round key is added to the data state. This result is then available at o_ciphertext notified by a $’1’$ at port o_ciphertext_ready informing that the encryption is done. The encryption result with their data and key state complemented by the round counter retain their values until the reset.
Task 1:
[!Question] Question For the implementation of the PRESENT top module present_top, add the testbench file present_top_tb.vhdl and the golden reference files present_top_stimuli_results.txt and present_top_128_stimuli_results.txt from the shared folder to your Vivado project. Under sources → design right click on present_top.vhdl and select Set as Top and under sources→ sim right click on present_top_tb.vhdl and select Set as Top. Implement present_top until the testbench tests are performed successfully.
use ieee.numeric_std.all;
entity present_top is
generic (
key_size : integer := 80
);
port (
i_plaintext : in std_ulogic_vector(63 downto 0);
i_key : in std_ulogic_vector(key_size - 1 downto 0);
i_clk : in std_ulogic;
i_reset : in std_ulogic;
o_ciphertext : out std_ulogic_vector(63 downto 0);
o_ciphertext_ready : out std_ulogic
);
end entity present_top;
architecture rtl of present_top is
signal data_state : std_ulogic_vector(63 downto 0);
signal key_state : std_ulogic_vector(key_size - 1 downto 0);
signal round_counter : unsigned(4 downto 0);
signal round_key : std_ulogic_vector(63 downto 0);
signal after_key : std_ulogic_vector(63 downto 0);
signal after_sub : std_ulogic_vector(63 downto 0);
signal after_perm : std_ulogic_vector(63 downto 0);
signal next_key : std_ulogic_vector(key_size - 1 downto 0);
begin
assert key_size = 80 or key_size = 128
report "key_size must be 80 or 128"
severity failure;
round_key <= key_state(key_size - 1 downto key_size - 64);
after_key <= data_state xor round_key;
substitution : entity work.present_sub_layer
port map (
i_data => after_key,
o_data => after_sub
);
permutation : entity work.present_perm_layer
port map (
i_data => after_sub,
o_data => after_perm
);
key_schedule : entity work.present_key_schedule
generic map (
key_size => key_size
)
port map (
i_data => key_state,
i_round_counter => std_ulogic_vector(round_counter),
o_data => next_key
);
registers : process(i_clk)
begin
if rising_edge(i_clk) then
if i_reset = '1' then
data_state <= i_plaintext;
key_state <= i_key;
round_counter <= to_unsigned(1, round_counter'length);
elsif round_counter /= 0 then
data_state <= after_perm;
key_state <= next_key;
-- Five-bit addition wraps from 31 to 0 after round 31.
round_counter <= round_counter + 1;
end if;
end if;
end process registers;
-- Round 32 is only the final key XOR; hold the registers at count 0.
o_ciphertext <= after_key when round_counter = 0 else (others => '0');
o_ciphertext_ready <= '1' when round_counter = 0 else '0';
end architecture rtl;
Need to know:
use ieee.numeric_std.allnumeric_stdis a standard IEE VHDL package that provides arithmetic operations for vectors interpreted as numbers. It introduces two important types (Also provides operations such as addition, subtraction, comparisons and conversions)- unsigned - represents a non-negative binary integer.
- signed - represents a binary integer that can be positive or negative.
Here in our programs we used to create 5-bit unsigned number. And why $5-bits$, Because PRESENT needs to represent round numbers from 0 to 31. $2^5=32$
-
Why not use
std_ulogic_vector: it is simply a collection of logic bits. It does not inherently represent a number.Type Best to use in Project std_ulogicIndividual control signals, such as reset and ready std_ulogic_vectorPlaintext, ciphertext, keys and intermediate bit patterns unsignedRound counters, numeric indices and arithmetic values signedSigned arithmetic, such as calculations involving negative values -
Why we are using
data_statewhen we already havei_plaintextBecausei_plaintextcontains the original message, whereasdata_statemust change after every encryption round. And same forkey_stateIntermediate signals - carry results between operations
Keys Otions round_key Selected upper 64-bits of the key after_key Result after XOR with round key after_sub Result after S-box substitution after_perm Result after bit permutation next_key Result of the key schedule -
Remember that data state (xor -> sbox -> permutation) and key schedule are calculated in parallel.
But results are stored at the next clock edge for both the data and key updates. - In expression:
to_unsigned (1, round_counter'length)Converts the integer $1$ into an unsigned value with the same width as
round_counter. Since theround_counteris $5$ bits, the result is:00001
I got similar error on running the implementation. So I have to change the FPGA device. After changing it runs successfully. The implemented design:

Now lets test the benchmark:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use std.env.all;
use std.textio.all;
use ieee.std_logic_textio.all;
entity present_top_tb is
end entity present_top_tb;
architecture tb of present_top_tb is
constant bit_width_80 : integer := 80;
constant bit_width_128 : integer := 128;
constant data_width : integer := 64;
component present_top is
generic (
key_size : integer
);
port(
i_plaintext : in std_ulogic_vector (63 downto 0);
i_key : in std_ulogic_vector (key_size-1 downto 0);
i_clk : in std_ulogic;
i_reset : in std_ulogic;
o_ciphertext : out std_ulogic_vector (63 downto 0);
o_ciphertext_ready : out std_ulogic
);
end component present_top;
signal key_80 : std_ulogic_vector(bit_width_80-1 downto 0);
signal plaintext_80 : std_ulogic_vector(data_width-1 downto 0);
signal ciphertext_80 : std_ulogic_vector(data_width-1 downto 0);
signal ciphertext_ready_80 : std_ulogic;
signal key_128 : std_ulogic_vector(bit_width_128-1 downto 0);
signal plaintext_128 : std_ulogic_vector(data_width-1 downto 0);
signal ciphertext_128 : std_ulogic_vector(data_width-1 downto 0);
signal ciphertext_ready_128 : std_ulogic;
signal clk : std_ulogic := '1';
signal reset : std_ulogic;
begin
present_top_80_inst: present_top
generic map (
key_size => 80
)
port map (
i_plaintext => plaintext_80,
i_key => key_80,
i_clk => clk,
i_reset => reset,
o_ciphertext => ciphertext_80,
o_ciphertext_ready => ciphertext_ready_80
);
present_top_128_inst: present_top
generic map (
key_size => 128
)
port map (
i_plaintext => plaintext_128,
i_key => key_128,
i_clk => clk,
i_reset => reset,
o_ciphertext => ciphertext_128,
o_ciphertext_ready => ciphertext_ready_128
);
clock_generator : process (clk)
begin
clk <= not clk after 10 ns;
end process clock_generator;
present_top_test : process
variable round_counter_val : integer;
variable plaintext_val : std_ulogic_vector(data_width-1 downto 0);
variable key_80_val : std_ulogic_vector(bit_width_80-1 downto 0);
variable key_128_val : std_ulogic_vector(bit_width_128-1 downto 0);
variable ciphertext_val : std_ulogic_vector(data_width-1 downto 0);
file in_out_vectors : text;
variable get_line : line;
variable sep_whitespace : character;
variable get_key : boolean;
begin
file_open(in_out_vectors, "present_top_80_stimuli_results.txt", read_mode);
plaintext_80 <= (others =>'X');
key_80 <= (others =>'X');
plaintext_128 <= (others =>'X');
key_128 <= (others =>'X');
while not endfile(in_out_vectors) loop
round_counter_val := 1;
plaintext_80 <= (others =>'X');
key_80 <= (others =>'X');
ciphertext_val := (others =>'0');
wait for 40 ns;
wait until falling_edge(clk);
readline(in_out_vectors, get_line);
read(get_line, plaintext_val);
read(get_line, sep_whitespace);
read(get_line, key_80_val);
read(get_line, sep_whitespace);
read(get_line, ciphertext_val);
plaintext_80 <= plaintext_val;
key_80 <= key_80_val;
reset <= '1';
wait until falling_edge(clk);
reset <= '0';
while round_counter_val < 31 loop
wait until falling_edge (clk);
assert ciphertext_ready_80 = '0' report "ciphertext_ready signal high during computation phase ... signal should be low!" & " | Round Counter Value: " & integer'image(round_counter_val) severity failure;
round_counter_val := round_counter_val + 1;
end loop;
wait until falling_edge (clk);
assert ciphertext_ready_80 = '1' report "ciphertext_ready signal low after computation phase when ciphertext is ready... signal should be high!" & " | Round Counter Value: " & integer'image(round_counter_val) severity failure;
round_counter_val := 0;
assert ciphertext_80 = ciphertext_val report "Ciphertext Ouput for 80 Bit Key wrong!" & " | Plaintext Input: " & to_hstring(to_bitvector(plaintext_val)) & "h" & " | 80-Bit Key Input: " & to_hstring(to_bitvector(key_80_val)) & "h" & " | Expected Ciphertext: " & to_hstring(to_bitvector(ciphertext_val)) & "h" & " | Actual Ciphertext: " & to_hstring(to_bitvector(ciphertext_80)) & "h" severity failure;
end loop;
plaintext_80 <= (others =>'X');
key_80 <= (others =>'X');
wait for 100 ns;
assert false report "80 Bit Key test passed!" severity note;
file_open(in_out_vectors, "present_top_128_stimuli_results.txt", read_mode);
plaintext_80 <= (others =>'X');
key_80 <= (others =>'X');
plaintext_128 <= (others =>'X');
key_128 <= (others =>'X');
while not endfile(in_out_vectors) loop
round_counter_val := 1;
plaintext_128 <= (others =>'X');
key_128 <= (others =>'X');
ciphertext_val := (others => '0');
wait for 40 ns;
wait until falling_edge(clk);
readline(in_out_vectors, get_line);
read(get_line, plaintext_val);
read(get_line, sep_whitespace);
read(get_line, key_128_val);
read(get_line, sep_whitespace);
read(get_line, ciphertext_val);
plaintext_128 <= plaintext_val;
key_128 <= key_128_val;
reset <= '1';
wait until falling_edge(clk);
reset <= '0';
while round_counter_val < 31 loop
wait until falling_edge (clk);
assert ciphertext_ready_128 = '0' report "ciphertext_ready signal high during computation phase ... signal should be low!" & " | Round Counter Value: " & integer'image(round_counter_val) severity failure;
round_counter_val := round_counter_val + 1;
end loop;
wait until falling_edge (clk);
assert ciphertext_ready_128 = '1' report "ciphertext_ready signal low after computation phase when ciphertext is ready... signal should be high!" & " | Round Counter Value: " & integer'image(round_counter_val) severity failure;
round_counter_val := 0;
assert ciphertext_128 = ciphertext_val report "Ciphertext Output for 128 Bit Key wrong!" & " | Plaintext Input: " & to_hstring(to_bitvector(plaintext_val)) & "h" & " | 128-Bit Key Input: " & to_hstring(to_bitvector(key_128_val)) & "h" & " | Expected Ciphertext: " & to_hstring(to_bitvector(ciphertext_val)) & "h" & " | Actual Ciphertext: " & to_hstring(to_bitvector(ciphertext_128)) & "h" severity failure;
end loop;
plaintext_128 <= (others =>'X');
key_128 <= (others =>'X');
wait for 100 ns;
assert false report "128 Bit Key test passed!" severity note;
std.env.finish;
end process present_top_test;
end architecture tb;
==Don’t forget to run the simulation for 8000ns to see the value for 128 bit==
The simulation output:


