Models
Definition & Hierarchy
A model can be any class that includes the Origen::Model
module, this will give you access to
all Origen APIs for defining registers, pins, etc. and generally makes Origen aware of any instances of the
model when you instantiate them.
class MyModel
include Origen::Model
end
As a general rule include the Origen::Model
module every time you create a new class in Origen,
even if the model is
not of an SoC or an IP block and the concept of registers and pins does not apply.
There is not really any downside to including these APIs if you are not going to use them and this
module is the main mechanism to hook your class into the Origen ecosystem.
If a given model represents the top-level of an device then you should also include the Origen::TopLevel
module, this specifically lets Origen know that it is a top-level model and as such it holds a special
place in the Origen runtime environment - for example any register write requests will be sent to this
object for dispatch.
Internally the Origen::TopLevel
module includes the Origen::Model
module and
therefore it does not need to be specifically included in top-level model classes (although there is
no harm from doing so).
class MySoCModel
include Origen::TopLevel
end
Regardless of what name is given to an instance of a top-level class it can always be looked up via
Origen.top_level
or more simply $dut
. All Origen developers can rely on this
convention to access the current top-level object:
soc = MySoCModel.new
Origen.top_level == soc # => true
$dut == soc # => true
As your experience with your application grows, the way to partition your models for easiest maintenance and development will start to become clear. At that point you may feel decide to start introducing abstract classes to handle the concepts that exist within your domain and which don’t necessarily have a direct counterpart in the physical domain.
However to get started we recommend that you closely follow the physical architecture of your target device, where each of the main IP blocks that you care about has an associated model.
In this example we are going to set up an Origen model structure that will allow us to write some test code for a fictional NVM module contained within an SoC.
We are going to call our application ‘NVM’ and all of our code will reside in the NVM namespace. However the top-level SoC model is something of a special case because potentially it could be shared by many applications - e.g. an application concerned with testing the SRAM could re-use our SoC model.
So to set us up to handle that eventuality in the future let’s put all of our top-level definitions into
their own namespace which
we will call SOC
. Here is how to define the top level model:
# lib/soc/eagle_m352.rb
module SOC
class EAGLE_M352
include Origen::TopLevel
def initialize(options={})
end
end
end
The initialize method will automatically be called by Ruby whenever a new instance of this class is instantiated - so this is a place to do any setup or initialization that is required whenever a new Eagle model comes into being.
At this point we have also chosen to have this method optionally accept a hash of options, we will do this almost every time we define a method since it builds in great flexibility and the ability to handle additional arguments in future that we may not have thought about when first defining a new method.
Our first model is now defined and we can now go and talk to it for the first time, to do so start an interactive Origen session from your terminal:
origen i
This command loads up an interactive Ruby terminal and automatically loads Origen and your application, so we can now experiment with our models:
$dut = SOC::EAGLE_M352.new
$dut.is_a?(SOC::EAGLE_M352) # => true
Above we simply instantiated a new instance of our class, and then asked it if it was an instance of
SOC::EAGLE_M352
, to which it replied: ‘yes’.
Sub-blocks should be used to model IP blocks or indeed any sub-components within that IP, a generic sub-block can be declared very simply within the top-level’s initialize method:
# lib/soc/eagle_m352.rb
def initialize(options={})
sub_block :nvm
end
By default this will instantiate an object that includes all of the usual Origen APIs (regs, pins, etc.) and this can then be decorated as required by the application (see example below). Most importantly this wires up everything internally such that the relationship between the child and the parent are known to Origen and it will automatically build an accessor to get the child module:
$dut = SOC::EAGLE_M352.new
$dut.nvm # => Generic Origen object
$dut.nvm.parent # => $dut
$dut.children # => {:nvm => <object>}
# Decorate as required by calling Origen APIs on the object
$dut.nvm.reg :reg1, 0x30 do |reg|
reg.bits 31..0, :data
end
# The NVM now has a register available...
$dut.nvm.reg1.write(0x1234)
The above approach is ideal where the models are being built from a 3rd party data source (e.g. standard XML) and all that is required is to get an Origen object representation of the same data.
However in cases where more native richness is required you can supply a class for the object, let’s create a dedicated model for our NVM IP:
# lib/nvm/nvm_m682.rb
module NVM
class NVM_M682
include Origen::Model
def initialize(options={})
# Add an example register
reg :reg1, 0x30 do |reg|
reg.bits 31..0, :data, reset: 0xFFFF_FFFF
end
end
end
end
This follows the same pattern as our first model, note the use of the NVM
namespace and the
subsequent storage of the file in the lib/nvm
directory rather than lib/soc
.
We can now refer to this class in our sub-block definition and verify that the register we added is available:
# lib/soc/eagle_m352.rb
def initialize(options={})
sub_block :nvm, class_name: "NVM_M682"
end
$dut = SOC::EAGLE_M352.new
$dut.nvm.reg1.address # => 0x30
$dut.nvm.reg1.data # => 0xFFFF_FFFF
If needing to add several similar sub blocks, a sub_block_group
can be used.
# lib/soc/eagle_m352.rb
def initialize(options={})
sub_block_group :memories do
sub_block :memory0, class_name: "MEMORY_128_B954"
sub_block :memory1, class_name: "MEMORY_64_B954"
sub_block :memory2, class_name: "MEMORY_32_B954"
end
end
$dut = SOC::EAGLE_M352.new
$dut.memory0.class # MEMORY_128_B954
$dut.memory1.class # MEMORY_64_B954
$dut.memory2.class # MEMORY_32_B954
$dut.memories.class # Array (Array of all 3 memory sub_blocks)
# By default uses sub_block_group uses Array class, so all Array methods are available
memories # => [<memory 0 instance>, <memory 1 instance>, <memory 2 instance>]
memories.size # => 3
memories.first # => <memory 0 instance>
In addition, a custom container class can also be used instead of an Array to contain the sub_block objects if desired. To do this, pass the desired container class as follows:
# lib/soc/eagle_m352.rb
def initialize(options={})
sub_block_group :memories, class_name: "MEMORIES_B294" do
sub_block :memory0, class_name: "MEMORY_128_B954"
sub_block :memory1, class_name: "MEMORY_64_B954"
sub_block :memory2, class_name: "MEMORY_32_B954"
end
end
$dut = SOC::EAGLE_M352.new
$dut.memory0.class # MEMORY_128_B954
$dut.memory1.class # MEMORY_64_B954
$dut.memory2.class # MEMORY_32_B954
$dut.memories.class # MEMORIES_B954 (custom class containing all 3 memory sub_blocks)
The recommended way to create a custom container class is to sub-class the Array class, then add additional methods, like this:
# lib/memories.rb
class MEMORIES_B294 < ::Array
# Example of a custom method to print the names of all contained memories
def print_ids
# Since this class is based on Array, we can use all Array methods like 'each'
# on the contained objects
each { |mem| puts mem.id }
end
end
# All conventional Array methods are supported since we sub-classed Array
memories.size # => 3
memories.first # => <memory 0 instance>
memories.map { |mem| mem.id } # => [:memory0, :memory1, :memory2]
# Plus we have our custom method(s)
memories.print_ids
=>
memory0
memory1
memory2
If you do not sub-class Array, then you will need to be sure to define a <<
push type method:
# lib/memories.rb
class MEMORIES_B294
def initialize
@sub_blocks = {}
end
# Example of a custom << method to permit adding sub_blocks to container
# Required for use with sub_block_group
def <<(sub_block)
@sub_blocks[sub_block.id] = sub_block
end
# Example of a custom method to print the names of all contained memories
def print_ids
each { |mem| puts mem.id }
end
# Example of custom 'each' method
def each
@sub_blocks.each_value do |sub_block|
yield sub_block
end
end
end
# Drawback is you only have methods defined in this class.
This process can now be repeated to model the complete design hierarchy, the sub_block
method
can be used within sub-blocks themselves with no limit on depth.
Here are the complete set of initial stub models for the Eagle and it’s NVM module:
# lib/soc/eagle_m352.rb
module SOC
class EAGLE_M352
include Origen::TopLevel
def initialize(options={})
sub_block :nvm, class_name: "NVM_M682"
end
end
end
# lib/nvm/nvm_m682.rb
module NVM
class NVM_M682
include Origen::Model
def initialize(options={})
sub_block :analog, class_name: "ANALOG_T921"
sub_block_group :memories, class_name: "MEMORIES_B294" do
sub_block :memory0, class_name: "MEMORY_128_B954"
sub_block :memory1, class_name: "MEMORY_64_B954"
sub_block :memory2, class_name: "MEMORY_32_B954"
end
sub_block :state_machine, class_name: "CONTROL_D345"
end
end
end
# lib/nvm/analog_t921.rb
module NVM
class ANALOG_T921
include Origen::Model
def initialize(options={})
end
end
end
# lib/nvm/memory_128_b954.rb
module NVM
class MEMORY_128_B954
include Origen::Model
def initialize(options={})
@size_in_kB = 128
end
end
end
# lib/nvm/memory_64_b954.rb
module NVM
class MEMORY_64_B954
include Origen::Model
def initialize(options={})
@size_in_kB = 64
end
end
end
# lib/nvm/memory_32_b954.rb
module NVM
class MEMORY_32_B954
include Origen::Model
def initialize(options={})
@size_in_kB = 32
end
end
end
# lib/memories.rb
class MEMORIES_B294 < ::Array
def print_ids
each { |mem| puts mem.id }
end
end
# file: lib/nvm/control_d345.rb
module NVM
class CONTROL_D345
include Origen::Model
def initialize(options={})
end
end
end
A couple of points are worth noting from the above code:
- The namespace reference is not required when making references to other models/classes within the same namespace.
- Multiple instances of the memory sub-block have been defined. By convention use the
plural for the group name, i.e.
:memories
and singular with an incrementing digit for the sub_blocks contained therein:memory0
,memory1
, etc. - The classes for the various NVM sub-blocks are all empty right now and as such they did not really need to be defined, however these are placeholders for us to go on and add more logic in the future.
Even though we have not yet added any logic to our models they are starting to become useful, for example we can now ask the Eagle how many NVM memory blocks that it has:
$dut = SOC::EAGLE_M352.new
$dut.nvm.memories.size # => 4
Ideally, you will not need to override or inherit blocks. But if you need to make some application specific overrides to methods defined in blocks that were instantiated in another application that you don’t have control over, overriding may be the solution. Similarly, if you need to make an application specific derivative of a block thats been defined in another app, and it doesn’t make sense to define the derivative in that app, then you may want to utilize the inherit functionality.
Override allows you to recreate a block that already existed.
For example, lets say that your dut included a sub block called ‘dummy_block’ of class
OtherApp::Block::BlockA
which was created in another app, and it instantiated a sub block
called ‘dummy_sub’ of class OtherApp::Block::BlockA::Sub1
.
# file: app/blocks/dut/my_dut/sub_blocks.rb
sub_block :dummy_block, class_name: 'OtherApp::Block::BlockA'
Which you can now access dummy_sub via dut.dummy_block.dummy_sub
. But now lets say that your
application needs a different sub block under dummy block, MyApp::DUT::MyDut::Sub2
.
You could override the existing dut.dummy_block.dummy_sub
like so:
# file: app/blocks/dut/my_dut/sub_blocks.rb
sub_block :dummy_block, class_name: 'OtherApp::Block::BlockA'
dummy_block.sub_block :dummy_sub, class_name: 'MyApp::DUT::MyDut::Sub2', override: true
skip_require_files
When overriding, previously instantiated subblocks have already initialzed some namespaces, which can result in some funny constant behavior:
NamespaceA::B::C
=> NameSpaceX::Y::Z
To get around this the override feature will require the relevant block files which returns the constant behavior back to sanity:
NamespaceA::B::C
=> NameSpaceA::B::C
However, there are many block files which aren’t meant to be required directly (e.g attributes.rb) as they could contain method calls which wouldn’t be available at the time they are required. So by default, these kinds of block files are skipped. Specifically: attributes.rb, parameters.rb, pins.rb, registers.rb, sub_blocks.rb, and timesets.rb
If you have other block files in your modeling that you need require to skip, you can do so by passing an array of files (without the extension) to the sub_block method as an option of skip_require_files
:
dummy_block.sub_block :dummy_sub, class_name: 'MyApp::DUT::MyDut::Sub2', override: true, skip_require_files: %w(file1 file2 file3)
Note however that you’ll likely need to include the files that are skipped by default in your custom skip_require_files option as this will override the default array.
Continuing the above example from Override, lets now say that you want your new
MyApp::DUT::MyDut::Sub2
sub block to inherit OtherApp::Block::BlockA::Sub1
as a starting point.
You can already mostly accomplish this the same way that derivatives operate by setting your class
definitions in your model.rb and controller.rb files of MyApp::DUT::MyDut::Sub2
to inherit the class
of OtherApp::Block::BlockA::Sub1
’s model.rb and controller.rb:
# file: app/blocks/dut/my_dut/sub_blocks/sub2/model.rb
class MyAPP::DUT::MyDut::Sub2 < OtherApp::Block::BlockA::Sub1
# file: app/blocks/dut/my_dut/sub_blocks/sub2/controller.rb
class MyAPP::DUT::MyDut::Sub2Controller < OtherApp::Block::BlockA::Sub1Controller
But what this does not do, is inherit the block files (attributes.rb, parameters.rb, etc…) associated
with OtherApp::Block::BlockA::Sub1
. To do that, you can also pass the inherit option to sub_block
, which
will instruct the block loader to pick up those files as well:
# file: app/blocks/dut/my_dut/sub_blocks.rb
sub_block :dummy_block, class_name: 'OtherApp::Block::BlockA'
dummy_block.sub_block :dummy_sub, class_name: 'MyApp::DUT::MyDut::Sub2', override: true, inherit: 'OtherApp::Block::BlockA'
Bug and Feature inheritance
By default, if the inherit option is passed it will attempt to propagate any features and bugs from the class specified in the inherit option to the sub block being defined. If you’d like to prevent either from happening, then you can pass either disable_bug_inheritance and/or disable_feature_inheritance options to the sub_block method:
dummy_block.sub_block :dummy_sub, class_name: 'MyApp::DUT::MyDut::Sub2', override: true, inherit: 'OtherApp::Block::BlockA', disable_bug_inheritance: true, disable_feature_inheritance: true