Notiz

Present Cipher Implementation

Present Cipher Implementation
Combining all the previous tasks to understand and implement PRESENT Cipher

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:

image1

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:

image1

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:

image1

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.

image1

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:

  1. use ieee.numeric_std.all numeric_std is 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$

  2. 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_ulogic Individual control signals, such as reset and ready
    std_ulogic_vector Plaintext, ciphertext, keys and intermediate bit patterns
    unsigned Round counters, numeric indices and arithmetic values
    signed Signed arithmetic, such as calculations involving negative values
  3. Why we are using data_state when we already havei_plaintext Because i_plaintext contains the original message, whereas data_state must change after every encryption round. And same for key_state

    Intermediate 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
  4. Remember that data state (xor -> sbox -> permutation) and key schedule are calculated in parallel.

    image1 But results are stored at the next clock edge for both the data and key updates.

  5. In expression:
     to_unsigned (1, round_counter'length)
    

    Converts the integer $1$ into an unsigned value with the same width as round_counter. Since the round_counter is $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:

image1

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:

image1

image1

image1

Investigate. Decode. Educate