From 36d889fced1508cd58813a97a6030cc4f0635665 Mon Sep 17 00:00:00 2001 From: kutningjo Date: Mon, 27 Oct 2025 18:34:24 +0100 Subject: [PATCH] Add the first version of the exercise with test bench --- Makefile | 12 +++ float_add.vhd | 133 ++++++++++++++++++++++++++++++ scripts/check_test_results.sh | 15 ++++ scripts/execute_and_highlight.sh | 14 ++++ scripts/ghdl.mk | 65 +++++++++++++++ scripts/highlight_test_results.sh | 7 ++ scripts/questa-sim.mk | 63 ++++++++++++++ scripts/vhdl.mk | 23 ++++++ test/tb_top_entity_float_add.vhd | 85 +++++++++++++++++++ test/test_utility.vhd | 71 ++++++++++++++++ test/vsim.wave | 27 ++++++ top_entity_float_add.vhd | 68 +++++++++++++++ 12 files changed, 583 insertions(+) create mode 100644 Makefile create mode 100644 float_add.vhd create mode 100755 scripts/check_test_results.sh create mode 100755 scripts/execute_and_highlight.sh create mode 100644 scripts/ghdl.mk create mode 100755 scripts/highlight_test_results.sh create mode 100644 scripts/questa-sim.mk create mode 100644 scripts/vhdl.mk create mode 100644 test/tb_top_entity_float_add.vhd create mode 100644 test/test_utility.vhd create mode 100644 test/vsim.wave create mode 100644 top_entity_float_add.vhd diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8b89545 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ + +vhdl_srcs = float_add.vhd \ + top_entity_float_add.vhd \ + test/test_utility.vhd \ + test/tb_top_entity_float_add.vhd \ + +main = tb_top_entity_float_add + +CHECK_RESULTS = true + +include scripts/vhdl.mk + diff --git a/float_add.vhd b/float_add.vhd new file mode 100644 index 0000000..29d0f52 --- /dev/null +++ b/float_add.vhd @@ -0,0 +1,133 @@ +library IEEE; +use IEEE.std_logic_1164.all; +use IEEE.numeric_std.all; +entity float_add is + port(A : in std_logic_vector(31 downto 0); + B : in std_logic_vector(31 downto 0); + clk : in std_logic; + reset : in std_logic; + start : in std_logic; + done : out std_logic; + sum : out std_logic_vector(31 downto 0) + ); +end float_add; + +architecture mixed of float_add is + type ST is (WAIT_STATE, ALIGN_STATE, ADDITION_STATE, NORMALIZE_STATE, OUTPUT_STATE); + signal state : ST := WAIT_STATE; + + ---Internal Signals latched from the inputs + signal A_mantissa, B_mantissa : std_logic_vector (24 downto 0); + signal A_exp, B_exp : std_logic_vector (8 downto 0); + signal A_sgn, B_sgn : std_logic; + + --Internal signals for Output + signal sum_exp: std_logic_vector (8 downto 0); + signal sum_mantissa : std_logic_vector (24 downto 0); + signal sum_sgn : std_logic; + +begin + + Control_Unit : process (clk, reset) is + variable diff : signed(8 downto 0); + begin + if(reset = '1') then + state <= WAIT_STATE; --start in wait state + done <= '0'; + elsif rising_edge(clk) then + case state is + when WAIT_STATE => + if (start = '1') then --wait till start request startes high + A_sgn <= A(31); + A_exp <= '0' & A(30 downto 23); --One bit is added for signed subtraction + A_mantissa <= "01" & A(22 downto 0); --Two bits are added extra, one for leading 1 and other one for storing carry + B_sgn <= B(31); + B_exp <= '0' & B(30 downto 23); + B_mantissa <= "01" & B(22 downto 0); + state <= ALIGN_STATE; + else + state <= WAIT_STATE; + end if; + when ALIGN_STATE => --Compare exponent and align it + --If any num is greater by 2**24, we skip the addition. + if unsigned(A_exp) > unsigned(B_exp) then + --B needs downshifting + diff := signed(A_exp) - signed(B_exp); --Small Alu + if diff > 23 then + sum_mantissa <= A_mantissa; --B insignificant relative to A + sum_exp <= A_exp; + sum_sgn <= A_sgn; + state <= OUTPUT_STATE; --start latch A as output + else + --downshift B to equilabrate B_exp to A_exp + sum_exp <= A_exp; + B_mantissa(24-to_integer(diff) downto 0) <= B_mantissa(24 downto to_integer(diff)); + B_mantissa(24 downto 25-to_integer(diff)) <= (others => '0'); + state <= ADDITION_STATE; + end if; + elsif unsigned(A_exp) < unsigned(B_exp) then --A_exp < B_exp. A needs downshifting + diff := signed(B_exp) - signed(A_exp); -- Small Alu + if diff > 23 then + sum_mantissa <= B_mantissa; --A insignificant relative to B + sum_sgn <= B_sgn; + sum_exp <= B_exp; + state <= OUTPUT_STATE; --start latch B as output + else + --downshift A to equilabrate A_exp to B_exp + sum_exp <= B_exp; + A_mantissa(24-to_integer(diff) downto 0) <= A_mantissa(24 downto to_integer(diff)); + A_mantissa(24 downto 25-to_integer(diff)) <= (others => '0'); + state <= ADDITION_STATE; + end if; + else -- Both exponent is equal. No need to mantissa shift + sum_exp <= A_exp; + state <= ADDITION_STATE; + end if; + when ADDITION_STATE => --Mantissa addition + state <= NORMALIZE_STATE; + if (A_sgn xor B_sgn) = '0' then --signs are the same. Just add them + sum_mantissa <= std_logic_vector((unsigned(A_mantissa) + unsigned(B_mantissa))); --Big Alu + sum_sgn <= A_sgn; --both nums have same sign + --Else subtract smaller from larger and use sign of larger + elsif unsigned(A_mantissa) >= unsigned(B_mantissa) then + sum_mantissa <= std_logic_vector((unsigned(A_mantissa) - unsigned(B_mantissa))); --Big Alu + sum_sgn <= A_sgn; + else + sum_mantissa <= std_logic_vector((unsigned(B_mantissa) - unsigned(A_mantissa))); --Big Alu + sum_sgn <= B_sgn; + end if; + + when NORMALIZE_STATE => --Normalization. + if unsigned(sum_mantissa) = TO_UNSIGNED(0, 25) then + --The sum is 0 + sum_mantissa <= (others => '0'); + sum_exp <= (others => '0'); + state <= OUTPUT_STATE; + elsif(sum_mantissa(24) = '1') then --If sum overflowed we downshift and are done. + sum_mantissa <= '0' & sum_mantissa(24 downto 1); --shift the 1 down + sum_exp <= std_logic_vector((unsigned(sum_exp)+ 1)); + state <= OUTPUT_STATE; + elsif(sum_mantissa(23) = '0') then --in this case we need to upshift + --This iterates the normalization shifts, thus can take many clocks. + sum_mantissa <= sum_mantissa(23 downto 0) & '0'; + sum_exp <= std_logic_vector((unsigned(sum_exp)-1)); + state<= NORMALIZE_STATE; --keep shifting till leading 1 appears + else + state <= OUTPUT_STATE; --leading 1 already there. Latch output + end if; + when OUTPUT_STATE => + sum(22 downto 0) <= sum_mantissa(22 downto 0); + sum(30 downto 23) <= sum_exp(7 downto 0); + sum(31) <= sum_sgn; + done <= '1'; -- signal done + if (start = '0') then -- stay in the state till request ends i.e start is low + done <= '0'; + state <= WAIT_STATE; + end if; + when others => + state <= WAIT_STATE; --Just in case. + end case; + end if; + end process; + +end mixed; diff --git a/scripts/check_test_results.sh b/scripts/check_test_results.sh new file mode 100755 index 0000000..e77051c --- /dev/null +++ b/scripts/check_test_results.sh @@ -0,0 +1,15 @@ +#!/bin/bash +script_dir=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) + +if [ $# -ne 1 ] +then + echo Usage $0 test-output-file + exit 1 +fi + +cat $1 | sed -z -e 's/\(py_.* \[\)/ \[/g' | ${script_dir}/highlight_test_results.sh + +if grep -q FAIL $1 +then + exit 1 +fi diff --git a/scripts/execute_and_highlight.sh b/scripts/execute_and_highlight.sh new file mode 100755 index 0000000..851117c --- /dev/null +++ b/scripts/execute_and_highlight.sh @@ -0,0 +1,14 @@ +#!/bin/bash +script_path="$( dirname "$( readlink -f "${BASH_SOURCE[0]}" )" )" + +if [ $# -lt 1 ] +then + echo " usage: execute_and_highlight command arguments" + exit 1 +fi + +cmd=$1 +shift 1 +$cmd $@ | $script_path/highlight_test_results.sh +test ${PIPESTATUS[0]} -eq 0 + diff --git a/scripts/ghdl.mk b/scripts/ghdl.mk new file mode 100644 index 0000000..6a082e4 --- /dev/null +++ b/scripts/ghdl.mk @@ -0,0 +1,65 @@ +# +# +# +# + +# Make sure that the top level is assigned to main +$(if $(main),,\ +$(error Assign top level entity name to variable "main")) + +# Make sure that at least on vhdl source is assigned +$(if $(vhdl_srcs),,\ +$(error Assign at least on vhdl source to variable "vhdl_srcs")) + +# Append prefix -d to all generics +generics = $(addprefix -g,$(generics)) + +# Add VHDL 2008 as default build standard +vhdl_flags += --std=08 +vhdl_flags += -frelaxed-rules +#vhdl_flags += --ieee-asserts=disable-at-0 + +vhdl_objs = $(vhdl_srcs:.vhd=.o) + +assert_level := error + +.PHONY: sim clean + +sim: ${main} + @scripts/execute_and_highlight.sh \ + ghdl \ + -r ${vhdl_flags} ${main} \ + --ieee-asserts=disable-at-0 \ + -gCHECK_RESULTS=${CHECK_RESULTS} \ + --read-wave-opt=test/${main}.wave \ + --assert-level=${assert_level} + +gui: test/${main}.ghw + @echo "Viewing $<" + @gtkwave $< --script=test/gtkwave.view + +test/${main}.ghw: ${main} test/${main}.wave + @ghdl -r ${vhdl_flags} ${main} \ + --read-wave-opt=test/${main}.wave \ + --wave=$@ + +${main}: $(vhdl_objs) + @echo "Elaborating ${main}" + @ghdl -e ${vhdl_flags} ${main} + +%.o: %.vhd + @echo "Analysing $<" + @ghdl -a ${vhdl_flags} $< + +clean: + @ghdl --clean + @rm -rf ${main}.ghw work-obj08.cf ${vhdl_objs} ${main} ${artifacts} + +help: + @echo Use ghdl to simulate and synthesis a vhdl design. + @echo + @echo Build configuration variables: + @echo main main entity + @echo vhdl_flags + @echo generics + diff --git a/scripts/highlight_test_results.sh b/scripts/highlight_test_results.sh new file mode 100755 index 0000000..eb1066a --- /dev/null +++ b/scripts/highlight_test_results.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +red=$(tput setaf 1) +green=$(tput setaf 2) +default=$(tput sgr0) + +sed "s/FAIL/${red}FAIL${default}/" | sed "s/OK/${green}OK${default}/" diff --git a/scripts/questa-sim.mk b/scripts/questa-sim.mk new file mode 100644 index 0000000..c48729f --- /dev/null +++ b/scripts/questa-sim.mk @@ -0,0 +1,63 @@ +# +# +# +# + +# Make sure that the top level is assigned to main +$(if $(main),,\ +$(error Assign top level entity name to variable "main")) + +# Make sure that at least on vhdl source is assigned +$(if $(vhdl_srcs),,\ +$(error Assign at least on vhdl source to variable "vhdl_srcs")) + +# Append prefix -d to all generics +generics = $(addprefix -g,$(generics)) + +# Add VHDL 2008 as default build standard +vhdl_flags += -2008 + +vhdl_objs = $(vhdl_srcs:.vhd=.vhdo) +verilog_objs = $(verilog_srcs:.v=.vo) + +assert_level := error + +.PHONY: sim clean + +sim: ${verilog_objs} ${vhdl_objs} + @vsim -voptargs=+acc -c work.${main} -g CHECK_RESULTS=true -do "run -all" \ + | scripts/highlight_test_results.sh + +gui: ${verilog_objs} ${vhdl_objs} + @vsim -voptargs=+acc work.${main} -g CHECK_RESULTS=false -do "do test/vsim.wave; run -all" + +%.vo: %.v .libwork + @echo "Analysing $<" + @vlog -work work ${verilog_flags} $< + +%.vhdo: %.vhd .libwork + @echo "Analysing $<" + @vcom -work work ${vhdl_flags} $< + + +.libwork: + @vlib work && vmap work work && touch $@ + +clean: + @rm -rf work \ + .libwork \ + transcript \ + modelsim.ini \ + vlog.opt \ + vsim.wlf \ + data.py \ + data.pyc \ + +help: + @echo Use ghdl to simulate and synthesis a vhdl design. + @echo + @echo Build configuration variables: + @echo main main entity + @echo vhdl_flags + @echo generics + diff --git a/scripts/vhdl.mk b/scripts/vhdl.mk new file mode 100644 index 0000000..c4a9bca --- /dev/null +++ b/scripts/vhdl.mk @@ -0,0 +1,23 @@ + +ghdl_version = $(shell ghdl --version 2> /dev/null) +vsim_version = $(shell vsim -version 2> /dev/null) + +# in case verilog is part of the build a verilog capable simulator is required +ifdef verilog_srcs +ifneq (${vsim_version},) +include scripts/questa-sim.mk +else +$(error No HDL simulation tool found for verilog!) +endif +else +ifneq (${vsim_version},) +include scripts/questa-sim.mk +else +ifneq (${ghdl_version},) +include scripts/ghdl.mk +else +$(error No HDL simulation tool found!) +endif +endif +endif + diff --git a/test/tb_top_entity_float_add.vhd b/test/tb_top_entity_float_add.vhd new file mode 100644 index 0000000..23d0067 --- /dev/null +++ b/test/tb_top_entity_float_add.vhd @@ -0,0 +1,85 @@ +library ieee; + use ieee.std_logic_1164.all; + +library std; + use std.env.all; + +library work; + use work.test_utility.all; + +entity tb_top_entity_float_add is + generic( CHECK_RESULTS : boolean; GUI_MODE : boolean := true ); +end; + +architecture test of tb_top_entity_float_add is + signal clk : std_logic := '0'; + signal reset : std_logic := '1'; + signal run_calc : std_logic := '0'; + signal operand_a : std_logic_vector(31 downto 0) := ( others => '0' ); + signal operand_b : std_logic_vector(31 downto 0) := ( others => '0' ); + signal result : std_logic_vector(31 downto 0); + signal calc_complete : std_logic; +begin + + u_top_entity_float_add : entity work.top_entity_float_add + port map ( + clk => clk, + reset => reset, + run_calc => run_calc, + operand_a => operand_a, + operand_b => operand_b, + result => result, + calc_complete => calc_complete + ); + + a_clk: clk <= not clk after 10 ns; + + a_reset: process( clk ) + begin + if falling_edge( clk ) then + reset <= '0'; + end if; + end process; + + delay : process + variable res : std_logic_vector( 31 downto 0 ); + variable expected : std_logic_vector( 31 downto 0 ); + begin + + wait until falling_edge( reset ); + + ----------------------------------------------------------------------- + wait until rising_edge( clk ); + run_calc <= '1'; + operand_a <= x"01000000"; + operand_b <= x"02000000"; + wait until rising_edge( calc_complete ); + run_calc <= '0'; + if ( CHECK_RESULTS ) then + res := result; + expected := x"02200000"; + assert_eq( res, expected ); + end if; + wait until rising_edge( clk ); + + ----------------------------------------------------------------------- + wait until rising_edge( clk ); + run_calc <= '1'; + operand_a <= x"4048f5c3"; -- 3.14 + operand_b <= x"402d70a4"; -- 2.71 + wait until rising_edge( calc_complete ); + if ( CHECK_RESULTS ) then + res := result; + expected := x"40bb3333"; -- 5.85 + assert_eq( res, expected ); + end if; + wait until rising_edge( clk ); + + if GUI_MODE = true then + finish; + else + stop; + end if; + end process delay; + +end architecture test; diff --git a/test/test_utility.vhd b/test/test_utility.vhd new file mode 100644 index 0000000..6a8ead6 --- /dev/null +++ b/test/test_utility.vhd @@ -0,0 +1,71 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + use ieee.float_pkg.all; + +library std; + use std.textio.all; + +package test_utility is + constant TEST_FAIL : string := "[ FAIL ]"; + constant TEST_OK : string := "[ OK ]" & LF; + + type real_array is array ( natural range <> ) of real; + + procedure assert_eq( variable a : in std_logic_vector; variable b : in std_logic_vector ); + + procedure assert_near( variable a : in real; + variable b : in real; + variable abs_err : in real ); + + procedure assert_element_near( variable a : in real; + variable b : in real; + variable abs_err : in real; + variable index : in integer ); + +end package test_utility; + +package body test_utility is + + procedure assert_eq( variable a : in std_logic_vector; variable b : in std_logic_vector ) is + begin + + assert( a = b ) + report TEST_FAIL & "assert_eq" & LF & + " a: " & to_string( a ) & LF & + " b: " & to_string( b ) & LF + severity error; + end procedure assert_eq; + + procedure assert_near( variable a : in real; + variable b : in real; + variable abs_err : in real ) is + variable abs_diff : real; + begin + abs_diff := abs( a - b ); + assert( abs_diff <= abs_err ) + report TEST_FAIL & "assert_near" & LF & + " a: " & to_string( a ) & LF & + " b: " & to_string( b ) & LF & + " " & to_string( abs_diff ) & " > " & to_string( abs_err ) & LF + severity error; + end procedure assert_near; + + procedure assert_element_near( variable a : in real; + variable b : in real; + variable abs_err : in real; + variable index : in integer ) is + variable abs_diff : real; + begin + abs_diff := abs( a - b ); + assert( abs_diff <= abs_err ) + report TEST_FAIL & "assert_element_near" & LF & + " element: " & integer'image( index ) & LF & + " a: " & to_string( a ) & LF & + " b: " & to_string( b ) & LF & + " " & to_string( abs_diff ) & " > " & to_string( abs_err ) & LF + severity error; + end procedure assert_element_near; + +end package body test_utility; + diff --git a/test/vsim.wave b/test/vsim.wave new file mode 100644 index 0000000..a3eff49 --- /dev/null +++ b/test/vsim.wave @@ -0,0 +1,27 @@ +onerror {resume} +quietly WaveActivateNextPane {} 0 +add wave -noupdate /tb_top_entity_float_add/clk +add wave -noupdate /tb_top_entity_float_add/reset +add wave -noupdate /tb_top_entity_float_add/run_calc +add wave -noupdate /tb_top_entity_float_add/operand_a +add wave -noupdate /tb_top_entity_float_add/operand_b +add wave -noupdate /tb_top_entity_float_add/result +add wave -noupdate /tb_top_entity_float_add/calc_complete +TreeUpdate [SetDefaultTree] +WaveRestoreCursors {{Cursor 1} {0 ns} 0} +quietly wave cursor active 1 +configure wave -namecolwidth 150 +configure wave -valuecolwidth 100 +configure wave -justifyvalue left +configure wave -signalnamewidth 0 +configure wave -snapdistance 10 +configure wave -datasetprefix 0 +configure wave -rowmargin 4 +configure wave -childrowmargin 2 +configure wave -gridoffset 0 +configure wave -gridperiod 1 +configure wave -griddelta 40 +configure wave -timeline 0 +configure wave -timelineunits ns +update +WaveRestoreZoom {242 ns} {377 ns} diff --git a/top_entity_float_add.vhd b/top_entity_float_add.vhd new file mode 100644 index 0000000..aa583c7 --- /dev/null +++ b/top_entity_float_add.vhd @@ -0,0 +1,68 @@ + +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; -- Bibliothek für std_logic und std_logic_vector +use IEEE.NUMERIC_STD.ALL; -- Bibliothek für signed und unsigned Datentypen + +entity top_entity_float_add is + port ( + clk : in std_logic; -- Eingangssignal Systemtakt + reset : in std_logic; -- Eingangssignal Reset zum Setzen der Default Werte + run_calc: in std_logic; -- Eingangssignal wenn aktiv soll gerechnet werden + operand_a : in STD_LOGIC_VECTOR(31 downto 0); -- Erster Operand in 32-bit float + operand_b : in STD_LOGIC_VECTOR(31 downto 0); -- Zweiter Operand in 32-bit float + result : out std_logic_vector(31 downto 0); -- Endergebnis Berechnung Addition (im slv ist ein 32-bit float gespeichert) + calc_complete : out std_logic -- Flag das anzeiugt wann die Berechnung fertig ist (bei 1=high) + ); +end top_entity_float_add; + +architecture Behavioral of top_entity_float_add is + + -- Legen Sie ein signal result_sum als std_logic_vector mit Laenge 32 Bit an + + -- Legen Sie ein signal start als std_logic + + -- Legen Sie ein signal done als std_logic + +begin + + -- Instanziieren Sie direkt die float_add Komponente + -- Als Takt und Reset sollen die jeweiligen Eingaenge der top_entity_float_add uebergeben werden + -- An den anderen Ports die jeweiligen zugehoerigen Signale (welche oben angelegt worden sind) + u_float_add : entity work.float_add + port map( + -- Eingangssignal fuer den Takt + clk => , + -- Eingangssignal zum Zuruecksetzen des Zaehlers + reset => , + -- Eingang um die Berechnung zu starten + start => , + -- Ausgang der anzeigt, dass die Berechnung fertig ist + done => , + -- floating point operand a + A => , + -- floating point operand b + B => , + -- Ergebnis der Addition in floating point + sum => + ); + + -- Realisieren Sie einen getakteten Prozess control mit folgenden Verhalten + -- + -- Initialisierung der Signale bei Reset aktiv (reset=1): + -- result soll=0 sein + -- start soll=0 sein + -- calc_complete soll=0 sein + -- + -- bei steigende Flanke: + -- Nur wenn run_calc gleich 1 ist: + -- dann soll wenn done=1 und start=1 ist + -- start=0, calc_complete=1 und result_sum dem Ausgang result zugewiesen werden + -- ansonsten soll + -- start=1, calc_complete=0 sein + -- + control : process(reset,clk) + begin + + end process control; + +end Behavioral;