-
1
module CrossOrigen
-
1
class IpXact < XMLDoc
-
1
AddressSpace = Struct.new(:name, :range, :width)
-
-
1
MemoryMaps = Struct.new(:name, :address_blocks)
-
-
1
AddressBlock = Struct.new(:name, :base_address, :range, :width)
-
-
# Create a shorthand way to reference Origen Core's Bit ACCESS_CODES
-
1
@@access_hash = Origen::Registers::Bit.const_get(:ACCESS_CODES)
-
-
# Import/reader that currently only supports creating registers and bit fields
-
1
def import(file, options = {}) # rubocop:disable CyclomaticComplexity
-
17
require 'kramdown'
-
-
17
filename = Pathname.new(file).basename('.*').to_s
-
-
17
unless options[:refresh] || CrossOrigen.refresh?
-
15
return if owner.import(filename, allow_missing: true)
-
end
-
-
2
model = CrossOrigen::Model.new
-
-
2
address_spaces = {}
-
-
2
doc(file, options) do |doc|
-
2
doc.xpath('//spirit:addressSpaces/spirit:addressSpace').each do |addr_space|
-
name = fetch addr_space.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true
-
range = fetch addr_space.at_xpath('spirit:range'), get_text: true, to_dec: true
-
width = fetch addr_space.at_xpath('spirit:width'), get_text: true, to_i: true
-
address_spaces[name] = AddressSpace.new(name, range, width)
-
end
-
2
open_memory_map(doc) do |mem_map|
-
2
if mem_map
-
2
mem_map_name = fetch mem_map.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true
-
2
if mem_map_name.to_s.empty?
-
mem_map_obj = model
-
else
-
2
model.sub_block mem_map_name
-
2
mem_map_obj = model.send(mem_map_name)
-
end
-
2
addr_blocks = mem_map.xpath('spirit:addressBlock')
-
else
-
mem_map_obj = model
-
addr_blocks = doc.xpath('//spirit:addressBlock')
-
end
-
2
addr_blocks.each do |addr_block|
-
2
name = fetch addr_block.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true
-
2
base_address = fetch addr_block.at_xpath('spirit:baseAddress'), get_text: true, to_dec: true
-
2
range = fetch addr_block.at_xpath('spirit:range'), get_text: true, to_dec: true
-
2
width = fetch addr_block.at_xpath('spirit:width'), get_text: true, to_i: true
-
2
if name.to_s.empty?
-
2
addr_block_obj = mem_map_obj
-
else
-
mem_map_obj.sub_block name, base_address: base_address, range: range, lau: width
-
addr_block_obj = mem_map_obj.send(name)
-
end
-
2
addr_block.xpath('spirit:register').each do |register|
-
4
name = fetch register.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true
-
4
size = fetch register.at_xpath('spirit:size'), get_text: true, to_i: true
-
4
addr_offset = fetch register.at_xpath('spirit:addressOffset'), get_text: true, to_dec: true
-
4
access = fetch register.at_xpath('spirit:access'), get_text: true
-
# Determine if a reset is defined for the register
-
4
if register.at_xpath('spirit:reset').nil?
-
# If a reset does not exist, need to set the reset_value to 0, as Origen does not (yet) have a concept
-
# of a register without a reset.
-
reset_value = 0
-
else
-
# If a reset exists, determine the reset_value (required) and reset_mask (if defined)
-
4
reset_value = fetch register.at_xpath('spirit:reset/spirit:value'), get_text: true, to_dec: true
-
4
reset_mask = fetch register.at_xpath('spirit:reset/spirit:mask'), get_text: true, to_dec: true
-
# Issue #8 fix - reset_mask is optional, keep reset value as imported when a mask is not defined.
-
# Only perform AND-ing if mask is defined. Only zero-out the reset_value if reset_value was nil.
-
4
if reset_value.nil?
-
# Set default for reset_value attribute if none was provided and issue a warning.
-
reset_value = 0
-
Origen.log.warning "Register #{name.upcase} was defined as having a reset, but did not have a defined reset value. This is not compliant with IP-XACT standard."
-
Origen.log.warning "The reset value for #{name.upcase} has been defined as 0x0 as a result."
-
4
elsif reset_mask.nil?
-
# If mask is undefined, leave reset_value alone.
-
else
-
# Do a logical bitwise AND with the reset value and mask
-
4
reset_value = reset_value & reset_mask
-
end
-
end
-
# Future expansion: pull in HDL path as abs_path in Origen.
-
4
addr_block_obj.reg name, addr_offset, size: size, access: access, description: reg_description(register) do |reg|
-
4
register.xpath('spirit:field').each do |field|
-
60
name = fetch field.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true
-
60
bit_offset = fetch field.at_xpath('spirit:bitOffset'), get_text: true, to_i: true
-
60
bit_width = fetch field.at_xpath('spirit:bitWidth'), get_text: true, to_i: true
-
60
xml_access = fetch field.at_xpath('spirit:access'), get_text: true
-
# Newer IP-XACT standards list access as < read or write>-< descriptor >, such as
-
# "read-write", "read-only", or "read-writeOnce"
-
60
if xml_access =~ /\S+\-\S+/ || xml_access == 'writeOnce'
-
# This filter alone is not capable of interpreting the 1685-2009 (and 2014). Therefore
-
# must reverse-interpret the content of access_hash (see top of file).
-
#
-
# First get the base access type, ie: read-write, read-only, etc.
-
# base_access = fetch field.at_xpath('spirit:access'), get_text: true
-
30
base_access = xml_access
-
# Next grab any modified write values or read actions
-
30
mod_write = fetch field.at_xpath('spirit:modifiedWriteValue'), get_text: true
-
30
read_action = fetch field.at_xpath('spirit:readAction'), get_text: true
-
# Using base_access, mod_write, and read_action, look up the corresponding access
-
# acronym from access_hash, noting it is not possible to differentiate write-only
-
# from write-only, read zero and read-write from dc.
-
#
-
# Matched needs to be tracked, as there is no way to differentiate :rw and :dc in IP-XACT.
-
# Everything imported will default to :rw, never :dc.
-
30
matched = false
-
30
@@access_hash.each_key do |key|
-
840
if @@access_hash[key][:base] == base_access && @@access_hash[key][:write] == mod_write && @@access_hash[key][:read] == read_action && !matched
-
30
access = key.to_sym
-
30
matched = true
-
end
-
end
-
# Older IP-XACT standards appear to also accept short acronyms like "ro", "w1c", "rw",
-
# etc.
-
30
elsif xml_access =~ /\S+/
-
30
access = xml_access.downcase.to_sym
-
else
-
# default to read-write if access is not specified
-
access = :rw
-
end
-
60
range = nil
-
60
if bit_width == 1
-
58
range = bit_offset
-
else
-
2
range = (bit_offset + bit_width - 1)..bit_offset
-
end
-
60
reg.bit range, name, reset: reset_value[range], access: access, description: bit_description(field)
-
end
-
end
-
end
-
end
-
end
-
end
-
2
model.export(filename, include_timestamp: CrossOrigen.include_timestamp?)
-
2
owner.import(filename, options)
-
end
-
-
1
def doc(path, options = {})
-
# If a fragment of IP-XACT is given, then wrap it with a valid header and we will try our best
-
2
if options[:fragment]
-
require 'nokogiri'
-
-
content = %(
-
<?xml version="1.0"?>
-
<spirit:component xmlns:spirit="https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4"
-
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation="$REGMEM_HOME/builder/ipxact/schema/ipxact
-
$REGMEM_HOME/builder/ipxact/schema/ipxact/index.xsd">
-
#{File.read(path)}
-
</spirit:component>
-
)
-
yield Nokogiri::XML(content)
-
else
-
2
super
-
end
-
end
-
-
# Returns a string representing the owner object in IP-XACT XML
-
# Usable / Available options:
-
# :vendor = Company name/web address, ex: 'nxp.com'
-
# :library = IP Library
-
# :schema = '1685-2009' or default of Spirit 1.4 (when no :schema option passed)
-
# :bus_interface = only 'AMBA3' supported at this time
-
# :mmap_name = Optionally set the memoryMap name to something other than the module name
-
# :mmap_ref = memoryMapRef name, ex: 'UserMap'
-
# :addr_block_name = addressBlock -> Name, ex: 'ATX'
-
1
def owner_to_xml(options = {})
-
4
require 'nokogiri'
-
-
options = {
-
4
include_bit_field_values: true
-
}.merge(options)
-
-
4
@format = options[:format]
-
-
# Compatible schemas: Spirit 1.4, 1685-2009
-
# Assume Spirit 1.4 if no schema provided
-
4
if options[:schema] == '1685-2009' # Magillem tool uses alternate schema
-
schemas = [
-
2
'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009',
-
'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009/index.xsd'
-
]
-
else # Assume Spirit 1.4 if not
-
schemas = [
-
2
'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4',
-
'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4/index.xsd'
-
]
-
end
-
-
4
if uvm? && !(options[:schema] == '1685-2009')
-
1
schemas << '$IREG_GEN/XMLSchema/SPIRIT/VendorExtensions.xsd'
-
end
-
-
4
if options[:schema] == '1685-2009' # Magillem tool uses alternate schema
-
headers = {
-
2
'xmlns:spirit' => 'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009',
-
'xmlns:xsi' => 'https://www.w3.org/2001/XMLSchema-instance',
-
'xsi:schemaLocation' => schemas.join(' ')
-
}
-
else # Assume Spirit 1.4 if not
-
headers = {
-
2
'xmlns:spirit' => 'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4',
-
'xmlns:xsi' => 'https://www.w3.org/2001/XMLSchema-instance',
-
'xsi:schemaLocation' => schemas.join(' ')
-
}
-
end
-
-
4
if uvm? && !(options[:schema] == '1685-2009')
-
1
headers['xmlns:vendorExtensions'] = '$IREG_GEN/XMLSchema/SPIRIT'
-
# Else:
-
# Do nothing ?
-
# headers['xmlns:vendorExtensions'] = '$UVM_RGM_HOME/builder/ipxact/schema'
-
end
-
-
4
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
-
4
spirit = xml['spirit']
-
4
spirit.component(headers) do
-
4
spirit.vendor options[:vendor] || 'Origen'
-
4
spirit.library options[:library] || 'Origen'
-
# I guess this should really be the register owner's owner's name?
-
4
spirit.name try(:ip_name) || owner.class.to_s.split('::').last
-
4
spirit.version try(:ip_version, :version, :revision)
-
# The 1685-2009 schema allows for a bus interface. AMBA3 (slave) supported so far.
-
4
if options[:schema] == '1685-2009'
-
2
if options[:bus_interface] == 'AMBA3'
-
1
spirit.busInterfaces do
-
1
spirit.busInterface do
-
1
spirit.name 'Slave'
-
bustype_header = {
-
1
'spirit:vendor' => options[:vendor] || 'Origen',
-
'spirit:library' => 'amba3',
-
'spirit:name' => 'APB3',
-
'spirit:version' => '1.0'
-
}
-
1
xml['spirit'].busType bustype_header
-
1
spirit.slave do
-
mmapref_header = {
-
1
'spirit:memoryMapRef' => options[:mmap_ref] || 'APB'
-
}
-
1
xml['spirit'].memoryMapRef mmapref_header
-
end
-
end
-
end
-
end
-
end
-
4
spirit.memoryMaps do
-
4
memory_maps.each do |map_name, _map|
-
4
spirit.memoryMap do
-
# Optionally assign memory map name to something other than the module name in Ruby,
-
# default to 'RegisterMap'
-
4
spirit.name options[:mmap_name] || 'RegisterMap'
-
4
address_blocks do |domain_name, _domain, sub_block|
-
10
spirit.addressBlock do
-
# When registers reside at the top level, do not assign an address block name
-
10
if sub_block == owner
-
4
if options[:addr_block_name].nil?
-
4
spirit.name nil
-
else
-
spirit.name options[:addr_block_name]
-
end
-
else
-
6
spirit.name address_block_name(domain_name, sub_block)
-
end
-
10
spirit.baseAddress sub_block.base_address.to_hex
-
10
spirit.range range(sub_block)
-
10
spirit.width width(sub_block)
-
10
sub_block.regs.each do |name, reg|
-
# Required for now to ensure that the current value is the reset value
-
30
reg.reset
-
30
spirit.register do
-
30
spirit.name name
-
30
spirit.description try(reg, :name_full, :full_name)
-
30
spirit.addressOffset reg.offset.to_hex
-
30
spirit.size reg.size
-
30
if reg.bits.any?(&:writable?)
-
26
spirit.access 'read-write'
-
else
-
4
spirit.access 'read-only'
-
end
-
30
spirit.reset do
-
30
spirit.value reg.data.to_hex
-
30
spirit.mask mask(reg).to_hex
-
end
-
30
reg.named_bits do |name, bits|
-
162
spirit.field do
-
162
spirit.name name
-
162
spirit.description try(bits, :brief_description, :name_full, :full_name)
-
162
spirit.bitOffset bits.position
-
162
spirit.bitWidth bits.size
-
# When exporting to 1685-2009 schema, need to handle special cases (writeOnce),
-
# modifiedWriteValue, and readAction fields.
-
162
if options[:schema] == '1685-2009'
-
81
if bits.writable? && bits.readable?
-
58
if bits.access == :w1
-
2
spirit.access 'read-writeOnce'
-
else
-
56
spirit.access 'read-write'
-
end
-
23
elsif bits.writable?
-
11
if bits.access == :wo1
-
2
spirit.access 'writeOnce'
-
else
-
9
spirit.access 'write-only'
-
end
-
12
elsif bits.readable?
-
12
spirit.access 'read-only'
-
end
-
81
if bits.readable?
-
70
unless @@access_hash[bits.access][:read].nil?
-
22
spirit.readAction @@access_hash[bits.access][:read]
-
end
-
end
-
81
if bits.writable?
-
69
unless @@access_hash[bits.access][:write].nil?
-
32
spirit.modifiedWriteValue @@access_hash[bits.access][:write]
-
end
-
end
-
else # Assume Spirit 1.4 if not
-
81
spirit.access bits.access
-
end
-
# HDL paths provide hooks for a testbench to directly manipulate the
-
# registers without having to go through a bus interface or read/write
-
# protocol. Because the hierarchical path to a register block can vary
-
# greatly between devices, allow the user to provide an abs_path value
-
# and define "full_reg_path" to assist.
-
#
-
# When registers reside at the top level without a specified path, use 'top'.
-
162
if reg.owner.path.nil? || reg.owner.path.empty?
-
8
regpath = 'top'
-
else
-
154
regpath = reg.owner.path
-
end
-
# If :full_reg_path is defined, the :abs_path metadata for a register will
-
# be used for regpath. This can be assigned at an address block (sub-block)
-
# level.
-
162
unless options[:full_reg_path].nil? == true
-
regpath = reg.path
-
end
-
162
if options[:schema] == '1685-2009'
-
81
spirit.parameters do
-
81
spirit.parameter do
-
81
spirit.name '_hdlPath_'
-
# HDL path needs to be to the declared bit field name, NOT to the bus slice
-
# that Origen's "abs_path" will yield. Ex:
-
#
-
# ~~~ ruby
-
# reg :myreg, 0x0, size: 32 do |reg|
-
# bits 7..4, :bits_high
-
# bits 3..0, :bits_low
-
# end
-
# ~~~
-
#
-
# The abs_path to ...regs(:myreg).bits(:bits_low).abs_path will yield
-
# "myreg.myreg[3:0]", not "myreg.bits_low". This is not an understood path
-
# in Origen (myreg[3:0] does not exist in either myreg's RegCollection or BitCollection),
-
# and does not sync with how RTL would create bits_low[3:0].
-
# Therefore, use the path to "myreg"'s owner appended with bits.name (bits_low here).
-
#
-
# This can be done in a register or sub_blocks definition by defining register
-
# metadata for "abs_path". If the reg owner's path weren't used, but instead the
-
# reg's path, that would imply each register was a separate hierarchical path in
-
# RTL (ex: "top.myblock.regblock.myreg.myreg_bits"), which is normally not the case.
-
# The most likely path would be "top.myblock.regblock.myreg_bits.
-
81
spirit.value "#{regpath}.#{bits.name}"
-
end
-
end
-
end
-
# C. Hume - Unclear which vendorExtensions should be included by default, if any.
-
# Future improvment: Allow passing of vendorExtensions enable & value hash/string
-
# if options[:schema] == '1685-2009'
-
# spirit.vendorExtensions do
-
# vendorext = { 'xmlns:vendorExtensions' => '$UVM_RGM_HOME/builder/ipxact/schema' }
-
# xml['vendorExtensions'].hdl_path vendorext, "#{reg.path}.#{bits.name}"
-
# end
-
# end
-
-
# Allow optional inclusion of bit field values and descriptions
-
162
if options[:include_bit_field_values]
-
162
if bits.bit_value_descriptions[0]
-
4
bits.bit_value_descriptions.each do |val, desc|
-
8
spirit.values do
-
8
spirit.value val.to_hex
-
8
spirit.name "val_#{val.to_hex}"
-
8
spirit.description desc
-
end
-
end
-
end
-
end
-
162
if uvm? && !(options[:schema] == '1685-2009')
-
51
spirit.vendorExtensions do
-
51
xml['vendorExtensions'].hdl_path "#{regpath}.#{bits.name}"
-
end
-
end
-
end
-
end
-
end
-
end
-
# Unclear whether addressBlock vendor extensions are supported in Spirit 1.4
-
# if uvm?
-
# spirit.vendorExtensions do
-
# xml['vendorExtensions'].hdl_path sub_block.path(relative_to: owner)
-
# end
-
# end
-
end
-
end
-
# Assume byte addressing if not specified
-
4
if owner.methods.include?(:lau) == false
-
4
if methods.include?(:lau) == true
-
spirit.addressUnitBits lau
-
else
-
4
spirit.addressUnitBits 8
-
end
-
else
-
spirit.addressUnitBits owner.lau
-
end
-
end
-
end
-
end
-
end
-
end
-
# When testing with 'origen examples', travis_ci (bash) will end up with empty tags -
-
# '<spirit:description/>' that do not appear on some user's tshell environments. To
-
# prevent false errors for this issue, force Nokogiri to use self-closing tags
-
# ('<spirit:description></spirit:description>'), but keep the XML formatted for readability.
-
# All tags with no content will appear as '<spirit:tag_name></spirit:tag_name>'.
-
#
-
4
builder.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
-
Nokogiri::XML::Node::SaveOptions::FORMAT)
-
end
-
-
1
private
-
-
1
def open_memory_map(doc)
-
2
maps = doc.xpath('//spirit:memoryMaps/spirit:memoryMap')
-
2
maps = [nil] if maps.empty?
-
2
maps.each do |mem_map|
-
2
yield mem_map
-
end
-
end
-
-
1
def reg_description(register)
-
4
fetch register.at_xpath('spirit:description'), get_text: true, whitespace: true
-
end
-
-
1
def bit_description(bit)
-
60
desc = fetch(bit.at_xpath('spirit:description'), get_text: true, whitespace: true) || ''
-
60
bit_val_present = false
-
60
bit.xpath('spirit:values').each do |val|
-
4
unless bit_val_present
-
2
desc += "\n"
-
2
bit_val_present = true
-
end
-
4
value = extract(val, 'spirit:value', format: :integer, hex: true)
-
4
value_desc = extract val, 'spirit:description'
-
4
if value && value_desc
-
4
desc += "\n#{value.to_s(2)} | #{value_desc}"
-
end
-
end
-
60
desc
-
end
-
-
1
def mask(reg)
-
30
m = 0
-
30
reg.size.times do |i|
-
464
unless reg[i].reset_val == :undefined
-
464
m |= (1 << i)
-
end
-
end
-
30
m
-
end
-
-
1
def uvm?
-
170
@format == :uvm
-
end
-
-
1
def memory_maps
-
4
{ nil => {} }
-
end
-
-
1
def sub_blocks(domain_name)
-
4
owner.all_sub_blocks.select do |sub_block|
-
8
sub_block.owns_registers? &&
-
6
(sub_block.domains[domain_name] || domain_name == :default)
-
end
-
end
-
-
1
def address_blocks
-
4
domains = owner.register_domains
-
4
domains = { default: {} } if domains.empty?
-
4
domains.each do |domain_name, domain|
-
4
if owner.owns_registers?
-
4
yield domain_name, domain, owner
-
end
-
4
sub_blocks(domain_name).each do |sub_block|
-
6
yield domain_name, domain, sub_block
-
end
-
end
-
end
-
-
1
def address_block_name(domain_name, sub_block)
-
6
if domain_name == :default
-
6
sub_block.name.to_s
-
else
-
"#{domain_name}_#{sub_block.name}"
-
end
-
end
-
-
1
def width(sub_block)
-
20
sub_block.try(:width) || 32
-
end
-
-
1
def range(sub_block)
-
10
range = sub_block.try(:range) || begin
-
# This is to work around an Origen bug where max_address_reg_size is not updated in the case of
-
# only one register being present
-
# TODO: Fix in Origen
-
10
max_address_reg_size = sub_block.max_address_reg_size || sub_block.regs.first[1].size
-
10
(sub_block.max_reg_address + (max_address_reg_size / 8))
-
end
-
10
width_in_bytes = width(sub_block) / 8
-
10
if range % width_in_bytes != 0
-
4
range += (width_in_bytes - (range % width_in_bytes))
-
end
-
10
range
-
end
-
end
-
end