cross_origen 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 %>