Timing and Waiting
A common workflow is to have a tester timing file defined and managed outside of Origen, in which case the main concern within your Origen source code is simply to refer to required timeset(s) by name.
However, Origen does also support a more complex timing definition where waveforms can be defined and mastered in Origen source code. That is discussed later on in the Complex Timing section.
This guide gives an overview of some of the most common timing related methods, but to get a complete overview of what is available consult the OrigenTesters::Timing API.
A timeset declaration is used to provide information to Origen about what timeset to use for future test cycles and what period of time each cycle represents.
Normally this would be initialized within the startup method before generating any vectors as shown below:
class MySoCController include Origen::Controller def startup(options) $tester.set_timeset("mode_entry", 40) end end
The first argument is the name of the timeset, this should correspond to how the timeset will be named within the test program, and the second argument is the cycle period in nano-seconds.
This method also accepts a block in which case the contained vectors will generate with the supplied timeset and subsequent vectors will return to the previous timeset automatically.
$tester.set_timeset("bist_50mhz", 20) do # Any cycles generated in here will use 20ns for the period end
The arguments can also be supplied as a single array, or not at all. In the latter case the existing timeset will simply be preserved. This is useful if you have timesets that can be conditionally set based on the target.
# Target 1 $dut.readout_timeset = ["readout", 120] # Target 2 $dut.readout_timeset = false # This code is compatible with both targets, in the first case the timeset will switch # over, in the second case the existing timeset will be preserved. $tester.set_timeset($dut.readout_timeset) do # Generate readout vectors... end
Currently the creation of the timing set for a given test platform must be done independently of Origen, however adding an Origen API for this is on the roadmap.
All $tester models will support the following API to generate wait states in the test patterns.
Wait for specific number of cycles:
Wait for a period of time:
$tester.wait(time_in_us: 500) $tester.wait(time_in_ms: 10)
A shorthand for the above cases is available:
500.us! # Wait for 500us 10.ms! # Wait for 10ms
Multiple times specified in different units will be added together, this can be useful if the delay is based on a complex calculation:
# Wait for 500us + 100 cycles $tester.wait(time_in_us: 500, cycles: 100)
All testers provide an API for generating match loops, these can be used to make the pattern wait dynamically for a pin-based or even a register-based event.
To do this enable the
:match option and supply a block, within the block
generate the vectors that will test if the condition has been met.
Any time options passed in will be applied as a timeout, i.e. the maximum time to
wait for the required condition to resolve.
Here are some examples:
# Wait for up to 1 second for the done bit to be set $tester.wait(match: true, time_in_s: 1) do reg(:status_reg).bit(:done).read!(1) end # Wait for up to 1 second for the done pin to be set $tester.wait(match: true, time_in_s: 1) do pin(:done).assert!(1) end
A wave object defines the waveform that should be applied to a pin, and each pin will have two wave objects assigned to it - one for drive cycles and one for cycles where the pin is being read/compared.
A timeset object is used to collect the wave objects for all pins. Multiple timesets can be defined and therefore the waveforms being applied to the pins can be changed by changing the timeset. The API to change the timeset is the same as that already discussed above:
The period of the timeset can be referenced in the wave definitions to make the waveforms keep the same shape with different period settings.
Here is the most basic timeset definition, this will give all pins a default waveform which drives for the whole period on drive cycles, and strobes at 50% of compare cycles:
# Simple definition, all pins have default waves add_timeset :t1
Here is a more complex definition, which changes the default compare for all pins to be at 25% of the period, and which adds a unique drive wave to the :tck pin to create a clock:
# Complex definition, defines an alternative default compare wave and specific timing for :tck timeset :func do |t| t.compare_wave do |w| w.compare :data, at: "period / 4" end t.drive_wave :tck do |w| w.drive :data, at: 0 w.drive 0, at: 25 w.dont_care at: "period - 10" # Just to show that dont_care can be used end end
Pin groups can also be referenced in the timeset definition, here to add common drive timing to all pins in :gpio, and with a special compare waveform for :gpio5 only:
# Another timeset to show the wave assignment to pin groups timeset :t2 do |t| t.compare_wave :gpio5 do |w| w.compare :data, at: 100 end t.drive_wave :gpio do |w| w.drive :data, at: 200 end end
Note that the API currently only supports edge compares, i.e. window compares cannot be defined.
Additional drive and compare waves can be defined for any pin by giving them a code which can be used to select the given waveform in a pattern - i.e. the approach used by the V93K tester to select from multiple available waveforms.
Here is an example with a single pulse waveform defined for a clk pin which will be applied
when the pin is driven to
1, and a double pulse waveform that will be applied when the pin
is drive to
timeset :func do |t| t.drive_wave :tck do |w| w.drive 1, at: 0 w.drive 0, at: 20 end t.drive_wave :tck, code: 'T' do |w| w.drive 1, at: 0 w.drive 0, at: 10 w.drive 1, at: 20 w.drive 0, at: 30 end end
Complex waveforms also support an initial, or default,
set_timeset to be used without providing a
dut.timeset :func do |t| t.period_in_ns = 10 end tester.set_timeset(:func) tester.timeset.period_in_ns #=> 10 dut.timesets[:func].period_in_ns = 11 tester.timeset.period_in_ns #=> 11
The currently selected timeset can be retrieved via the following methods which are aliases:
dut.timeset # => instance of OrigenTesters::Timing::Timeset dut.current_timeset
Even though this returns a
OrigenTesters::Timing::Timeset object, its linked
back to the corresponding
Origen::Pins::Timing::Timeset object underneath. To
access it directly:
dut.timeset.dut_timeset #=> instance of Origen::Pins::Timing::Timeset tester.timeset.dut_timeset #=> instance of Origen::Pins::Timing::Timeset
However, this is generally not needed, as anything that the
cannot do is given to the corresponding
Using the plural returns a hash containing all timesets, thereby allowing access to specific timesets regardless of the current selection:
dut.timesets # => Hash dut.timesets[:func] # => instance of Origen::Pins::Timing::Timeset # This will also work: dut.timeset(:func)
Each timeset stores the waves in two arrays, one for drive waves and another for the compare waves:
dut.timeset(:func).drive_waves # => Array containing instances of Origen::Pins::Timing::Wave dut.timeset(:func).compare_waves
Each wave object has an events array, and the pins assigned to it can also be retrieved:
drive_wave = dut.timeset(:func).drive_waves.first compare_wave = dut.timeset(:func).compare_waves.first drive_wave.events # => [[0, :data], [50, 0], [75, :x]] drive_wave.pins # => Array of Origen::Pins::Pin instances compare_wave.events # => [["period / 2", :data]]
The method evaluated_events can be used to get the events with the formulas evaluated based on the current period:
compare_wave.events # => [["period / 2", :data]] compare_wave.evaluated_events # => [[50, :data]]
The waves assigned to a given pin by the current timeset can also be accessed via the pin API:
dut.pin(:tms).compare_wave.events # => ["period / 2", :data]
The waveform associated with a particular code can be accessed as follows:
# The wave for driving 0 and 1 is returned by default, these are all aliases: dut.pin(:clk).drive_wave.events # => [[0, 1], [20, 0]] dut.pin(:clk).drive_wave(0).events # => [[0, 1], [20, 0]] dut.pin(:clk).drive_wave(1).events # => [[0, 1], [20, 0]] dut.pin(:clk).drive_wave('T').events # => [[0, 1], [10, 0], [20, 1], [30, 0]]
You can retrieve the same
current period information from this scope as from
tester.set_timeset(:test, 10) tester.current_period #=> 10 dut.current_timeset_period #=> 10 dut.current_period_in_ns #=> 10 dut.current_period_in_seconds #=> 1/100000000
You can freely swap between timesets during pattern execution. For example, a sequence resembling below is perfectly valid:
# Start with an initial timeset for pattern startup tester.set_timeset("timing_for_startup", 100) tester.cycle # ... # Switch to a timeset for ramloading tester.set_timeset("timing_for_ramload", 200) tester.cycle # ... # Switch to a timeset for actual execution tester.set_timeset("timing_for_execution", 100) tester.cycle # ...
The period for that timeset will be remembered, allowing simpler timeset switches back to existing ones:
# Execute using timing for SWD tester.set_timeset("timing_for_swd", 40) tester.timeset.period_in_ns #=> 40 # ... # Execute using timing for an APB protocol tester.set_timeset("timing_for_apb", 80) tester.timeset.period_in_ns #=> 80 # ... # Switch back to using SWD tester.set_timeset("timing_for_swd") tester.timeset.period_in_ns #=> 40 # ... # Switch back to using APB tester.set_timeset("timing_for_apb") tester.timeset.period_in_ns #=> 80 # ... # End the pattern using SWD tester.set_timeset("timing_for_swd") tester.timeset.period_in_ns #=> 40 # ...
#cycled? method to denotes if the timeset has been cycled or not:
tester.set_timeset(:cycle_test, 10) tester.timeset.name #=> "cycle_test" tester.timeset.cycled? #=> false tester.cycle tester.timeset.cycled? #=> true tester.set_timeset(:cycle_test_2, 11) tester.timeset.cycled? #=> false tester.timesets[:cycle_test].cycled? #=> true tester.timesets[:cycle_test_2].cycled? #=> false