0

I'm working with GHDL (GHDL - v6.0.0-dev - Ubuntu 24.04 (x86-64, LTS) - gcc backend) and LCOV to analyze code coverage for VHDL projects, but I'm encountering unexpected behavior in the branch coverage reporting. The

Issue: LCOV is showing branch coverage metrics for lines of code that don't appear to contain actual branches.

Question:
How can I configure LCOV or GHDL to prevent branch coverage reporting for non-branch operations in VHDL?

The full project code is available at: https://github.com/huettern/ghdl-coverage

Details:

Here's an example (lcov report) from a simple adder component:

Branch data     Line data    Source code
       1                 :          13 : entity adder is
       2                 :             :   -- `i0`, `i1` and the carry-in `ci` are inputs of the adder.
       3                 :             :   -- `s` is the sum output, `co` is the carry-out.
       4                 :             :   port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
       5                 :             : end adder;
       6                 :             : 
       7                 :           2 : architecture rtl of adder is
       8                 :             : begin
       9                 :             :    --  This full-adder architecture contains two concurrent assignment.
      10                 :             :    --  Compute the sum.
      11   [ -  +  +  + ]:          17 :    s <= i0 xor i1 xor ci;
      12                 :             :    --  Compute the carry.
      13   [ +  +  +  +  :          10 :    co <= (i0 and i1) or (i0 and ci) or (i1 and ci);
          +  +  +  +  +  
             +  -  +  +  
                      + ]
      14                 :             : end rtl;
      15                 :             :

Note that line 11 (s <= i0 xor i1 xor ci;) shows branch coverage data ([ - + + + ]) despite being a simple XOR operation with no explicit branches. I would expect branch coverage to only appear for control flow statements like if, case, etc.

Additional Output from gcov Here's the raw .gcov output from running gcov -b -c -s . adder.vhd, which may help clarify what's happening under the hood:

-:    0:Source:/home/aaghmour/ghdl-coverage/projects/adder/adder.vhd
        -:    0:Graph:adder.gcno
        -:    0:Data:adder.gcda
        -:    0:Runs:1
       13:    1:entity adder is
        -:    2:  -- `i0`, `i1` and the carry-in `ci` are inputs of the adder.
        -:    3:  -- `s` is the sum output, `co` is the carry-out.
        -:    4:  port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
        -:    5:end adder;
        -:    6:
        2:    7:architecture rtl of adder is
        -:    8:begin
        -:    9:   --  This full-adder architecture contains two concurrent assignment.
        -:   10:   --  Compute the sum.
      17*:   11:   s <= i0 xor i1 xor ci;
        -:   12:   --  Compute the carry.
      10*:   13:   co <= (i0 and i1) or (i0 and ci) or (i1 and ci);
call    0 returned 1
branch  1 taken 4 (fallthrough)
branch  2 taken 4
branch  3 taken 6 (fallthrough)
branch  4 taken 2
branch  5 taken 2 (fallthrough)
branch  6 taken 4
branch  7 taken 5 (fallthrough)
branch  8 taken 3
branch  9 taken 2 (fallthrough)
branch 10 taken 3
branch 11 taken 0 (fallthrough)
branch 12 taken 8
branch 13 taken 3 (fallthrough)
branch 14 taken 5
call   15 returned 3
------------------
work__adder__ARCH__rtl__STMT_ELAB:
function work__adder__ARCH__rtl__STMT_ELAB called 1 returned 100% blocks executed 100%
        1:    1:entity adder is
        -:    2:  -- `i0`, `i1` and the carry-in `ci` are inputs of the adder.
        -:    3:  -- `s` is the sum output, `co` is the carry-out.
        -:    4:  port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
        -:    5:end adder;
        -:    6:
        1:    7:architecture rtl of adder is
call    0 returned 1
        -:    8:begin
        -:    9:   --  This full-adder architecture contains two concurrent assignment.
        -:   10:   --  Compute the sum.
        1:   11:   s <= i0 xor i1 xor ci;
call    0 returned 1
call    1 returned 1
call    2 returned 1
call    3 returned 1
call    4 returned 1
        -:   12:   --  Compute the carry.
        1:   13:   co <= (i0 and i1) or (i0 and ci) or (i1 and ci);
call    0 returned 1
call    1 returned 1
call    2 returned 1
call    3 returned 1
call    4 returned 1
------------------
work__adder__ARCH__rtl__DECL_ELAB:
function work__adder__ARCH__rtl__DECL_ELAB called 1 returned 100% blocks executed 100%
        1:    1:entity adder is
        -:    2:  -- `i0`, `i1` and the carry-in `ci` are inputs of the adder.
        -:    3:  -- `s` is the sum output, `co` is the carry-out.
        -:    4:  port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
        -:    5:end adder;
        -:    6:
        1:    7:architecture rtl of adder is
