VHDL poor naming conventions

TLDR

  1. Not every package name requires the _pkg suffix.
  2. Do not add the package name as the prefix to the symbols declared within the package.
  3. Do not use the source type name in conversion function names.
  4. Do not use camelCase or PascalCase.

Naming convention is a very subjective topic. I usually don't even get into a discussion as the arguments are hard to prove and irrelevant from the technical perspective. However, in the case of VHDL, I often see 4 poor naming conventions having solid arguments against them.

The "_pkg" suffix

Somehow people writing VHDL descriptions started adding the _pkg suffix to the package names like it was required by the language reference manual. What is the point of this? Have you seen people adding _lib, _pkg, or _mod suffixes to libraries, packages, or modules in other languages? I bet you haven't, and if you have, I bet this is the minority of minority. In VHDL, it became an unformal standard.

If you are an author of some package, you enforce users of your package to type the _pkg suffix every time they refer to a symbol from the package when they haven't imported the symbol, but only the package. See the below example.

library foo;
   use foo.bar_pkg;

architecture behavioral of some_entity is

  signal a : bar_pkg.type_a := bar_pkg.init();
  signal b : bar_pkg.type_b := bar_pkg.init();

begin
end architecture;

Now, look at the same code but without the _pkg suffix. The readability increased even though the example code is very short. In the case of actual description, the difference feels like taking a smooth breath.

library foo;
   use foo.bar;

architecture behavioral of some_entity is

  signal a : bar.type_a := bar.init();
  signal b : bar.type_b := bar.init();

begin
end architecture;

Some people argue that thanks to the _pkg suffix, they know the symbol denotes a package. My question is, what problem does it solve? I have never heard any good rationale. It always comes down to the same answer: "I just want to know."

Some people argue that they never use package names as they always import all symbols from the package. This is an even worse approach, as they usually add the package name to all symbols names within the package. I try to explain why such an approach is bad in the Package symbols section.

There are some cases when adding the _pkg suffix makes a lot of sense. The first one is when you provide some entity and the user (usually you are the user of your code) must use custom types or functions to interact with the entity. For example, the entity uses custom types for ports. In such a case it seems natural (it is even desirable) to name the entity my_entity and the package my_entity_pkg. The second case when you probably want to add the _pkg suffix is when you write a generic package. In such a case, it seems reasonable to use the _pkg suffix for the generic package declaration and discard the suffix when you instantiate the package. There are probably more use cases when adding the _pkg suffix makes sense, but I need help to think of anyone now.

Package symbols

There is a very decent JSON library for VHDL, which is an excellent example of this poor naming convention. In short, people add package names to all symbol names declared within the package. What problem does it solve? None. What problems does it create? At least 2. The first one is extra boilerplate. The second one is even more boilerplate.

The first boilerplate is handled by the package authors, as they have to type unnecessary long names within the package. See the below 2 code snippets and ask yourself which one is more readable. If you think the first one, please email me explaining why adding package names to symbol names declared within the package is a good idea.

package foo is
   type foo_rec_t is record
      a : std_logic;
      b : std_logic;
      c : std_logic_vector(7 downto 0);
   end record;

   constant foo_rec_const : foo_rec_t := (a => '0', b => '0', c => (others => '0'));

   function foo_process_rec(rec : foo_rec_t) return foo_rec_t;
end package;
package foo is
   type rec_t is record
      a : std_logic;
      b : std_logic;
      c : std_logic_vector(7 downto 0);
   end record;

   constant rec_const : rec_t := (a => '0', b => '0', c => (others => '0'));

   function process_rec(rec : rec_t) return rec_t;
end package;

The second boilerplate is handled by the package user. If you add a package name to symbol names declared within the package, you leave the user no choice. The user can't use short names anymore. Take a look at the below 2 code snippets. The second one is so verbose that it doesn't even make sense.

library libfoo;
   use libfoo.foo.all;

architecture behavioral of some_entity is

  constant a : foo_rec_t := foo_rec_const;
  signal   b : foo_rec_t := foo_process_rec(a);

begin
end architecture;
library libfoo;
   use libfoo.foo;

architecture behavioral of some_entity is

  constant a : foo.foo_rec_t := foo.foo_rec_const;
  signal   b : foo.foo_rec_t := foo.foo_process_rec(a);

begin
end architecture;

Now look at the following 2 corresponding code snippets. The user now has the freedom to use short names or full symbol paths. The first one makes sense when the context in which the package is used is small. The second one makes sense when the context uses multiple external packages, and it is reasonable to explicitly indicate which package symbols come from.

library libfoo;
   use libfoo.foo.all;

architecture behavioral of some_entity is

  constant a : rec_t := rec_const;
  signal   b : rec_t := process_rec(a);

begin
end architecture;
library libfoo;
   use libfoo.foo;

architecture behavioral of some_entity is

  constant a : foo.rec_t := foo.rec_const;
  signal   b : foo.rec_t := foo.process_rec(a);

begin
end architecture;

Conversion functions

Many people name conversion functions in such a way that it includes the source type name and the target type name. See the below code snippet:

package foo is
  type animal_t is (APE, BAT, COW);
  type fruit_t  is (APPLE, BANANA, CHERRY);
  type letter_t is (A, B, C);

  function animal_to_letter (a : animal_t) letter_t;
  function fruit_to_letter  (f : fruit_t)  letter_t;
end package;

The questions are as always. Why? What problem does it solve? VHDL is a strongly typed language. The user must be so explicit about the typing that converting by accident to an invalid type is (almost?) impossible. VHDL was designed to support ad hoc polymorphism, so it seems reasonable to use this feature. In practice, function overloading should be used, with the function name containing only the target type name. This is shown in the below snippet. Not only is the code shorter, but if you later change the source type name or decide to use a different source type for the same signal, you will not have to adjust the function name, as the target type remains the same.

package foo is
  type animal_t is (APE, BAT, COW);
  type fruit_t  is (APPLE, BANANA, CHERRY);
  type letter_t is (A, B, C);

  function to_letter (a : animal_t) letter_t;
  function to_letter (f : fruit_t)  letter_t;
end package;

camelCase and PascalCase

I generally do not prefer any of the snake_case, camelCase, PascalCase, or Pascal_Snake_Case over the others. I usually simply follow what the language or project recommends or enforces. However, VHDL is case insensitive. This means the following names refer to the same symbol: someName, somename, SOMENAME, SomeName, and sOmEnAmE. I remember I once had to get through some code utilizing the OSVVM library. The library mostly uses the PascalCase for symbol names. However, the code I had to read used lowercase for everything, so it was full of names looking like this naturalvbooltype instead of NaturalVBoolType. As I was already after 10 hours of work, my ability to focus was approaching zero. Everything seemed more complex than it was because reading the symbol names required extra effort to separate the words.

Later on, I analyzed why it was formatted with all lowercase. It turned out some person working on the code had some emacs hook reformatting the VHDL code to all lowercase on file save. The change was simply committed and pushed to the repo. You may use a counterargument that this would never happen if the file was reviewed before being merged. The problem with this counterargument is that sometimes there is no one who can review the code, and the person editing the code is not the one who has written it. They just want to fix a bug and move forward. In the case of the snake_case or Pascal_Snake_Case, the code would never become unreadable, as the floor character '_' is simply an unmodifiable part of the symbol name.

I do not discourage anyone from using the OSVVM library. I used it solely to present an example of what might happen. I instead encourage you to use snake_case or Pascal_Snake_Case for symbol names whenever you have a choice.