Class: CrossOrigen::IpXact

Inherits:
XMLDoc
  • Object
show all
Defined in:
lib/cross_origen/ip_xact.rb

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

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, options = {})
  # If a fragment of IP-XACT is given, then wrap it with a valid header and we will try our best
  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
    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, options = {}) # rubocop:disable CyclomaticComplexity
  require 'kramdown'

  filename = Pathname.new(file).basename('.*').to_s

  unless options[:refresh] || CrossOrigen.refresh?
    return if owner.import(filename, allow_missing: true)
  end

  model = CrossOrigen::Model.new

  address_spaces = {}

  doc(file, options) 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.include_timestamp?)
  owner.import(filename, options)
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(options = {})
  require 'nokogiri'

  options = {
    include_bit_field_values: true
  }.merge(options)

  @format = options[:format]

  # Compatible schemas: Spirit 1.4, 1685-2009
  # Assume Spirit 1.4 if no schema provided
  if options[: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? && !(options[:schema] == '1685-2009')
    schemas << '$IREG_GEN/XMLSchema/SPIRIT/VendorExtensions.xsd'
  end

  if options[: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? && !(options[: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 options[:vendor] || 'Origen'
      spirit.library options[: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 options[:schema] == '1685-2009'
        if options[:bus_interface] == 'AMBA3'
          spirit.busInterfaces do
            spirit.busInterface do
              spirit.name 'Slave'
              bustype_header = {
                'spirit:vendor'  => options[:vendor] || 'Origen',
                'spirit:library' => 'amba3',
                'spirit:name'    => 'APB3',
                'spirit:version' => '1.0'
              }
              xml['spirit'].busType bustype_header
              spirit.slave do
                mmapref_header = {
                  'spirit:memoryMapRef' => options[: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 options[: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 options[:addr_block_name].nil?
                    spirit.name nil
                  else
                    spirit.name options[: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 options[: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 options[:full_reg_path].nil? == true
                          regpath = reg.path
                        end
                        if options[: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 options[: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? && !(options[: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