ruby-ise 0.4.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|