Getting OSVVM Working Under GHDL

Getting OSVVM Working Under GHDL

Introduction

This post focuses on getting OSVVM working under GHDL. In addition, I also take a look at a few basic OSVVM features. I’m starting to use OSVVM and I’ve decided that a really good starting place is the AlertLogPkg and the TbUtilPkg. The following is a short tutorial that creates a testbench around a simple core using AlertLogPkg.

The core I’m using as an example is a simple AXI FIFO. A FIFO is a fairly simple core that I can evolve over time to demonstrate the development of more complex test benches. Additionally, I present a simple makefile as an alternative to the OSVVM TCL based script environment. I understand the motivation for using TCL. I have found, however, that the initial learning curve associated with getting OSVVM up and running, in conjunction with GHDL, is hard enough. A simple makefile should help with understanding the GHDL build process. I’ll take closer look at the TCL scripting capabilities in a later post (and I’m looking forward to the Python equivalent when it’s released!)

Prerequisites

Windows 10 installation of both GHDL and OSVVM is covered in this post. If you’re working under Linux, your life should be much simpler. I’ll talk a bit, below, about some useful debugging features and some of GHDL’s idosyncracies below. Most of this is probably due to my own inexperience with GHDL, but if I’m working through this, it’s probably safe to say that others will hit the same roadblocks.

GHDL

I personally worked for about an hour trying to sort out how GHDL searches for libraries. Online documentation is a little sketchy, and I eventually ended up digging through the GHDL source code. Using GHDL under MSYS2 probably complicates things a bit. It bears pointing out that I am a long term Unix user. I’m a bit cornered into being a reluctant Windows user, as many are, by an employer that has trouble with deploying Linux on the average desktop. In the section below, I’ll talk a bit about the process of installing OSVVM under the nominal GHDL library tree to avoid the use of the “-P” command line flag when running GHDL. Passing a library search directory to GHDL doesn’t seem to work correctly under MSYS2. I’ll revise this section if I discover otherwise.

OSVVM

I recommend pre-compiling the OSVVM libraries as a vendor library under GHDL. This post details how to do this.

Tutorial Core

The AXI-FIFO Core

The core shown below is a straightforward reconfigurable FIFO. My intent is to eventually add an AXI4 interface. The core builds under GHDL, and is a straightforward testbench target for learning purposes.

