diff --git a/.github/test.sh b/.github/test.sh index af67c7b..25e96bc 100755 --- a/.github/test.sh +++ b/.github/test.sh @@ -22,6 +22,6 @@ run_task() { echo '::endgroup::' } -for item in aes cbcdes cbcmac_des cbctdes ctraes des tdes; do +for item in aes cbcaes cbcdes cbcmac_des cbctdes ctraes des tdes; do run_task "$item" "$1" done diff --git a/README.md b/README.md index d97f87d..1deaf0b 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,7 @@ They serve as proof of concept, for example how to implement a pipeline using only (local) variables instead of (global) signals. Furthermore they were used how to do a VHDL-to-Verilog conversion for learning purposes. -The testbenches to verify [DES](des/sim/vhdl/), [AES](aes/sim/vhdl/) and [CTR-AES](ctraes/sim/vhdl/) are examples -how useful GHDLs VHPIdirect is. They use openSSL as reference models to check the correctness -of the VHDL implementation. +The testbenches to verify [DES](des/sim/vhdl/), [AES](aes/sim/vhdl/), [CTR-AES](ctraes/sim/vhdl/) and [CBC-AES](cbcaes/sim/vhdl/) are examples how useful GHDLs VHPIdirect is. They use openSSL as reference models to check the correctness of the VHDL implementation. *HINT:* diff --git a/cbcaes/sim/vhdl/Makefile b/cbcaes/sim/vhdl/Makefile new file mode 100644 index 0000000..87ca198 --- /dev/null +++ b/cbcaes/sim/vhdl/Makefile @@ -0,0 +1,99 @@ +# ====================================================================== +# AES CBC encryption/decryption +# Copyright (C) 2021 Torsten Meissner +#----------------------------------------------------------------------- +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# ====================================================================== + + +.SECONDARY: + + +DESIGN_NAME := cbcaes +RTL_SRC := \ + ../../../aes/rtl/vhdl/aes_pkg.vhd \ + ../../../aes/rtl/vhdl/aes_enc.vhd \ + ../../../aes/rtl/vhdl/aes_dec.vhd \ + ../../../aes/rtl/vhdl/aes.vhd \ + ../../rtl/vhdl/$(DESIGN_NAME).vhd + + +SIM_SRC := tb_$(DESIGN_NAME).vhd +C_SRC := tb_$(DESIGN_NAME).c + + +OSVVM_DIR := ../../../lib/osvvm +OSVVM_SRC := \ + $(OSVVM_DIR)/NamePkg.vhd \ + $(OSVVM_DIR)/OsvvmGlobalPkg.vhd \ + $(OSVVM_DIR)/VendorCovApiPkg.vhd \ + $(OSVVM_DIR)/TranscriptPkg.vhd \ + $(OSVVM_DIR)/TextUtilPkg.vhd \ + $(OSVVM_DIR)/AlertLogPkg.vhd \ + $(OSVVM_DIR)/MessagePkg.vhd \ + $(OSVVM_DIR)/SortListPkg_int.vhd \ + $(OSVVM_DIR)/RandomBasePkg.vhd \ + $(OSVVM_DIR)/RandomPkg.vhd \ + $(OSVVM_DIR)/CoveragePkg.vhd \ + $(OSVVM_DIR)/MemoryPkg.vhd \ + $(OSVVM_DIR)/ScoreboardGenericPkg.vhd \ + $(OSVVM_DIR)/ScoreboardPkg_slv.vhd \ + $(OSVVM_DIR)/ScoreboardPkg_int.vhd \ + $(OSVVM_DIR)/ResolutionPkg.vhd \ + $(OSVVM_DIR)/TbUtilPkg.vhd \ + $(OSVVM_DIR)/OsvvmContext.vhd + +VHD_STD := 08 + + +.PHONY: sim +sim: tb_$(DESIGN_NAME).ghw + + +.PHONY: compile +compile: tb_$(DESIGN_NAME) + + +osvvm work: + mkdir $@ + + +osvvm/OsvvmContext.o: $(OSVVM_SRC) | osvvm + @echo "Analyze OSVVM library ..." + ghdl -a --std=$(VHD_STD) -Wno-hide --work=osvvm --workdir=osvvm $(OSVVM_SRC) + + +tb_$(DESIGN_NAME): ${RTL_SRC} ${SIM_SRC} ${C_SRC} osvvm/OsvvmContext.o | work + @echo "Analyze testbench & design ..." + ghdl -a --std=$(VHD_STD) -fpsl --workdir=work -P=osvvm ${RTL_SRC} ${SIM_SRC} + @echo "Elaborate testbench & design ..." + ghdl -e --std=$(VHD_STD) -fpsl --workdir=work -P=osvvm -Wl,$@.c -Wl,-lcrypto -Wl,-lssl $@ + + +tb_$(DESIGN_NAME).ghw: tb_$(DESIGN_NAME) + @echo "Run testbench ..." + ghdl -r $(basename $@) --wave=$@ --assert-level=error --psl-report=$(basename $@)_psl_report.json + + +.PHONY: wave +wave: tb_$(DESIGN_NAME).ghw + @echo "Run GTKwave ..." + gtkwave -S tb_$(DESIGN_NAME).tcl tb_$(DESIGN_NAME).ghw + + +.PHONY: clean +clean: + @echo "Cleaning simulation files ..." + rm -rf tb_$(DESIGN_NAME) tb_$(DESIGN_NAME).ghw *.o *.json work/ osvvm/ diff --git a/cbcaes/sim/vhdl/tb_cbcaes.c b/cbcaes/sim/vhdl/tb_cbcaes.c new file mode 100644 index 0000000..b2b88bb --- /dev/null +++ b/cbcaes/sim/vhdl/tb_cbcaes.c @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include + +static const char HDL_LOGIC_CHAR[] = { 'U', 'X', '0', '1', 'Z', 'W', 'L', 'H', '-'}; + +enum HDL_LOGIC_STATES { +HDL_U = 0, +HDL_X = 1, +HDL_0 = 2, +HDL_1 = 3, +HDL_Z = 4, +HDL_W = 5, +HDL_L = 6, +HDL_H = 7, +HDL_D = 8, +}; + +EVP_CIPHER_CTX *ctx; + +void slv_to_uchar(char* datain, unsigned char* dataout, int bytelen) { + + for (int i = 0; i < bytelen; i++) { + for (int y = 0; y < 8; y++) { + if (*datain == HDL_1) { + *dataout |= 1 << y; + } else if (*datain == HDL_0) { + *dataout &= ~(1 << y); + } + datain++; + } + dataout++; + } + + return; + +} + + +void slv_to_string(char* datain, char* dataout, int bytelen) { + + for (int i = 0; i < bytelen; i++) { + *dataout = HDL_LOGIC_CHAR[*datain]; + datain++; + dataout++; + } + + return; + +} + + +void uchar_to_slv(unsigned char* datain, char* dataout, int bytelen) { + + for (int i = 0; i < bytelen; i++) { + for (int y = 0; y < 8; y++) { + if ((*datain >> y) & 1 == 1) { + *dataout = HDL_1 ; + } else { + *dataout = HDL_0; + } + dataout++; + } + datain++; + } + + return; + +} + + +void handleErrors(void) { + + ERR_print_errors_fp(stderr); + abort(); + +} + + +// Create and initialize the context and nitialize the +// de/encryption operation with keys and iv. +// No padding is done +void init(unsigned char *key, unsigned char *iv, char mode) { + + if (!(ctx = EVP_CIPHER_CTX_new())) + handleErrors(); + + if (mode) { + if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) + handleErrors(); + } else { + if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) + handleErrors(); + } + + if(1 != EVP_CIPHER_CTX_set_padding(ctx, 0)) + handleErrors(); + +} + + +// Provide the message to be de/encrypted and +// obtain the encrypted output +int crypt(char mode, + unsigned char *input, + int input_len, + unsigned char *output) { + + int len = 0; + + if (mode) { + if (1 != EVP_DecryptUpdate(ctx, output, &len, input, input_len)) + handleErrors(); + } else { + if (1 != EVP_EncryptUpdate(ctx, output, &len, input, input_len)) + handleErrors(); + } + + return len; + +} + + +// Finalize the de/encryption. No further bytes are written +// as padding is switched off +int finalize(char mode) { + + int len = 0; + unsigned char data[16]; + + if (mode) { + if (1 != EVP_DecryptFinal_ex(ctx, data, &len)) + handleErrors(); + } else { + if (1 != EVP_EncryptFinal_ex(ctx, data, &len)) + handleErrors(); + } + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + return len; + +} + + +void cryptData(char* datain, char* key, char* iv, char mode, char start, char final, char* dataout, int bytelen) { + + int crypt_len; + unsigned char c_din[bytelen]; + unsigned char c_key[bytelen]; + unsigned char c_iv[bytelen]; + unsigned char c_dout[bytelen]; + + slv_to_uchar(datain, c_din, bytelen); + slv_to_uchar(key, c_key, bytelen); + slv_to_uchar(iv, c_iv, bytelen); + + if (start) { + init(c_key, c_iv, mode); + } + + crypt_len = crypt(mode, c_din, bytelen, c_dout); + + if (crypt_len != bytelen) { + printf("Warning: encrypt() returned with unexpected length %d\n", crypt_len); + } + + if (final) { + crypt_len = finalize(mode); + if (crypt_len != 0) { + printf("Warning: finalize() returned with unexpected length %d\n", crypt_len); + } + } + + uchar_to_slv(c_dout, dataout, bytelen); + + return; + +} diff --git a/cbcaes/sim/vhdl/tb_cbcaes.tcl b/cbcaes/sim/vhdl/tb_cbcaes.tcl new file mode 100644 index 0000000..62c39e8 --- /dev/null +++ b/cbcaes/sim/vhdl/tb_cbcaes.tcl @@ -0,0 +1,13 @@ +set signals [list] +lappend signals "top.tb_cbcaes.s_reset" +lappend signals "top.tb_cbcaes.s_clk" +lappend signals "top.tb_cbcaes.s_validin" +lappend signals "top.tb_cbcaes.s_acceptin" +lappend signals "top.tb_cbcaes.s_start" +lappend signals "top.tb_cbcaes.s_key" +lappend signals "top.tb_cbcaes.s_iv" +lappend signals "top.tb_cbcaes.s_datain" +lappend signals "top.tb_cbcaes.s_validout" +lappend signals "top.tb_cbcaes.s_acceptout" +lappend signals "top.tb_cbcaes.s_dataout" +set num_added [ gtkwave::addSignalsFromList $signals ] diff --git a/cbcaes/sim/vhdl/tb_cbcaes.vhd b/cbcaes/sim/vhdl/tb_cbcaes.vhd new file mode 100644 index 0000000..33766f6 --- /dev/null +++ b/cbcaes/sim/vhdl/tb_cbcaes.vhd @@ -0,0 +1,165 @@ +-- ====================================================================== +-- AES Counter mode testbench +-- Copyright (C) 2020 Torsten Meissner +------------------------------------------------------------------------- +-- This program is free software; you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation; either version 2 of the License, or +-- (at your option) any later version. + +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. + +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, write to the Free Software +-- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +-- ====================================================================== + + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +library osvvm; + use osvvm.RandomPkg.all; + +use std.env.all; + + +entity tb_cbcaes is +end entity tb_cbcaes; + + +architecture sim of tb_cbcaes is + + + signal s_reset : std_logic := '0'; + signal s_clk : std_logic := '0'; + + signal s_start : std_logic := '0'; + signal s_mode : std_logic := '0'; + signal s_iv : std_logic_vector(0 to 127) := (others => '0'); + signal s_key : std_logic_vector(0 to 127) := (others => '0'); + signal s_datain : std_logic_vector(0 to 127) := (others => '0'); + signal s_validin : std_logic := '0'; + signal s_acceptin : std_logic; + + signal s_dataout : std_logic_vector(0 to 127); + signal s_validout : std_logic := '0'; + signal s_acceptout : std_logic := '0'; + + procedure cryptData(datain : in std_logic_vector(0 to 127); + key : in std_logic_vector(0 to 127); + iv : in std_logic_vector(0 to 127); + mode : in boolean; + start : in boolean; + final : in boolean; + dataout : out std_logic_vector(0 to 127); + bytelen : in integer) is + begin + report "VHPIDIRECT cryptData" severity failure; + end procedure; + + attribute foreign of cryptData: procedure is "VHPIDIRECT cryptData"; + + function swap (datain : std_logic_vector(0 to 127)) return std_logic_vector is + variable v_data : std_logic_vector(0 to 127); + begin + for i in 0 to 15 loop + for y in 0 to 7 loop + v_data((i*8)+y) := datain((i*8)+7-y); + end loop; + end loop; + return v_data; + end function; + +begin + + + i_cbcaes : entity work.cbcaes + port map ( + reset_i => s_reset, + clk_i => s_clk, + start_i => s_start, + mode_i => s_mode, + key_i => s_key, + iv_i => s_iv, + data_i => s_datain, + valid_i => s_validin, + accept_o => s_acceptin, + data_o => s_dataout, + valid_o => s_validout, + accept_i => s_acceptout + ); + + + s_clk <= not(s_clk) after 10 ns; + s_reset <= '1' after 100 ns; + + + process is + variable v_key : std_logic_vector(0 to 127); + variable v_iv : std_logic_vector(0 to 127); + variable v_datain : std_logic_vector(0 to 127); + variable v_dataout : std_logic_vector(0 to 127); + variable v_random : RandomPType; + begin + v_random.InitSeed(v_random'instance_name); + wait until s_reset = '1' and rising_edge(s_clk); + -- ENCRYPTION TESTs + report "Test CBC-AES encryption"; + s_start <= '1'; + s_mode <= '0'; + v_iv := v_random.RandSlv(128); + v_key := v_random.RandSlv(128); + for i in 0 to 31 loop + v_datain := v_random.RandSlv(128); + s_validin <= '1'; + s_key <= v_key; + s_iv <= v_iv; + s_datain <= v_datain; + cryptData(swap(v_datain), swap(v_key), swap(v_iv), false, i = 0, i = 31, v_dataout, v_datain'length/8); + wait until s_acceptin = '1' and rising_edge(s_clk); + s_validin <= '0'; + s_start <= '0'; + wait until s_validout = '1' and rising_edge(s_clk); + s_acceptout <= '1'; + assert s_dataout = swap(v_dataout) + report "Encryption error: Expected 0x" & to_hstring(swap(v_dataout)) & ", got 0x" & to_hstring(s_dataout) + severity failure; + wait until rising_edge(s_clk); + s_acceptout <= '0'; + end loop; + -- DECRYPTION TESTs + report "Test CBC-AES decryption"; + s_start <= '1'; + s_mode <= '1'; + v_iv := v_random.RandSlv(128); + v_key := v_random.RandSlv(128); + for i in 0 to 31 loop + v_datain := v_random.RandSlv(128); + s_validin <= '1'; + s_key <= v_key; + s_iv <= v_iv; + s_datain <= v_datain; + cryptData(swap(v_datain), swap(v_key), swap(v_iv), true, i = 0, i = 31, v_dataout, v_datain'length/8); + wait until s_acceptin = '1' and rising_edge(s_clk); + s_validin <= '0'; + s_start <= '0'; + wait until s_validout = '1' and rising_edge(s_clk); + s_acceptout <= '1'; + assert s_dataout = swap(v_dataout) + report "Decryption error: Expected 0x" & to_hstring(swap(v_dataout)) & ", got 0x" & to_hstring(s_dataout) + severity failure; + wait until rising_edge(s_clk); + s_acceptout <= '0'; + end loop; + wait for 100 ns; + report "Simulation finished without errors"; + finish(0); + end process; + + +end architecture sim;