Models
Pins
Origen provides a pin (and pin group) API that should be used to define and manipulate the physical pins of the device.
Pins are mainly intended to be defined on models that will be used for pattern generation and where the pins represent the physical device pins that the tester can interact with. However in its basic form a pin is just an object that can hold a data state and there are probably some creative uses of the pin models outside of pattern generation.
The pin API is made available to your model by including the
Origen::TopLevel
or Origen::Model
modules.
While pins can be added from any sub-module the ownership of all pins is always assigned to the top-level object, this is reflecting real life where the pin is a physical property of the top-level SoC. Therefore it is recommended that pin definitions are always made on the top-level object and most modern Origen applications now take this approach.
Pins should be defined as part of the object initialization process, but to avoid the initialize method growing out of hand it is recommended that pins are instantiated in a dedicated method that is called upon initialization.
Here is an example of how to define some JTAG interface pins in an SoC model:
class MySoC
include Origen::TopLevel
def initialize(options={})
instantiate_pins(options)
end
def instantiate_pins(options={})
add_pin :tclk, reset: :drive_hi, meta: { max_freq: 15.Mhz }
add_pin :tdi, direction: :input
add_pin :tdo, direction: :output
add_pin :tms
end
end
Some points to note in the above code:
-
add_pin
will instantiate an instance of Origen::Pins::Pin - Each pin must be given a name, or handle, similar to how registers are named. However unlike registers the name must not only be unique within the parent object but it must also be unique within the scope of all objects instantiated by a target. The easiest way to comply with this requirement is to follow the advice of handling all pin definitions at the top level.
- Pins are initialized in the don’t care state by default, an alternative default state can be specified via
the
:reset
option. - Pins are I/Os by default, however they can be defined as input or output only by using the
:direction
option. - Application-specific meta data can be attached to the given pin via the
:meta
option
Pins are accessed via the pin
method:
# Select a single pin
$dut.pin(:tdi)
#=> <pin object>
# Select multiple pins
$dut.pin(:tdi, :tdo, :tclk)
#=> [<pin object>, <pin object>, <pin object>]
Note that pins can be accessed locally from any object that includes the Origen::Model
module:
$dut.pin(:tdi) == $dut.memories.rambist.pin(:tdi) # => true
However references to pins within a sub-block would typically use an alias that reflected the pin function related to that sub-block.
Pin aliases are added like this:
has_pin?(:tdi) # => true
has_pin?(:bisti) # => false
add_pin_alias :bisti, :tdi
has_pin?(:bisti) # => true
pin(:bisti) == pin(:tdi) # => true
Or called directly on the pin:
pin(:tdi).add_alias(:bisti)
For applications which have many, many pins pin groups
(discussed below) can
help organize them logically. However, multiple pins can also be selected
by providing either a block and one or more Regexp
s:
# Find all pins matching /porta/
dut.pins(/porta/)
#=> [<pin object>, ...]
dut.pins(/porta/).map(&:name)
#=> [:porta31, :porta30, ...]
# Find all pins matching /porta/ or /portc/
dut.pins(/porta/, /clk/)
#=> [<pin object>, ...]
dut.pins(/porta/, /clk/).map(&:name)
#=> [:porta31, :porta30, ..., :tclk]
# Retrieve all pins matching /porta/ and :tdi, :tdo
dut.pins(/porta/, :tdi, :tdo)
#=> [<pin object>, ...]
dut.pins(/porta/, :tdi, :tdo).map(&:name)
#=> [:porta31, :porta30, ..., :tdi, :tdo]
# Retrieve all pins for which the block is true
# (Find all even-indexed pins for porta)
dut.pins { |n, p| n.to_s.start_with?('porta') && (n.to_s.gsub('porta', '').to_i % 2 == 0) }
#=> [<pin object>, ...]
dut.pins { |n, p| n.to_s.start_with?('porta') && (n.to_s.gsub('porta', '').to_i % 2 == 0) }.map(&:name)
#=> [:porta30, :porta28, :porta26, ...]
# Retrieve all pins for which /clk/ matches OR the block is true
dut.pins(/clk/) { |n, p| n.to_s.start_with?('porta') && (n.to_s.gsub('porta', '').to_i % 2 == 0) }
#=> [<pin object>, ...]
dut.pins(/clk/) { |n, p| n.to_s.start_with?('porta') && (n.to_s.gsub('porta', '').to_i % 2 == 0) }.map(&:name)
#=> [:porta30, :porta28, :porta26, ..., :tclk]
Pin groups are modelled by an object called a PinCollection which is very similar in principle to the BitCollection used for modelling registers. A PinCollection is essentially an extension of a regular Ruby array object which has some of its own attributes (such as an id for example) and everything else is proxied to the contained pin objects.
For pattern generation pins and pin groups will support the same API so that application code does not have to give much concern as to whether it is dealing with one or the other.
Pin groups are defined in a similar manner to single pins:
add_pins :porta, size: 32
add_pins :portb, size: 16, endian: :little
Some points to note in the above code:
-
add_pins
will generate a new pin group, e.g. named:porta
and individual pin objects named:porta0
through:porta31
. See below for how to define pin groups based on existing pins. - Pin groups can be any size, define via the
:size
option. - For pin groups whose indexes should start somewhere other than
0
(e.g. modeling the portportc[8:1]
instead ofportc[7:0]
), an:offset
option can be provided. - Pin groups are big endian by default, however little endian can be specified via the
:endian
option. Changing this attribute will have the effect of reversing the order that data is applied in a test pattern.
Pin groups can be looked up like regular pins, or via a dedicated method if it makes application code clearer:
pins(:porta) # => <pin collection>
pin_group(:porta) # => <pin collection>
A pin group is a regular Ruby array and can be iterated on and so on:
pins(:porta).size # => 32
pins(:porta).each do |pin|
# Do this for pin :porta0, :porta1, etc.
end
Within a pin group individual pins can be addressed via a local index:
pins(:porta)[0] == pins(:porta0) # => true
The reverse is achieved using the index
method and providing a context
option:
pins(:porta0).index(context: :porta)
#=> 0
pins(:porta1).index(context: :porta)
#=> 1
If no context is given, Origen attempts to discern any implicit indexing, using the appendix of the pin, if it ends in a numeral.
pins(:porta7).index
#=> 7
pin(:tclk).index
#=> nil
Anonymous pin groups can be composed from a range of indexes at runtime:
pins(:porta).data # => 0x0000_0000
pins(:porta)[0,1,2,3].drive(0xF)
pins(:porta).data # => 0x0000_000F
pins(:porta)[8..15].drive(0x55)
pins(:porta).data # => 0x0000_550F
Pin groups can be composed from any arbitrary group of existing pins using the add_pin_group
method:
# The first argument is the ID for the new pin group, followed by the IDs of the pins it should contain
add_pin_group :jtag, :tdi, :tdo, :tclk, :tms
pins(:jtag).size # => 4
Pin groups can be aliased in the same way as pins, for readability the method add_pin_group_alias
can be used, although internally it is the same as add_pin_alias
:
add_pin_group_alias :porta, :data
pin_group(:data).dont_care # Can now be used as if it were a regular first class pin group
It is possible to alias a single pin within a pin group, in that case a pin number argument
must be supplied to add_pin_alias
:
add_pin_alias :moda, :porta, pin: 4
It is also possible to create a pin group from a subset of pins of a regular pin group, simply
pass in a range or an array of pin indexes to add_pin_group_alias
:
add_pin_group_alias :data_byte_0, :porta, pins: [7..0]
add_pin_group_alias :data_byte_1, :porta, pins: [8,9,10,11,12,13,14,15]
pins(:data_byte_0).dont_care # Can now be used as if it were a regular first class pin group
For GPIOs, PORTs, etc., situations in which you want to set or clear a single
pin may arise. Origen can help here by generating masks to set or clear a pin
within a given context. The context works the same here as when using index
:
# Generate a mask which sets only :porta4 in the context of :porta
dut.pin(:porta4).mask(context: :porta)
#=> 0b10000
dut.pin(:porta4).clr_mask(context: :porta)
#=> 0b1111_1111_1111_1111_1111_1111_1110_1111
Implicit masks work similarly, but the size of the clear mask
is not known.
A :size
option can be given to set the size of the generated mask:
dut.pin(:porta4).mask
#=> 0b10000
dut.pin(:porta4).clr_mask
#=> 0b1111
#=> (0b0_1111)
dut.pin(:porta4).clr_mask(size: 32)
#=> 0b1111_1111_1111_1111_1111_1111_1110_1111
The API supports modelling power and ground pins as follows:
add_pin :pinx
add_power_pin :vdd1
add_power_pin :vdd2
add_ground_pin :gnd1
add_ground_pin :gnd2
add_ground_pin :gnd3
add_power_pin_group :vdd, :vdd1, :vdd2
add_ground_pin_group :gnd, :gnd1, :gnd2, :gnd3
This will cause these pins to be stored in separate collections to make pattern and other content generation easier:
# Based on the above example
pins.size # => 1
power_pins.size # => 2
ground_pins.size # => 3
Power and ground pin lookup should be done via dedicated methods:
power_pin(:vdd1)
ground_pins(:gnd)
ground_pin_groups.each do |id, group|
# Do this for each ground pin group
end
Power pins have some dedicated attributes that can be defined, in additional to the generic meta-data hash supported by all pins:
add_power_pin :vddio, voltage: 3, current_limit: 50.mA, meta: { min_voltage: 1.5 }
power_pin(:vddio).voltage # => 3
power_pin(:vddio).current_limit # => 50E-03
power_pin(:vddio).meta[:min_voltage] # => 1.5
The API supports modelling virtual pins. Virtual pins are essentially user-defined pins that may not necessarily be associated with the SoC, such as:
- Tester utility pins that control relay states on the device interface board (DIB)
- Tester channels to control components on the DIB
add_virtual_pin(:virtual1, type: :utility_pin)
add_virtual_pin(:virtual2, type: :utility_pin)
add_virtual_pin(:virtual3, type: :relay_pin)
add_virtual_pin_group :utility, :virtual1, :virtual2
add_virtual_pin_group :relay, :virtual3
This will cause these pins to be stored in separate collections to make content generation easier:
# Based on the above example
virtual_pins.size # => 3
Virtual pin lookup should be done via a dedicated method:
virtual_pin(:virtual1)
virtual_pin_groups.each do |id, group|
# Do this for each virtual pin group
end
See the Pin API for full details of what methods are available.
Here are a few examples of working with pins:
# Make the tester drive a value on a pin
pin(:tdi).drive_hi
# Equivalent to above, this form is better when serially applying data in a loop...
pin(:tdi).drive(1)
# ...like this...
reg(:data).shift_out_left do |bit|
pin(:porta).drive(bit.data)
end
# Asserting that a pin drives a certain value has various aliases, these are all equivalent
pin(:tdo).assert(1)
pin(:tdo).compare(1)
pin(:tdo).expect(1)
# Current state can be queried
pin(:tdo).driving? # false
pin(:tdo).comparing? # true
pin(:tdo).dont_care
pin(:tdo).comparing? # false
pin(:tms).drive_lo
pin(:tms).toggle
# Pin groups support the same methods as pins
pins(:porta).drive(0x55) # 01010101
pins(:porta).assert(0x55) # LHLHLHLH
pins(:porta).dont_care # XXXXXXXX
# Individual pins within a pin group can be accessed by index
pins(:porta)[1].drive(0) # XXXXXX0X
Almost all pin methods have a bang form, for example:
pin(:tdi).drive!(1)
The bang methods will set the state on the pin in the same way as the regular method but will then automatically fire off a tester cycle to generate a vector.
In other words the above is the shorthand equivalent of:
pin(:tdi).drive(1)
$tester.cycle
Pins can be defined based on a specific package context which can be used to gate their availability.
Here is an example:
# Initially define the available packages at the top level before adding any pins
class MySoC
include Origen::TopLevel
def initialize
add_package :pkg1
add_package :pkg2
end
end
Pins can then be defined by package:
add_pin :pin1, packages: :all
add_pin :pin2 # No constraint is equivalent to :all
add_pin :pin3, package: :pkg2
add_pin :pin4, packages: [:pkg1, :pkg2]
# Pins can also be added within a package scope, these will add to :pkg1 only
with_package :pkg1 do
add_pin :pin5
add_pin :pin6
end
# All pins are available when the package is set to nil, no package represents the die
package # => nil
pins.size # => 6
package = :pkg1
pins.size # => 5 (no pin3)
has_pin?(:pin3) # => false
package = :pkg2
pins.size # => 4 (no pin5 or pin6)
has_pin?(:pin3) # => true
Pin groups can contain different sets of pins in different packages:
add_pin(:pin1)
add_pin(:pin2)
add_pin(:pin3)
add_pin_group :g1, :pin1, :pin2, package: :pkg1
add_pin_group :g1, :pin1, :pin2, :pin3, package: :pkg2
has_pins?(:g1) # => false
pin_groups.size # => 0
package = :pkg1
has_pins?(:g1) # => true
pins(:g1).size # => 2
pin_groups.size # => 1
package = :pkg2
has_pins?(:g1) # => true
pins(:g1).size # => 3
pin_groups.size # => 1
The attributes listed in the constant PACKAGE_SCOPED_ATTRIBUTES
in the Pin API
can be set to specific values per package.
Here is an example of how to set the pin location by package:
# At definition time
add_pin :pin1, packages: {pkg1: {location: "A2"}, pkg2: {location: "A3"}}
add_pin :pin2
# Or later
pins(:pin2).add_location "A5", package: :pkg1
pins(:pin2).add_location "B2", package: :pkg2
package = :pkg1
pins(:pin1).location # => "A2"
pins(:pin2).location # => "A5"
package = :pkg2
pins(:pin1).location # => "A3"
pins(:pin2).location # => "B2"
A context can also be supplied when reading such attributes to override the current context:
package = :pkg1
pins(:pin1).location # => "A2"
pins(:pin1).location(package: :pkg2) # => "A3"
Origen will automatically create an alias for the given location (a lower cased symbol) that is scoped to the package.
package = :pkg1
has_pin?(:a2) # => true
has_pin?(:a3) # => false
package = :pkg2
has_pin?(:a2) # => false
has_pin?(:a3) # => true
Certain types of metadata only become available at distinct times in a product lifecycle, for example Device Interface Board (DIB) information. Depending on when the pin model is constructed the user can specify the DIB metadata in two ways:
# All at once
$dut.add_pin :tdi, packages: { bga: { location: 'BF2', dib_meta: { channel: 100106, connection: "PE117.08", slot: "PE117", spring_pin: "08" } } }
# Over time
$dut.add_pin :tdi, packages: { bga: { location: 'BF2' } }
$dut.pins(:tdi).add_dib_meta :bga, { channel: 100106, connection: "PE117.08", slot: "PE117", spring_pin: "08" }
There might be a need to update the packages for a pin that has already been defined. This can now be done as follows:
$dut.add_pin :p1
$dut.add_package: package1
$dut.add_package: package2
$dut.pin(:p1).packages # => {}
$dut.pin(:p1).update_packages :packages => [:package1, :package2]
$dut.pin(:p1).packages # => {:package1=>{}, :package2=>{}}
Pin aliases are an example of modelling different pin functions that can be mux’d onto the same pin, in this case the pin attributes are the same regardless of the function name being used.
However sometimes it is necessary to model different attributes depending on the pin function - for example under one function the pin may be an input, but when used as another function it is an output.
The attributes listed in the constant FUNCTION_SCOPED_ATTRIBUTES
in the Pin API
can be set to specific values per context (mode and/or configuration).
If any of these function-scoped attributes are a hash then the function-specific value will be merged with any default value that has been defined on the pin. This allows the function to override default values and inherit default values for attributes that it doesn’t care about. Look at the meta data in the example below to see this in action.
Here is an example of adding a function:
class MySoC
include Origen::TopLevel
def initialize
add_pin :pin1
add_pin :pin2, meta: { vol: 0.4, voh: 0.6 } # Example of default meta data
# Define some pin functions
pin(:pin1).add_function :nvm_fail, direction: :output
pin(:pin2).add_function :nvm_done, direction: :output, meta: { voh: 0.7 } # Override some of the meta data
pin(:pin1).add_function :tdi, direction: :input
pin(:pin2).add_function :tdo, direction: :output
# Add some groupings that are associated with specific pin functions
add_pin_group :jtag, :tdi, :tdo
add_pin_group :nvm, :nvm_fail, :nvm_done
end
end
$dut = MySoC.new
# The name used to look up a pin now returns the pin with a thin wrapper around it
# which defines its current function:
$dut.pin(:nvm_fail).direction # => :output
$dut.pin(:nvm_done).direction # => :output
$dut.pin(:tdi).direction # => :input
$dut.pin(:tdo).direction # => :output
# If a function name is given when defining a pin group, the pin will always have
# that function when it is accessed through the resultant group
$dut.pins(:jtag)[0].direction # => :output
$dut.pins(:jtag)[1].direction # => :input
$dut.pins(:nvm)[0].direction # => :output
$dut.pins(:nvm)[1].direction # => :output
# The pin's meta data can be overridden by function
$dut.pin(:tdo).meta # => { vol: 0.4, voh: 0.6 }
$dut.pin(:nvm_done).meta # => { vol: 0.4, voh: 0.7 }
The API supports creating a free-running clock on any existing pin, where the timing parameters of the clock can be specified and vector generation (in relation to the current tester timeset) will be automatically handled. See the Pin Clock API below.
enable_clock(options)
Enable the clock functionality on a given pin. Does not start the clock, only sets up its internally tracked parameters. Clock can be specified in cycles, time, or frequency
$dut.pin(:pin1).enable_clock(period_in_ns: 200) # create free-running clock with 200ns period
$dut.pin(:pin2).enable_clock(frequency_in_khz: 32) # create 32Khz free-running clock
start_clock(options = {})
Start an existing clock or create and start a free-running clock on a given pin
$dut.pin(:pin2).start_clock # start up existing clock pin2
$dut.pin(:pin3).start_clock(period_in_us: 2) # create and start clock with 2us period
stop_clock, pause_clock
Stop the clock running on a given pin
$dut.pin(:pin1).stop_clock # stop clock on pin1
$dut.pin(:pin2).pause_clock # stop clock on pin2
$dut.pin(:pin3).pause_clock # stop clock on pin3
resume_clock(options = {})
Restart an existing clock. Can be restarted with existing parameters or restarted with a new clock setup
$dut.pin(:pin2).resume_clock # resume clocking on pin2 with existing clock setup
$dut.pin(:pin3).resume_clock(period_in_ns: 100) # resume clocking on pin3 with new clock parameters
Some addtional examples:
# Create timeset with 2-ns period
$tester.set_timeset('ts_50Mhz', 20) # timeset period is 20ns
# In this example, we enable (without immediately starting) a clock pin with
# 100ns period and then pause, resume, and stop later in the pattern.
$dut.pin(:pin1).enable_clock(period_in_ns: 100)
$dut.pin(:pin1).start_clock
...
$dut.pin(:pin1).pause_clock
...
$dut.pin(:pin1).resume_clock
...
$dut.pin(:pin1).stop_clock
...
# In this example, we start free-running clock with 200ns period, then later stop
# and restart with a different (100ns period) setup.
$dut.pin(:pin2).start_clock(period_in_ns: 200)
...
$dut.pin(:pin2).stop_clock
...
$dut.pin(:pin2).start_clock(period_in_ns: 100)
...
$dut.pin(:pin2).stop_clock
...