Feeds:
Posts
Comments

Posts Tagged ‘VHDL’

Free Range VHDL

Free Range VHDL, by Brian Mealy and Fabrizio Tappero, is the latest in a long line of books written about VHDL. What makes this a unique offering is its simplicity as an introductory text, and the fact that it is absolutely free. The book is offered under the Creative Commons Attribution-ShareAlike Unported License, and can be downloaded in PDF and LaTeX format from the Free Range Factory website.

The book is far from a complete reference on VHDL, missing coverage of several important topics. These omissions are actually the basis for its strength. As anyone trying to learn a new language can attest, there is a fine line between introducing basic concepts and overloading the reader with unneccesary information. If you want to learn VHDL, this book may not be the final word, but can most certainly serve as the first.

Read Full Post »

Non-determinism in HDLs

Digital logic can be modeled and simulated using an event-driven simulator in a hardware description language (HDL), such as Verilog or VHDL. When these models are used for synthesis into logic gates, the HDL code is necessarily restricted to a subset of the respective language, as required by the synthesis tool in use. There exist industry-established guidelines for writing synthesizable code, and every synthesis tool vendor can provide a good reference.

Unfortunately, one of the greatest challenges during integration and IP reuse of legacy Verilog code is the potential for non-deterministic behavior during execution. These issues typically arise when there is an execution race condition within the Verilog code. Although a wider discussion of the relative strengths of each language is beyond the scope of this essay, these race conditions are not possible in legacy VHDL code, due to the determinism inherently guaranteed by the language definition.

Unlike strongly-typed languages such as C or VHDL, the Verilog language was not standardized by the IEEE until after it had achieved widespread popularity. As a result, the language started off with a de facto standard as defined by the behavior of the Verilog-XL compiler/simulator (which is currently owned by Cadence). Unfortunately, flaws in this de facto language definition allowed users the latitude to create code that exhibited simulator-dependent behavior during execution.

One such example of non-deterministic behavior can occur when blocking assignments are used without care. In any HDL, there are two types of signal assignments: blocking and non-blocking. A blocking assignment is executed immediately, just as in any sequential programming language such as C. A non-blocking assignment is added to an event-queue and not executed until a subsequent iteration, in keeping with the semantics of the particular event-driven programming language being used. This behavior gets further complicated when time delays are added.

The following snippet of Verilog code (from Clifford Cummings, Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!, SNUG-2000) illustrates an example of simulator-dependent behavior:

module fbosc1 (y1, y2, clk, rst);
    output y1, y2;
    input clk, rst;
    reg y1, y2;
    always @(posedge clk or posedge rst)
        if (rst)  y1 = 0; // reset
        else      y1 = y2;
    always @(posedge clk or posedge rst)
        if (rst)  y2 = 1; // preset
        else      y2 = y1;
endmodule // fbosc1

The Verilog standard does not require the two “always” blocks to be executed by the simulator in any particular order, and as a result, the final values of the variables y1 and y2 become dependent on the order in which the simulator decides to execute the blocks. Fortunately, this problem can be entirely avoided by using non-blocking statements instead, as shown below.

module fbosc2 (y1, y2, clk, rst);
    output y1, y2;
    input clk, rst;
    reg y1, y2;
    always @(posedge clk or posedge rst)
        if (rst)  y1 <= 0; // reset
        else      y1 <= y2;
    always @(posedge clk or posedge rst)
        if (rst)  y2 <= 1; // preset
        else      y2 <= y1;
endmodule // fbosc2

In fact, there are many published guidelines on good Verilog design practices, and these are largely followed by the design community nowadays. However, it is a significant shortcoming of a language when “best practices” are required to ensure deterministic behavior of the executed code.

