Class: CrossOrigen::IpXact
Defined Under Namespace
Classes: AddressBlock, AddressSpace, MemoryMaps
Constant Summary collapse
- @@access_hash =
Create a shorthand way to reference Origen Core's Bit ACCESS_CODES
Origen::Registers::Bit.const_get(:ACCESS_CODES)
Constants inherited from XMLDoc
XMLDoc::HTML_SANITIZATION_CONFIG, XMLDoc::HTML_TRANSFORMER, XMLDoc::HTML_TRANSFORMS
Instance Attribute Summary
Attributes inherited from XMLDoc
#creation_info, #import_info, #owner
Instance Method Summary collapse
- #doc(path, options = {}) ⇒ Object
-
#import(file, options = {}) ⇒ Object
Import/reader that currently only supports creating registers and bit fields.
-
#owner_to_xml(options = {}) ⇒ Object
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'.
Methods inherited from XMLDoc
#extract, #fetch, #initialize, #pre_sanitize, #to_html, #to_markdown, #try
Constructor Details
This class inherits a constructor from CrossOrigen::XMLDoc
Instance Method Details
#doc(path, options = {}) ⇒ Object
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/cross_origen/ip_xact.rb', line 143 def doc(path, = {}) # If a fragment of IP-XACT is given, then wrap it with a valid header and we will try our best if [: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 super end end |
#import(file, options = {}) ⇒ Object
Import/reader that currently only supports creating registers and bit fields
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/cross_origen/ip_xact.rb', line 13 def import(file, = {}) # rubocop:disable CyclomaticComplexity require 'kramdown' filename = Pathname.new(file).basename('.*').to_s unless [:refresh] || CrossOrigen.refresh? return if owner.import(filename, allow_missing: true) end model = CrossOrigen::Model.new address_spaces = {} doc(file, ) do |doc| 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 open_memory_map(doc) do |mem_map| if mem_map mem_map_name = fetch mem_map.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true if mem_map_name.to_s.empty? mem_map_obj = model else model.sub_block mem_map_name mem_map_obj = model.send(mem_map_name) end addr_blocks = mem_map.xpath('spirit:addressBlock') else mem_map_obj = model addr_blocks = doc.xpath('//spirit:addressBlock') end addr_blocks.each do |addr_block| name = fetch addr_block.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true base_address = fetch addr_block.at_xpath('spirit:baseAddress'), get_text: true, to_dec: true range = fetch addr_block.at_xpath('spirit:range'), get_text: true, to_dec: true width = fetch addr_block.at_xpath('spirit:width'), get_text: true, to_i: true if name.to_s.empty? 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 addr_block.xpath('spirit:register').each do |register| name = fetch register.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true size = fetch register.at_xpath('spirit:size'), get_text: true, to_i: true addr_offset = fetch register.at_xpath('spirit:addressOffset'), get_text: true, to_dec: true access = fetch register.at_xpath('spirit:access'), get_text: true # Determine if a reset is defined for the register 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) reset_value = fetch register.at_xpath('spirit:reset/spirit:value'), get_text: true, to_dec: true 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. 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." elsif reset_mask.nil? # If mask is undefined, leave reset_value alone. else # Do a logical bitwise AND with the reset value and mask reset_value = reset_value & reset_mask end end # Future expansion: pull in HDL path as abs_path in Origen. addr_block_obj.reg name, addr_offset, size: size, access: access, description: reg_description(register) do |reg| register.xpath('spirit:field').each do |field| name = fetch field.at_xpath('spirit:name'), downcase: true, to_sym: true, get_text: true bit_offset = fetch field.at_xpath('spirit:bitOffset'), get_text: true, to_i: true bit_width = fetch field.at_xpath('spirit:bitWidth'), get_text: true, to_i: true 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" 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 base_access = xml_access # Next grab any modified write values or read actions mod_write = fetch field.at_xpath('spirit:modifiedWriteValue'), get_text: true 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. matched = false @@access_hash.each_key do |key| if @@access_hash[key][:base] == base_access && @@access_hash[key][:write] == mod_write && @@access_hash[key][:read] == read_action && !matched access = key.to_sym matched = true end end # Older IP-XACT standards appear to also accept short acronyms like "ro", "w1c", "rw", # etc. elsif xml_access =~ /\S+/ access = xml_access.downcase.to_sym else # default to read-write if access is not specified access = :rw end range = nil if bit_width == 1 range = bit_offset else range = (bit_offset + bit_width - 1)..bit_offset end reg.bit range, name, reset: reset_value[range], access: access, description: bit_description(field) end end end end end end model.export(filename, include_timestamp: CrossOrigen.) owner.import(filename, ) end |
#owner_to_xml(options = {}) ⇒ Object
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'
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/cross_origen/ip_xact.rb', line 172 def owner_to_xml( = {}) require 'nokogiri' = { include_bit_field_values: true }.merge() @format = [:format] # Compatible schemas: Spirit 1.4, 1685-2009 # Assume Spirit 1.4 if no schema provided if [:schema] == '1685-2009' # Magillem tool uses alternate schema schemas = [ '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 = [ 'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4', 'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4/index.xsd' ] end if uvm? && !([:schema] == '1685-2009') schemas << '$IREG_GEN/XMLSchema/SPIRIT/VendorExtensions.xsd' end if [:schema] == '1685-2009' # Magillem tool uses alternate schema headers = { '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 = { 'xmlns:spirit' => 'https://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4', 'xmlns:xsi' => 'https://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation' => schemas.join(' ') } end if uvm? && !([:schema] == '1685-2009') headers['xmlns:vendorExtensions'] = '$IREG_GEN/XMLSchema/SPIRIT' # Else: # Do nothing ? # headers['xmlns:vendorExtensions'] = '$UVM_RGM_HOME/builder/ipxact/schema' end builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| spirit = xml['spirit'] spirit.component(headers) do spirit.vendor [:vendor] || 'Origen' spirit.library [:library] || 'Origen' # I guess this should really be the register owner's owner's name? spirit.name try(:ip_name) || owner.class.to_s.split('::').last spirit.version try(:ip_version, :version, :revision) # The 1685-2009 schema allows for a bus interface. AMBA3 (slave) supported so far. if [:schema] == '1685-2009' if [:bus_interface] == 'AMBA3' spirit.busInterfaces do spirit.busInterface do spirit.name 'Slave' bustype_header = { 'spirit:vendor' => [:vendor] || 'Origen', 'spirit:library' => 'amba3', 'spirit:name' => 'APB3', 'spirit:version' => '1.0' } xml['spirit'].busType bustype_header spirit.slave do mmapref_header = { 'spirit:memoryMapRef' => [:mmap_ref] || 'APB' } xml['spirit'].memoryMapRef mmapref_header end end end end end spirit.memoryMaps do memory_maps.each do |map_name, _map| spirit.memoryMap do # Optionally assign memory map name to something other than the module name in Ruby, # default to 'RegisterMap' spirit.name [:mmap_name] || 'RegisterMap' address_blocks do |domain_name, _domain, sub_block| spirit.addressBlock do # When registers reside at the top level, do not assign an address block name if sub_block == owner if [:addr_block_name].nil? spirit.name nil else spirit.name [:addr_block_name] end else spirit.name address_block_name(domain_name, sub_block) end spirit.baseAddress sub_block.base_address.to_hex spirit.range range(sub_block) spirit.width width(sub_block) sub_block.regs.each do |name, reg| # Required for now to ensure that the current value is the reset value reg.reset spirit.register do spirit.name name spirit.description try(reg, :name_full, :full_name) spirit.addressOffset reg.offset.to_hex spirit.size reg.size if reg.bits.any?(&:writable?) spirit.access 'read-write' else spirit.access 'read-only' end spirit.reset do spirit.value reg.data.to_hex spirit.mask mask(reg).to_hex end reg.named_bits do |name, bits| spirit.field do spirit.name name spirit.description try(bits, :brief_description, :name_full, :full_name) spirit.bitOffset bits.position spirit.bitWidth bits.size # When exporting to 1685-2009 schema, need to handle special cases (writeOnce), # modifiedWriteValue, and readAction fields. if [:schema] == '1685-2009' if bits.writable? && bits.readable? if bits.access == :w1 spirit.access 'read-writeOnce' else spirit.access 'read-write' end elsif bits.writable? if bits.access == :wo1 spirit.access 'writeOnce' else spirit.access 'write-only' end elsif bits.readable? spirit.access 'read-only' end if bits.readable? unless @@access_hash[bits.access][:read].nil? spirit.readAction @@access_hash[bits.access][:read] end end if bits.writable? unless @@access_hash[bits.access][:write].nil? spirit.modifiedWriteValue @@access_hash[bits.access][:write] end end else # Assume Spirit 1.4 if not 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'. if reg.owner.path.nil? || reg.owner.path.empty? regpath = 'top' else 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. unless [:full_reg_path].nil? == true regpath = reg.path end if [:schema] == '1685-2009' spirit.parameters do spirit.parameter do 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. 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 if [:include_bit_field_values] if bits.bit_value_descriptions[0] bits.bit_value_descriptions.each do |val, desc| spirit.values do spirit.value val.to_hex spirit.name "val_#{val.to_hex}" spirit.description desc end end end end if uvm? && !([:schema] == '1685-2009') spirit.vendorExtensions do 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 if owner.methods.include?(:lau) == false if methods.include?(:lau) == true spirit.addressUnitBits lau else 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>'. # builder.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS | Nokogiri::XML::Node::SaveOptions::FORMAT) end |