--
--  @file  axi_fifo.vhd
--  @author John Wiley (jw@darkmagicdesign.com)
--  @brief AXI FIFO entity
--	@date 18 Dec 2021
--
-----------------------------------------------------------------------------
--
--  Revision History:      
--    Date      Version    Description
--    12/2021   2021.01    Initial Revision
--
--  Developed by/for:
--        Dark Magic Design, LLC
--        Colorado Springs, CO 80918
--        http://www.darkmagicdesign.com
--
-----------------------------------------------------------------------------
--
--  Copyright (c) 2021 - 2022 by Dark Magic Design, LLC
--
--  Licensed under:  CERN-OHL-W (v2)
--
-----------------------------------------------------------------------------
-- You may redistribute and modify this code under the terms of the
-- CERN-OHL-W (v2) Open Hardware License. (http://ohwr.org/cernohl). 
-- This documentation is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, 
-- INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A
-- PARTICULAR PURPOSE. Please see the CERN OHL v2 for applicable
-- conditions.
-----------------------------------------------------------------------------
 



library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity axi_fifo is
	generic (
		FIFO_WIDTH: natural;
		FIFO_DEPTH: natural
	);
	
	port(
	
		clk	:	in std_ulogic;
		rst : 	in std_ulogic;
		
		-- AXI Input Interface
		in_ready_o : out std_logic;
		in_valid_i : in std_logic;
		in_data_i  : in std_logic_vector(FIFO_WIDTH - 1 downto 0);
		
		-- AXI Output Interface
		out_ready_i : in std_logic;
		out_valid_o : out std_logic;
		out_data_o  : out std_logic_vector(FIFO_WIDTH - 1 downto 0)

		-- Debug ports

	);

end axi_fifo;

architecture rtl of axi_fifo is

	-- FIFO is full when we hit FIFO_DEPTH - 1 elements
	type mem_t is array (0 to FIFO_DEPTH -1) of std_logic_vector(in_data_i'range);
	signal fifo_mem : mem_t;

	-- The last element in is at the head... The first element in is at the tail...
	subtype mem_index_t is natural range mem_t'range;
	signal head : mem_index_t;
	signal tail : mem_index_t;
	signal count: mem_index_t;
	signal count_dly_1 : mem_index_t;
	
	-- Debug signals...
	signal in_ready_o_int : std_logic;
	signal out_valid_o_int : std_logic;
	
	signal read_while_write : std_logic;
	
	-- Function to manage the index
	
	function next_index(
		index : mem_index_t;
		ready : std_logic;
		valid : std_logic ) return mem_index_t is	
	begin
	
		if ready = '1' and valid = '1' then 
			if index = mem_index_t'high then 
				return mem_index_t'low;
			else
				return index + 1;
			end if;
		end if;
		
		return index;
		
	end function;


	-- Handle head and tail signals
	
	procedure index_proc(
		signal clk  : in std_logic;
		signal rst 	: in std_logic; 
		signal index : inout mem_index_t;
		signal ready : in std_logic;
		signal valid : in std_logic) is 
	begin
		if rising_edge(clk) then
			if rst = '1' then
				index <= mem_index_t'low;
			else
				index <= next_index(index, ready, valid);
			end if;		
		end if;
	end procedure;	
	
begin
	-- Copy internal signals to output
	in_ready_o <= in_ready_o_int;
	out_valid_o <= out_valid_o_int;
	
	-- Update the head index on write
	PROC_HEAD : index_proc(clk, rst, head, in_ready_o_int, in_valid_i);	
		
	-- Update the tail index on read
	PROC_TAIL : index_proc(clk, rst, tail, out_ready_i, out_valid_o_int);


	-- Write to and read from FIFO memory
	PROC_FIFO : process(clk)
	begin
		if rising_edge(clk) then
			fifo_mem(head) <= in_data_i;
			out_data_o <= fifo_mem(next_index(tail, out_ready_i, out_valid_o_int));
		end if;
	end process PROC_FIFO;
	
	-- Determine the number of elements in memory
	
	PROC_COUNT : process(head, tail)
	begin
		if head < tail then
			count <= head - tail + fifo_depth;
		else
			count <= head - tail;
		end if;
	end process PROC_COUNT;
	
	
	-- Delay count by one clock cycle

	PROC_COUNT_DLY_1  :  process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				count_dly_1 <= 0;
			else
				count_dly_1 <= count;
			end if;
		end if;
	end process PROC_COUNT_DLY_1;

	
	-- Assert ready_o when FIFO memory isn't full

	PROC_IN_READY  :  process(count)
	begin
		if count < fifo_depth - 1 then
			in_ready_o_int <= '1';
		else
			in_ready_o_int <= '0';
		end if;
	end process PROC_IN_READY;

	
	-- Detect simultaneous read/write
	PROC_READ_WHILE_WRITE : process(clk)
	begin
		if rising_edge(clk) then
			if rst = '1' then
				read_while_write <= '0';
			else
				read_while_write <= '0';
				if in_ready_o_int = '1' and in_valid_i = '1' and
					out_ready_i = '1' and out_valid_o_int = '1' then
						read_while_write <= '1';
				end if;
			end if;
		end if;
	end process PROC_READ_WHILE_WRITE;

	
	-- Assert valid_o when the FIFO outputs valid data
	PROC_VALID_O  :  process(count, count_dly_1, read_while_write)
	begin
		out_valid_o_int <= '1';
		
		-- If the FIFO is empty or was empty in the previous clk cycle
		if count = 0 or count_dly_1 = 0 then
			out_valid_o_int <= '0';
		end if;
	
		-- If we're reading and writing simultaneously when nearly empty
		if count = 1 and read_while_write = '1' then
			out_valid_o_int <= '0';
		end if;
	end process PROC_VALID_O;
	


end architecture;

The Testbench

The testbench for the FIFO core is below. Relevant lines are highlighted, and there is a detailed discussion that follows the code.

--
--  @file  axi_fifo_tb.vhd
--  @author John Wiley (jw@darkmagicdesign.com)
--  @brief Basic AXI FIFO Testbench
--  @date 18 Dec 2021
--
-----------------------------------------------------------------------------
--
--  Revision History:      
--    Date      Version    Description
--    12/2021   2021.01    Initial Revision
--
--  Developed by/for:
--        Dark Magic Design, LLC
--        Colorado Springs, CO 80918
--        http://www.darkmagicdesign.com
--
-----------------------------------------------------------------------------
--
--  Copyright (c) 2021 - 2022 by Dark Magic Design, LLC
--
--  Licensed under:  CERN-OHL-W (v2)
--
-----------------------------------------------------------------------------
-- You may redistribute and modify this code under the terms of the
-- CERN-OHL-W (v2) Open Hardware License. (http://ohwr.org/cernohl). 
-- This documentation is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, 
-- INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A
-- PARTICULAR PURPOSE. Please see the CERN OHL v2 for applicable
-- conditions.
-----------------------------------------------------------------------------


library ieee;
	use ieee.std_logic_1164.all;
	use ieee.numeric_std.all;

	use std.textio.all ;
	use ieee.std_logic_textio.all ;


	use std.textio.all;

library osvvm;
	use osvvm.OsvvmGlobalPkg.all;
	use osvvm.TranscriptPkg.all;
	use osvvm.AlertLogPkg.all;
	
use std.env.finish;

entity axi_fifo_tb is
end axi_fifo_tb;


architecture sim of axi_fifo_tb is

	-- TB constants
	constant CLK_PERIOD : time := 10 ns;
	constant FIFO_WIDTH : natural := 16;
	constant FIFO_DEPTH : natural := 256;
	constant FifoID : AlertLogIDType := GetAlertLogID("FIFO", ALERTLOG_BASE_ID); 

	
	-- DUT signals
	signal clk : std_logic := '1';
	signal rst : std_logic := '1';
	signal ready_i : std_logic;
	signal valid_i : std_logic := '0';
	signal data_i  : std_logic_vector(FIFO_WIDTH - 1 downto 0) := (others => '0');
	signal ready_o : std_logic := '0';
	signal valid_o : std_logic;
	signal data_o  : std_logic_vector(FIFO_WIDTH - 1 downto 0);
	

begin

-- Read the requirements file
	
	ReadSpecification("Axi_FIFO_Specification.txt");
	TranscriptOpen("sim/Axi_FIFO_Transcript.txt");
	SetAlertLogName("Axi_FIFO_tb");
	SetLogEnable(DEBUG, TRUE);
	SetAlertLogJustify;

	clk <= not clk after CLK_PERIOD / 2;
	
	DUT	 :  entity work.axi_fifo(rtl)
	generic map(
		FIFO_WIDTH => FIFO_WIDTH,
		FIFO_DEPTH => FIFO_DEPTH
	)
	port map(
		clk => clk,
		rst => rst,
		in_ready_o => ready_o,
		in_valid_i => valid_i,
		in_data_i => data_i,
		out_ready_i => ready_i,
		out_valid_o => valid_o,
		out_data_o => data_o
	);

	
	PROC_SEQUENCER  :  process is
	begin
		wait for 10 * CLK_PERIOD;
		rst <= '0';
		wait until rising_edge(clk);
		
		
		-- Write until full 
		valid_i <= '1';
		while ready_o = '1' loop
			data_i <= std_logic_vector(unsigned(data_i) + 1);
			Log(FifoID,"Writing data to the FIFO.  Data = " & to_hstring(data_i), ALWAYS);
			wait until rising_edge(clk);
		end loop;
		

		AffirmIF(GetReqID("FIFO_T1"), TRUE, "FIFO Write Completed Successfully");

		
		valid_i <= '0';		-- Tell the FIFO we no longer have data for it... 
		
		-- Check to make sure ready_o has been de-asserted
		AffirmIF("FIFO_T3", ready_o = '0',"FIFO Indicates Full (ready_o = " & to_string(ready_o) & ")");
		
		
		Log(FifoID, "FIFO Full...", ALWAYS);
		
		data_i <= (others => 'X');
		
		-- Read until the FIFO is empty
		
		ready_i <= '1';
		while valid_o = '1' loop
			wait until rising_edge(clk);
			Log(FifoID,"Reading from the FIFO.  Data = " & to_hstring(data_o), ALWAYS);
		end loop;
		ready_i <= '0';
		
		Log(FifoID, "FIFO Empty...", ALWAYS);
		
		AffirmIF(GetReqID("FIFO_T2"), TRUE, "FIFO Read Completed Successfully");

		-- Check to make sure valid_o has been de-asserted
		AffirmIF("FIFO_T4", valid_o = '0',"FIFO Indicates Empty (valid_o = " & to_string(valid_o) & ")");
	
		
		Log(FifoID, "axi_fifo_tb Simulation Complete", ALWAYS);
		
		ReportAlerts;
		ReportRequirements;
		
		finish;
	end process PROC_SEQUENCER;

end architecture sim;

Lines 44-47: This library statement pulls in the osvvm library and specifies that we only want to use the OsvvmGlobalPkg, TranscriptPkg, and AlertLogPkg packages. If you run into problems when running analysis on the testbench, it’s likely that you’ve gotten the directory hierarchy wrong when installing OSVVM.

Line 61: This line defines an Alert Log ID (FifoID) that we can use as a target for specific log events.

Lines 79-83: This code segment calls ReadSpecification() to read the Requirements file. TranscriptOpen() opens the text based transcript file. SetAlertLogName() sets the file name of the alert log. SetLogEnable() sets the log level. There are four available log levels (ALWAYS, DEBUG, PASSED, INFO). Log levels can be set for individual AlertLogIDs, as well. This is a fairly handy feature for more complex testbenches. The final line, calling SetAlertLogJustify(), sets text justification on for the alert log.

Line 115: This is a fairly simple demonstration of the use of a call to Log(). We reference FIFO_ID to log a general message to the transcript. I use log level ALWAYS here, but “DEBUG” can be used, for example, to allow selective reduction of testbench chattiness.

Line 120: The AffirmIF() call here declares that our FIFO Write test, which is testing the requirement FIFO_T1, has completed and successfully passed. Not a super comprehensive test, but…

Line 126: The AffirmIF() call here is a little more complex. We’re checking here to make sure that when the FIFO is full, we actually de-asserted “ready_o”. We wouldn’t have escaped the write loop above, if we hadn’t so again, a very simple test that’s pretty certain to pass, but this example is more about demonstrating OSVVM features than demonstrating proper test methodology.

Line 129: We fire a log message at the FifoID target indicating that the FIFO is full. Time stamping events like this, from a debugging standpoint, makes it a lot easier to review the event in GtkWave later!

Line 138: Another set of log entries for each read from the FIFO!

Lines 142-153: In this code segment we mostly do the same things for reading from the FIFO that we did for writing to the FIFO. This code should be pretty obvious.

The Requirements File

The structure/format of the requirements file isn’t documented clearly. In the testbench (Line 79) we read this file in using a call to ReadSpecification(). Each row represents a test requirement “bin”. The first column is the Requirement ID. The second column is a description of the requirement. The last column represents the goal for PASSED tests within that requirement bin. The requirements are pretty self-explanatory. This binning structure, as simple as it sounds, is really handy for tracing tested requirements back to documentation for more complex development efforts.

FIFO_T1,Successful Write to FIFO,1
FIFO_T2,Successful Read from FIFO,1
FIFO_T3,FIFO Full,1
FIFO_T4,FIFO Empty,1

Using a Makefile

The simple makefile, shown below, is a useful alternative to the use of OSVVM’s TCL script approach. This is not a negative comment on OSVVM’s use of TCL as a build scripting language. I’m using a makefile here for the sake of simplicity. The TCL based build environment included with OSVVM is complex and evolving. Where the GHDL implementation is concerned the TCL script interface also seems a little ideosyncratic. I’ve also got a good deal more investigation to do with respect to using the I’m happy to hear that there is a Python based build environment apparently evolving alongside the TCL build environment.

This basic makefile compiles the core and the test bench, and then runs the executable. I’m using GHDL with an LLVM back end. Compilation is to an executable. Some folks tend to use the mcode backend and there are issues with running the OSVVM regression tests using the LLVM back end. I haven’t run into any problems with building against OSVVM using the LLVM back end. The GCC back end WILL NOT WORK under Windows. The “view” target runs gtkwave against the VCD file generated during the run.

This makefile should be useful for getting a working “sandbox” up and running pretty quickly, with a minimum of frustration. Note the directory structure! I typically use a “src” directory to contain the source for the DUT, a “tb” directory to contain testbench code, and a “sim” directly to keep track of simulation and test artifacts.

#  @file  makefile
#  @author John Wiley (jw@darkmagicdesign.com)
#  @brief generic GHDL/OSVVM makefile
#  @date 18 Dec 2021
#
#-----------------------------------------------------------------------------
#
#  Revision History:      
#    Date      Version    Description
#    12/2021   2021.01    Initial Revision
#
#  Developed by/for:
#        Dark Magic Design, LLC
#        Colorado Springs, CO 80918
#        http://www.darkmagicdesign.com
#
#-----------------------------------------------------------------------------
#
#  Copyright (c) 2021 - 2022 by Dark Magic Design, LLC
#
#  Licensed under:  CERN-OHL-W (v2)
#
#-----------------------------------------------------------------------------
# You may redistribute and modify this code under the terms of the
# CERN-OHL-W (v2) Open Hardware License. (http://ohwr.org/cernohl). 
# This documentation is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, 
# INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A
# PARTICULAR PURPOSE. Please see the CERN OHL v2 for applicable
# conditions.
#-----------------------------------------------------------------------------
# vhdl files
SRCFILES = ./src/*.vhd
VHDLEX = .vhd

# osvvm files
OSVVM_LIB_DIR = d:/distrib/osvvm/OsvvmLibraries

# testbench
TESTBENCHFILE = ${TESTBENCH}_tb
TESTBENCHPATH = tb/${TESTBENCHFILE}$(VHDLEX)

#GHDL CONFIG
GHDL_CMD = ghdl
GHDL_FLAGS  = --std=08 --warn-no-vital-generic 

SIMDIR = sim
STOP_TIME = 500ns
# Simulation break condition
#GHDL_SIM_OPT = --assert-level=error
GHDL_SIM_OPT = --stop-time=$(STOP_TIME)
VCDFILE = ${SIMDIR}/${TESTBENCHFILE}.vcdgz

WAVEFORM_VIEWER = gtkwave

.PHONY: clean

all: clean compile run view

compile:
ifeq ($(strip $(TESTBENCH)),)
		@echo "TESTBENCH not set. Use TESTBENCH=<value> to set it."
			@exit 1
endif

	@mkdir -p sim
	@$(GHDL_CMD) -a $(GHDL_FLAGS) --workdir=sim --work=work -P$(OSVVM_LIB_DIR) $(SRCFILES)
	@$(GHDL_CMD) -a $(GHDL_FLAGS) --workdir=sim --work=work -P$(OSVVM_LIB_DIR) $(TESTBENCHPATH)
	@$(GHDL_CMD) -m  $(GHDL_FLAGS) --workdir=sim --work=work -P$(OSVVM_LIB_DIR) $(TESTBENCHFILE)
	#@mv $(TESTBENCHFILE) simulation/$(TESTBENCHFILE)

run:
	@$(GHDL_CMD) -r $(GHDL_FLAGS) --workdir=sim --work=work $(TESTBENCHFILE) --vcdgz=$(VCDFILE) $(GHDL_SIM_OPT)

view:
	@gunzip --stdout $(SIMDIR)/$(TESTBENCHFILE).vcdgz | $(WAVEFORM_VIEWER) --vcd

clean:
	@rm -rf $(SIMDIR)

Results

Transcript file excerpts are shown below… The “cut marks” are denoted by “<…>”.

%% Log   ALWAYS  in FIFO,         Writing data to the FIFO.  Data = 0000 at 110 ns
%% Log   ALWAYS  in FIFO,         Writing data to the FIFO.  Data = 0001 at 120 ns
%% Log   ALWAYS  in FIFO,         Writing data to the FIFO.  Data = 0002 at 130 ns
<...>
%% Log   ALWAYS  in FIFO,         Writing data to the FIFO.  Data = 00FD at 2640 ns
%% Log   ALWAYS  in FIFO,         Writing data to the FIFO.  Data = 00FE at 2650 ns
%% Log   ALWAYS  in FIFO,         Writing data to the FIFO.  Data = 00FF at 2660 ns
%% Log   ALWAYS  in FIFO,         FIFO Full... at 2670 ns
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = 0001 at 2680 ns
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = 0002 at 2690 ns
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = 0003 at 2700 ns
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = 0004 at 2710 ns
<...>
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = 00FE at 5210 ns
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = 00FF at 5220 ns
%% Log   ALWAYS  in FIFO,         Reading from the FIFO.  Data = XXXX at 5230 ns
%% Log   ALWAYS  in FIFO,         FIFO Empty... at 5230 ns
%% Log   ALWAYS  in FIFO,         axi_fifo_tb Simulation Complete at 5230 ns
%% DONE  PASSED  Axi_FIFO_tb  Passed: 4  Affirmations Checked: 4  Requirements Passed: 4 of 4  at 5230 ns
%% DONE  PASSED  Axi_FIFO_tb  Passed: 4  Affirmations Checked: 4  Requirements Passed: 4 of 4  at 5230 ns
%%    FIFO_T1       Failures: 0  Errors: 0  Warnings: 0  Passed: 1 of 1
%%    FIFO_T2       Failures: 0  Errors: 0  Warnings: 0  Passed: 1 of 1
%%    FIFO_T3       Failures: 0  Errors: 0  Warnings: 0  Passed: 1 of 1
%%    FIFO_T4       Failures: 0  Errors: 0  Warnings: 0  Passed: 1 of 1

2670 ns : We’ve written a full FIFO

5230 ns : We’ve emptied the FIFO (and at 5230 ns it looks like we may have read one to many times! Possible Bug!)

Line 19: This is a summary of the test results.

Lines 20-24: This is output from the call to ReportRequirements(). We’ve asked for a summary of all of the requirements.

Basic OSVVM Features

The main objective of this post is to get OSVVM up and running under GHDL. I’m using the logging/reporting features found in the AlertLogPkg package to build a very simple testbench against a core that is straightforward to understand and simulate.

Conclusion

Getting OSVVM working under GHDL is a step forward in building more comprehensive testbenches. This is a continuing series of posts that document my efforts to become proficient with OSVVM. I’m no expert, but I am happy to share my experiences in the hope of blazing a trail for others. The initial learning curve, with respect to getting OSVVM up and running under GHDL has been steeper than it probably needs to be. The OSVVM documentation is comprehensive, but seems to be written for an audience that’s fairly far along in applying formal verification methods to testbench construction.

My next post is planning to cover development of a more complex testbench harness and possibly integration with AXI bus verification IP. I’m hopefully going to be able to meet a pace where I’m posting about twice a month. Stay tuned!