This kind of behavior is avoided when using VHDL, which purposefully limits the scope of variables which can accept blocking assignments. Where Verilog uses the reg and wire transport types, VHDL uses only signal. According to the VHDL language definition, a signal can only accept non-blocking assignments, which in turn are handled via the event queue. However, blocking assignments can be made to the variable type, but by definition the scope of a variable is restricted to within a process block (a process block in VHDL is equivalent to an always block in Verilog). If it is desired that the value of a computed variable be made visible outside a process block, the value of the variable needs to first be assigned to a signal (whose scope by definition extends to the hierarchy above). As a result, the language definition preserves determinism by preventing the occurrence of race conditions created by careless use of blocking assignments.

As mentioned above, this post is not meant for passing judgement on the suitability of using either VHDL or Verilog for a particular purpose. Rather, it is intended as a warning and encouragement to heed established guidelines for achieving the best results. Nowadays, robust design methodologies require certain Verilog compiler flags and Lint options to be enabled in order to allow detection of such cases so they may be corrected before simulation. However, older code bases of unknown origin and questionable quality can still pose a potential source of problems and project delays. Seasoned industry veterans advocate the use of extensive simulation verification test suites in order to provide confidence, but a “deterministic by compilation” approach will always be preferred.

Copyright © 2008 – 2012 Waqas Akram. All Rights Reserved.

Read Full Post »

Before embarking on a new design, it is important to plan ahead and create a strategy for verifying the correct operation of that design. In the case of designing digital circuitry in a hardware description language (HDL), it is best to begin with the testbench before creating the actual design itself. In this way, as the design evolves, the testbench can be continuously modified to actively verify the incremental changes as they occur.

When designing a testbench, file input/output can be an extremely useful feature in order to read-in stimulus data from a file, and write-out results to another file. This allows one to create stimulus and verify correctness of results outside the actual language domain used for designing the block. For example, when designing a DSP system, it is common to use a high-level modeling tool such as Matlab to create and model the system. When a particular portion of the datapath is implemented in RTL, the stimulus at the input and the expected data at the output can all be written out directly from Matlab. These files can then be fed to the event-driven HDL simulation environment of the block and testbench in order to ensure direct equivalence between the two embodiments of the design.

For example, the following Matlab commands can write out the elements of a vector Vstim as single integers on each line in the text file named “teststim.txt”:

% create the vector of integers
Vstim = [5, -3, 19, 7, -13, 10];
% open the file in 'write' mode
FID = fopen('teststim.txt','w');
% write the vector to the file, one element per line
fprintf(FID,'%d\n',Vstim)
% close the file
fclose(FID)

The file “teststim.txt” should now contain the following:

5
-3
19
7
-13
10

Reading and writing files in VHDL is a fairly straight-forward process, but some details need to be settled before proceeding. There are multiple approaches to the read and write flow in a testbench: all-at-once, just-in-time, or some mixture of both. This decision can have an effect on simulation time and size, and also depends on the amount of data involved at each end.

For example, one approach might be to read-in the entire stimulus file before presenting each element to the device under test (DUT) on a clock-cycle to clock-cycle basis. Another approach might be to read-in only what is required for each cycle and waiting until the next element is actually required. Similarly at the output side, data can either be written out as it is produced by the DUT, or it can be saved until the simulation is complete, and then written out all at once. The following is a description of an example VHDL testbench which performs file I/O. It is missing a DUT, but it illustrates file I/O in VHDL.

The first thing to be declared is the testbench entity. Since the testbench is the top-level module, the entity declaration is empty. This is followed by library declarations and the definition of the testbench architecture. The internal constants and signals are described within the code comments:

-- entity declaration for testbench named "fileio"
entity fileio is
end fileio;

-- library declarations
library STD, IEEE;
-- needed for the write and read calls
use STD.textio.all;
-- needed for std_logic data types
use IEEE.std_logic_1164.all;
-- needed for calls to WRITE(LINE, std_logic)
use IEEE.std_logic_textio.all;
-- needed for calls to CONV_STD_LOGIC_VECTOR
use IEEE.std_logic_arith.all;

