Add the first version of the exercise with test bench

This commit is contained in:
kutningjo 2025-10-27 18:34:24 +01:00
commit 36d889fced
12 changed files with 583 additions and 0 deletions

12
Makefile Normal file
View File

@ -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

133
float_add.vhd Normal file
View File

@ -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;

15
scripts/check_test_results.sh Executable file
View File

@ -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

View File

@ -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

65
scripts/ghdl.mk Normal file
View File

@ -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

View File

@ -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}/"

63
scripts/questa-sim.mk Normal file
View File

@ -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

23
scripts/vhdl.mk Normal file
View File

@ -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

View File

@ -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;

71
test/test_utility.vhd Normal file
View File

@ -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;

27
test/vsim.wave Normal file
View File

@ -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}

68
top_entity_float_add.vhd Normal file
View File

@ -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;