cross_origen 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ require 'kramdown'
2
+ require 'sanitize'
3
+
4
+ module CrossOrigen
5
+ # This is the base class of all doc formats that are
6
+ # XML based
7
+ class XMLDoc
8
+ CreationInfo = Struct.new(:author, :date, :revision, :source)
9
+
10
+ ImportInfo = Struct.new(:name, :date)
11
+
12
+ attr_accessor :creation_info, :import_info
13
+
14
+ # These (in many cases illegal) tags will be forced to their valid equivalents
15
+ # These will be executed in the defined order, so for later xfrms you can for example
16
+ # assume that all 'rows' have already been converted to 'tr'
17
+ # valid equivalents
18
+ HTML_TRANSFORMS = {
19
+ 'table/title' => 'caption',
20
+ 'table//row' => 'tr',
21
+ 'thead//entry' => 'th',
22
+ 'table//entry' => 'td',
23
+ 'td/p' => 'span',
24
+ 'th/p' => 'span'
25
+ }
26
+
27
+ # This can be used to perform additional by-node transformation if required, normally
28
+ # this should be used if transform of a node attribute is required
29
+ HTML_TRANSFORMER = lambda do |env|
30
+ if env[:node_name] == 'td' || env[:node_name] == 'th'
31
+ if env[:node].attr('nameend')
32
+ first = env[:node].attr('namest').sub('col', '').to_i
33
+ last = env[:node].attr('nameend').sub('col', '').to_i
34
+ env[:node].set_attribute('colspan', (last - first + 1).to_s)
35
+ end
36
+ end
37
+ end
38
+
39
+ # Defines the rules for sanitization of any HTML strings that will be converted
40
+ # to markdown for representation within Origen
41
+ HTML_SANITIZATION_CONFIG = {
42
+ # Only these tags will be allowed through, everything else will be stripped
43
+ # Note that this is applied after the transforms listed above
44
+ elements: %w(b em i strong u p ul ol li table tr td th tbody thead),
45
+ attributes: {
46
+ 'td' => ['colspan'],
47
+ 'th' => ['colspan']
48
+ },
49
+ # Not planning to allow any of these right now, but keeping around
50
+ # as an example of how to do so
51
+ #:protocols => {
52
+ # 'a' => {'href' => ['http', 'https', 'mailto']}
53
+ # }
54
+ transformers: HTML_TRANSFORMER
55
+ }
56
+
57
+ # Returns the object that included the CrossOrigen module
58
+ attr_reader :owner
59
+
60
+ def initialize(owner)
61
+ @owner = owner
62
+ @creation_info = CreationInfo.new
63
+ @import_info = ImportInfo.new
64
+ end
65
+
66
+ # Tries the given methods on the owner and returns the first one to return a value,
67
+ # ultimately returns nil if no value is found.
68
+ #
69
+ # To test an object other than the owner pass it as the first argument.
70
+ def try(*methods)
71
+ if methods.first.is_a?(Symbol)
72
+ obj = owner
73
+ else
74
+ obj = methods.shift
75
+ end
76
+ methods.each do |method|
77
+ if obj.respond_to?(method)
78
+ val = obj.send(method)
79
+ return val if val
80
+ end
81
+ end
82
+ nil
83
+ end
84
+
85
+ # This returns the doc wrapped by a Nokogiri doc
86
+ def doc(path, _options = {})
87
+ require 'nokogiri'
88
+
89
+ File.open(path) do |f|
90
+ yield Nokogiri::XML(f)
91
+ end
92
+ end
93
+
94
+ def extract(element, path, options = {})
95
+ options = {
96
+ format: :string,
97
+ hex: false,
98
+ default: nil,
99
+ downcase: false,
100
+ return: :text,
101
+ # A value or array or values which are considered to be nil, if this is the value
102
+ # to be returned then nil will be returned instead
103
+ nil_on: false
104
+ }.merge(options)
105
+ node = element.at_xpath(path)
106
+ if node
107
+ if options[:format] == :string
108
+ str = node.send(options[:return]).strip
109
+ str = str.downcase if options[:downcase]
110
+ if options[:nil_on] && [options[:nil_on]].flatten.include?(str)
111
+ nil
112
+ else
113
+ str
114
+ end
115
+ elsif options[:format] == :integer
116
+ val = node.send(options[:return])
117
+ if val =~ /^0x(.*)/
118
+ Regexp.last_match[1].to_i(16)
119
+ elsif options[:hex]
120
+ val.to_i(16)
121
+ else
122
+ val.to_i(10)
123
+ end
124
+ else
125
+ fail "Unknown format: #{options[:format]}"
126
+ end
127
+ else
128
+ options[:default]
129
+ end
130
+ end
131
+
132
+ # Freescale register descriptions are like the wild west, need to do some pre-screening
133
+ # to approach valid HTML before handing off to other off the shelf sanitizers
134
+ def pre_sanitize(html)
135
+ html = Nokogiri::HTML.fragment(html)
136
+ HTML_TRANSFORMS.each do |orig, new|
137
+ html.xpath(".//#{orig}").each { |node| node.name = new }
138
+ end
139
+ html.to_html
140
+ end
141
+
142
+ # Does its best to convert the given html fragment to markdown
143
+ #
144
+ # The final markdown may still contain some HTML tags, but any weird
145
+ # markup which may break a future markdown -> html conversion will
146
+ # be removed
147
+ def to_markdown(html, _options = {})
148
+ cleaned = html.scrub
149
+ cleaned = pre_sanitize(cleaned)
150
+ cleaned = Sanitize.fragment(cleaned, HTML_SANITIZATION_CONFIG)
151
+ Kramdown::Document.new(cleaned, input: :html).to_kramdown.strip
152
+ rescue
153
+ 'The description could not be imported, the most likely cause of this is that it contained illegal HTML markup'
154
+ end
155
+
156
+ # Convert the given markdown string to HTML
157
+ def to_html(string, _options = {})
158
+ # Escape any " that are not already escaped
159
+ string.gsub!(/([^\\])"/, '\1\"')
160
+ # Escape any ' that are not already escaped
161
+ string.gsub!(/([^\\])'/, %q(\1\\\'))
162
+ html = Kramdown::Document.new(string, input: :kramdown).to_html
163
+ end
164
+
165
+ # fetch an XML snippet passed and extract and format the data
166
+ def fetch(xml, options = {})
167
+ options = {
168
+ type: String,
169
+ downcase: false,
170
+ symbolize: false,
171
+ strip: false,
172
+ squeeze: false,
173
+ squeeze_lines: false,
174
+ rm_specials: false,
175
+ whitespace: false,
176
+ get_text: false,
177
+ to_i: false,
178
+ to_html: false,
179
+ to_bool: false,
180
+ children: false,
181
+ to_dec: false,
182
+ to_f: false,
183
+ underscore: false
184
+ }.update(options)
185
+ options[:symbolize] = options[:to_sym] if options[:to_sym]
186
+ # Check for incompatible options
187
+ xml_orig = xml
188
+ numeric_methods = [:to_i, :to_f, :to_dec]
189
+ if options[:get_text] == true && options[:to_html] == true
190
+ fail 'Cannot use :get_text and :to_html options at the same time, exiting...'
191
+ end
192
+ if options[:symbolize] == true
193
+ fail 'Cannot convert to a number of any type and symbolize at the same time' if numeric_methods.reject { |arg| options[arg] == true }.size < 3
194
+ end
195
+ fail 'Cannot select multiple numeric conversion args at the same time' if numeric_methods.reject { |arg| options[arg] == true }.size < 2
196
+ if xml.nil?
197
+ Origen.log.debug 'XML data is nil!'
198
+ return nil
199
+ end
200
+ xml = xml.text if options[:get_text] == true
201
+ # Sometimes XML snippets get sent as nodes or as Strings
202
+ # Must skip this code if a String as it is designed to change
203
+ # the XML node into a string
204
+ unless xml.is_a? String
205
+ if options[:to_html] == true
206
+ if xml.children
207
+ # If there are children to this XMl node then grab the content there
208
+ if xml.children.empty? || options[:children] == false
209
+ xml = xml.to_html
210
+ else
211
+ xml = xml.children.to_html
212
+ end
213
+ end
214
+ end
215
+ end
216
+ unless xml.is_a? options[:type]
217
+ Origen.log.debug "XML data is not of correct type '#{options[:type]}'"
218
+ Origen.log.debug "xml is \n#{xml}"
219
+ return nil
220
+ end
221
+ if options[:type] == String
222
+ if xml.match(/\s+/) && options[:whitespace] == false
223
+ Origen.log.debug "XML data '#{xml}' cannot have white space"
224
+ return nil
225
+ end
226
+ xml.downcase! if options[:downcase] == true
227
+ xml = xml.underscore if options[:underscore] == true
228
+ xml.strip! if options[:strip] == true
229
+ xml.squeeze!(' ') if options[:squeeze] == true
230
+ xml = xml.squeeze_lines if options[:squeeze_lines] == true
231
+ xml.gsub!(/[^0-9A-Za-z]/, '_') if options[:rm_specials] == true
232
+ if options[:symbolize] == true
233
+ return xml.to_sym
234
+ elsif options[:to_i] == true
235
+ return xml.to_i
236
+ elsif options[:to_dec] == true
237
+ return xml.to_dec
238
+ elsif options[:to_f] == true
239
+ return xml.to_f
240
+ elsif [true, false].include?(xml.to_bool) && options[:to_bool] == true
241
+ # If the string can convert to Boolean then return TrueClass or FalseClass
242
+ return xml.to_bool
243
+ else
244
+ return xml
245
+ end
246
+ else
247
+ # No real examples yet of non-string content
248
+ return xml
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,108 @@
1
+ require 'origen'
2
+ require_relative '../config/application.rb'
3
+ require_relative '../config/environment.rb'
4
+
5
+ module CrossOrigen
6
+ if RUBY_VERSION < '2.0.0'
7
+ require 'scrub_rb'
8
+ end
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Origen::Model
13
+ end
14
+
15
+ def instance_respond_to?(method_name)
16
+ public_methods.include?(method_name)
17
+ end
18
+
19
+ def cr_import(options = {})
20
+ file = cr_file(options)
21
+ cr_translator(file, options).import(file, options)
22
+ end
23
+
24
+ def to_ralf(options = {})
25
+ cr_ralf.owner_to_ralf(options)
26
+ end
27
+
28
+ def to_ip_xact(options = {})
29
+ cr_ip_xact.owner_to_xml(options)
30
+ end
31
+ alias_method :to_ipxact, :to_ip_xact
32
+
33
+ def to_origen(options = {})
34
+ options[:obj] = self
35
+ cr_to_origen(options)
36
+ end
37
+
38
+ def to_header(options = {})
39
+ cr_headers.owner_to_header(options)
40
+ end
41
+
42
+ # Tries the given methods and returns the first one to return a value,
43
+ # ultimately returns nil if no value is found.
44
+ def cr_try(*methods)
45
+ methods.each do |method|
46
+ if self.respond_to?(method)
47
+ val = send(method)
48
+ return val if val
49
+ end
50
+ end
51
+ nil
52
+ end
53
+
54
+ # Returns an instance of the DesignSync interface
55
+ def cr_design_sync
56
+ @cr_design_sync ||= DesignSync.new(self)
57
+ end
58
+
59
+ def cr_headers
60
+ @cr_headers ||= Headers.new(self)
61
+ end
62
+
63
+ # Creates Ruby files necessary to model all sub_blocks and registers found (recursively) owned by options[:obj]
64
+ # The Ruby files are created at options[:path] (app output directory by default)
65
+ def cr_to_origen(options = {})
66
+ options = {
67
+ obj: $dut,
68
+ path: Origen.app.config.output_directory
69
+ }.update(options)
70
+ # This method assumes and checks for $self to contain Origen::Model
71
+ error "ERROR: #{options[:obj].class} does not contain Origen::Model as required" unless options[:obj].class < Origen::Model
72
+ # Check to make sure there are sub_blocks or regs directly under $dut
73
+ error "ERROR: options[:obj]ect #{options[:obj].object_id} of class #{options[:obj].class} does not contain registers or sub_blocks" unless options[:obj].owns_registers? || options[:obj].instance_respond_to?(:sub_blocks)
74
+ OrigenFormat.new(options).export
75
+ end
76
+
77
+ def cr_ralf
78
+ @cr_ralf ||= Ralf.new(self)
79
+ end
80
+
81
+ def cr_ip_xact
82
+ @cr_ip_xact ||= IpXact.new(self)
83
+ end
84
+
85
+ private
86
+
87
+ # Returns an instance of the translator for the format of the given file
88
+ def cr_translator(file, _options = {})
89
+ snippet = IO.read(file, 2000) # Read first 2000 characters
90
+ case snippet
91
+ when /spiritconsortium/
92
+ cr_ip_xact
93
+ else
94
+ fail "Unknown file format for file: #{file}"
95
+ end
96
+ end
97
+
98
+ # Returns a local path to the given file defined by the options.
99
+ def cr_file(options = {})
100
+ if options[:path]
101
+ options[:path]
102
+ elsif options[:vault]
103
+ cr_design_sync.fetch(options)
104
+ else
105
+ fail 'You must supply a :path or :vault option pointing to the import file!'
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,18 @@
1
+ % full_name = cr_try(:full_name, :ip_name, :name, :pdm_part_name) || self.class.to_s
2
+ //---------------------------------------------------------------------------------------------
3
+ // <%= full_name %> Registers Memory Map
4
+ //---------------------------------------------------------------------------------------------
5
+ % regs.each do |name, reg|
6
+ #define <%= name.to_s.upcase.ljust(30) %> (REG<%= reg.size %>(0x<%= reg.address.to_s(16).upcase %>))
7
+ % end
8
+
9
+ //---------------------------------------------------------------------------------------------
10
+ // <%= full_name %> Registers Bit Definition
11
+ //---------------------------------------------------------------------------------------------
12
+ % regs.each do |reg_name, reg|
13
+ // <%= reg_name.to_s.upcase %>
14
+ % reg.named_bits do |name, bits|
15
+ #define <%= "#{reg_name}_#{name}".upcase.ljust(30) %> (<%= bits.sort_by{ |bit| bit.position }.reverse.map{ |bit| "BIT#{bit.position}"}.join(" + ") %>)
16
+ % end
17
+
18
+ % end
@@ -0,0 +1,21 @@
1
+ % reg = options[:reg]
2
+ register <%= options[:name].to_s.downcase %> (<%= reg.path(:relative_to => options[:parent]) + options[:reg_path_postfix] %>) @'h<%= reg.offset.to_s(16).upcase %> {
3
+ bytes <%= reg.size / 8 %>;
4
+ % reg.named_bits do |name, bits|
5
+ % if bits.size == 1
6
+ field <%= name %> ([<%= bits.position %>]) {
7
+ % else
8
+ field <%= name %> ([<%= bits.position + bits.size - 1 %>:<%= bits.position %>]) {
9
+ % end
10
+ % if bits.is_readable? && bits.is_writable?
11
+ access rw;
12
+ % elsif bits.is_writable?
13
+ access wo;
14
+ % else bits.is_writable?
15
+ access ro;
16
+ % end
17
+ bits <%= bits.size %>;
18
+ reset 'b<%= bits.reset_val.to_s(2) %>;
19
+ }
20
+ % end
21
+ }
@@ -0,0 +1,37 @@
1
+ % # The following options are available:
2
+ % options[:reg_path_postfix] ||= ""
3
+ %
4
+ system <%= (cr_try(:path, :ip_name, :name) || self.class.to_s.split("::").last).downcase %> {
5
+ % unless regs.empty?
6
+ % # Mapping this to the size of all registers, correct?
7
+ bytes <%= regs.inject(0){ |sum, reg| sum + reg[1].size } / 8 %>;
8
+ % regs.each do |reg_name, reg|
9
+ <%= render "register", options.merge(name: reg_name, reg: reg, parent: self, indent: 4) %>
10
+
11
+ % end
12
+ % end
13
+
14
+ % sub_blocks.each do |name, block|
15
+ % # Should this be the address?
16
+ block <%= name %> (<%= block.path(:relative_to => self) %>) @'h<%= block.reg_base_address.to_s(16).upcase %> {
17
+ % # Mapping this to the size of all registers, correct?
18
+ bytes <%= regs.inject(0){ |sum, reg| sum + reg[1].size } / 8 %>;
19
+ % block.regs.each do |reg_name, reg|
20
+ <%= render "register", options.merge(name: reg_name, reg: reg, parent: block, indent: 8) %>
21
+
22
+ % end
23
+ % block.domains.each do |domain_name, domain|
24
+ domain <%= domain_name %> {
25
+ bytes 1;
26
+ endian <%= domain.endian %>;
27
+ % block.regs.each do |reg_name, reg|
28
+ <%= render "register", options.merge(name: "#{reg_name}_#{domain_name}", reg: reg, parent: block, indent: 12) %>
29
+
30
+ % end
31
+ }
32
+
33
+ % end
34
+ }
35
+
36
+ % end
37
+ }
@@ -0,0 +1 @@
1
+ <%= $dut.to_ralf %>
@@ -0,0 +1 @@
1
+ <%= $dut.to_header %>
@@ -0,0 +1 @@
1
+ <%= $dut.to_ip_xact format: :uvm %>