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 ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-18mode # JRuby in 1.8 mode
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-18mode
7
+ - rbx-19mode
8
+ - 1.8.7
data/Gemfile CHANGED
@@ -2,3 +2,15 @@
2
2
  source "https://rubygems.org"
3
3
  gem "inifile"
4
4
  gem "nokogiri"
5
+ gem "rake"
6
+ gem "highline"
7
+ gem "smart_colored"
8
+
9
+ group :development do
10
+ gem "pry"
11
+ gem "pry-debundle"
12
+ end
13
+
14
+ group :test do
15
+ gem "rspec"
16
+ end
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 #{project.filename.bold} to RAM.".yellow
26
- puts "This will dramatically speed up synthesis and save disk space, but you'll need to re-run 'Generate Programming Files' after each reboot."
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].respond_to?(:[])
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.first
35
+ @preferences.sections.last
29
36
  end
30
37
 
31
38
  #
32
39
  #
33
40
  #
34
41
  def preferences
35
- load_preferences
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
- Project.load(most_recent_project_path)
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
@@ -1,5 +1,3 @@
1
- module Ruby
2
- module Ise
3
- VERSION = "0.4.0"
4
- end
1
+ module ISE
2
+ VERSION = "0.6.1"
5
3
  end
@@ -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
@@ -1,5 +1,8 @@
1
1
 
2
+ require 'ise/xml_file'
3
+
2
4
  require 'ise/project_navigator'
3
- require 'ise/preference_set'
5
+ require 'ise/preference_file'
4
6
  require 'ise/project'
7
+ require 'ise/symbol'
5
8
 
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 = Ruby::Ise::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
@@ -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