VHDL: Shared Variables, Protected Types, and Memory Modeling
VHDL-93 (IEEE 1076-1993) created shared variables of an ordinary type as a temporary solution – which was noted in the standard document (aka LRM). VHDL-2000 (IEEE 1076-2000) created protected types as the only type to be used with shared variables and deprecated and removed the usage of a shared variable with an ordinary type.
One of the places shared variables of an ordinary type are still used is with memory models. However, with this coding style, the entire memory is implemented in the declaration of the shared variable. The result is that for even FPGA memories, the memory implementation causes simulations to run slow.
Protected types are a powerful mechanism to create data structures within a shared variable. In the Open Source VHDL Verification methodology (OSVVM) protected types are used to create the data structures for Coverage Modeling, Scoreboard and FIFOs, AlertLog Error and Message handling, and Memory modeling.
The OSVVM Memory Modeling package (MemoryPkg) creates a sparse allocation data structure. Sparse allocation data structures only implement storage for a location if it (or a nearby neighbor) is written to. This means if only a portion of a memory model is used in a test case (which is often the case), the simulation will run faster since it did not create the whole memory.
Lets take a look at what is being done and what can be done instead.
VHDL Non-Compliant Coding Styles
FPGA vendors for some time have used shared variables of an ordinary type to implement dual ported memory models, such as the one shown in Figure 1. Just to be clear, this coding style was deprecated and removed by 1076-2000 and is not language compliant.
architecture RTL of DPRam_SV_RIRO_RF_64k is type MemType is array (0 to 2**16-1) of std_logic_vector(7 downto 0) ; shared variable Mem : MemType ; begin MemProcA : process (ClkA) begin if rising_edge(ClkA) then DataOutA <= Mem(to_integer(unsigned(AddrA))) ; if WriteA = '1' then Mem(to_integer(unsigned(AddrA))) := DataInA ; end if ; end if ; end process MemProcA ; MemProcB : process (ClkB) begin if rising_edge(ClkB) then DataOutB <= Mem(to_integer(unsigned(AddrB))) ; if WriteB = '1' then Mem(to_integer(unsigned(AddrB))) := DataInB ; end if ; end if ; end process MemProcB ; end RTL ;
Figure 1: Dual Port Memory Using Shared Variables of an Ordinary Type (Illegal)
VHDL Compliant Code with regular Variables or Signals
The standard IEEE 1076.6-2004 expanded the synthesis coding styles to include the code shown in Figure 2 as a method to create either a dual-port memory or a multiple edge flip-flops using an ordinary process variable. It should be noted that while a signal could also be used here, it is less simulation efficient (since signals require more simulation overhead). Unfortunately, this coding style and standard flopped because too many FPGA vendors and FPGA compiler tool vendors chose to ignore it.
MemProc : process (ClkA, ClkB) type MemType is array (0 to 2**16-1) of std_logic_vector(7 downto 0) ; variable Mem : MemType ; begin if rising_edge(ClkA) then DataOutA <= Mem(to_integer(unsigned(AddrA))) ; if WriteA = '1' then Mem(to_integer(unsigned(AddrA))) := DataInA ; end if ; end if ; if rising_edge(ClkB) then DataOutB <= Mem(to_integer(unsigned(AddrB))) ; if WriteB = '1' then Mem(to_integer(unsigned(AddrB))) := DataInB ; end if ; end if ; end process MemProc ;
Figure 2: Dual Port Memory Using Process Variables (VHDL Legal)
Hey What About the Implied Priority Logic?
In the VHDL Compliant code (Figure 2), if the two clocks both change at the same time, the assignments done under ClkB would have priority over the assignments done under ClkA. True. However, what about the non-compliant code (Figure 1)?
The non-compilant code (Figure 1) is still has priority. Both processes will still run at the same time. However, since process execution order is not defined by the language the priority is random. But not random in a good way as different simulators may (and do) behave differently. This introduces race conditions – just like the ones that plague Verilog and SystemVerilog.
The real question is, in real hardware is it ever correct to write the the same memory location at the same time using different clocks? No. In fact, some FPGA vendors insert extra hardware to protect against this (makes me wonder if it damages their devices). Hence, there is no reason to pay attention to the priority logic in either Figure 1 or 2.
VHDL Compliant and Efficient Memories
With the code in Figures 1 and 2, as memory size grows, simulation slows further since the entire memory is allocated in the variable declaration. However, in most simulations, the entire memory is not used during any given simulation run.
In Open Source VHDL Verification Methodology (OSVVM) the package MemoryPkg creates a protected type (MemoryPType) which implements a sparse memory data structure. Internally it uses an array of pointers to block of storage locations (nominally 1K). This is shown in Figure 3. When a block is written to the first time, the block is allocated. Allocating memory 1K at a time minimizes the amount of memory consumed by the memory model. As a result test cases that only use a portion of the memory model run faster since they did not have to create the entire memory. For further efficiency internally it uses type integer rather than std_logic_vector.
Figure 3: Sparse Memory Allocation
The benefit of a protected type is that all of the hard work is hidden within the package and the only thing a user needs to know is the methods (API) of the protected type.
Figure 4 implements a dual port memory using OSVVM MemoryPkg and MemoryPType. The model starts with a reference to MemoryPkg (use clause). Then the shared variable Mem of MemoryPType is declared. Next the memory is sized by calling MemInit. After the memory has been sized MemRead is used to read from the memory and MemWrite to write to it. You might note that this coding style is actually slightly easier to use than the array type based memories (Figures 1 and 2).
library OSVVM ; use OSVVM.MemoryPkg.all ; architecture RTL of DPRam_SV_PT is shared variable Mem : MemoryPType ; begin Mem.MemInit(AddrWidth => 16, DataWidth => 8) ; MemProcA : process (ClkA) begin if rising_edge(ClkA) then DataOutA <= Mem.MemRead(AddrA) ; if WriteA = '1' then Mem.MemWrite(AddrA, DataInA) ; end if ; end if ; end process MemProcA ; MemProcB : process (ClkB) begin if rising_edge(ClkB) then DataOutB <= Mem.MemRead(AddrB) ; if WriteB = '1' then Mem.MemWrite(AddrB, DataInB) ; end if ; end if ; end process MemProcB ; end RTL ;
Figure 4: Dual Port Memory Using Protected Types (VHDL Legal)
Want to know more, see MemoryPkg_user_guide.pdf in the OSVVM documentation on GitHub.
Wait What About Synthesis?
You might be thinking, surely a protected type cannot be synthesized, right?
Well, neither can the internals of rising_edge (from std_logic_1164) or “+” (from numeric_std). Instead, these packages include guides (meta comments or attributes depending on the vendor) for the synthesis tools that identify what the synthesis tool is to implement for a particular subprogram.
Hence, we can have simulation efficient, synthesizable memory models – all we need is for vendors to create appropriate guides for MemoryPkg
What About Generics?
MemoryPkg was created in 2005 before packages supported generics. Adding package generics would only be different but not better – however …
VHDL-2019 adds generics to protected types and allows the generics to be specified in a shared variable declaration. This would allow the memory to be sized in the shared variable declaration rather than in MemInit. This is an improvement and will be supported by the OSVVM MemoryPkg.
If you are skeptical, you might be thinking it is going to take time for vendors to support generics on a protected type. However, Aldec has announced support for this feature in Riviera-PRO 2020.10 (in the release notes). I look forward to start testing it.
Hey Verific Design Automation please add this capability to your FPGA compiler tools.
Are you listening Xilinx/AMD? Altera/Intel? Lattice?
Simulations that use FPGA memories are slow. The solution is here – protected types. It is as simple as supporting OSVVM.
Further improvements are in VHDL-2019 (1076-2019) in the form generics for protected types. Plan on supporting both.
A Call To Action
VHDL is the most popular FPGA design and verification language world wide. This has been shown year after year by the Wilson Verification Survey. The 2020 results are here. Given these results supporting VHDL should be a priority for FPGA vendors.
Vendors claim to listen to their users. So test out the legal VHDL coding styles (Figure 2) and submit a bug report if they do not support it – they should at least support one legal coding style and Figure 1 is not legal. You can reference this article as support.
Next submit the protected type code and package as a feature request. If they need meta comments, pragmas, or attributes in the file, I would be happy to add them.
Finally, the standard 1076.6 was neither updated nor reaffirmed after 2004. As a result it is a retired standard. Ask your FPGA vendors to come together and take a leadership role in reviving this standard. The VHDL FPGA community needs a standardized set of attributes and/or pragmas that control statemachines and memory implementation. What we have now is annoying similar vendor solutions.
I have demonstrated here that dual port memory models can be implemented using regular variables and a coding style that is advocated for in 1076.6-2004. Hence, there is no need to use shared variables of an ordinary type – a usage of shared variables that was deprecated and removed from the language 20 years ago. I don’t have a problem with vendors supporting deprecated coding styles, however, I do see it as an issue if they don’t provide a legal alternative.
I have also shown that protected types are a better way to implement memories. While MemoryPkg is currently only used for testbenches, it has a great potential to for synthesis since it solves the simulation speed issue.
While I have referenced VHDL standards by their revision date, from an IEEE perspective, there is only one VHDL standard, 1076-2019. Hence, if you are using Shared Variables with an ordinary type, you are not VHDL compliant.
Contract VHDL Testbench (OSVVM) Development
Jim Lewis, the founder of SynthWorks, is an innovator and leader in the VHDL community. He has thirty plus years of design, teaching, and problem solving experience. In addition to working as a Principal Trainer for SynthWorks, Mr. Lewis is available for ASIC and FPGA design and verification consulting/contract work. Mr Lewis prefers challenging verification problems that push his expert VHDL skills and allow use of OSVVM.