cross_origen 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/config/application.rb +78 -0
- data/config/commands.rb +47 -0
- data/config/development.rb +17 -0
- data/config/environment.rb +35 -0
- data/config/users.rb +18 -0
- data/config/version.rb +8 -0
- data/lib/cross_origen/design_sync.rb +54 -0
- data/lib/cross_origen/headers.rb +21 -0
- data/lib/cross_origen/ip_xact.rb +243 -0
- data/lib/cross_origen/origen_format.rb +541 -0
- data/lib/cross_origen/ralf.rb +15 -0
- data/lib/cross_origen/test/dut.rb +51 -0
- data/lib/cross_origen/xml_doc.rb +252 -0
- data/lib/cross_origen.rb +108 -0
- data/templates/headers/default.h.erb +18 -0
- data/templates/ralf/_register.ralf.erb +21 -0
- data/templates/ralf/default.ralf.erb +37 -0
- data/templates/test/default.ralf.erb +1 -0
- data/templates/test/headers_default.h.erb +1 -0
- data/templates/test/ip_xact.xml.erb +1 -0
- data/templates/web/_history.md +547 -0
- data/templates/web/example.md.erb +73 -0
- data/templates/web/examples/ip_xact_export.md.erb +25 -0
- data/templates/web/examples/origen_export.md.erb +96 -0
- data/templates/web/examples/ralf_export.md.erb +18 -0
- data/templates/web/examples.md.erb +13 -0
- data/templates/web/index.md.erb +104 -0
- data/templates/web/layouts/_basic.html.erb +13 -0
- data/templates/web/layouts/_doc.html.erb +61 -0
- data/templates/web/partials/_navbar.html.erb +23 -0
- data/templates/web/release_notes.md.erb +5 -0
- metadata +117 -0
@@ -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
|
data/lib/cross_origen.rb
ADDED
@@ -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 %>
|