Chapter
1
Introduction
- Create complex Verilog test benches easily and wholly in Ruby.
- Apply agile software development practices to develop hardware.
- Perform specification-driven functional verification (PDF version).
Ruby-VPI is open source software (see Section 1.2: License) so feel free to contribute your improvements and discuss your ideas in the project mailing list. See Chapter 5: Hacking for details.
1.1 Features
Portable
- Works on Windows, Mac OSX, GNU/Linux, and UNIX.
- Supports all major Verilog simulators available today.
Agile
- Enables agile practices such as
- test-driven development
- behavior-driven development
- rapid prototyping for design exploration
- Eliminates unneccesary work:
- Specifications are executable, portable, and human-readable.
- Automated test generator helps you accomodate design changes with minimal effort.
- There is absolutely no compiling!
Powerful
- Employs the power and elegance of Ruby:
- Unlimited length integers
- Automatic memory management (garbage collection)
- Regular expressions
- Closures for functional programming
- Pure OOP with dynamic typing
- Portable multi-threading (green threads)
- Native system calls and I/O
- Highly readable and maintainable code
- Uses ruby-debug for interactive debugging.
- Uses rcov for test coverage analysis and report generation.
1.2 License
Copyright 2006 Suraj N. Kurapati <SNK at GNA dot ORG>
Copyright 1999 Kazuhiro HIWADA <HIWADA at KUEE dot KYOTO-U dot AC dot JP>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- All copies and substantial portions of the Software (the "Derivatives") and their corresponding machine-readable source code (the "Code") must include the above copyright notice and this permission notice.
- Upon distribution, the Derivatives must be accompanied by either the Code or—provided that the Code is obtainable for no more than the cost of distribution plus a nominal fee—information on how to obtain the Code.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1.3 Appetizers
- Assign the value 22048 to a register:
# NOTE: these are all equivalent your_register.intVal = 2 ** 2048 your_register.put_value 2 ** 2048
- Check if all nets in a module are at high impedance:
your_module.all_net? { |your_net| your_net.z? } # or, alternatively: your_nets = your_module.net_a your_nets.all? { |net| net.z? }
- See a register’s path, width, and location (file & line number):
# NOTE: these are all equivalent puts your_register p :path => your_register.fullName p :width => your_register.size p :file => your_register.fileName p :line => your_register.lineNo
- Access the first five elements in a memory:
# NOTE: these are all equivalent your_memory.memoryWord_a.first(5) your_memory.memoryWord_a[0..4] your_memory.memoryWord_a[0...5] your_memory.memoryWord_a[0, 5]
- Clear a memory by filling it with zeroes:
# NOTE: these are all equivalent your_memory.each_memoryWord { |w| w.f! } your_memory.each_memoryWord { |w| w.vpi0! } your_memory.each_memoryWord { |w| w.intVal = 0 } your_memory.each_memoryWord { |w| w.put_value 0 }
1.4 Applications
- From the second edition of The Verilog PLI Handbook:
- C language bus-functional models
- Reading test vector files
- Delay calculation
- Custom output displays
- Co-simulation
- Design debug utilities
- Simulation analysis
- Adapted from Pin Hong’s observations:
- Writing hardware models in Ruby
- Dumping or processing netlist data from Verilog database
- Dumping or processing simulation data
- Feeding dynamic simulation stimuli
- Back-annotating delay information
- Interactive logic simulation
- Building a distributed simulation
1.5 Related works
- ANVIL is a C++ interface to VPI.
- Teal is a C++ interface to VPI.
- JOVE is a Java interface to VPI.
- ScriptEDA is a Perl, Python, and Tcl interface to VPI.
- RHDL is a hardware description and verification language based on Ruby.
- MyHDL is a hardware description and verification language based on Python, which features conversion to Verilog and co-simulation.
Ye olde PLI
- ScriptSim is a Perl, Python, and Tcl/Tk interface to PLI.
- Verilog::Pli is a Perl interface to PLI.
Chapter
2
Setup
2.1 Requirements
Your system needs the following software to run Ruby-VPI.
Verilog simulator
Simulator | Notes |
---|---|
Synopsys VCS | Any version that supports the -load option is acceptable. |
Mentor Modelsim | Any version that supports the -pli option is acceptable. |
Cadence NC-Sim | Any version that supports the +loadvpi option is acceptable for Ruby-VPI versions 20.0.0 or older. Version 21.0.0 of Ruby-VPI does not work with Cadence NC-Sim. |
GPL Cver | Version 2.11a or newer is acceptable. |
Icarus Verilog | Version 0.8 is mostly acceptable because you will not be able to access child handles through method calls. The reason for this limitation is explained in Section 6.1.1: Give full paths to Verilog objects. |
Compilers
Software | Notes |
---|---|
make | Any flavor or distribution should be acceptable. |
C compiler | the GNU Compiler Collection is preferred, but any C compiler should be acceptable. |
SWIG | Version 1.3.29 or newer is required. |
Ruby | Version 1.8 or newer, including header and linkable object files for building extensions, is required. You can install Ruby by following these instructions. |
Libraries
Library | Notes |
---|---|
RubyGems | Any recent version should be acceptable. You can install RubyGems by following these instructions. |
2.2 Recommendations
The following software might make your interactions with Ruby-VPI more pleasant.
Text merging tool
Software | Description |
---|---|
kdiff3 | A graphical, three-way merging tool for KDE. |
meld | A graphical, three-way merging tool for GNOME. |
tkdiff | A graphical, two-way merging tool that uses the cross-platform Tk windowing toolkit. |
xxdiff | A graphical, three-way merging tool. |
imediff2 | A textual, fullscreen two-way merging tool. It is very useful when you are working remotely via SSH. |
2.3 Installation
Tip 1. Tuning for maximum performance
CFLAGS
environment variable before you run the installation command (shown below). For example, if your C compiler is GCC, then you can set CFLAGS
to -O9 for maximum optimization.gem install ruby-vpi ruby-vpi -vAlternatively, if you do not wish to use RubyGems:
- Download the newest tar.gz release package from the project download area.
- Extract the release package anywhere you want on your system.
- Go inside the extracted directory and run the following commands:
rake build ruby bin/ruby-vpi -v
If the installation was successful, then you will see output like this:
ruby-vpi 21.0.0 (2008-06-08) http://ruby-vpi.rubyforge.org /home/sun/src/ruby-vpi
Otherwise, you can ask for help in the project mailing list.
2.3.1 Installing on Windows
After Ruby-VPI is compiled, it is linked to symbols whose names begin with _vpi. In GNU/Linux and similar operating systems, these symbols are allowed to be undefined. However, this is not the case in Windows because we cannot compile a shared object file with references to undefined symbols in Windows.
One solution to this problem is to supply the Verilog simulator’s VPI object file, which contains definitions of all VPI symbols, to the linker. The following steps illustrate this process.
- Install Cygwin, the Linux-like environment for Windows.
- Search for object files whose names end with .so, .o, or .dll in your Verilog simulator’s installation directory.
- Determine which object files, among those found in the previous step, contain symbols whose names begin with “_vpi” by running the following command in Cygwin:
for x in *.{o,so,dll}; do nm $x | grep -q '[Tt] _vpi' && echo $x; done
- If you are using Mentor Modelsim, the desired object file can be found at a path similar to C:\Modeltech\win32\libvsim.dll.
- If you are using GPL Cver, the desired object file can be found at a path similar to C:\gplcver\objs\v_vpi.o.
- Assign the path of the object file (determined in the previous step) to the
LDFLAGS
environment variable. For example, if the object file’s path is /foo/bar/vpi.so, then you would run the following command in Cygwin:export LDFLAGS=/foo/bar/vpi.so
- You may now install Ruby-VPI by running the following command in Cygwin:
gem install ruby-vpi
2.4 Maintenance
You can upgrade to the latest release of Ruby-VPI by running the following command:
gem update ruby-vpi
You can uninstall Ruby-VPI by running the following command:
gem uninstall ruby-vpi
Learn more about using RubyGems in the RubyGems user guide.
2.5 Manifest
- If you installed Ruby-VPI manually, then you already know the location of its installation directory.
- If you installed Ruby-VPI using RubyGems, then run
ruby-vpi -v
and select the right-most item in the output—that is the path of Ruby-VPI’s installation directory.
- bin/ – contains executable programs (see Section 4.8: Tools).
- doc/ – contains user documentation in various formats.
- ref/ – contains reference API documentation in HTML format.
- examples/ – contains example Ruby-VPI tests (see Section 4.9.8: More examples).
- ext/ – contains source code, written in the C language, for the core of Ruby-VPI.
- lib/ – contains Ruby libraries provided by Ruby-VPI.
Chapter
3
Background
Ruby-VPI is a bridge between IEEE 1364-2005 Verilog VPI and the Ruby language. It enables Ruby programs to use VPI either (1) in the same, verbose way that C programs do, or (2) in a simpler, higher level way.
Figure 1. Where does Ruby-VPI fit in?
3.1 Motivation
3.2 Tests
In Ruby-VPI, the process of functional verification is neatly packaged into self-contained, executable tests. As Figure 2: Organization of a test in Ruby-VPI illustrates, a test is composed of a bench, a design, and a specification.
Figure 2. Organization of a test in Ruby-VPI
The design is an instantiated Verilog module. To extend the analogy of the electronics laboratory, it corresponds to the electronic component that is verified by an engineer.
The specification is a Ruby program. In the electronics laboratory analogy, it corresponds to the engineer who inspects, manipulates, and verifies the electronic component. In terms of specification-driven functional verification, it corresponds to the executable specification.
Chapter
4
Usage
4.1 Theory of operation
When you run a Ruby-VPI test, the following chain reaction occurs.
Rake (via the Rakefile)
- Specifies a user-defined Ruby script that will be loaded by Ruby-VPI once the simulator has been launched.
- Determines where all necessary Verilog modules & Verilog libraries are and launches the Verilog simulator with this knowledge.
Verilog simulator
- Launches the Ruby interpreter and loads Ruby-VPI into it.
Ruby-VPI
- Establishes the basic verification environment (the nice library for working with VPI handles, and so on).
- Loads the user-defined Ruby script that was specified by Rake.
User-defined Ruby script
- Determines what Verilog modules are going to be tested.
- Determines what Ruby files are going to do the testing.
- Associates them together and starts the simulation.
For example, consider this user-defined Ruby script:
designHandle = vpi_handle_by_name("whatever", nil) testFiles = Dir['path/to/files/*.whatever'] RubyVPI.load_test( designHandle, testFiles )Here, the
RubyVPI.load_test
method will:
- Create a temporary module (a sandbox).
- Define a constant named
DUT
inside the sandbox. - Load all the test files into the sandbox.
In this manner, we can (1) load as many tests into the simulation as we wish and (2) use any files we want (and organize them however we like) to perform the testing.
4.1.1 Test files
Ruby-VPI tests (when produced by the automated test generator) are typically composed of the following files.
- runner.rake
- Launches the Verilog simulator, loads Ruby-VPI into it, and executes the user-defined Ruby script.
- design.rb
- Defines convenience methods on the
DUT
object. - May define other data structures to aid the testing.
- Defines convenience methods on the
- proto.rb
- Uses the concurrency model to emulate the design under test’s Verilog implementation.
- spec.rb
- Does some tests on the
DUT
object. - If you want to test multiple Verilog objects, then simply
use the
vpi_handle_by_name()
function to get access to the Verilog objects you want and proceed as usual.
- Does some tests on the
- loader.rb
- The user-defined Ruby script which loads the test.
Note that because the user-defined Ruby script (loader.rb among the files above) decides what files to load, you are free to organize your tests however you like. As a result, the files presented above are a mere convention that you can choose to follow rather than being forced to follow.
4.2 Interacting with the Verilog simulator
In a typical VPI application written in C, the Verilog simulator is in charge. Verilog code temporarily transfers control to C by invoking C functions, which return control to Verilog when they finish.
In contrast, Ruby-VPI puts the specification in charge. The specification temporarily transfers control to the Verilog simulator by invoking the advance_time
method, which returns control to the specification after a given number of time steps. This process is illustrated in Figure 3: Interaction between Ruby and Verilog. You can also use the wait
method, which is just an alias to the advance_time
method, if you prefer.
Ruby-VPI’s approach is the same as any software testing framework, where the specification drives the design under test. In contrast, the VPI & C approach is literally backwards because the design under test drives the specification.
Figure 3. Interaction between Ruby and Verilog
- The current simulation time is X.
- The specification invokes the
advance_time
method with parameter Y, which specifies the number of simulation time steps to be simulated. - The Verilog simulator is now in control (temporarily).
- The current simulation time has not changed; it is still X.
- The Verilog simulator simulates Y simulation time steps.
- The current simulation time is now X + Y.
- The Verilog simulator returns control back to the specification.
4.3 VPI in Ruby
Ruby-VPI provides the entire IEEE Std 1364-2005 VPI interface to Ruby. This section will show you how to make use of it.
Note 1. Constants are capitalized in Ruby
In the remainder of this guide, you may be surprised to see that VPI constants such as vpiIntVal
are written with a captialized name, as VpiIntVal
. The reason for this discrepancy is that in Ruby, the names of constants are capitalized.
However, keep in mind that Ruby-VPI provides all VPI constants in both (1) their original, uncapitalized form and (2) their capitalized Ruby form. You may use either version according to your preference; they are functionally equivalent.
4.3.1 Handles
A handle is a reference to an object (such as a module, register, wire, and so on) inside the Verilog simulation. Handles allows you to inspect and manipulate the design under test and its internal components. They are instances of the VPI::Handle
class (see reference documentation for details) in Ruby-VPI.
Handles have various properties, listed in the second column of Table 1: Possible accessors and their implications, which provide different kinds of information about the underlying Verilog objects they represent. These properties are accessed through the VPI functions listed in the last column of Table 1: Possible accessors and their implications.
Handles are typically obtained through the vpi_handle_by_name
and vpi_handle
functions. These functions are hierarchical in nature, as they allow you to obtain new handles that are related to existing ones. For example, to obtain a handle to a register contained within a module, one would typically write: your_reg = vpi_handle( VpiReg, your_handle )
Shortcuts for productivity
handle.get_value
and handle.put_value
methods.4.3.1.1 Accessing a handle’s relatives
Imagine that the design under test, say foo, instantiated a Verilog module named bar, which in turn contained a register named baz. To access baz from Ruby, one could employ VPI idioms by writing:
foo = vpi_handle_by_name( "foo", nil ) bar = vpi_handle_by_name( "bar", foo ) baz = vpi_handle_by_name( "baz", bar )
or by writing:
baz = vpi_handle_by_name( "foo.bar.bar", nil )
These idioms seem excessively verbose in a higher level language such as Ruby, so Ruby-VPI allows you to access a handle’s relative by simply invoking the relative’s name as a method on the handle:
foo.bar.baz
4.3.1.2 Accessing a handle’s properties
Imagine that the design under test, say foo, contained a register named bar. To access the integer value of bar in Ruby-VPI, one could employ VPI idioms by writing:
wrapper = S_vpi_value.new wrapper.format = VpiIntVal vpi_get_value( foo.bar, wrapper ) result = wrapper.value.integer
or, if bar is capable of storing more than 32 bits, one would convert a string representation of bar’s integer value into a limitless Ruby integer by writing:
wrapper = S_vpi_value.new wrapper.format = VpiHexStrVal vpi_get_value( foo.bar, wrapper ) result = wrapper.value.str.to_i( 16 )
These idioms seem excessively verbose in a higher level language such as Ruby, so Ruby-VPI allows you to access a handle’s properties by simply invoking property names, using the special naming format shown in Figure 4: Method naming format for accessing a handle’s properties, as methods on the handle:
result = foo.bar.intVal
Figure 4. Method naming format for accessing a handle’s properties
Operation | _ | Property | _ | Accessor | Addendum |
---|---|---|---|---|---|
optional | required | optional |
- Operation suggests a method that should be invoked in the context of the Property parameter. All methods in Ruby’s
Enumerable
module are valid operations.
- Property suggests a VPI property that should be accessed. The “vpi” prefix, which is common to all VPI properties, can be omitted if you wish. For example, the VPI property “vpiFullName” is considered equivalent to “fullName” and “FullName”, but not equivalent to “full_name”.
- Accessor suggests a VPI function that should be used in order to access the VPI property. When this parameter is not specified, Ruby-VPI will attempt to guess the value of this parameter.
Table 1: Possible accessors and their implications shows a list of valid accessors and how they influence the means by which a property is accessed.
- When Addendum is an equal sign (=), it suggests that the specified VPI property should be written to.
When it is a question mark (?), it suggests that the specified VPI property should be accessed as a boolean value. This suggestion is the same as specifying “b” as the Accessor.
Table 1. Possible accessors and their implications
Accessor | Kind of value accessed | VPI functions used to access the value |
---|---|---|
d | delay | vpi_get_delays , vpi_put_delays |
l | logic | vpi_get_value , vpi_put_value |
i | integer | vpi_get |
b | boolean | vpi_get |
s | string | vpi_get_str |
h | handle | vpi_handle |
a | array | vpi_iterate |
Table 2. Examples of accessing a handle’s properties
Ruby expression | Method naming format | Description | |||||
---|---|---|---|---|---|---|---|
Operation | _ | Property | _ | Accessor | Addendum | ||
handle.vpiIntVal |
vpiIntVal | Obtain the logic value of the handle’s VpiIntVal property. |
|||||
handle.vpiIntVal_l |
vpiIntVal | _ | l | ||||
handle.intVal |
intVal | ||||||
handle.intVal_l |
intVal | _ | l | ||||
handle.vpiIntVal = 15 |
vpiIntVal | = | Assign the integer 15 to the logic value of the handle’s VpiIntVal property. |
||||
handle.vpiIntVal_l = 15 |
vpiIntVal | _ | l | = | |||
handle.intVal = 15 |
intVal | = | |||||
handle.intVal_l = 15 |
intVal | _ | l | = | |||
handle.vpiType |
vpiType | Obtain the integer value of the handle’s VpiType property. |
|||||
handle.vpiType_i |
vpiType | _ | i | ||||
handle.type |
type | ||||||
handle.type_i |
type | _ | i | ||||
handle.vpiProtected |
vpiProtected | Obtain the boolean value of the handle’s VpiProtected property. |
|||||
handle.vpiProtected_b |
vpiProtected | _ | b | ||||
handle.vpiProtected? |
vpiProtected | ? | |||||
handle.protected |
protected | ||||||
handle.protected_b |
protected | _ | b | ||||
handle.protected? |
protected | ? | |||||
handle.vpiFullName |
vpiFullName | Obtain the string value of the handle’s VpiFullName property. |
|||||
handle.vpiFullName_s |
vpiFullName | _ | s | ||||
handle.fullName |
fullName | ||||||
handle.fullName_s |
fullName | _ | s | ||||
handle.vpiParent |
vpiParent | Obtain the handle value of the handle’s VpiParent property. |
|||||
handle.vpiParent_h |
vpiParent | _ | h | ||||
handle.parent |
parent | ||||||
handle.parent_h |
parent | _ | h | ||||
handle.each_vpiNet {|net| puts net.fullName} |
each | _ | vpiNet | Use the each operation to print the full name of each VpiNet object associated with the handle. |
|||
handle.each_net {|net| puts net.fullName} |
each | _ | net | ||||
handle.all_vpiReg? {|reg| reg.size == 1} |
all? | _ | vpiReg | Use the all? operation to check whether all VpiReg objects associated with the handle are capable of storing only one bit of information. |
|||
handle.all_reg? {|reg| reg.size == 1} |
all? | _ | reg | ||||
handle.select_vpiNet {|net| net.x?} |
select | _ | VpiNet | Use the select operation to obtain a list of VpiNet objects whose logic value is unknown (x). |
|||
handle.select_net {|net| net.x?} |
select | _ | net |
4.3.2 Callbacks
A callback is a mechanism that makes the Verilog simuluator execute a block of code (known as a “callback handler”) when some prescribed event occurs in the simulation.
Callbacks are added using the vpi_register_cb
function and removed using the vpi_remove_cb
function. However, instead of storing the address of a C function in the cb_rtn
field of the s_cb_data
structure (as you would do in C) you pass a block of code to the vpi_register_cb
method in Ruby. This block will be executed whenever the callback occurs.
Example 1. Using a callback for value change notification
This example shows how to use a callback for notification of changes in a handle’s VpiIntVal
property. When you no longer need this callback, you can tear it down using vpi_remove_cb
.
In this example, the handle being monitored is the Counter.count
signal from Example 5: Declaration of a simple up-counter with synchronous reset.
time = S_vpi_time.new time.type = VpiSimTime time.low = 0 time.high = 0 value = S_vpi_value.new value.format = VpiIntVal alarm = S_cb_data.new alarm.reason = CbValueChange alarm.obj = Counter.count alarm.time = time alarm.value = value alarm.index = 0 vpi_register_cb( alarm ) do |info| time = info.time.integer count = info.value.value.integer puts "hello from callback! time=#{time} count=#{count}" end
Append this code to the RSpec/counter_spec.rb file (provided in Section 4.9.8: More examples and discussed in Section 4.9.3: Specify your expectations) and run the counter_RSpec test
4.4 Concurrency
Ruby-VPI provides a concurrency model that allows you to run blocks of code in parallel. These blocks of code are known as concurrent processes and they are equivalent to the “initial”, “always” and “forever” blocks in Verilog.
Ruby-VPI’s concurrency model imposes two important constraints, which are inspired by GPGPU and fragment/vertex shader programming, in order to avoid race conditions and to make parallel programming simpler.
First, all processes execute in the same time step. That is, we only advance the entire simulation to the next time step when all processes are finished with the current time step. In this manner, we avoid race conditions where a process advances the entire simulation to a future time step but the other processes still think they are executing in the original time step (because they were not notified of the advancement).
Second, all processes see the same input (the state of the simulation database at the start of the current time step) while executing in a time step. That is, when a process modifies the simulation database, say, by changing the logic value of a register, the modification only takes effect at the end of the current time step. In this manner, we avoid race conditions where one process modifies the simulation midflight but some/all of other processes are unaware of that modification (because they were not notified of its occurence).
Note that these constraints are automatically enforced “under the hood”, so to speak, by Ruby-VPI. As a user, you need not do anything extra to implement or support these constraints; they are already taken care of.
Caution 1. Assignments inside processes are non-blocking
4.4.1 Creating a concurrent process
You can create a concurrent proceess by passing a block of code to the process
method. Once the process finishes executing this block of code, it will disappear automatically. This behavior mimics the “initial” blocks of the Verilog language.
You can create concurrent processes that loop forever by passing a block of code to the always
and forever
methods, which mimic the “always” and “forever” blocks, respectively, of the Verilog language.
Example 2. An edge-triggered “always” block
Suppose you have the following Verilog code:
always @(posedge clock1 and negedge clock2) begin
foo <= foo + 1;
bar = bar + 5;
end
In Ruby-VPI, this code would be written as:
always do wait until clock.posedge? and clock2.negedge? foo.intVal += 1 bar.intVal += 5 # this is a NON-blocking assignment! end
Example 3. A change-triggered (combinational) “always” block
Suppose you have the following Verilog code:
always @(apple, banana, cherry, date) begin
$display("Yum! Fruits are good for health!");
end
In Ruby-VPI, this code would be written as:
always do wait until apple.change? or banana.change? or cherry.change? or date.change? puts "Yum! Fruits are good for health!" end
Or, if you are lazy like I am, you can express the sensitivity list programatically:
always do wait until [apple, banana, cherry, date].any? {|x| x.change?} puts "Yum! Fruits are good for health!" end
4.5 Prototyping
Ruby-VPI enables you to rapidly prototype your designs in Ruby without having to do full-scale implementations in Verilog. This lets you explore and evaluate different design choices quickly.
The prototyping process is completely transparent: there is absolutely no difference, in the eyes of your executable specification, between a real Verilog design or its Ruby prototype. In addition, the prototyping process is completely standard-based: Ruby prototypes emulate the behavior of real Verilog designs using nothing more than the VPI itself.
For example, compare the Verilog design shown in Example 13: Implementation of a simple up-counter with synchronous reset with its Ruby prototype shown in figure Example 10: Ruby prototype of our Verilog design. The prototype uses only VPI to (1) detect changes in its inputs and (2) manipulate its outputs accordingly. In addition, notice how well the prototype’s syntax reflects the intended behavior of the Verilog design. This similarity facilitates rapid translation of a prototype from Ruby into Verilog later in the design process.
4.5.1 Creating a prototype
- Start with a Verilog module declaration for your design.
- Generate a test using that module declaration.
- Implement the prototype in the generated proto.rb file.
- Verify the prototype against its specification.
Once you are satisfied with your prototype, you can proceed to implement your design in Verilog. This process is often a simple translation your Ruby prototype into your Verilog. At the very least, your prototype serves as a reference while you are implementing your Verilog design.
Once your design has been implemented in Verilog, you can use the same specification, which was originally used to verify your prototype, to verify your Verilog design (see Section 4.7: Test runner for details).
4.6 Interactive debugging
The ruby-debug project serves as the interactive debugger for Ruby-VPI.
- Enable the debugger by activating the
DEBUGGER
environment variable (see Section 4.7: Test runner for details). - Put the
debugger
command in your code—anywhere you wish to activate an interactive debugging session. These commands are automatically ignored when the debugger is disabled; so you can safely leave them in your code, if you wish.
4.6.1 Advanced initialization
Debugger.start
method. If you wish to perform more advanced initialization, such as having the debugger accept remote network connections for interfacing with a remote debugging session or perhaps with an IDE (see the ruby-debug documentation for details), then:
- Deactivate the
DEBUG
environment variable. - Put your own code, which initializes the debugger, at the top of your test’s spec.rb file.
4.7 Test runner
A test runner is a file, generated by the automated test generator whose name ends with .rake. It helps you run generated tests—you can think of it as a makefile if you are familiar with C programming in a UNIX environment.
When you invoke a test runner without any arguments, it will show you a list of available tasks:
% rake -f your_test_runner.rake (in /home/sun/src/ruby-vpi) rake clean # Remove any temporary products. rake clobber # Remove any generated file. rake cver # Simulate with GPL Cver. rake default # Show a list of available tasks. rake ivl # Simulate with Icarus Verilog. rake ncsim # Simulate with Cadence NC-Sim. rake setup # User-defined task that is invoked before the simulator runs. rake vcs # Simulate with Synopsys VCS. rake vsim # Simulate with Mentor Modelsim.
4.7.1 Environment variables
Test runners support the following environment variables, which allow you to easily change the behavior of the test runner.
PROTOTYPE
enables the Ruby prototype for the design under test so that the prototype, rather than the real Verilog design, is verified by the specification.COVERAGE
enables code coverage analysis and generation of code coverage reports.PROFILER
enables the ruby-prof Ruby code profiler, which collects statistics on the runtime usage of the source code. This data allows you to identify performance bottlenecks.DEBUGGER
enables the interactive debugger in its post-mortem debugging mode.
To activate these variables, simply assign the number 1 to them. For example, DEBUGGER=1
activates the DEBUGGER
variable.
To deactivate these variables, simply assign a different value to them or unset them in your shell. For example, both DEBUGGER=0
and DEBUGGER=
dectivate the DEBUGGER
variable.
4.7.1.1 Variables as command-line arguments
rake DEBUGGER=1is equivalent to
DEBUGGER=1 export DEBUGGER rakein Bourne shell or
setenv DEBUGGER 1 rakein C shell.
Example 4. Running a test with environment variables
Below, we enable the prototype and code coverage analysis:
rake -f your_test_runner.rake PROTOTYPE=1 COVERAGE=1
Below, we disable the prototype and enable the code coverage analysis. These invocations are equivalent if the PROTOTYPE
environment variable is unset.
rake -f your_test_runner.rake PROTOTYPE=0 COVERAGE=1
rake -f your_test_runner.rake PROTOTYPE= COVERAGE=1
rake -f your_test_runner.rake COVERAGE=1
4.8 Tools
The ruby-vpi command serves as a front-end to the tools provided by Ruby-VPI. You can see its help information (reproduced below) by simply running the command without any arguments.
This is a front-end for tools provided by Ruby-VPI. Usage: ruby-vpi Show this help message ruby-vpi -v Show version information ruby-vpi TOOL --help Show help message for TOOL ruby-vpi TOOL arguments... Run TOOL with some arguments Tools: convert Converts Verilog `defines, constants, ranges, etc. into Ruby syntax. generate Generates test skeletons from Verilog 2001/1995 module declarations. Simulators: cver GPL Cver ivl Icarus Verilog ncsim Cadence NC-Sim vcs Synopsys VCS vsim Mentor Modelsim
4.8.1 Automated test generation
The generate tool generates scaffolding for Ruby-VPI tests from Verilog module declarations (written in either Verilog 2001 or Verilog 95 style).
A Ruby-VPI test is composed of the following files:- runner.rake runs the test by starting a Verilog simulator and loading Ruby-VPI into it.
- spec.rb is the executable specification for the design under test.
- design.rb is an optional file that provides convenience methods for controlling the design under test.
- proto.rb is an optional file that defines a Ruby prototype of the design under test.
- loader.rb is a user-defined Ruby script which loads the test (see Section 4.1: Theory of operation).
- Rakefile is an optional file that recursively executes all runner.rake files found immediately within or beneath the current directory. It lets you simply run
rake ...
instead of having to writerake -f runner.rake ...
every time.
As Example 6: Generating a test with specification in RSpec format shows, the name of each generated file is prefixed with the name of the Verilog module for which the test was generated. This convention helps organize tests within the file system, so that they are readily distinguishable from one another.
Caution 2. Do not rename generated files
You can try this tool by running the
ruby-vpi generate --helpcommand.
Tip 2. Using kdiff3 with the automated test generator.
- Create a file named merge2 with the following content:
#!/bin/sh # args: old file, new file kdiff3 --auto --output "$2" "$@"
- Make the file executable by running the
chmod +x merge2
command. - Place the file somewhere accessible by your
PATH
environment variable. - Assign the value “merge2” to the
MERGER
environment variable using your shell’s export or setenv command.
From now on, kdiff3 will be invoked to help you transfer your changes between generated files. When you are finished transferring changes, simply issue the “save the file” command and quit kdiff3. Or, if you do not want to transfer any changes, simply quit kdiff3 without saving the file.
4.8.2 Verilog to Ruby conversion
The convert tool can be used to convert Verilog header files into Ruby. You can try it by running the
ruby-vpi convert --helpcommand.
By converting Verilog header files into Ruby, your test can utilize the same `define
constants that are used in the Verilog design.
4.9 Tutorial
- Declare a design using Verilog 2001 syntax.
- Generate a test for the design using the automated test generator tool.
- Identify your expectations for the design and implement them in the specification.
- (Optional) Implement the prototype of the design in Ruby.
- (Optional) Verify the prototype against the specification.
- Implement the design in Verilog once the prototype has been verified.
- Verify the design against the specification.
4.9.1 Start with a Verilog design
First, we need a Verilog design to test. In this tutorial, Example 5: Declaration of a simple up-counter with synchronous reset will serve as our design under test. Its interface is composed of the following parts:
Size
defines the number of bits used to represent the counter’s value.clock
causes thecount
register to increment whenever it reaches a positive edge.reset
causes thecount
register to become zero when asserted.count
is a register that contains the counter’s value.
Example 5. Declaration of a simple up-counter with synchronous reset
module counter #(parameter Size = 5) (
input clock,
input reset,
output reg [Size-1 : 0] count
);
endmodule
4.9.2 Generate a test
Now that we have a Verilog design to test, we shall use the generate tool to generate some scaffolding for our test. This tool allows us to implement our specification using RSpec, xUnit, or any other format.
Each format represents a different software development methodology:In this tutorial, you will see how both RSpec and xUnit formats are used. So let us make separate directories for both formats to avoid generated tests from overwriting each other:
mkdir RSpec xUnit cp counter.v RSpec cp counter.v xUnit
Once we have decided how we want to implement our specification, we can proceed to generate a test for our design. This process is illustrated by Example 6: Generating a test with specification in RSpec format and Example 7: Generating a test with specification in xUnit format.
Example 6. Generating a test with specification in RSpec format
$ ruby-vpi generate counter.v --RSpec module counter create counter_runner.rake create counter_design.rb create counter_proto.rb create counter_spec.rb create Rakefile
Example 7. Generating a test with specification in xUnit format
$ ruby-vpi generate counter.v --xUnit module counter create counter_runner.rake create counter_design.rb create counter_proto.rb create counter_spec.rb create Rakefile
4.9.3 Specify your expectations
So far, the test generation tool has created a basic foundation for our test Now we must build upon this foundation by identifying our expectation of the design under test. That is, how do we expect the design to behave under certain conditions?
Here are some reasonable expectations for our simple counter:- A resetted counter’s value should be zero.
- A resetted counter’s value should increment by one count upon each rising clock edge.
- A counter with the maximum value should overflow upon increment.
Now that we have identified a set of expectations for our design, we are ready to implement them in our specification. This process is illustrated by Example 8: Specification implemented in RSpec format and Example 9: Specification implemented in xUnit format.
Example 8. Specification implemented in RSpec format
require 'spec' # lowest upper bound of counter's value LIMIT = 2 ** DUT.Size.intVal # maximum allowed value for a counter MAX = LIMIT - 1 describe "A #{DUT.name} after being reset" do setup do DUT.reset! # reset the counter end it "should be zero" do DUT.count.intVal.should == 0 end it "should increment upon each subsequent posedge" do LIMIT.times do |i| DUT.count.intVal.should == i DUT.cycle! # increment the counter end end end describe "A #{DUT.name} with the maximum value" do setup do DUT.reset! # reset the counter # increment the counter to maximum value MAX.times { DUT.cycle! } DUT.count.intVal.should == MAX end it "should overflow upon increment" do DUT.cycle! # increment the counter DUT.count.intVal.should == 0 end end
Example 9. Specification implemented in xUnit format
require 'test/unit' # lowest upper bound of counter's value LIMIT = 2 ** DUT.Size.intVal # maximum allowed value for a counter MAX = LIMIT - 1 class A_counter_after_being_reset < Test::Unit::TestCase def setup DUT.reset! # reset the counter end def test_should_be_zero assert_equal( 0, DUT.count.intVal ) end def test_should_increment_upon_each_subsequent_posedge LIMIT.times do |i| assert_equal( i, DUT.count.intVal ) DUT.cycle! # increment the counter end end end class A_counter_with_the_maximum_value < Test::Unit::TestCase def setup DUT.reset! # reset the counter # increment the counter to maximum value MAX.times { DUT.cycle! } assert_equal( MAX, DUT.count.intVal ) end def test_should_overflow_upon_increment DUT.cycle! # increment the counter assert_equal( 0, DUT.count.intVal ) end end
- Replace the contents of the file named RSpec/counter_spec.rb with the source code shown in Example 8: Specification implemented in RSpec format.
- Replace the contents of the file named xUnit/counter_spec.rb with the source code shown in Example 9: Specification implemented in xUnit format.
4.9.4 Implement the prototype
Now that we have a specification against which to verify our design let us build a prototype of our design. By doing so, we exercise our specification, experience potential problems that may arise when we later implement our design in Verilog, and gain confidence in our work. The result of this proceess is illustrated by Example 10: Ruby prototype of our Verilog design.
Example 10. Ruby prototype of our Verilog design
if RubyVPI::USE_PROTOTYPE always do wait until DUT.clock.posedge? if DUT.reset.t? DUT.count.intVal = 0 else DUT.count.intVal += 1 end end end
4.9.5 Verify the prototype
Now that we have implemented our prototype, we are ready to verify it against our specification by running the test This process is illustrated by Example 11: Running a test with specification in RSpec format and Example 12: Running a test with specification in xUnit format.
In these examples, the PROTOTYPE
environment variable is assigned the value 1 while running the test so that, instead of our design, our prototype is verified against our specification (see Section 4.7.1: Environment variables for details). Also, the GPL Cver simulator denoted by cver, is used to run the simulation.
Example 11. Running a test with specification in RSpec format
$ cd RSpec $ rake cver PROTOTYPE=1 Ruby-VPI: prototype is enabled ... Finished in 0.05106 seconds 3 examples, 0 failures cd -
Example 12. Running a test with specification in xUnit format
$ cd xUnit $ rake cver PROTOTYPE=1 Ruby-VPI: prototype is enabled Loaded suite counter Started ... Finished in 0.043859 seconds. 3 tests, 35 assertions, 0 failures, 0 errors
Tip 3. What can the test runner do?
4.9.6 Implement the design
Now that we have implemented and verified our prototype, we are ready to implement our design This is often quite simple because we translate existing code from Ruby (our prototype) into Verilog (our design). The result of this process is illustrated by Example 13: Implementation of a simple up-counter with synchronous reset.
Example 13. Implementation of a simple up-counter with synchronous reset
/**
A simple up-counter with synchronous reset.
@param Size Number of bits used to represent the counter's value.
@param clock Increments the counter's value upon each positive edge.
@param reset Zeroes the counter's value when asserted.
@param count The counter's value.
*/
module counter #(parameter Size = 5) (
input clock,
input reset,
output reg [Size-1 : 0] count
);
always @(posedge clock) begin
if (reset)
count <= 0;
else
count <= count + 1;
end
endmodule
4.9.7 Verify the design
Now that we have implemented our design we are ready to verify it against our specification by running the test Example 14: Running a test with specification in RSpec format and Example 15: Running a test with specification in xUnit format illustrate this process.
In these examples, the PROTOTYPE
environment variable is not specified while running the test, so that our design, instead of our prototype, is verified against our specification. You can also achieve this effect by assigning an empty value to PROTOTYPE
, or by using your shell’s unset command. Finally, the GPL Cver simulator denoted by cver, is used to run the simulation.
Example 14. Running a test with specification in RSpec format
$ cd RSpec $ rake cver ... Finished in 0.041198 seconds 3 examples, 0 failures
Example 15. Running a test with specification in xUnit format
$ cd xUnit $ rake cver Loaded suite counter Started ... Finished in 0.040262 seconds. 3 tests, 35 assertions, 0 failures, 0 errors
4.9.8 More examples
rakecommand to get started.
Chapter
5
Hacking
5.1 Building from source code
Obtain the source code from the project Darcs repository:
darcs get http://ruby-vpi.rubyforge.org/src ruby-vpi
Go inside the obtained directory and run the following commands:
rake build ruby bin/ruby-vpi -v
If the commands were successful, then you will see output like this:
ruby-vpi 21.0.0 (2008-06-08) http://ruby-vpi.rubyforge.org /home/sun/src/ruby-vpi
Otherwise, you can ask for help in the project mailing list.
5.2 Installing without really installing
After successfully building from source (see Section 5.1: Building from source code), set the RUBYLIB
environment variable to the path where you checked out the source code plus the lib/ directory.
For example, if you checked out the source code into /home/foo/ruby-vpi/ then you would set the value of the RUBYLIB
environment variable to /home/foo/ruby-vpi/lib/. Afterwards, any Ruby-VPI tests you run will use the checked-out source code directly.
5.3 Building release packages
In addition to the normal requirements you need the following software to build release packages:
Once you have satisfied these requirements, you can run the following command to build the release packages:
rake release
For more build options, see below:
$ rake -T (in /home/sun/src/ruby-vpi) rake build # Builds object files for all simulators. rake build_cver # Builds object files for GPL Cver. rake build_ivl # Builds object files for Icarus Verilog. rake build_ncsim # Builds object files for Cadence NC-Sim. rake build_vcs # Builds object files for Synopsys VCS. rake build_vsim # Builds object files for Mentor Modelsim. rake clean # Remove any temporary products. rake clobber # Remove any generated file. rake clobber_doc/api/ruby # Remove rdoc products rake clobber_package # Remove package products rake dist # Build release packages. rake doc # Build the documentation. rake doc/api/c # Build API reference for C. rake doc/api/ruby # Build the doc/api/ruby HTML Files rake gem # Build the gem file ruby-vpi-21.0.0.gem rake gem_config_inst # Configures the gem during installation. rake package # Build all the packages rake redoc/api/ruby # Force a rebuild of the RDOC files rake ref # Build API reference. rake repackage # Force a rebuild of the package files rake test # Ensure that examples work with $SIMULATOR rake upload # Upload to project website.
Chapter
6
Known problems
This chapter presents known problems and possible solutions.
6.1 Icarus Verilog
The following sections describe problems that occur when Icarus Verilog is used with Ruby-VPI.
6.1.1 Give full paths to Verilog objects
In version 0.8 and snapshot 20061009 of Icarus Verilog, the vpi_handle_by_name
function requires an absolute path (including the name of the bench which instantiates the design) to a Verilog object. In addition, vpi_handle_by_name
always returns nil
when its second parameter is specified.
For example, consider Example 16: Part of a bench which instantiates a Verilog design. Here, one must write vpi_handle_by_name("TestFoo.my_foo.clk", nil)
instead of vpi_handle_by_name("my_foo.clk", TestFoo)
in order to access the clk
input of the my_foo
module instance.
Example 16. Part of a bench which instantiates a Verilog design
module TestFoo;
reg clk_reg;
Foo my_foo(.clk(clk_reg));
endmodule
6.1.2 Registers must be connected
In version 0.8 of Icarus Verilog, if you want to access a register in a design, then it must be connected to something (either assigned to a wire or passed as a parameter to a module instantiation). Otherwise, you will get a nil
value as the result of vpi_handle_by_name
method.
For example, suppose you wanted to access the clk_reg
register, from the bench shown in Example 17: Bad design with unconnected registers If you execute the statement clk_reg = vpi_handle_by_name("TestFoo.clk_reg", nil)
in a specification, then you will discover that the vpi_handle_by_name
method returns nil
instead of a handle to the clk_reg
register.
The solution is to change the design such that it appears like the one shown in Example 18: Fixed design with wired registers where the register is connected to a wire, or Example 16: Part of a bench which instantiates a Verilog design where the register is connected to a module instantiation.
Example 17. Bad design with unconnected registers
Here the clk_reg
register is not connected to anything.
module TestFoo;
reg clk_reg;
endmodule
Example 18. Fixed design with wired registers
Here the clk_reg
register is connected to the clk_wire
wire.
module TestFoo;
reg clk_reg;
wire clk_wire;
assign clk_wire = clk_reg;
endmodule
6.1.3 VPI::reset
vpi_control(vpiReset)
VPI function causes an assertion to fail inside the simulator. As a result, the simulation terminates and a core dump is produced.6.2 Cadence NC-Sim
Chapter
7
Glossary
7.1 Test
7.2 Design
7.3 Specification
7.4 Expectation
7.5 Handle
7.6 Rake
Rake is a build tool, written in Ruby, using Ruby as a build language. Rake is similar to make in scope and purpose.
7.7 RSpec
The BDD framework for Ruby.
See the RSpec website and tutorial for more information.
7.8 Test driven development
An agile software development methodology which emphasizes (1) testing functionality before implementing it and (2) refactoring.
See this introductory article for more information.
7.9 Behavior driven development
An agile software development methodology which emphasizes thinking in terms of behavior when designing, implementing, and verifying software.
See the official wiki for more information.