Hi Suraj,
Sorry to have dropped off the radar for so long.. Anyway, I notice you have
done two major releases since we last communicated!
I have just installed 19.0.0, and been playing around with it.
I am starting to refine my system level testing needs, using a RISC system as
the target.
So here's what I want to do:
BACKGROUND:
-----------
I ignore the RISC itself to start with - this is usually obtained as a
simulator model or somesuch later on.
The majority of the work involves describing the system around the RISC, and
the interactions with it.
So to start with, I will be building a system using a bus functional model,
that just initiates bus transactions, And checks the required results. This is
not sufficient for all system testing, but it is usually the first step When
developing bus-based peripherals, or memory systems.
Furthermore, I will develop two bus models: a low speed peripheral bus, and a
high-speed bus that connects the RISC to The memories and to the bridge that
translates between the high speed bus and the peripheral bus.
For a concrete example, I will use an ARM AHB bus as the high speed bus
connected to the ARM core and memories, and an APB bridge connecting the AHB
bus to a set of peripherals on an APB bus.
This is a very typical situation - in the Open Source world, there are also the
Wishbone buses, which are similar (and probably better; AHB has some glaring
faults)
Typically, a verilog hierarchy would look something like this:
AHBSys------|
(AHB bus)
|
|---- ARM7TDMI
|
|---- Memory
|
|---- AHB2APB
|
|---- Peripheral1
|
|---- Peripheral2
Etc.
Also, in a traditional HDL verification setup, outside of all this would be
a 'testbench' - this contains models of the environment With which the design
has to interact. This may contain very little, if the device is standalone, or
it may contain many models:
memory
external serial port model
USB slave or host
JTAG host
etc.. I have had to develop all these at one time or another
More on the external harness later.
BUS MODELLING 1
---------------
First of all, let's develop some of the peripheral blocks. This corresponds
more to the way you have looked at Ruby-VPI so far.
We can develop a simple register based peripheral, that is attached to the APB
(peripheral) bus. This will have an interface that Connects to the bus, and a
few simple internals.
Now we can use Ruby-VPI pretty much as described in the tutorial; we can
develop individual tests that manipulate the bus signals In the correct order,
read back values, check to see whether they are correct, etc.
But, notice that this will become tedious, if we have a lot of tests; we are
performing the same actions over and over again.
What we want is a set of routines that emulate the bus transactions, so that we
can write tests like
describe "A peripheral register" do
setup do
Peripheral.reset!
end
it "should be reset to zero" do
# use a bus read function to obtain the value of reg
Peripheral.read(ADDR).should == 0
end
it "should be writeable" do
# write a value, then read it back
# use a bus write and bus read function
# not exhaustive..
Peripheral.write(ADDR, TEST_VAL)
Peripheral.read(ADDR).should == TEST_VAL
end
end
When implementing in Ruby-VPI, there are some questions we need to ask:
1. Where should we implement the bus functions?
At the moment, I have implemented them (successfully) in the _design.rb
file, alongside the cycle!, reset! Functions
But there are possible issues with this, as we will see later.
2. We need to make sure that we can re-use the tests later on. This means
that the address used to access the peripheral
and the scope will need to change.
So the question is, how should we refer to the DUT? Here we have
referenced it explicitly (as Peripheral), but this will
not be sufficient later on.
BUS MODELLING 2
---------------
The next step is to place several peripherals on a peripheral bus. We will want
to develop peripherals in separate verilog files, plus a System level file that
connects them together.
The first query is just one about the mechanics of Ruby-VPI. How do we include
multiple files in the build?
One way is to add the files into the filelist generated in the _runner.rake
file; this works. However, I don't find it satisfactory.
An easier way normally is to use the -y switch, together with diectory names.
Only the top level file needs to be given, and the rest are Found by inspection
on the directory paths. The problem is that it works for CVER on the command
line, but I can't seem to get the arguments Passed in correctly by changing
things in the _runner.rake file. That is, it seems to generate a correct
command line, but it doesn't work.
However, that's a minor (if frustrating) detail.
Secondly, we now have several blocks to test, and also, the top level that we
are testing has changed. Hence, how do we refer to the different Peripherals?
One way is to recognise that we are not performing tests on the peripherals,
but on the bus - which is our top level. So the tests should Include statements
like:
PeriphBus.read(ADDR).should == TEST_VAL
But this means we have to edit all our tests now, which is annoying. And later
on, we may have to do it again. One way might be to always call Our top level
System; a further refinement would be to have the very top level just a wrapper
around the actual top level, so that we never Need to change the name of the
module in the verilog code. The wrapper will always be called System.
BUS MODELLING 3
---------------
Now we want to instantiate our peripheral bus system within the main bus.
To start with, this is just an extension of our previous technique. We build
the verilog of the complete system, and wrap it with a new System wrapper.
We now have to build bus functions that perform main bus functions, and put
them in the _design.rb file.
This should work, because our tests just use
System.read(ADDR).should == TEST_VAL
The translation between the main bus and the peripheral bus transactions is now
performed by the HDL itself - as it will be in real life.
However, we have had to perform major edits to our System_design.rb file, which
is not good. What is the best way of abstracting this out?
Obviously, we should encapsulate the bus modelling functions somewhere, in say,
an AHBBus and APBBus class or module?. However, what would be the best way of
getting these functions in scope? Use an 'include' in the System_design.rb
file. I'm a bit hazy about the namespace issues here.
DISTRIBUTED DESIGN
------------------
But actually, there will probably be several teams developing modules. Ideally
we would want to be able to develop the verilog, and more importantly, the
tests, and then import these into the top level.
So I'm not sure how to do set this up. The top level System would need to
import the verilog (which could be done by using library directives), but it
would be good to be able to, say, get Ruby-VPI to recursively pull all the
required modules from a directory.
It is also worth pointing out that one tends to have both project specific
libraries, and shared libraries. For example there might be a Library of AHB
modules, APB modules, and then some project-specific peripherals.
So a project layout might be something like:
|
|----AHBLib
| |
| |----verilog
| |
| |----System
| | |
| | |---- System_design.rb
| | | AHBmodule1.v
| | | AHBmodule1_spec.rb
| | | AHBmodule2.v
| | | AHBmodule2_spec.rb //
| | | AHB_helper.rb //bus models
| | | System_spec.rb //refers to specs
|
|----APBLib
| |
| |----verilog
| |
| |----System
| | |
| | |---- System_design.rb
| | | APBmodule1.v
| | | APBmodule1_spec.rb
| | | APBmodule2.v
| | | APBmodule2_spec.rb //
| | | APB_helper.rb //bus models
| | | System_spec.rb //refers to specs
|
|----MyProject
| |
| |----verilog
| |
| |----System
| | |
| | |---- System_design.rb
| | | MyAPBmodule1.v
| | | MyAPBmodule1_spec.rb //refers to APBLib
| | | MyAHBmodule2.v
| | | MyAHBmodule2_spec.rb //refers to AHBLib
| | | MyProject_helper.rb //test harness models
| | | System_spec.rb //refers to specs
here, in AHBLib, APBLib
Note that we will also want to integrate revision control into the main project
rake files.
As pointed out before, the tests either specific to the project, OR referred to
from other libs, Will use the bus model referenced in the project, NOT the one
used locally in their library test System.
TESTING
-------
As pointed out above, it should be possible to get hierarchical builds for the
verilog (after sorting out those pesky rake problems).
However, I am less sure about the Rspec specifications.
Ideally, I would want to have the master System_spec.rb file look something
like:
describe "APBSubstem" do
it_should_behave_like "APBModule1" (ADDR1)
it_should_behave_like "APBModule2" (AADR2)
it_should_behave_like "MyAPBModule1" (AADR3)
..etc
Where we refer to 'shared' Rspec behaviour
Now there are two issues here:
1. how to set up the namespaces correctly (doable, except I don't know
enough about it yet..)
2. we need to parametrise the specs with the actual address / size /
etc. of the module
This is particularly true of library modules, since they will in
general, be parametrisable.
Any ideas about point (2)? At the moment it looks impossible, but its
difficult to get any hard info About Rspec, the documentation is terrible. I'll
have a poke round.
Note also, that we would like to have a standard means of assigning addresses,
etc. In practice this means a Header file with verilog definitions (enter your
verilog to ruby translator). There is one small verilog point Here - it is
probably better to use 'parameters' to set addresses, sizes, etc., rather than
rely on macro Substitutions (`defines) as these can be set from anywhere within
the hierarchy. However, one can use macros In the parameter instantiations, if
necessary.
BUS MODELS vs CODE
------------------
All the above use the bus functions to perform testing. This works well will
the Rspec framework.
Eventually, though, we would like to run code.
It is possible to modify the bus functions, so that, as well as performing the
transaction, they emit Code (possibly assembler code) to execute the same
action. So
write (ADDR,DATA)
gets turned into (pseudo-assembler):
load r4, #DATA // load immediate
store @#ADDR, r4 // store absolute
One point, though, is that the read needs to have an expected value; at the
moment the bus function read returns The value so that it can be checked by the
Rspec framework. The assember code will need to know the expected value, So
would probably need something like
expect(ADDR, DATA) // returns value as well, so can use expect
(ADDR,DATA).should == DATA
becomes:
load r4,#DATA
load r5,@#ADDR
cmp r4,r5
bnz FAIL_EXPECT
This approach does mean that the code version can't be checked using the Rspec
framework; it gets emitted, assembled Then loaded into a memory model for
execution by a harness that uses the real core (or a C model thereof).
Again, I can't work out whether Rspec could be made to do anything more clever.
STATUS
------
I have got as far as using simple bus models, but these are still at the early
stage, i.e. just shoved into the Top_level_design.rb file.
I haven't tackled the hierarchy problems yet.
If you are interested, I can send you the files (such as they are!)
Best Regards (and keep up the good work!)
Rob MacAulay