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!