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 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