------------------
work__adder__ARCH__rtl__P0__PROC:
function work__adder__ARCH__rtl__P0__PROC called 8 returned 100% blocks executed 83%
        8:    1:entity adder is
        -:    2:  -- `i0`, `i1` and the carry-in `ci` are inputs of the adder.
        -:    3:  -- `s` is the sum output, `co` is the carry-out.
        -:    4:  port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
        -:    5:end adder;
        -:    6:
        -:    7:architecture rtl of adder is
        -:    8:begin
        -:    9:   --  This full-adder architecture contains two concurrent assignment.
        -:   10:   --  Compute the sum.
       8*:   11:   s <= i0 xor i1 xor ci;
branch  0 taken 0 (fallthrough)
branch  1 taken 8
branch  2 taken 5 (fallthrough)
branch  3 taken 3
call    4 returned 5
------------------
work__adder__STMT_ELAB:
function work__adder__STMT_ELAB called 1 returned 100% blocks executed 100%
        1:    1:entity adder is
------------------
work__adder__DECL_ELAB:
function work__adder__DECL_ELAB called 1 returned 100% blocks executed 100%
        1:    1:entity adder is
call    0 returned 1
call    1 returned 1
call    2 returned 1
call    3 returned 1
call    4 returned 1
------------------
work__adder__PKG_ELAB:
function work__adder__PKG_ELAB called 1 returned 100% blocks executed 100%
        1:    1:entity adder is
------------------
        -:   14:end rtl;
        -:   15:

My Code and Build Process Here's the full adder code:

entity adder is
  -- `i0`, `i1` and the carry-in `ci` are inputs of the adder.
  -- `s` is the sum output, `co` is the carry-out.
  port (i0, i1 : in bit; ci : in bit; s : out bit; co : out bit);
end adder;

architecture rtl of adder is
begin
   --  This full-adder architecture contains two concurrent assignment.
   --  Compute the sum.
   s <= i0 xor i1 xor ci;
   --  Compute the carry.
   co <= (i0 and i1) or (i0 and ci) or (i1 and ci);
end rtl;

And here's the **Makefile **I'm using to build, run, and generate reports:

# Specify target
PROJECT = adder

################################################################################
# Defs
PROJ_DIR = projects
TB_EXTENSION = _tb
BUILD = build

################################################################################
# Sources
SRCS := $(shell find $(PROJ_DIR)/$(PROJECT)/ -type f -name "*.vhd")
OBJS := $(addprefix $(BUILD)/, $(notdir $(patsubst %.vhd,%.o,$(SRCS))))

TB = $(PROJECT)$(TB_EXTENSION)

################################################################################
# Executables

GHDL_COV_FLGAS = -Wc,-fprofile-arcs -Wc,-ftest-coverage
GHDL_ANALYZE = ghdl -a --workdir=.
GHDL_ELAB = ghdl -e --workdir=. -Wl,-lgcov
GHDL_RUN = ghdl -r --workdir=.

GCOV = gcov
LCOV = lcov -c --rc branch_coverage=1 --ignore-errors mismatch
GENHTML = genhtml --rc genhtml_branch_coverage=1
RM = rm -rf

################################################################################
# Output files
all: run

run: $(TB)
    cd $(BUILD)/ && $(GHDL_RUN) $(TB)
    cd $(BUILD)/ && $(GCOV) -s . $(PROJECT).vhd
    cd $(BUILD)/ && $(LCOV) -d . -o $(PROJECT)$(TB_EXTENSION).info
    cd $(BUILD)/ && $(GENHTML) -o html $(PROJECT)$(TB_EXTENSION).info

$(TB) : $(OBJS)
    cd $(BUILD)/ && $(GHDL_ELAB) $@

# GHDL_COV_FLGAS only if not tb file
cov_flags=$(if $(findstring _tb,$(1)),,$(GHDL_COV_FLGAS))
$(OBJS) : $(SRCS)
    cd $(BUILD)/ && $(GHDL_ANALYZE) $(call cov_flags,$(notdir $@)) ../$(addprefix $(PROJ_DIR)/$(PROJECT)/,$(notdir $(patsubst %.o,%.vhd,$@)))

clean:
    $(RM) $(BUILD)/*

.PHONY: all run clean

My Environment Ubuntu 24.04.2 LTS GHDL version: 6.0.0-dev (4.1.0.r593.gdd13f1d38) [Dunoon edition] GCC version: 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04) LCOV version: 2.0-1

3
  • OT: Apparently ghdl implements the combinatorial expressions as conditional jumps, and the coverage results show you combinations not tested. As a tester, I would appreciate that. Commented Apr 3 at 5:42
  • github.com/ghdl/ghdl/issues/2964 Commented Apr 7 at 6:55
  • This is my question for ghdl team in github: github.com/ghdl/ghdl/issues/2964 Commented Apr 8 at 9:39

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.