-- definition of architecture "BEH" for testbench "fileio"
architecture BEH of fileio is

    constant CLK_PERIOD : time := 5 ns; -- period of system clock
    constant DATA_WIDTH : integer := 6; -- width of data vector

    -- definitions of input and output file names and access types
    file INPUT_FILE     : TEXT open READ_MODE is "teststim.txt"; 
    file OUTPUT_FILE    : TEXT open WRITE_MODE is "testout.txt";
    
    signal clk_sys      : std_logic := '0'; -- system clock
    signal eof          : std_logic := '0'; -- flag end of file
    signal read_en      : std_logic := '0'; -- enable reading
    signal write_en     : std_logic := '0'; -- enable writing

    -- data vector for representing stimulus integers
    signal current_data_vec : std_logic_vector(DATA_WIDTH-1 downto 0) 
        := (others=>'0');

begin -- begin body of architecture BEH

      -- [ THIS IS WHERE ALL PROCESSES GO, AS DEFINED BELOW ]

end BEH; -- end body of architecture BEH

The system clock is defined in order to provide a cyclic reference point for all operations, including reading and writing data. There is a constant declaration for the clock period (arbitrarily defined as 5 ns in this example). The following VHDL line defines the system clock with a perfect 50% duty-cycle:

    -- create the system CLOCK
    clk_sys <= not clk_sys after CLK_PERIOD/2;

The main control process is where the testbench is controlled, and in this example, the process lacks a sensitivity list. A process without a sensitivity list simply executes code sequentially and repeats indefinitely (until the simulation is terminated). In the case of this example, the simulation is terminated using a “fake” assertion failure, in order to allow the testbench to work on arbitrarily-long stimulus files.

    -- MAIN CONTROL process
    main_control:
    process
    begin
        -- wait until the next rising clock edge
        wait until clk_sys'event and clk_sys='1';
        read_en <= '1'; -- enable reading
        -- delay writing to the output file by one clock cycle
        wait for CLK_PERIOD;
        write_en <= '1'; -- enable writing
        -- wait for the eof flag to go high
        wait until (eof='1');
        wait for CLK_PERIOD;
        -- close both files
        file_close(INPUT_FILE);
        file_close(OUTPUT_FILE);
        -- terminate the simulation
        assert false
            report "INFO: this is used to terminate the simulation"
            severity failure;
    end process main_control;

