Test Program Generator
Creating Flows
The test flow is where you describe everything about what your test program must do:
- The tests it must run
- The conditions applied by these tests
- Test limits
- Bin and test numbers
- Conditional test execution and other flow control
- Notes about the reason for running or the methodology behind each test
When writing your flow file you should try and distill down the amount of information to the bare minimum, the goal here is to write as little as possible yet still capture the details about what makes each test unique.
When describing each test the syntax and naming conventions are entirely up to you and based on your knowledge about the application domain and the type and variety of tests that you need to generate. It will be the job of the interface (which you will write later) to translate this description into method calls to the generation API for the target platform.
By convention all flow files should reside in the “program” directory within the application top-level directory.
Create the file “program/probe.rb” as shown below:
# program/probe.rb
Flow.create do
end
The above represents all of the officially required syntax to define a flow, what goes inside is now completely down to the needs of the application.
When creating a flow like this it is often useful to supply some context about the environment in which it will run. For example we have indicated via the name that this is a flow intended to run at probe, we can also note this in the code:
# program/probe.rb
Flow.create(environment: :probe) do
end
Again the naming of the terms :environment
and :probe
are
completely arbitrary, you may want to note some details about the temperature, parallelism
or other details which may be useful to you when it comes to actually generating
the flow for a given ATE platform.
Or not. Don’t worry about this too much right now, in practice it is probably best to
just add things like this as you find that you need them. The main thing for now is just to
appreciate that you can pass some details about the execution environment of the given
flow to the interface via this mechanism.
Origen extends Ruby to add the following methods which are very helpful when specifying test conditions:
3.A # => 3
3.mA # => 0.003
3.uA # => 0.000003
3.nA # => 0.000000003
3.pA # => 0.000000000003
3.V # => 3
3.mV # => 0.003
# => etc
3.Hz # => 3
3.kHz # => 3000
3.MHz # => 3000000
So let’s say we are writing a test for a voltage regulator module, we have a functional test and then two parametric tests that we wish to run - one that simply tests the output and one that tests the output under load. Let’s start with this:
# program/probe.rb
Flow.create(environment: :probe) do
log "Vreg test module"
func :vreg_functional, pattern: "vreg/functional", vdd: :min, bin: 5, softbin: 101, tnum: 101000
func :vreg_functional, pattern: "vreg/functional", vdd: :max, bin: 5, softbin: 101, tnum: 101001
para :vreg_meas, pattern: "vreg/meas", vdd: :min, bin: 5, softbin: 105, tnum: 105000, lo: 1.12, hi: 1.34
para :vreg_meas, pattern: "vreg/meas", vdd: :max, bin: 5, softbin: 105, tnum: 105001, lo: 1.12, hi: 1.34
para :vreg_meas, pattern: "vreg/meas", vdd: :min, bin: 5, softbin: 105, tnum: 105002, load: 5.mA, lo: 1.10, hi: 1.34
para :vreg_meas, pattern: "vreg/meas", vdd: :max, bin: 5, softbin: 105, tnum: 105003, load: 5.mA, lo: 1.12, hi: 1.34
end
This is a good start, we have something that resembles a test flow and we have gone with the general convention that a flow line is structured like this:
<function> <name>, <attributes>
- function - The main function of a particular test, here we have gone with the categories ‘log’, ‘func(tional)’ and ‘para(metric)’. You may wish to have other categories based on your domain. For example in some NVM applications we have ‘program’, ‘read’, ‘erase’, ‘measure’, ‘bitmap’, and so on. Each of these functions correspond to the methods that you will need to implement in your interface later.
- name - Almost every test will need a name and so we make this a mandatory argument.
- attributes - There then follows a free-format list of attributes, use these to describe the test and especially including anything that makes the test unique, such as the load attribute which has been used above.
As mentioned previously this choice of convention is just that, a personal choice, however this is the convention that is currently used in the flagship Origen applications that are currently driving the development of the framework. As the Origen program generator matures and the number of applications using it grows, it is possible that alternative and better conventions will begin to emerge. If you come across a better approach then please come and tell us about via the Origen community channels, however we will stick with this one for now.
Remember that the goal here is to be as concise as possible, since the more concise you are at this level then the more you can automate later. This eliminates duplication and redundancy, reduces the amount of code you need to write to add a new test later, and the more that can be generated the less chance there is for human error.
There are a few opportunities for simplification that we can consider at this point:
- The pattern field seems redundant - it very closely mirrors what has been assigned to the test name, therefore we can infer the pattern name without explicitly declaring it. It is generally a good approach to name the test after the pattern if you normally have one pattern per test.
- The bin number also looks to be redundant, it is always 5. In a case like this what we can do is set a default of 5 within the interface, then if it is not declared for a specific test it will be 5, but we can always override it for a specific test.
- The tnum attribute seems to follow the general rule that it is the softbin number * 1000 and then a counter. Anything that is the result of a function based on other attributes is ripe for elimination.
With those optimizations applied we end up with:
# program/probe.rb
Flow.create(environment: :probe) do
log "Vreg test module"
func :vreg_functional, vdd: :min, softbin: 101
func :vreg_functional, vdd: :max, softbin: 101
para :vreg_meas, vdd: :min, softbin: 105, lo: 1.12, hi: 1.34
para :vreg_meas, vdd: :max, softbin: 105, lo: 1.12, hi: 1.34
para :vreg_meas, vdd: :min, softbin: 105, load: 5.mA, lo: 1.10, hi: 1.34
para :vreg_meas, vdd: :max, softbin: 105, load: 5.mA, lo: 1.12, hi: 1.34
end
Finally we seem to always be running our tests at min vdd and then max vdd, so again there seems to be a convention that we could choose to implement by default. One issue we have is that the loaded test has a different limit between min and max, so this a good example of one case where we would override the default.
So switching to the convention that each test will execute at min and max vdd unless otherwise specified, our final flow is:
# program/probe.rb
Flow.create(environment: :probe) do
log "Vreg test module"
func :vreg_functional, softbin: 101
para :vreg_meas, softbin: 105, lo: 1.12, hi: 1.34V
para :vreg_meas, vdd: :min, softbin: 105, load: 5.mA, lo: 1.10, hi: 1.34
para :vreg_meas, vdd: :max, softbin: 105, load: 5.mA, lo: 1.12, hi: 1.34
end
That very clearly and concisely describes what the flow must do. Also by establishing conventions and eliminating redundancy we have made it really easy for us to add a new test in the future.
See the next section for details on run-time flow control.
For build-time flow control you have complete access to the current target objects if you need to conditionally build some tests.
Lets say we have two DUT designs, and one of them has a bug which means that the vreg load test will not work properly. Let’s say its a problem with the pad connection such that the vreg works ok but it just can’t be tested via an external load. Our models to represent this situation would look something like this:
# lib/dut_1.rb
class DUT1
include Origen::Model
end
# lib/dut_2.rb
class DUT2 < DUT
include Origen::Model
bug :vreg_pad
end
DUT1.new.has_bug?(:vreg_pad) # => false
DUT2.new.has_bug?(:vreg_pad) # => true
There are two ways that we could build the flow to ensure that DUT2 can still give a bin 1 - either skip the loaded test completely, or else don’t care its result. Here is an example of both:
# program/probe.rb
Flow.create(environment: :probe) do
log "Vreg test module"
func :vreg_functional, softbin: 101
para :vreg_meas, softbin: 105, lo: 1.12, hi: 1.34
# Don't build this test if the target DUT has the bug
unless $dut.has_bug?(:vreg_pad)
para :vreg_meas, vdd: :min, softbin: 105, load: 5.mA, lo: 1.10, hi: 1.34
end
# Continue on fail if the target DUT has the bug
para :vreg_meas, vdd: :max, softbin: 105, load: 5.mA, lo: 1.12, hi: 1.34, continue: $dut.has_bug?(:vreg_pad)
end
It is ofter useful to extract sections of a flow into a sub-module, so that it can be re-used and parameterized. In our case let’s say that we have a new DUT on the horizon which has two vreg instances, so let’s extract this suite of vreg tests to a module so that we can instantiate two of them in our flow.
By convention, sub flow file names that are meant only to be used from inside another top-level flow should begin with a “_”. (We’ll discuss importing top-level flows below). The sub-flow syntax is virtually identical to the regular flow syntax except that a hash of options are passed in which will contain any arguments passed in by the caller when they instantiate the sub-flow.
Let’s convert our flow to a sub-flow in file “_vreg.rb”:
# program/_vreg.rb
Flow.create do |options|
log "Vreg test module"
func :vreg_functional, softbin: 101
para :vreg_meas, softbin: 105, lo: 1.12, hi: 1.34
# Don't build this test if the target DUT has the bug
unless $dut.has_bug?(:vreg_pad)
para :vreg_meas, vdd: :min, softbin: 105, load: 5.mA, lo: 1.10, hi: 1.34
end
# Continue on fail if the target DUT has the bug
para :vreg_meas, vdd: :max, softbin: 105, load: 5.mA, lo: 1.12, hi: 1.34, continue: $dut.has_bug?(:vreg_pad)
end
Not much has changed so far - the environment option has been removed from the Flow.create
definition since that is more of a top-level concern, and the options have been added.
There are a couple of opportunities for improvement here:
- The flow allows the loaded tests to be skipped, however it would be better if the sub-flow did not define the conditions under which they should be skipped - that decision should be made by to the top-level.
- If this test is going to be instantiated multiple times it will need some kind of index to target the test at vreg 0 or vreg 1.
Here is the flow with these modifications:
# program/_vreg.rb
Flow.create do |options|
# These are the option defaults that will be used unless specified by the caller
options = {
include_loaded_output_tests: true,
index: 0,
}.merge(options)
log "Vreg test module"
func :vreg_functional, softbin: 101, index: options[:index]
para :vreg_meas, softbin: 105, lo: 1.12, hi: 1.34, index: options[:index]
if options[:include_loaded_output_tests]
para :vreg_meas, vdd: :min, softbin: 105, load: 5.mA, lo: 1.10, hi: 1.34, index: options[:index]
para :vreg_meas, vdd: :max, softbin: 105, load: 5.mA, lo: 1.12, hi: 1.34, index: options[:index]
end
end
For now we have not really committed to how the indexed tests will be generated, but we have passed the information along to the interface. Most likely when it comes to generating the pattern name within the interface we will just append the index to the name.
So the original top-level flow is now really simple:
# program/probe.rb
Flow.create(environment: :probe) do
import "vreg", include_loaded_output_tests: !$dut.has_bug?(:vreg_pad)
end
The path argument supplied to the import method can be either a relative path from the
current file (as it is above), for example "../components/vreg"
, or an
absolute path such as "#{Origen.root}/program/components/vreg"
. In both cases
the underscore prefix is not required.
The original reason for doing this was to add support for testing multiple
vreg instances. To support such a case let’s say that our DUT model will implement a
method called vregs
which will return one or more vreg models wrapped
in an array.
Our final flow is:
# program/probe.rb
Flow.create(environment: :probe) do
$dut.vregs.each_with_index do |vreg, i|
import "vreg", include_loaded_output_tests: !$dut.has_bug?(:vreg_pad), index: i
end
end
Occasionally you may have the need to import a top-level flow for debug or simulation purposes.
This is generally not recommended since any Flow.create
options in the top-level flow being imported
will be lost, but if you are careful that the Flow.create
options are aligned between the real top-level flow
and any imported top-level flows, then you should be fine.
The same import command is used as before, as it will also look also for a file name not starting with "_" if one with "_" is not found.
So for example we have 3 top-level flows that we know work ok stand-alone, but we want to check that they work ok when running back to back:
# program/probe.rb
Flow.create(environment: :probe) do # options here match those in the probe_* flows imported below
import "probe_start" # will import file called 'probe_start.rb'
import "probe_middle" # will import file called 'probe_middle.rb'
import "probe_end" # will import file called 'probe_end.rb'
end
All of the test attributes will be available later when it comes to generating documentation of the test program, so there is no need to document them manually. However in some cases it will also be useful to include some text describing perhaps how the test works, or why it is being done. Such information can be added via regular Ruby comments immediately before the given test.
Origen will extract these later when it comes to generating documentation.
The comments will be parsed as Markdown so this can be used to make things like bulleted lists or tables.
Here is our vreg test suite with some example documentation added:
# program/_vreg.rb
Flow.create do |options|
# These are the option defaults that will be used unless specified by the caller
options = {
include_loaded_output_tests: true,
index: 0,
}.merge(options)
log "Vreg test module"
# This test verifies that the following things work:
#
# * The vreg can be disabled
# * The trim register can be written to and read from
func :vreg_functional, softbin: 101, index: options[:index]
# Measure the output of the vreg under no load, this is a simple
# test to catch any gross defects that prevent the vreg from working
para :vreg_meas, softbin: 105, lo: 1.12, hi: 1.34, index: options[:index]
if options[:include_loaded_output_tests]
# Measure the output of the vreg under the given load, this is approximately
# equivalent to 1.5x the maximum load anticipated in a customer application.
para :vreg_meas, vdd: :min, softbin: 105, load: 5.mA, lo: 1.10, hi: 1.34, index: options[:index]
# As above
para :vreg_meas, vdd: :max, softbin: 105, load: 5.mA, lo: 1.12, hi: 1.34, index: options[:index]
end
end