ruby-ise 0.4.0 → 0.6.1
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.
- data/.travis.yml +8 -0
- data/Gemfile +12 -0
- data/Rakefile +24 -0
- data/bin/xoptimize +6 -2
- data/lib/ise/{preference_set.rb → preference_file.rb} +2 -1
- data/lib/ise/project.rb +2 -27
- data/lib/ise/project_navigator.rb +15 -4
- data/lib/ise/symbol.rb +235 -0
- data/lib/ise/version.rb +2 -4
- data/lib/ise/xml_file.rb +38 -0
- data/lib/ise.rb +4 -1
- data/ruby-ise.gemspec +3 -3
- data/spec/ise/project_navigator_spec.rb +20 -0
- data/spec/ise/project_spec.rb +3 -3
- data/spec/ise/symbol_spec.rb +132 -0
- data/spec/ise/test_data/ise.conf +1267 -0
- data/spec/ise/{project.xise → test_data/project.xise} +0 -0
- data/spec/ise/test_data/symbol.sym +90 -0
- data/spec/ise/{toplevel.bit → test_data/toplevel.bit} +0 -0
- metadata +27 -10
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1 +1,25 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
task :default => :test_all
|
4
|
+
|
5
|
+
task :test_all do
|
6
|
+
sh "rspec -Ilib"
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Builds the ruby-ise gem.'
|
10
|
+
task :build do
|
11
|
+
sh "gem build ruby-ise.gemspec"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Builds and installs the ruby-ise gem.'
|
15
|
+
task :install => :build do
|
16
|
+
sh "gem install pkg/ruby-ise-#{ISE::VERSION}.gem"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Tags the current version, pushes it GitHub, and pushes the gem.'
|
20
|
+
task :release => :build do
|
21
|
+
sh "git tag v#{ISE::VERSION}"
|
22
|
+
sh "git push origin master"
|
23
|
+
sh "git push origin v#{ISE::VERSION}"
|
24
|
+
sh "git push pkg.ruby-ise-#{ISE::VERSION}.gem"
|
25
|
+
end
|
data/bin/xoptimize
CHANGED
@@ -22,8 +22,12 @@ rescue
|
|
22
22
|
end
|
23
23
|
|
24
24
|
puts
|
25
|
-
puts "You're about to move the working directory for
|
26
|
-
puts
|
25
|
+
puts "You're about to move the working directory for"
|
26
|
+
puts project.filename.bold.yellow
|
27
|
+
puts " to RAM.".yellow
|
28
|
+
puts
|
29
|
+
puts "This will dramatically speed up synthesis and save disk space,"
|
30
|
+
puts "but you'll need to re-run 'Generate Programming Files' after each reboot."
|
27
31
|
puts
|
28
32
|
exit unless prompt.agree('Do you want to continue? [y/n]'.bold + " ")
|
29
33
|
|
@@ -11,6 +11,7 @@ module ISE
|
|
11
11
|
|
12
12
|
#
|
13
13
|
# Determines the location of the ISE preferences file.
|
14
|
+
# TODO: Generalize to work on Windows?
|
14
15
|
#
|
15
16
|
def self.ise_preference_file_path
|
16
17
|
"~/.config/Xilinx/ISE.conf"
|
@@ -49,7 +50,7 @@ module ISE
|
|
49
50
|
|
50
51
|
#Traverse the path, creating any "folders" necessary along the way.
|
51
52
|
until keys.one?
|
52
|
-
target[keys.first] = {} unless target[keys.first].
|
53
|
+
target[keys.first] = {} unless target[keys.first].is_a?(Hash)
|
53
54
|
target = target[keys.shift]
|
54
55
|
end
|
55
56
|
|
data/lib/ise/project.rb
CHANGED
@@ -6,8 +6,8 @@ require 'tmpdir'
|
|
6
6
|
|
7
7
|
module ISE
|
8
8
|
|
9
|
-
class Project
|
10
|
-
|
9
|
+
class Project < XMLFile
|
10
|
+
|
11
11
|
GoalProperty = 'Last Applied Goal'
|
12
12
|
ShortNameProperty = 'PROP_DesignName'
|
13
13
|
OutputNameProperty = 'Output File Name'
|
@@ -16,31 +16,6 @@ module ISE
|
|
16
16
|
|
17
17
|
attr_reader :filename
|
18
18
|
|
19
|
-
|
20
|
-
#
|
21
|
-
# Creates a new ISE Project from an XML string or file object.
|
22
|
-
#
|
23
|
-
def initialize(xml, filename)
|
24
|
-
@xml = Nokogiri.XML(xml)
|
25
|
-
@filename = filename
|
26
|
-
@base_path = File.dirname(filename)
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
# Factory method which creates a new Project from a project file.
|
32
|
-
#
|
33
|
-
def self.load(file_path)
|
34
|
-
new(File::read(file_path), file_path)
|
35
|
-
end
|
36
|
-
|
37
|
-
#
|
38
|
-
# Writes the project to disk, saving any changes.
|
39
|
-
#
|
40
|
-
def save(file_path=@filename)
|
41
|
-
File::write(file_path, @xml)
|
42
|
-
end
|
43
|
-
|
44
19
|
#
|
45
20
|
# Returns the value of a project property.
|
46
21
|
#
|
@@ -11,13 +11,20 @@ module ISE
|
|
11
11
|
|
12
12
|
RecentProjectsPath = 'Project Navigator/Recent Project List1'
|
13
13
|
|
14
|
+
#
|
15
|
+
# Sets the path of the preference file to look for.
|
16
|
+
#
|
17
|
+
def set_preference_file(preference_file=nil)
|
18
|
+
@preference_file = preference_file
|
19
|
+
end
|
20
|
+
|
14
21
|
#
|
15
22
|
# Loads preferences.
|
16
23
|
# By default, preferences are only loaded once.
|
17
24
|
#
|
18
25
|
def load_preferences(force_reload=false)
|
19
26
|
@preferences = nil if force_reload
|
20
|
-
@preferences ||= PreferenceFile.load
|
27
|
+
@preferences ||= PreferenceFile.load(@preference_file)
|
21
28
|
end
|
22
29
|
|
23
30
|
#
|
@@ -25,14 +32,14 @@ module ISE
|
|
25
32
|
#
|
26
33
|
def version
|
27
34
|
load_preferences
|
28
|
-
@preferences.sections.
|
35
|
+
@preferences.sections.last
|
29
36
|
end
|
30
37
|
|
31
38
|
#
|
32
39
|
#
|
33
40
|
#
|
34
41
|
def preferences
|
35
|
-
|
42
|
+
load_preferencers
|
36
43
|
return @preferences[version]
|
37
44
|
end
|
38
45
|
|
@@ -49,6 +56,9 @@ module ISE
|
|
49
56
|
# that project will be used. This function re-loads the preferences file upon each call,
|
50
57
|
# to ensure we don't have stale data.
|
51
58
|
#
|
59
|
+
# TODO: When more than one ISE version is loaded, parse _all_ of the recent projects,
|
60
|
+
# and then return the project with the latest timestamp.
|
61
|
+
#
|
52
62
|
def most_recent_project_path
|
53
63
|
|
54
64
|
#Re-load the preference file, so we have the most recent project.
|
@@ -66,7 +76,8 @@ module ISE
|
|
66
76
|
# Returns a project object representing the most recently open project.
|
67
77
|
#
|
68
78
|
def most_recent_project
|
69
|
-
|
79
|
+
path = most_recent_project_path
|
80
|
+
path ? Project.load(path) : nil
|
70
81
|
end
|
71
82
|
|
72
83
|
end
|
data/lib/ise/symbol.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
|
2
|
+
require 'ise'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'enumerator'
|
5
|
+
|
6
|
+
module ISE
|
7
|
+
|
8
|
+
#
|
9
|
+
# Class representing an ISE symbol file.
|
10
|
+
#
|
11
|
+
class Symbol < XMLFile
|
12
|
+
|
13
|
+
PIN_NAME_REGEX = /^([A-Za-z0-9_]+)(\(([0-9]+)\:([0-9]+)\))?$/
|
14
|
+
|
15
|
+
#
|
16
|
+
# Get the name of the given schematic symbol;
|
17
|
+
# this also indicates which component this symbol is meant to wrap.
|
18
|
+
#
|
19
|
+
def name
|
20
|
+
@xml.css('symbol').attribute('name').value
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Sets the name of the given schematic symbol;
|
25
|
+
# adjusting the name of the
|
26
|
+
#
|
27
|
+
def name=(new_name)
|
28
|
+
@xml.css('symbol').attribute('name').value = new_name
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Special constructor used for creating deep copies of
|
33
|
+
# this object. We use this to clone the inner XML AST.
|
34
|
+
#
|
35
|
+
def initialize_copy(source)
|
36
|
+
super
|
37
|
+
@xml = @xml.clone
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Returns a true-like value if the symbol has the given attribute.
|
42
|
+
#
|
43
|
+
def has_attribute?(name)
|
44
|
+
attribute_value_object(name)
|
45
|
+
end
|
46
|
+
alias_method :include?, :has_attribute?
|
47
|
+
|
48
|
+
#
|
49
|
+
# Gets the value of the provided Symbol attribute.
|
50
|
+
#
|
51
|
+
def get_attribute(name)
|
52
|
+
attribute_value_object(name).value
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Sets the value of the provided Symbol attribute.
|
57
|
+
#
|
58
|
+
def set_attribute(name, new_value)
|
59
|
+
attribute_value_object(name).value = new_value.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
#Allow the indexing operator to be used to access attributes.
|
63
|
+
alias_method :[], :get_attribute
|
64
|
+
alias_method :[]=, :set_attribute
|
65
|
+
|
66
|
+
#
|
67
|
+
# Returns an array of I/O pins present on the given symbol.
|
68
|
+
#
|
69
|
+
def pins
|
70
|
+
@xml.css("symbol pin")
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Iterates over each attribute present in the given symbol.
|
75
|
+
#
|
76
|
+
def each_attribute
|
77
|
+
|
78
|
+
#If we weren't passed a block, return an enumerator referencing this function.
|
79
|
+
return enum_for(:each_attribute) unless block_given?
|
80
|
+
|
81
|
+
#Yield each of the known attributes in turn.
|
82
|
+
@xml.css("symbol attr").each do |attr|
|
83
|
+
yield attr.attribute('name').value, attr.attribute('value').value
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Iterates over all of the I/O pins in the given design.
|
90
|
+
#
|
91
|
+
def each_pin(&block)
|
92
|
+
|
93
|
+
#If no block was provided, return an enumerator.
|
94
|
+
return pins.to_enum unless block
|
95
|
+
|
96
|
+
#Otherwise, iterate over the pins.
|
97
|
+
pins.each(&block)
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
#
|
103
|
+
# Iterates over all of the I/O pins in the given design, providing their names
|
104
|
+
# as well as their node objects.
|
105
|
+
#
|
106
|
+
def each_pin_with_name
|
107
|
+
|
108
|
+
#If we weren't passed a block, return an enumerator referencing this function.
|
109
|
+
return enum_for(:each_pin_with_name) unless block_given?
|
110
|
+
|
111
|
+
#Otherwise, yield each pin with its name.
|
112
|
+
each_pin do |pin|
|
113
|
+
yield pin, get_pin_name(pin)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Renames each of the pins in the design according to a rule.
|
120
|
+
#
|
121
|
+
# Requires a block, which should accept a pin name and provide the
|
122
|
+
# pin's new name.
|
123
|
+
#
|
124
|
+
def rename_pins!
|
125
|
+
pins.each { set_pin_name(yield get_pin_name(pin)) }
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Returns the name of a given pin, given its node.
|
130
|
+
#
|
131
|
+
def get_pin_name(node)
|
132
|
+
node.attribute("name").value
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Sets name of the pin represented by the given node, updating all values
|
137
|
+
#
|
138
|
+
def set_pin_name(node, name)
|
139
|
+
|
140
|
+
return unless node.name == "pin"
|
141
|
+
|
142
|
+
#Change the name of any pin-label "text attributes" that reference the given pin.
|
143
|
+
original_name = get_pin_name(node)
|
144
|
+
|
145
|
+
#Retrieve a collection of all attributes that match the pin's original name...
|
146
|
+
pin_labels = @xml.css("symbol graph attrtext[attrname=\"PinName\"][type=\"pin #{original_name}\"]")
|
147
|
+
|
148
|
+
#And modify them so they now match the new node's name.
|
149
|
+
pin_labels.each { |pin| pin.attribute('type').value = "pin #{name}" }
|
150
|
+
|
151
|
+
#Finally, set the name of the node itself.
|
152
|
+
node.attribute("name").value = name
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# Adjusts the "bounds" of the given bus-- its maximum and minimum pin numbers-- in-place.
|
158
|
+
#
|
159
|
+
# node: The node whose value should be adjusted.
|
160
|
+
# left: The left bound. This is typically higher than the right bound, but doesn't have to be.
|
161
|
+
# right: The right bound.
|
162
|
+
#
|
163
|
+
def set_pin_bounds!(node, left, right)
|
164
|
+
|
165
|
+
#Extract the base name of the pin, removing any existing bounds.
|
166
|
+
name, _, _ = parse_pin_name(get_pin_name(node))
|
167
|
+
|
168
|
+
#And adjust the pin's name to feature the new bounds.
|
169
|
+
set_pin_name(node, "#{name}(#{left}:#{right})")
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Adjusts the "bounds" of the given bus so the bus is of the provided width
|
175
|
+
# by modifying the bus's upper bound. If the node is not a bus, it will be
|
176
|
+
# made into a bus whose right bound is 0.
|
177
|
+
#
|
178
|
+
# node: The node to be modified.
|
179
|
+
# width: The width to apply.
|
180
|
+
#
|
181
|
+
def set_pin_width!(node, width)
|
182
|
+
|
183
|
+
#Get the components of the given bus' name.
|
184
|
+
_, left, right = parse_pin_name(get_pin_name(node))
|
185
|
+
|
186
|
+
#If the pin wasn't initially a bus, make it one.
|
187
|
+
left ||= 0
|
188
|
+
right ||= 0
|
189
|
+
|
190
|
+
#If our right bound is greater than our left one, adjust it.
|
191
|
+
if right > left
|
192
|
+
right = left + width - 1
|
193
|
+
#Otherwise, adjust the left width.
|
194
|
+
else
|
195
|
+
left = right + width - 1
|
196
|
+
end
|
197
|
+
|
198
|
+
#Set the pin's bounds.
|
199
|
+
set_pin_bounds!(node, left, right)
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
#
|
206
|
+
# Break a schematic-format pin name into three elements, and return them.
|
207
|
+
# Returns a list including: 1) the base name, 2) the range's left bound, and 3) the range's right bound.
|
208
|
+
#
|
209
|
+
def parse_pin_name(name)
|
210
|
+
components = PIN_NAME_REGEX.match(name)
|
211
|
+
return components[1], components[3].to_i, components[4].to_i
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
#
|
216
|
+
# Returns a reference to the XML attribute that corresponds to the _value_
|
217
|
+
# of an ISE _symbol_ attribute. (If that's not a confusing clash of nomenclature, what is?)
|
218
|
+
#
|
219
|
+
def attribute_value_object(name)
|
220
|
+
|
221
|
+
#Ensure we have a string- this allows us to use symbols as attribute names, as well.
|
222
|
+
#This is more idiomatic.
|
223
|
+
name = name.to_s
|
224
|
+
|
225
|
+
#Get the node that corresponds to the given attribute, if it exists.
|
226
|
+
node = @xml.at_css("symbol attr[name=\"#{name}\"]")
|
227
|
+
|
228
|
+
#If the node exists, return its value; otherwise, return nil.
|
229
|
+
return node ? node.attribute('value') : nil
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
data/lib/ise/version.rb
CHANGED
data/lib/ise/xml_file.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module ISE
|
5
|
+
|
6
|
+
#
|
7
|
+
# Module which implements the functionality used to wrap an ISE XML file.
|
8
|
+
#
|
9
|
+
class XMLFile
|
10
|
+
|
11
|
+
attr_accessor :filename
|
12
|
+
|
13
|
+
#
|
14
|
+
# Creates a new ISE Project from an XML string or file object.
|
15
|
+
#
|
16
|
+
def initialize(xml, filename)
|
17
|
+
@xml = Nokogiri.XML(xml)
|
18
|
+
@filename = filename
|
19
|
+
@base_path = File.dirname(filename)
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Factory method which creates a new instance from an XML file.
|
24
|
+
#
|
25
|
+
def self.load(file_path)
|
26
|
+
new(File::read(file_path), file_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Writes the project to disk, saving any changes.
|
31
|
+
#
|
32
|
+
def save(file_path=@filename)
|
33
|
+
File::write(file_path, @xml)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/ise.rb
CHANGED
data/ruby-ise.gemspec
CHANGED
@@ -6,11 +6,11 @@ require 'ise/version'
|
|
6
6
|
|
7
7
|
Gem::Specification.new do |gem|
|
8
8
|
gem.name = "ruby-ise"
|
9
|
-
gem.version =
|
9
|
+
gem.version = ISE::VERSION
|
10
10
|
gem.authors = ["Kyle J. Temkin"]
|
11
11
|
gem.email = ["ktemkin@binghamton.edu"]
|
12
|
-
gem.description = %q{Simple gem which extracts meta-data from Xilinx ISE files. Intended to simplify using Rake with ruby-adept, and with ISE/XST.}
|
13
|
-
gem.summary = %q{Simple gem which extracts metadata from Xilinx ISE files.}
|
12
|
+
gem.description = %q{Simple gem which extracts and modifies meta-data from Xilinx ISE files. Intended to simplify using Rake with ruby-adept, and with ISE/XST.}
|
13
|
+
gem.summary = %q{Simple gem which extracts and modifies metadata from Xilinx ISE files.}
|
14
14
|
gem.homepage = "http://www.github.com/ktemkin/ruby-ise"
|
15
15
|
|
16
16
|
gem.files = `git ls-files`.split($/)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require 'ise'
|
3
|
+
|
4
|
+
describe ISE::ProjectNavigator do
|
5
|
+
|
6
|
+
describe "#most_recent_project_path" do
|
7
|
+
|
8
|
+
subject { ISE::ProjectNavigator }
|
9
|
+
|
10
|
+
#Adjust the preference file location so it points to the test-data preference file.
|
11
|
+
let(:preference_file) { File.expand_path('../test_data/ISE.conf', __FILE__) }
|
12
|
+
before(:each) { ISE::ProjectNavigator.set_preference_file(preference_file) }
|
13
|
+
|
14
|
+
it "should return the path of the most recently edited ISE project file" do
|
15
|
+
subject.most_recent_project_path.should == "/home/ktemkin/Documents/Projects/ISESymbolLibrary/MSI_Components/MSI_Components.xise"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/spec/ise/project_spec.rb
CHANGED
@@ -4,7 +4,7 @@ require 'ise'
|
|
4
4
|
describe ISE::Project do
|
5
5
|
|
6
6
|
#Operate on the same file provided.
|
7
|
-
subject { ISE::Project.load(File.expand_path('../project.xise', __FILE__)) }
|
7
|
+
subject { ISE::Project.load(File.expand_path('../test_data/project.xise', __FILE__)) }
|
8
8
|
|
9
9
|
|
10
10
|
describe ".get_property" do
|
@@ -22,7 +22,7 @@ describe ISE::Project do
|
|
22
22
|
describe ".top_level_file" do
|
23
23
|
|
24
24
|
let(:relative_path) { './toplevel.vhd' }
|
25
|
-
let(:full_path) { File.expand_path(relative_path, "#{__FILE__}
|
25
|
+
let(:full_path) { File.expand_path(relative_path, "#{__FILE__}/../test_data") }
|
26
26
|
|
27
27
|
context "when absolute_path is false" do
|
28
28
|
it "should return a relative path to top-level file, as it appears in the project file" do
|
@@ -43,7 +43,7 @@ describe ISE::Project do
|
|
43
43
|
#
|
44
44
|
describe ".bit_file" do
|
45
45
|
|
46
|
-
let(:full_path) { File.expand_path('toplevel.bit', "#{__FILE__}
|
46
|
+
let(:full_path) { File.expand_path('toplevel.bit', "#{__FILE__}/../test_data") }
|
47
47
|
|
48
48
|
it "should return the absolute path to the top-level bit file, if it exists" do
|
49
49
|
subject.bit_file.should == full_path
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'ise'
|
2
|
+
|
3
|
+
describe ISE::Symbol do
|
4
|
+
|
5
|
+
#Operate on the sample symbol file.
|
6
|
+
subject { ISE::Symbol.load(File.expand_path('../test_data/symbol.sym', __FILE__)) }
|
7
|
+
|
8
|
+
#Provide an example node for some tests to operate on.
|
9
|
+
let(:node) { subject.pins.first }
|
10
|
+
|
11
|
+
#Get a reference to the Symbol's internal XML.
|
12
|
+
let(:xml) { subject.instance_variable_get(:@xml) }
|
13
|
+
|
14
|
+
def pin_should_exist(name)
|
15
|
+
xml.at_css("symbol graph attrtext[type=\"pin #{name}\"]").should_not be_nil, "Pin #{name} should exist, but does not."
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".name" do
|
19
|
+
it "should return the symbol's name" do
|
20
|
+
subject.name.should == "BusMux16"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".name=" do
|
25
|
+
it "should set the symbol's name" do
|
26
|
+
subject.name = "NewName"
|
27
|
+
subject.name.should == "NewName"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe ".has_attribute?" do
|
32
|
+
context "when provided with an existing attribute" do
|
33
|
+
it"should return true" do
|
34
|
+
subject.has_attribute?(:BusWidth).should be_true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when provided with an attribute that does not exist" do
|
39
|
+
it "should return false" do
|
40
|
+
subject.has_attribute?(:SomethingElse).should be_false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe ".get_attribute" do
|
46
|
+
it "should return the value of the provided attribute" do
|
47
|
+
subject.get_attribute(:BusWidth).should == '8'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".set_attribute" do
|
52
|
+
it "should set the value of the provided attribute" do
|
53
|
+
subject.set_attribute(:BusWidth, 3)
|
54
|
+
subject.get_attribute(:BusWidth).should == '3'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".each_attribute" do
|
59
|
+
#TODO: better test that doesn't require enumerator funcitonality?
|
60
|
+
it "should iterate over each of the attributes in the file" do
|
61
|
+
subject.each_attribute.to_a.should == [['BusWidth', '8']]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".pins" do
|
66
|
+
it "should return a list of each pin in the design" do
|
67
|
+
#Generate a list of expected pin names.
|
68
|
+
expected_names = (0..15).map { |n| "i#{n}(7:0)" }
|
69
|
+
expected_names |= ['sel(3:0)', 'o(7:0)']
|
70
|
+
|
71
|
+
#Get a list of pin names in the design.
|
72
|
+
pin_names = subject.pins.collect { |p| p.attribute("name").value }
|
73
|
+
pin_names.should == expected_names
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe ".rename_pin" do
|
78
|
+
it "should change the name of the provided pin" do
|
79
|
+
subject.set_pin_name(node, 'a(1:0)')
|
80
|
+
node.attribute('name').value.should == 'a(1:0)'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should change the name of any pin labels referencing the pin" do
|
84
|
+
subject.set_pin_name(node, 'a(1:0)')
|
85
|
+
pin_should_exist('a(1:0)')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe ".set_pin_bounds!" do
|
90
|
+
|
91
|
+
it "should change the boundaries of the given pin to match its arguments" do
|
92
|
+
subject.set_pin_bounds!(node, 14, 3)
|
93
|
+
pin_should_exist('i0(14:3)')
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ".set_pin_width!" do
|
99
|
+
|
100
|
+
context "when provided with an MSB-to-the-left range" do
|
101
|
+
it "should move the upper (left) bound to create the correct width" do
|
102
|
+
subject.set_pin_width!(node, 4)
|
103
|
+
pin_should_exist('i0(3:0)')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when provided with an LSB-to-the-left range" do
|
108
|
+
it "should move the upper (right) bound to create the correct width" do
|
109
|
+
|
110
|
+
#Create an LSB-to-the-left range.
|
111
|
+
subject.set_pin_name(node, 'i0(0:7)')
|
112
|
+
|
113
|
+
subject.set_pin_width!(node, 4)
|
114
|
+
pin_should_exist('i0(0:3)')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "when provided with a non-bus" do
|
119
|
+
it "should create a bus that is bounded at zero" do
|
120
|
+
|
121
|
+
#Create a non-bus input.
|
122
|
+
subject.set_pin_name(node, 'i')
|
123
|
+
|
124
|
+
subject.set_pin_width!(node, 10)
|
125
|
+
pin_should_exist('i(9:0)')
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
end
|