The read process is sensitive only to the system clock, responds on every rising edge of the system clock and continues to read while reading is enabled. This process keep reading until end-of-file is reached. Reading is enabled through the “read_en” signal, and end-of-file is indicated through the “eof” signal. Data is converted to a vector so the DUT can process the digital representation of the data.

    -- READ FILE process
    read_from_file:
    process (clk_sys) is
        variable tmp_line : line;
        variable tmp_int : integer;
    begin
        if (clk_sys'event and clk_sys='1' and read_en='1' 
	    and eof='0') then
            if endfile(input_file) then
                -- place the status string into a line
                write(tmp_line, string'("INFO: reached end of file"));
                -- write the line to the output
                writeline(output, tmp_line);
                -- update the flag
                eof <= '1';
            else
                -- read the next line in the file
                readline(INPUT_FILE, tmp_line);
                -- parse the line and convert data to integer
                read(tmp_line, tmp_int);
                -- convert the integer to vector and assign to a signal
                current_data_vec <= 
                    CONV_STD_LOGIC_VECTOR(tmp_int,DATA_WIDTH);
                -- maintain the flag
                eof <= '0';
            end if;
        end if;
    end process read_from_file;

The write process is sensitive only to the system clock, responds on every rising edge of the system clock and continues writing until one clock cycle after the end-of-file is reached in the stimulus file. This single-cycle delay at the end is specified in the main control process, and can be adjusted according to the desired latency through the DUT.

    -- WRITE FILE process
    write_to_file:
    process (clk_sys) is
        variable tmp_line : line;
    begin
        if (clk_sys'event and clk_sys='1' and write_en='1' 
	    and eof='0') then
            write(tmp_line, current_data_vec);
            writeline(OUTPUT_FILE, tmp_line);
        end if;
    end process write_to_file;

The above testbench can be easily be compiled and simulated using GHDL (as outlined in this post) using the following shell script:

# Compile the vhdl file
wine ghdl.exe -a --ieee=synopsys fileio.vhd
# Elaborate the testbench
wine ghdl.exe -e --ieee=synopsys fileio
# Simulate the testbench
wine ghdl.exe -r --ieee=synopsys fileio

When the simulation is run, the shell output should look something like this:

INFO: reached end of file
fileio.vhd:52:9:@42500ps:(assertion failure): INFO: this is used to terminate the simulation
C:\Program Files\Ghdl\Bin\ghdl.exe:error: assertion failed
C:\Program Files\Ghdl\Bin\ghdl.exe:error: simulation failed

The output file “testout.txt” should contain the following lines of signed integer converted to a two’s complement representation of the contents of “teststim.txt”.

000101
111101
010011
000111
110011
001010

In this example the digital data is being written directly to the output file in order to illustrate file I/O. In reality, it would be more convenient to convert the output of the DUT back to a signed integer before writing to the output file, thereby allowing Matlab to easily process and verify the output.

This type of testbench is just the first step to block-level design. The next step would be to actually design and instantiate the block (or DUT) into the testbench, taking care of any interface requirements. Throughout the design process, the testbench will have to keep up with the signalling needs of the block to be tested, as the needs arise.

Copyright © 2010-2012 Waqas Akram. All Rights Reserved.

Read Full Post »

Installing GHDL on a Mac

GHDL is a popular open-source compiler and simulator for VHDL. Precompiled binaries are available for Windows, Gnu/Linux and MacOS-X (PowerPC), but unfortunately, not for MacOS-X (Intel). In order to run GHDL on an Intel-based Macbook Pro, compiling GHDL from source is one option, and various packages (including ADA) need to first be installed. Another option is to use the WINE software layer to run an existing GHDL binary compiled for Windows or Gnu/Linux.

WINE is available for installation through the MacPorts project. Installation instructions for MacPorts can be found here, if needed. Before installing anything through MacPorts, it’s a good idea to update the dependencies and the MacPorts installation by using the ‘selfupdate’ option. WINE can then be installed using the ‘install’ option:

# update the MacPorts installation
sudo port selfupdate
# install software using the official project name
sudo port install wine

Periodic maintenance and updates through MacPorts can be achieved by running the ‘upgrade’ option, and the following command updates all software installed through MacPorts, if anything is out-of-date.

# upgrade any outdated installations
sudo port upgrade outdated

The next step is to download and install the precompiled binary from here. Here’s the direct download link to GHDL-0.26-2008Apr07.dmg. Once downloaded, double-clicking the file will mount the disk image ‘GHDL-0.26’. Navigate down to the sub-folder called ‘wine’, in which two items will be found: a sub-folder called ‘GHDL’ and a bundle called ‘Wine.bundle’. Copy the ‘GHDL’ folder to your home-directory and rename it to ‘.wine’ (make sure a .wine folder doesn’t already exist in your home directory). This places the executable ‘ghdl.exe’ in the WINE search path.

# change directory to the sub-folder
cd /Volumes/GHDL-0.26/wine/.
# copy and rename the executable folder
cp GHDL ~/.wine

As an example, if your top-level design is called ‘mydesign’ and the VHDL source code is placed in the file ‘mycode.vhd’, the following sequence of commands will compile and run your code. The compilation command can be run as many times as necessary to compile all source files. The elaboration command is for linking/loading and creating the final binary, which is executed when the simulation is run.

# compile the design
wine ghdl.exe -a mycode.vhd
# elaborate the design
wine ghdl.exe -e mydesign
# run the simulation
wine ghdl.exe -r mydesign

For more details on other GHDL options, use the ‘–help’ argument. Useful options include ‘-work’ for specifying the work library, ‘–ieee=synopsys’ for specifying the use of a particular version of the IEEE standard VHDL libraries, and ‘–vcd=FILENAME’ for specifying the name of the VCD waveform dump file.

# compile the design
wine ghdl.exe --help

Read Full Post »