Package: slvcodec
slvcodec is a tool that analyzes VHDL and generates:Functions to convert arbitrary VHDL types to and from std_logic_vector.Generate testbenches for entities that read inputs from a file, and
write outputs to a file.Utilities so that unit tests for VHDL code can easily to be written
in python.Generation of functions to convert to and from std_logic_vectorHere’s an example VHDL package.libraryieee;useieee.numeric_std.all;packagecomplexisconstantFIXED_WIDTH:natural:=8;subtypefixed_tisunsigned(FIXED_WIDTH-1downto0);typecomplex_tisrecordreal:fixed_t;imag:fixed_t;endrecord;typearray_of_complexisarray(naturalrange<>)ofcomplex_t;endpackage;The following python script is used to generate a helper package that contains
functions to convert the types to and from std_logic_vector.importosfromslvcodecimportfiletestbench_generatorthisdir=os.path.dirname(__file__)defmake_slvcodec_package():complex_pkg_fn=os.path.join(thisdir,'complex_pkg.vhd')directory=os.path.join(thisdir,'generated')os.mkdir(directory)filetestbench_generator.add_slvcodec_files(directory,[complex_pkg_fn])if__name__=='__main__':make_slvcodec_package()Here is what the generated VHDL looks like.libraryieee;useieee.std_logic_1164.all;useieee.numeric_std.all;usework.complex.all;usework.slvcodec.all;packagecomplex_slvcodecisfunctionto_slvcodec(constantdata:array_of_complex)returnstd_logic_vector;functionfrom_slvcodec(constantslv:std_logic_vector)returnarray_of_complex;constantfixed_t_slvcodecwidth:natural:=fixed_width;constantcomplex_t_slvcodecwidth:natural:=2*fixed_width;functionto_slvcodec(constantdata:complex_t)returnstd_logic_vector;functionfrom_slvcodec(constantslv:std_logic_vector)returncomplex_t;endpackage;packagebodycomplex_slvcodecisfunctionto_slvcodec(constantdata:array_of_complex)returnstd_logic_vectorisconstantW:natural:=complex_t_slvcodecwidth;constantN:natural:=data'length;variableslv:std_logic_vector(N*W-1downto0);beginforiiin0toN-1loopslv((ii+1)*W-1downtoii*W):=to_slvcodec(data(ii));endloop;returnslv;endfunction;functionfrom_slvcodec(constantslv:std_logic_vector)returnarray_of_complexisconstantW:natural:=complex_t_slvcodecwidth;constantN:natural:=slv'length/W;variablemapped:std_logic_vector(slv'length-1downto0);variableoutput:array_of_complex(N-1downto0);beginmapped:=slv;foriiin0toN-1loopoutput(ii):=from_slvcodec(mapped((ii+1)*W-1downtoii*W));endloop;returnoutput;endfunction;functionto_slvcodec(constantdata:complex_t)returnstd_logic_vectorisconstantW0:natural:=0;constantW1:natural:=W0+fixed_width;constantW2:natural:=W1+fixed_width;variableslv:std_logic_vector(complex_t_slvcodecwidth-1downto0);beginslv(W1-1downtoW0):=to_slvcodec(data.real);slv(W2-1downtoW1):=to_slvcodec(data.imag);returnslv;endfunction;functionfrom_slvcodec(constantslv:std_logic_vector)returncomplex_tisconstantW0:natural:=0;constantW1:natural:=W0+fixed_width;constantW2:natural:=W1+fixed_width;variabledata:complex_t;variablemapped:std_logic_vector(complex_t_slvcodecwidth-1downto0);beginmapped:=slv;data.real:=from_slvcodec(mapped(W1-1downtoW0));data.imag:=from_slvcodec(mapped(W2-1downtoW1));returndata;endfunction;endpackagebody;Generation of file-based testbenchesHere’s an example entity that just returns the magnitude squared of a complex data type
that we defined earlier.libraryieee;useieee.numeric_std.all;usework.complex.all;entitycomplex_mag2isport(i:incomplex_t;o:outunsigned(FIXED_WIDTH+1-1downto0));endentity;architecturearchofcomplex_mag2issignalreal2:signed(FIXED_WIDTH*2-1downto0);signalimag2:signed(FIXED_WIDTH*2-1downto0);signalmag2:unsigned(FIXED_WIDTH*2-1downto0);signalscaled_mag2:unsigned(FIXED_WIDTH+1-1downto0);beginreal2<=i.real*i.real;imag2<=i.imag*i.imag;mag2<=unsigned(real2)+unsigned(imag2);scaled_mag2<=mag2(FIXED_WIDTH*2-1-1downtoFIXED_WIDTH-2);o<=scaled_mag2;endarchitecture;We can use slvcodec to generate a testbench that reads input data from a file,
and writes output data to another file.importosfromslvcodecimportfiletestbench_generatorthisdir=os.path.dirname(__file__)defmake_slvcodec_package():complex_pkg_fn=os.path.join(thisdir,'complex_pkg.vhd')directory=os.path.join(thisdir,'generated')os.mkdir(directory)slvcodec_files=filetestbench_generator.add_slvcodec_files(directory,[complex_pkg_fn])returnslvcodec_filesdefmake_complex_mag2_testbench():base_filenames=[os.path.join(thisdir,'complex_pkg.vhd'),os.path.join(thisdir,'complex_mag2.vhd'),]slvcodec_fns=make_slvcodec_package()with_slvcodec_fns=base_filenames+slvcodec_fnsdirectory=os.path.join(thisdir,'generated')generated_fns,generated_wrapper_fns,resolved=filetestbench_generator.prepare_files(directory=directory,filenames=with_slvcodec_fns,top_entity='complex_mag2')returngenerated_fnsif__name__=='__main__':make_complex_mag2_testbench()This will generate the following VHDL testbench.libraryieee;useieee.std_logic_1164.all;usework.slvcodec.all;useieee.numeric_std.all;usework.complex.all;usework.complex_slvcodec.all;entitycomplex_mag2_tbisgeneric(CLOCK_PERIOD:time:=10ns;RUNNER_CFG:string;OUTPUT_PATH:string);endentity;architecturearchofcomplex_mag2_tbistypet_inputisrecordi:complex_t;endrecord;typet_outputisrecordo:unsigned((1+fixed_width)-1downto0);endrecord;constantt_input_slvcodecwidth:natural:=2*fixed_width;functionto_slvcodec(constantdata:t_input)returnstd_logic_vector;functionfrom_slvcodec(constantslv:std_logic_vector)returnt_input;functionto_slvcodec(constantdata:t_input)returnstd_logic_vectorisconstantW0:natural:=0;constantW1:natural:=W0+2*fixed_width;variableslv:std_logic_vector(t_input_slvcodecwidth-1downto0);beginslv(W1-1downtoW0):=to_slvcodec(data.i);returnslv;endfunction;functionfrom_slvcodec(constantslv:std_logic_vector)returnt_inputisconstantW0:natural:=0;constantW1:natural:=W0+2*fixed_width;variabledata:t_input;variablemapped:std_logic_vector(t_input_slvcodecwidth-1downto0);beginmapped:=slv;data.i:=from_slvcodec(mapped(W1-1downtoW0));returndata;endfunction;constantt_output_slvcodecwidth:natural:=(1+fixed_width);functionto_slvcodec(constantdata:t_output)returnstd_logic_vector;functionfrom_slvcodec(constantslv:std_logic_vector)returnt_output;functionto_slvcodec(constantdata:t_output)returnstd_logic_vectorisconstantW0:natural:=0;constantW1:natural:=W0+(1+fixed_width);variableslv:std_logic_vector(t_output_slvcodecwidth-1downto0);beginslv(W1-1downtoW0):=to_slvcodec(data.o);returnslv;endfunction;functionfrom_slvcodec(constantslv:std_logic_vector)returnt_outputisconstantW0:natural:=0;constantW1:natural:=W0+(1+fixed_width);variabledata:t_output;variablemapped:std_logic_vector(t_output_slvcodecwidth-1downto0);beginmapped:=slv;data.o:=from_slvcodec(mapped(W1-1downtoW0));returndata;endfunction;signalinput_data:t_input;signaloutput_data:t_output;signalinput_slv:std_logic_vector(t_input_slvcodecwidth-1downto0);signaloutput_slv:std_logic_vector(t_output_slvcodecwidth-1downto0);signalclk:std_logic;signalread_clk:std_logic;signalwrite_clk:std_logic;begininput_data<=from_slvcodec(input_slv);output_slv<=to_slvcodec(output_data);file_reader:entitywork.ReadFilegenericmap(FILENAME=>OUTPUT_PATH&"/indata.dat",PASSED_RUNNER_CFG=>RUNNER_CFG,WIDTH=>t_input_slvcodecwidth)portmap(clk=>read_clk,out_data=>input_slv);file_writer:entitywork.WriteFilegenericmap(FILENAME=>OUTPUT_PATH&"/outdata.dat",WIDTH=>t_output_slvcodecwidth)portmap(clk=>write_clk,in_data=>output_slv);clock_generator:entitywork.ClockGeneratorgenericmap(CLOCK_PERIOD=>CLOCK_PERIOD,CLOCK_OFFSET=>0ns)portmap(clk=>clk);read_clock_generator:entitywork.ClockGeneratorgenericmap(CLOCK_PERIOD=>CLOCK_PERIOD,CLOCK_OFFSET=>CLOCK_PERIOD/10)portmap(clk=>read_clk);write_clock_generator:entitywork.ClockGeneratorgenericmap(CLOCK_PERIOD=>CLOCK_PERIOD,CLOCK_OFFSET=>4*CLOCK_PERIOD/10)portmap(clk=>write_clk);dut:entitywork.complex_mag2portmap(i=>input_data.i,o=>output_data.o);endarchitecture;But generating a test bench that just reads and writes the input and output data
to and from files isn’t particularly useful unless we have a way of generating the
input data, and checking the output data.  Slvcodec include tools to do this
with python.Python-based testingWe define a python class with amake_input_datamethod that returns an iterable of
dictionaries specifying the input data, and acheck_output_datamethod that receives
a list of input_data dictionaries and a list of output data dictionaries, that raises an
exeception is the output data is incorrect.classComplexMag2Test:def__init__(self,resolved,generics,top_params):# Here we're taking advantage of the fact that when the test is intialized it# has access to the parsed VHDL.  We use that to get the value of the constant# FIXED_WIDTH that is defined in complex_pkg.vhd.self.fixed_width=resolved['packages']['complex'].constants['fixed_width'].value()self.max_fixed=pow(2,self.fixed_width-1)-1self.min_fixed=-pow(2,self.fixed_width-1)self.n_data=100deffixed_to_float(self,f):r=f/pow(2,self.fixed_width-2)returnrdefmake_input_data(self,seed=None,n_data=3000):input_data=[{'i':{'real':random.randint(self.min_fixed,self.max_fixed),'imag':random.randint(self.min_fixed,self.max_fixed)},}foriinrange(self.n_data)]returninput_datadefcheck_output_data(self,input_data,output_data):inputs=[self.fixed_to_float(d['i']['real'])+self.fixed_to_float(d['i']['imag'])*1jfordininput_data]input_float_mag2s=[abs(v)*abs(v)forvininputs]outputs=[self.fixed_to_float(d['o'])fordinoutput_data]differences=[abs(expected-actual)forexpected,actualinzip(input_float_mag2s,outputs)]allowed_error=1/pow(2,self.fixed_width-2)assertall([d<allowed_errorfordindifferences])We then useslvcodec.test_utils.register_test_with_vunitto generate an appropriate testbench and input
data file, and register the produced test with vunit.  VUnit can then be run as normal.fromslvcodecimporttest_utils,configimportosif__name__=='__main__':random.seed(0)# Initialize vunit with command line parameters.vu=config.setup_vunit()# Set up logging.config.setup_logging(vu.log_level)# Get filenames for testthis_dir=os.path.dirname(os.path.realpath(__file__))filenames=[os.path.join(this_dir,'complex_pkg.vhd'),os.path.join(this_dir,'complex_mag2.vhd'),]# Register the test with VUnit.test_output_directory=os.path.join(this_dir,'generated')test_utils.register_test_with_vunit(vu=vu,directory=test_output_directory,filenames=filenames,top_entity='complex_mag2',all_generics=[{}],test_class=ComplexMag2Test,top_params={},)# Run the tests with VUnitvu.set_sim_option('disable_ieee_warnings',True)vu.main()