Decompilation
Working with Decompiled Patterns
The Universal API
attempts to tie all supported platforms together and provide
a generic yet flexible interface with which decompiled patterns from any source
or any platform can be treated the same.
This obvisously has its limitations and various hooks are in place to still allow
for platform-specifics to peek through the universal API
but the API detailed
in this section should be applicable across all supported platforms.
The OrigenTesters::Decompiler::Pattern base class provides this interface. All supported platforms should return a class which inherits from this. For example,
OrigenTesters::IGXLBasedTester::Pattern.ancestors.include?(OrigenTesters::Decompiler::Pattern)
#=> true
The preferred method to decompile a pattern source is to use the Decompiler API but you can choose to instantiate the decompiler yourself, provided you know the decompiler you want to use.
To decompile a source from the J750
, we’ll instantiate a new
OrigenTesters::IGXLBasedTester::Pattern
class:
pat = OrigenTesters::IGXLBasedTester::Pattern.new('path/to/src.atp')
Instantiating the decompiler does not automatically decompile it. However,
a call to #decompile
will do just that.
pat.decompiled?
#=> false
pat.decompile
pat.decompiled?
#=> true
#decompile
on the
Decompiler API does this automatically.
Any decompiler will accept the pattern source as either:
- A
string
containing the filename of the pattern source. - A
Pathname
containing the filename of the pattern source.
At times, such as in the example,
you’ll want to decompile a text source directly. Indicate this by providing
the direct_source: true
option during instantiation:
direct_pat = OrigenTesters::IGXLBasedTester::Pattern.new('path/to/src.atp', direct_source: true)
The decompiled pattern will keep track of its source and whether it was direct or not:
pat.direct_source?
#=> false
pat.source
#=> /home/origen_stuff/origen_testers/approved/j750/decompiler/sample/sample.atp
direct_pat.direct_source?
#=> true
direct_pat.source
#=>
"// Sample pattern text for the J750
// Source located at: lib/origen_testers/igxl_based_tester/decompiler
import tset tp0;
svm_only_file = no;
opcode_mode = extended;
compressed = yes;
vector ($tset, tclk, tdi, tdo, tms)
{
start_label pattern_st:
// Start of vector body
repeat 2 > tp0 X X X X ; // First Vector
repeat 5 > tp0 1 0 X 1 ;
end_module > tp0 X X X X ; // Last Vector
}"
source
,
so its not recommended to use direct sources for large patterns.
OrigenTesters::Decompiler::Pattern
object yourself, this is just the base class and has no grammars. Any attempts
to actually work with this class directly will not end well.
See the supported platforms page
for the class names of the actual decompiler implementations.
Both of these have been covered in the example, but for a quick review:
# Add any missing pins to the DUT, returning the pins that were added
dut.pins
#=> {}
pat.add_pins
#=> [:tclk, :tdi, :tdo, :tms]
dut.pins
#=> {:tclk=><Origen::Pins::Pin:47005605128940>, :tdi=><Origen::Pins::Pin:47005605149700>, :tdo=><Origen::Pins::Pin:47005605146120>, :tms=><Origen::Pins::Pin:47005605281700>}
# Execute the vectors
pat.execute
As also shown in the example,
the pattern is divided into sections: the frontmatter
, the pinlist
, and the vector body elements
.
The frontmatter
and pinlist
are parsed fully, stored in memory, and can be
accessed directly.
# Access the pattern's frontmatter
pat.frontmatter
#=> #<OrigenTesters::Decompiler::Pattern::Frontmatter:0x00005580b0c8b578>
# Retrieve the pattern header
pat.frontmatter.pattern_header
#=> ["// Sample pattern text for the J750", "// Source located at: lib/origen_testers/igxl_based_tester/decompiler"]
# Access the pattern's pinlist
pat.pinlist
#=> #<OrigenTesters::Decompiler::Pattern::Pinlist:0x00005580b0c8a9c0>
# pat.pinlist.pins
#=> [:tclk, :tdi, :tdo, :tms]
Methods to retrieve the pins are also available directly on the decompiled pattern:
# Return an array of pins, in the order they appear in the pattern
pat.pins
#=> [:tclk, :tdi, :tdo, :tms]
# Return the pin names and their respective size
pat.pin_sizes
#=> {:tclk=>1, :tdi=>1, :tdo=>1, :tms=>1}
See the OrigenTesters API for full details on the Frontmatter and the Pinlist.
Platform Specifics
The frontmatter
and pinlist
may contain platform-specific setup information. Any
platform-specifics should be documented
on the supported platforms page,
but some methods are provided to programmatically check what’s available.
For the example pattern,
using the J750 decompiler
:
pat.frontmatter.platform_nodes
#=> [:variable_assignments, :imports]
pat.pinlist.platform_nodes
#=> []
Any platform node
will have an accessor associated with it:
pat.frontmatter.imports
#=> {"tp0"=>"tset"}
pat.frontmatter.variable_assignments
#=> {"svm_only_file"=>"no", "opcode_mode"=>"extended", "compressed"=>"yes"}
Obviously, these nodes are decompiler specific, but what's less obvious is
that the implementation, and return values, are not defined by the universal API
.
Two decompilers that both implement the same platform node
may do so
differently and even have different meanings.
For some functions, it’ll be essential to know the initial state of the pattern. This may include the initial pin states, the initial timeset, or the first vector, in its entirety.
First Vector
An exception to the don’t store vectors in memory rule is the first vector
.
The first vector contains the initial state of the pattern, the initial timeset,
and, in many text representations, the sizes of the pins in the pinlist. This
vector is always available:
# Access the first vector
pat.first_vector
#=> OrigenTesters::Decompiler::Pattern::Vector
An important observation is that this returns the first vector, not the first vector body element.
v93k
, do not actually require a vector
in the pattern source. In these cases, retrieving the first vector or querying
aspects of the decompiled pattern that relies on the first vector will raise
an OrigenTesters::Decompiler::ParseError
exception with a
message containing "Could not locate the first vector"
.
See the approved V93K simple pattern
for an example of such a pattern.
First Pin States and First Timeset
Assuming the first vector is available, you can retrieve the first pin states and the first timeset directly. In the context of the example pattern:
# Retrieve the initial pin states
pat.initial_pin_states
#=> ["X", "X", "X", "X"]
# Retrieve the initial timeset
pat.initial_timeset
#=> tp0
The final section is the vector body elements
. This is a collection of not
just the vectors, but everything that may appear interweaved with them.
The most common non-vector will most likey be comment blocks
which may be
scattered about among the actual vectors. Other non-vectors will be platform
specific. Some examples include labels,
on the J750
or Ultraflex
, or sequencer instructions,
on the V93K
.
Anything in the vector body
will have a class of
OrigenTesters::Decompiler::VectorBodyElement,
which serves as a placeholder for anything that may come along and provides the
means to further decide how this particular element should be interfaced with.
Every vector body element
will have a type
, which is assigned during
decompilation - all you need to do is retrieve it, using the #type
method.
For a comment
this will be :comment_block
. For a bonafide
vector, this will be :vector
.
Platforms can, and will, interject their own types. A label
in a .atp
pattern
source will have type :label
. Retrieving and interacting with element
types will be shown throughout the remainder of this section.
Knowing what type
the element is lets you know what kind of accessors this
element should have. You would expect anything of type :vector
to have pin_states
,
but you would not expect the :comment_block
type to. However, the vector body element
is just a placeholder, so the #element
method must be used to retrieve the
underlying element that actually contains the content. Examples of this are
shown in the sections below.
The universal API
supports two built-in vector types:
comment blocks
and vectors.
The simplest vector body element to start working with is a comment_block
.
The decompiler will mash sequential comment lines in the pattern together to
form a single vector body element
of type :comment block
. A shorthand
method is provided to indicate when a comment block
is encountered:
vector_body_element.is_a_comment?
#=> true/false
There’s only so much which can be done with comments. The main operation will be retrieving them:
comment_block.comments
#=> [
#=> "Any comments in the block..."
#=> "Separated by newlines (or whatever the platform separator is)"
#=> "Will be its own array entry."
#=> ]
Note that the comments are returned as an Array
of all the comments mashed
together. This is standard though, so a standalone, single-line comment will be an Array
of size 1
.
You can view the API here,
but there’s not much more to comment blocks
than that.
Vectors
, on the other hand, have a bit more going on.These will have
type :vector
and a vector body element
shorthand method is available here as well:
vector_body_element.is_a_vector?
#=> true/false
Any vector, from any platform,
is expected to provide a timeset
, repeat
, pin_states
, and comment
accessor.
The comment
in this case is a end-of-line comment, sharing the same line with the vector.
Each of these can be retrieved for any vector
, but its best to see it as an example.
Returning to the example pattern,
we can retrieve all this content from the first vector:
pat.first_vector.timeset
#=> tp0
pat.first_vector.pin_states
#=> ["X", "X", "X", "X"]
pat.first_vector.repeat
#=> 2
pat.first_vector.comment
#=> First Vector
Every vector
is expected to contain at least these accessors. Some may
be empty, but the accessor should always work (no undefined method...
errors).
The platform, will likey want to throw in its own content that it considers part of
a standard vector
, such as opcodes
, for the J750.
The platform will register these as platform nodes
, and any platform node
will
have a corresponding accessor. For example, the J750 registers
opcode
and opcode_arguments
as platform nodes so, when we’ve decompiled
using the J750
decompiler, we’ll have access those as well:
pat.first_vector.opcode
#=> repeat
pat.first_vector.opcode_arguments
#=> ["2"]
Trying these platform nodes on other decompilers is not guaranteed to
give you anything. Check the platform specifics
for any additional platform nodes placed on the vector
type.
You can also list the platform nodes
programmatically using #platform_nodes
:
pat.first_vector.platform_nodes
#=> [:opcode, :opcode_arguments]
Platform
The question of ‘what was the decompiler?’ may come up for complex scripts
geared towards handling different pattern sources or supporting various platforms.
The #decompiler
method will return the decompiler used (which is, not coincidentally,
the class
of the decompiled pattern object).
pat.decompiler
#=> OrigenTesters::IGXLBasedTester::Pattern
In cases where decisions are made depending on the decompiler, the #decompiler?(<platform>)
method queries if the decompiled pattern was decompiled using the given platform:
pat.decompiler?(OrigenTesters::IGXLBasedTester::Pattern)
#=> true
pat.decompiler?(OrigenTesters::SmartestBasedTester::Pattern)
#=> false
Now that we have all the tools to deal with vector types and the underlying elements, we can begin to interface with the vector body itself.
The simplest operation is just to iterate through all the available vector body elements. The given block will be run for each one sequentially:
# Iterate through all vector body elements, running the given block for each one.
# For example, to print the type of each vector body element:
pat.each_vector { |v| puts v.type }
:start_label
:comment_block
:vector
:vector
:vector
# Do the above, but with the index
pat.each_vector_with_index { |v, i| puts "Type at index #{i}: #{v.type}" }
"Type at index 0: start_label"
"Type at index 1: comment_block"
"Type at index 2: vector"
"Type at index 3: vector"
"Type at index 4: vector"
This is the basis for working with the vector body elements
section and from this
more complex operations are derived. For example, to cycle the tester for each
vector
in the vector body:
pat.each_vector do |v|
if v.is_a_vector?
tester.cycle(repeat: v.element.repeat)
end
end
Due to the non-standard #each
method implementation, the
Enumerable mixin cannot be used
directly. However, some select Enumerable
methods are implemented:
# Collect all the vectors, after having run the given block.
# For example, to collect all the types that appear in the example pattern:
pat.collect { |v| v.type }
#=> [:start_label, :comment_block, :vector, :vector, :vector]
# Filting out duplicates...:
pat.collect { |v| v.type }.uniq
#=> [:start_label, :comment_block, :vector]
# Find the first vector for which the block returns true.
# For example, to find the first vector body element that is of type vector:
pat.find { |v| v.is_a_vector? }
#=> OrigenTesters::Decompiler::Pattern::VectorBodyElement
pat.find { |v| v.is_a_vector? }.type
#=> :vector
# Find all the vectors for which the block returns true:
# For example, to find the all vector body elements that are of type vector:
pat.find_all { |v| v.is_a_vector? }
#=> [OrigenTesters::Decompiler::Pattern::VectorBodyElement, OrigenTesters::Decompiler::Pattern::VectorBodyElement, OrigenTesters::Decompiler::Pattern::VectorBodyElement]
# Find all the vectors after filting out those for which the block returns true:
# For example, to find the all vector body elements that are NOT of type vector:
pat.reject { |v| v.is_a_vector? }
#=> [OrigenTesters::Decompiler::Pattern::VectorBodyElement, OrigenTesters::Decompiler::Pattern::VectorBodyElement]
See the API for a full listing.
#collect
is used without any block provided, all
vector body elements
will be returned. This may not bode well
for large patterns that contain thousands, or even hundreds of thousands, of vectors.
At times, you may want to grab a vector body element
at a specific index. The method #vector_at(i)
decompiles and returns
the vector at the index, i
:
pat.vector_at(3)
#=> OrigenTesters::Decompiler::Pattern::VectorBodyElement
pat.vector_at(3).type
#=> :vector
pat.vector_at(3).element
#=> OrigenTesters::Decompiler::Pattern::Vector
pat.vector_at(3).element.repeat
#=> 5
pat.vector_at(4).element.repeat
#=> 1
Observe that #vector_at
is a bit of a misnomer, as it actually
returns the vector body elements
at i
, not necessary one of type vector
.
#vector_at(i)
extensively.
Recall that the vector body is not stored in memory, so direct access to a given
vector index is not inheritently supported.
Therefore, each time #vector_at(i)
is called, the decompiler
simply runs #each_vector_with_index
and bails once the given index is hit,
returning the vector body element
at that index. This gives the desired behavior,
but comes at a cost…
That cost being runtime. In
a normal array, you’d expect to retrieve a vector body element
in constant time, O(1)
;
however, iterating through the vector body behaves akin to that of a
linked list,
where the retrieval time for an arbitrary vector body element
is O(n)
.
Extending this a bit: if using
#each_vector
, to iterate over vector body elements
from 0
to n
, you’ll get O(n)
runtime. However, if
using #vector_at(i)
where i
is the range from 0
to n
, you’ll actually get a
O(n^2)
runtime, as it starts from the beginning each time it retrieves a vector.
So, although more complex operations that require working with single vectors may take a drastic runtime hit as compared to storing all vectors in memory, the resource requirements of such operations will not, allowing the decompiler to operate on absolutely massive patterns, even when run on sub-optimal machines.
If you have the computational resources (such as access to a distributed system, as most corporations will have) or you know that the pattern sizes you'll be working with are limited to what your system can handle, the enumerable method
#collect
can be used to grab the entire vector body and store it in memory.
tl;dr: if jumping around the vector body, pre-processing, or otherwise working with the entire vector body simultaneously, please be prepared to take a runtime hit.
Its quite likely that you’ll encounter platform-specific elements when iterating
through the vector body. Recall the type
attribute on each vector body element
.
This, in conjunction with the platform’s documentation
will tell you what other elements may be encountered.
From a programmatic perspective, the vector body element
in question knows if
it is platform-specific.
Returning to the example pattern:
pat.vector_at(0).type
#=> :start_label
pat.vector_at(0).is_platform_specific?
#=> true
pat.vector_at(1).type
#=> :comment_block
pat.vector_at(1).is_platform_specific?
#=> false
For platform-specific nodes, same as vectors
, the parsed nodes from the decompilation
process are retrievable:
pat.vector_at(0).platform_nodes
#=> [:start_label]
Any platform nodes
can be retrieved using the accessor:
pat.vector_at(0).start_label
#=> "pattern_st"