origen 0.31.0 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 437a40756e6194c3308c3044388d48c4c0b0c31c
4
- data.tar.gz: d9341d81f27c7bbbf014fe26844d67f53bc1facf
3
+ metadata.gz: 4b636bd97c227eecf2344ccbdc17e29712953325
4
+ data.tar.gz: cd69d451f85313509d2a2c4a31887cbf81fb57c2
5
5
  SHA512:
6
- metadata.gz: 8ce734ee2bd0f466c05c38c7a9205ac7bfbee4165780bfdca9c57f4eb3390b49819882a43e0c24f512f4b65c866a2edf5c057b6c6eb4015d7bea48c1cbbfa3a5
7
- data.tar.gz: 967eafac1a53b990d7b8b5f689278f27527ece71e76cfd6db63faecd7d8b4dbce5a8c87c96c037c1451925267b52d5216179f1108d06183c9dd21ad65188f996
6
+ metadata.gz: 4e104a65bd531b7e52ec7f9f74d002b822d004fb0c46556e412392c3b874101a8be06c27fd83bb05e35acad5fa3b2c62251d9ba729a5de08dc472004ffd11b57
7
+ data.tar.gz: dc29b4f3a4567780af525fc9b023bae374af3d71ed96f241e294ae35aba9d241009f4496d4c7f25c112dca0a8d3978b3f6223b378488b8dc967bbabff6501af6
data/config/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Origen
2
2
  MAJOR = 0
3
- MINOR = 31
3
+ MINOR = 32
4
4
  BUGFIX = 0
5
5
  DEV = nil
6
6
 
@@ -79,6 +79,7 @@ module Origen
79
79
  else
80
80
  if options[:action] == :program
81
81
  Origen.generator.generate_program(expand_lists_and_directories(options[:files], options), options)
82
+ Origen.app.listeners_for(:program_generated).each(&:program_generated)
82
83
  else
83
84
  temporary_plugin_from_options = options[:current_plugin]
84
85
  expand_lists_and_directories(options[:files], options).each do |file|
@@ -1,16 +1,18 @@
1
1
  module Origen
2
2
  module Clocks
3
3
  class Clock
4
- attr_accessor :id, :owner, :users, :startup_freq, :nominal_freq, :frequency_range, :setpoint
4
+ attr_accessor :id, :owner, :users, :freq_target, :freq_range, :setpoint, :instantiate_users
5
5
 
6
6
  def initialize(id, owner, options = {}, &block)
7
7
  @id = id
8
8
  @owner = owner
9
9
  @id = @id.symbolize unless id.is_a? Symbol
10
+ @instantiate_users = true
10
11
  options.each { |k, v| instance_variable_set("@#{k}", v) }
11
12
  (block.arity < 1 ? (instance_eval(&block)) : block.call(self)) if block_given?
12
13
  @users = [@users] unless @users.is_a? Array
13
- instantiate_users
14
+ add_users_sub_blocks if @instantiate_users
15
+ @setpoint = @freq_target
14
16
  end
15
17
 
16
18
  def name
@@ -21,29 +23,30 @@ module Origen
21
23
  def users
22
24
  @users
23
25
  end
24
- alias_method :ips, :users
25
- alias_method :sub_blocks, :users
26
26
 
27
27
  def setpoint=(val)
28
- setpoint_ok?
29
- @setpoint = val
28
+ if val == :gated || val == 'gated'
29
+ @setpoint = :gated
30
+ else
31
+ setpoint_ok?(val) # This just warns if the clock is set out of range
32
+ @setpoint = val
33
+ end
30
34
  end
31
35
 
32
36
  def setpoint_ok?(val = nil)
33
- return nil if val.nil? && setpoint.nil?
34
- if freq_range == :fixed
35
- if val.nil? || val == nominal_frequency
37
+ val = @setpoint if val.nil?
38
+ if @freq_range == :fixed
39
+ if val == @freq_target
36
40
  return true
37
41
  else
38
- Origen.log.warn("Clock '#{id}' is a fixed clock with a nominal frequency of #{nominal_frequency.as_Hz}, setting it to #{val.as_Hz}")
42
+ Origen.log.warn("Clock '#{id}' is a fixed clock with a target frequency of #{@freq_target.as_Hz}")
39
43
  return false
40
44
  end
41
45
  else
42
- val = setpoint if val.nil?
43
- if freq_range.include?(val)
46
+ if @freq_range.include?(val)
44
47
  return true
45
48
  else
46
- Origen.log.warn("Setpoint (#{setpoint_string(val)}) for clock '#{id}' is not within the frequency range (#{freq_range_string}), setting it to #{val.as_Hz}")
49
+ Origen.log.warn("Setpoint (#{setpoint_string(val)}) for clock '#{id}' is not within the frequency range (#{freq_range_string})")
47
50
  return false
48
51
  end
49
52
  end
@@ -51,17 +54,10 @@ module Origen
51
54
  alias_method :value_ok?, :setpoint_ok?
52
55
  alias_method :val_ok?, :setpoint_ok?
53
56
 
54
- # Set the clock to the nominal frequency
55
- def setpoint_to_nominal
56
- @setpoint = nominal_frequency
57
- end
58
-
59
- # Nominal frequency
60
- def nominal_frequency
61
- @nominal_frequency
57
+ # Set the clock to the target frequency
58
+ def setpoint_to_target
59
+ @setpoint = @freq_target
62
60
  end
63
- alias_method :nominal, :nominal_frequency
64
- alias_method :nom, :nominal_frequency
65
61
 
66
62
  # Current setpoint, defaults top nil on init
67
63
  def setpoint
@@ -71,11 +67,34 @@ module Origen
71
67
  alias_method :value, :setpoint
72
68
 
73
69
  # Acceptable frequency range
74
- def frequency_range
75
- @frequency_range
70
+ def freq_range
71
+ @freq_range
72
+ end
73
+ alias_method :range, :freq_range
74
+
75
+ # Acceptable frequency range
76
+ def freq_target
77
+ @freq_target
78
+ end
79
+ alias_method :target, :freq_target
80
+
81
+ # min method
82
+ def min
83
+ if @freq_range == :fixed
84
+ @freq_target
85
+ else
86
+ @freq_range.first
87
+ end
88
+ end
89
+
90
+ # max method
91
+ def max
92
+ if @freq_range == :fixed
93
+ @freq_target
94
+ else
95
+ @freq_range.last
96
+ end
76
97
  end
77
- alias_method :freq_range, :frequency_range
78
- alias_method :range, :frequency_range
79
98
 
80
99
  # Check if the clock users are defined anywhere in the DUT
81
100
  def users_defined?
@@ -108,12 +127,12 @@ module Origen
108
127
  private
109
128
 
110
129
  # Instantiate and IP/users that use/access the clock
111
- def instantiate_users
112
- users.each do |ip|
113
- if owner.respond_to? ip
130
+ def add_users_sub_blocks
131
+ @users.each do |ip|
132
+ if @owner.respond_to? ip
114
133
  next
115
134
  else
116
- owner.sub_block ip
135
+ @owner.sub_block ip
117
136
  end
118
137
  end
119
138
  end
@@ -130,20 +149,20 @@ module Origen
130
149
  end
131
150
 
132
151
  def frequencies_ok?
133
- if nominal_frequency.nil?
152
+ if @freq_target.nil?
134
153
  false
135
- elsif frequency_range.nil?
154
+ elsif @freq_range.nil?
136
155
  Origen.log.error("Missing frequency range for clock '#{name}'!")
137
156
  false
138
- elsif frequency_range.is_a? Range
139
- if frequency_range.include?(nominal_frequency)
157
+ elsif freq_range.is_a? Range
158
+ if freq_range.include?(@freq_target)
140
159
  true
141
160
  else
142
- Origen.log.error("PPEKit: Nominal frequency #{nominal_frequency} is not inbetween the frequency range #{frequency_range} for clock '#{name}'!")
161
+ Origen.log.error("PPEKit: Frequency target #{@freq_target} is not inbetween the frequency range #{freq_range} for clock '#{name}'!")
143
162
  false
144
163
  end
145
164
  else
146
- Origen.log.error("Clock attribute 'frequency_range' must be a Range!")
165
+ Origen.log.error("Clock attribute 'freq_range' must be a Range!")
147
166
  return_value = false
148
167
  end
149
168
  end
@@ -157,24 +176,24 @@ module Origen
157
176
  end
158
177
 
159
178
  def freq_range_string
160
- start_freq = freq_range.first
161
- end_freq = freq_range.last
179
+ start_freq = @freq_range.first
180
+ end_freq = @freq_range.last
162
181
  "#{start_freq.as_Hz}\.\.#{end_freq.as_Hz}"
163
182
  end
164
183
 
165
184
  def clock_freqs_ok?
166
- if nominal_freq.nil?
185
+ if @freq_target.nil?
167
186
  false
168
- elsif freq_range == :fixed
187
+ elsif @freq_range == :fixed
169
188
  true
170
189
  else
171
- if freq_range.nil?
190
+ if @freq_range.nil?
172
191
  Origen.log.error("PPEKit: Missing frequency target or range for clock '#{id}'!")
173
192
  false
174
- elsif freq_range.include?(nominal_freq)
193
+ elsif @freq_range.include?(@freq_target)
175
194
  true
176
195
  else
177
- Origen.log.error("PPEKit: Frequency target #{nominal_freq} is not inbetween the freq range #{freq_range} for clock '#{id}'!")
196
+ Origen.log.error("PPEKit: Frequency target #{@freq_target} is not inbetween the freq range #{@freq_range} for clock '#{id}'!")
178
197
  false
179
198
  end
180
199
  end
@@ -92,7 +92,11 @@ The following options are available:
92
92
  server.close
93
93
  # Start the server
94
94
  puts ''
95
- puts "Point your browser to this address: http://#{host}#{domain.empty? ? '' : '.' + domain}:#{port}"
95
+ if host.include? domain
96
+ puts "Point your browser to this address: http://#{host}:#{port}"
97
+ else
98
+ puts "Point your browser to this address: http://#{host}#{domain.empty? ? '' : '.' + domain}:#{port}"
99
+ end
96
100
  puts ''
97
101
  puts 'To shut down the server use CTRL-C'
98
102
  puts ''
@@ -210,9 +210,10 @@ module Origen
210
210
  file = inject_import_path(file, type: :template)
211
211
  file = add_underscore_to(file)
212
212
  file = add_extension_to(file)
213
+ web_file = file =~ /\.(html|md)(\.|$)/
213
214
  begin
214
215
  # Allow relative references to templates/web when compiling a web template
215
- if Origen.lsf.current_command == 'web'
216
+ if Origen.lsf.current_command == 'web' || web_file
216
217
  clean_path_to(file, load_paths: "#{Origen.root}/templates/web")
217
218
  else
218
219
  clean_path_to(file)
@@ -220,7 +221,7 @@ module Origen
220
221
  rescue
221
222
  # Try again without .erb
222
223
  file = file.gsub('.erb', '')
223
- if Origen.lsf.current_command == 'web'
224
+ if Origen.lsf.current_command == 'web' || web_file
224
225
  clean_path_to(file, load_paths: "#{Origen.root}/templates/web")
225
226
  else
226
227
  clean_path_to(file)
@@ -349,10 +349,21 @@ module Origen
349
349
 
350
350
  unless options[:inhibit] || !Origen.tester.generate? || job.test?
351
351
  stats.collect_for_pattern(job.output_pattern) do
352
- File.open(job.output_pattern, 'w') do |f|
353
- [:header, :body, :footer].each do |section|
354
- Origen.tester.format(stage.bank(section), section) do |line|
355
- f.puts line
352
+ # If the tester is going to deal with writing out the final pattern. The use case for this is when
353
+ # the pattern is comprised of multiple files instead of the more conventional case here which each
354
+ # pattern is one file.
355
+ if tester.respond_to?(:open_and_write_pattern)
356
+ tester.open_and_write_pattern(job.output_pattern) do
357
+ [:header, :body, :footer].each do |section|
358
+ Origen.tester.format(stage.bank(section), section)
359
+ end
360
+ end
361
+ else
362
+ File.open(job.output_pattern, 'w') do |f|
363
+ [:header, :body, :footer].each do |section|
364
+ Origen.tester.format(stage.bank(section), section) do |line|
365
+ f.puts line
366
+ end
356
367
  end
357
368
  end
358
369
  end
@@ -362,7 +373,7 @@ module Origen
362
373
  log.info "Pattern vectors: #{stats.number_of_vectors_for(job.output_pattern).to_s.ljust(10)}"
363
374
  log.info 'Execution time'.ljust(15) + ': %.6f' % stats.execution_time_for(job.output_pattern)
364
375
  log.info '----------------------------------------------------------------------'
365
- check_for_changes(job.output_pattern, job.reference_pattern)
376
+ check_for_changes(job.output_pattern, job.reference_pattern) unless tester.try(:disable_pattern_diffs)
366
377
  stats.record_pattern_completion(job.output_pattern)
367
378
  end
368
379
 
@@ -0,0 +1,126 @@
1
+ module Origen
2
+ module Limits
3
+ class Limit
4
+ attr_accessor :expr, :value, :owner, :type
5
+
6
+ def initialize(expr, type, owner, options = {})
7
+ @expr = expr
8
+ @owner = owner
9
+ @type = type
10
+ @value = evaluate_expr
11
+ end
12
+
13
+ # If the value is still a String then it could be
14
+ # due to referencing another LimitSet that is
15
+ # yet to be defined
16
+ def value
17
+ if @value.is_a?(String)
18
+ evaluate_expr
19
+ else
20
+ @value
21
+ end
22
+ end
23
+
24
+ # Backwards compatibility
25
+ def exp
26
+ @expr
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_reference_value(ref)
32
+ ref = ref.to_sym unless ref.is_a?(Symbol)
33
+ # Check if the reference is to another limit set
34
+ if owner.limits.include? ref
35
+ return owner.limits(ref).send(type).value
36
+ # Check if the reference is to a power domain
37
+ end
38
+ if Origen.top_level.respond_to? :power_domains
39
+ if Origen.top_level.power_domains.include? ref
40
+ # Need to check the limit type and retrieve the appropriate value
41
+ case @type.to_s
42
+ when /target|typ/
43
+ return Origen.top_level.power_domains(ref).nominal_voltage
44
+ else
45
+ return Origen.top_level.power_domains(ref).send(@type)
46
+ end
47
+ end
48
+ end
49
+ # Check if the reference is to a clock
50
+ if Origen.top_level.respond_to? :clocks
51
+ if Origen.top_level.clocks.include? ref
52
+ # Need to check the limit type and retrieve the appropriate value
53
+ case @type.to_s
54
+ when /target|typ/
55
+ return Origen.top_level.clocks(ref).freq_target
56
+ else
57
+ return Origen.top_level.clocks(ref).send(@type)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def evaluate_expr
64
+ return @expr if @expr.is_a?(Numeric)
65
+ return nil if @expr.nil?
66
+ if @expr.is_a? Symbol
67
+ @expr = ':' + @expr.to_s
68
+ else
69
+ @expr.gsub!("\n", ' ')
70
+ @expr.scrub!
71
+ end
72
+ result = false
73
+ if @expr.match(/\:\S+/)
74
+ limit_items = @expr.split(/\:|\s+/).reject(&:empty?)
75
+ if limit_items.size == 1
76
+ return fetch_reference_value(limit_items.first)
77
+ else
78
+ references = @expr.split(/\:|\s+/).select { |var| var.match(/^[a-zA-Z]\S+$/) }
79
+ new_limit_items = [].tap do |limit_ary|
80
+ limit_items.each do |item|
81
+ if references.include? item
82
+ limit_ary << fetch_reference_value(item)
83
+ next
84
+ else
85
+ limit_ary << item
86
+ end
87
+ end
88
+ end
89
+ new_limit = new_limit_items.join(' ')
90
+ new_limit_references = new_limit.split(/\:|\s+/).select { |var| var.match(/^[a-zA-Z]\S+$/) }
91
+ if new_limit_references.empty?
92
+ result = eval(new_limit).round(4)
93
+ else
94
+ return @expr
95
+ end
96
+ end
97
+ elsif !!(@expr.match(/^\d+\.\d+$/)) || !!(@expr.match(/^-\d+\.\d+$/))
98
+ result = Float(@expr).round(4) rescue false # rubocop:disable Style/RescueModifier
99
+ elsif !!(@expr.match(/\d+\.\d+\s+\d+\.\d+/))
100
+ Origen.log.debug "Found two numbers without an operator in the @expr string '#{@expr}', choosing the first..."
101
+ first_number = @expr.match(/(\d+\.\d+)\s+\d+\.\d+/).captures.first
102
+ result = Float(first_number).round(4) rescue false # rubocop:disable Style/RescueModifier
103
+ else
104
+ result = Integer(@expr) rescue false # rubocop:disable Style/RescueModifier
105
+ end
106
+ if result == false
107
+ # Attempt to eval the @expr because users could write a @expr like "3.3 + 50.mV"
108
+ # which would not work with the code above but should eval to a number 3.35
109
+ begin
110
+ result = eval(@expr)
111
+ return result.round(4) if result.is_a? Numeric
112
+ rescue ::SyntaxError, ::NameError, ::TypeError
113
+ Origen.log.debug "Limit '#{@expr}' had to be rescued, storing it as a #{@expr.class}"
114
+ if @expr.is_a? Symbol
115
+ return @expr
116
+ else
117
+ return "#{@expr}"
118
+ end
119
+ end
120
+ else
121
+ return result
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,120 @@
1
+ require_relative './limit'
2
+ module Origen
3
+ module Limits
4
+ class LimitSet
5
+ attr_accessor :id, :min, :typ, :max, :target, :description, :static, :owner, :type
6
+
7
+ def initialize(id, owner, options)
8
+ @id = id
9
+ @description = options[:description]
10
+ @owner = owner
11
+ @min = Limit.new(options[:min], :min, @owner) unless options[:min].nil?
12
+ @typ = Limit.new(options[:typ], :typ, @owner) unless options[:typ].nil?
13
+ @max = Limit.new(options[:max], :max, @owner) unless options[:max].nil?
14
+ @target = Limit.new(options[:target], :target, @owner) unless options[:target].nil?
15
+ unless options[:static].nil?
16
+ unless [true, false].include? options[:static]
17
+ Origen.log.error("Static option must be set to 'true' or 'false'!")
18
+ fail
19
+ end
20
+ end
21
+ @static = options[:static].nil? ? false : options[:static]
22
+ fail unless limits_ok?
23
+ end
24
+
25
+ # Common alias
26
+ def name
27
+ @id
28
+ end
29
+
30
+ def frozen?
31
+ @static
32
+ end
33
+
34
+ def min=(val)
35
+ if frozen?
36
+ Origen.log.warn('Cannot change a frozen limit set!')
37
+ else
38
+ @min = Limit.new(val, :min, @owner)
39
+ end
40
+ end
41
+
42
+ def max=(val)
43
+ if frozen?
44
+ Origen.log.warn('Cannot change a frozen limit set!')
45
+ else
46
+ @max = Limit.new(val, :max, @owner)
47
+ end
48
+ end
49
+
50
+ def typ=(val)
51
+ if frozen?
52
+ Origen.log.warn('Cannot change a frozen limit set!')
53
+ else
54
+ @typ = Limit.new(val, :typ, @owner)
55
+ end
56
+ end
57
+
58
+ def target=(val)
59
+ if frozen?
60
+ Origen.log.warn('Cannot change a frozen limit set!')
61
+ else
62
+ @target = Limit.new(val, :target, @owner)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # Check that min, max are not mixed with typ. If a user wants
69
+ # a baseline value for a spec use target as it will not be
70
+ # checked against pass/fail
71
+ def limits_ok?
72
+ status = true
73
+ # Must have at least one of the limit types defined
74
+ if @min.nil? && @max.nil? && @typ.nil? && @target.nil?
75
+ Origen.log.error("Limit set #{@id} does not have any limits defined!")
76
+ return false
77
+ end
78
+ if @min.nil? ^ @max.nil?
79
+ @type = :single_sided
80
+ unless @typ.nil?
81
+ status = false
82
+ Origen.log.error "Limit set #{@id} has a typical limit defined with either min or max. They are mutually exclusive, use 'target' when using min or max"
83
+ end
84
+ # Check if the target is OK
85
+ unless @target.nil?
86
+ if @min.nil?
87
+ unless @target < @max
88
+ status = false
89
+ Origen.log.error("Limit set #{@id} has the target value #{@target} set greater than the max value #{@max}!")
90
+ end
91
+ else
92
+ unless @target > @min
93
+ status = false
94
+ Origen.log.error("Limit set #{@id} has the target value #{@target} set less than the min value #{@min}!")
95
+ end
96
+ end
97
+ end
98
+ elsif @min.expr && @max.expr
99
+ @type = :double_sided
100
+ # Both min and max must be numerical to compare them
101
+ if @min.value.is_a?(Numeric) && @max.value.is_a?(Numeric)
102
+ # Check that min and max make sense
103
+ if @max.value <= @min.value || @min.value >= @max.value
104
+ status = false
105
+ Origen.log.error "Limit set #{@id} has min (#{@min.value}) and max (#{@max.value}) reversed"
106
+ end
107
+ # Check that target is OK
108
+ unless @target.nil?
109
+ if @target.value <= @min.value || @target.value >= @max.value
110
+ status = false
111
+ Origen.log.error "Limit set #{@id} has a target (#{@target.value}) that is not within the min (#{@min.value}) and max #{@max.value}) values"
112
+ end
113
+ end
114
+ end
115
+ end
116
+ status
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,37 @@
1
+ require_relative './limits/limit'
2
+ require_relative './limits/limit_set'
3
+ module Origen
4
+ module Limits
5
+ TYPES = [:min, :typ, :max, :target]
6
+
7
+ def add_limits(set, options)
8
+ @_limits ||= {}
9
+ options.ids.each do |limit_type|
10
+ unless TYPES.include? limit_type
11
+ Origen.log.error("Limit type '#{limit_type}' not supported, choose from #{TYPES}!")
12
+ fail
13
+ end
14
+ end
15
+ if @_limits.include? set
16
+ # Limit set already exists, modify it unless it is frozen
17
+ unless @_limits[set].frozen?
18
+ options.each do |limit_type, limit_expr|
19
+ @_limits[set].send("#{limit_type}=", limit_expr)
20
+ end
21
+ end
22
+ else
23
+ # Create a default limit set
24
+ @_limits[set] = LimitSet.new(set, self, options)
25
+ end
26
+ end
27
+
28
+ def limits(set = nil)
29
+ @_limits ||= {}
30
+ if set.nil?
31
+ @_limits
32
+ else
33
+ @_limits[set]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -11,16 +11,13 @@ module Origen
11
11
  }.merge(options)
12
12
  # file_path is for internal use, don't pass it from the application, use the :dir option if you
13
13
  # want to change where the exported files are
14
- if options[:file_path]
15
- file = File.join(options[:file_path], "#{name}.rb")
16
- else
17
- file = export_path(name, options)
18
- end
19
- file = Pathname.new(file)
20
- FileUtils.rm_rf(file.sub_ext('').to_s) if File.exist?(file.sub_ext('').to_s)
21
- FileUtils.rm_rf(file.to_s) if File.exist?(file.to_s)
22
- FileUtils.mkdir_p(file.dirname)
23
- File.open(file, 'w') do |f|
14
+ file = options[:file_path] || export_path(name, options)
15
+ dir = options[:dir_path] || export_dir(options)
16
+ path_to_file = Pathname.new(File.join(dir, file))
17
+ FileUtils.rm_rf(path_to_file.sub_ext('').to_s) if File.exist?(path_to_file.sub_ext('').to_s)
18
+ FileUtils.rm_rf(path_to_file.to_s) if File.exist?(path_to_file.to_s)
19
+ FileUtils.mkdir_p(path_to_file.dirname)
20
+ File.open(path_to_file, 'w') do |f|
24
21
  export_wrap_with_namespaces(f, options) do |indent|
25
22
  f.puts((' ' * indent) + 'def self.extended(model)')
26
23
  indent += 2
@@ -51,12 +48,15 @@ module Origen
51
48
  ground_pin_groups.each do |id, pins|
52
49
  f.puts export_pin_group(id, pins, indent: indent, method: :add_ground_pin_group)
53
50
  end
51
+ virtual_pins.each do |id, pin|
52
+ f.puts export_pin(id, pin, indent: indent, method: :add_virtual_pin)
53
+ end
54
54
  f.puts
55
55
  end
56
56
  end
57
57
  if options[:include_sub_blocks]
58
58
  sub_blocks.each do |name, block|
59
- f.puts export_sub_block(name, block, options.merge(indent: indent, file_path: file))
59
+ f.puts export_sub_block(name, block, options.merge(indent: indent, file_path: file, dir_path: dir))
60
60
  end
61
61
  f.puts unless sub_blocks.empty?
62
62
  end
@@ -74,10 +74,18 @@ module Origen
74
74
  end
75
75
 
76
76
  def import(name, options = {})
77
- path = export_path(name, options)
77
+ path = File.join(export_dir(options), export_path(name, options))
78
78
  if File.exist?(path)
79
79
  require path
80
- extend "#{Origen.app.namespace.underscore.camelcase}::#{name.to_s.camelcase}".constantize
80
+ if options.key?(:namespace) && !options[:namespace]
81
+ extend name.to_s.gsub('.', '_').camelcase.constantize
82
+ else
83
+ if options[:namespace]
84
+ extend "#{options[:namespace].to_s.gsub('.', '_').camelcase}::#{name.to_s.gsub('.', '_').camelcase}".constantize
85
+ else
86
+ extend "#{Origen.app.namespace}::#{name.to_s.gsub('.', '_').camelcase}".constantize
87
+ end
88
+ end
81
89
  true
82
90
  else
83
91
  if options[:allow_missing]
@@ -143,16 +151,16 @@ module Origen
143
151
  if n == ''
144
152
  nil
145
153
  else
146
- n.camelcase
154
+ n.to_s.gsub('.', '_').camelcase
147
155
  end
148
156
  end.compact
149
157
  end
150
158
 
151
159
  def export_path(name, options = {})
152
160
  if options.key?(:namespace) && !options[:namespace]
153
- File.join(export_dir(options), "#{name.to_s.underscore}.rb")
161
+ "#{name.to_s.underscore}.rb"
154
162
  else
155
- File.join(export_dir(options), (options[:namespace] || Origen.app.namespace).to_s.underscore, "#{name.to_s.underscore}.rb")
163
+ File.join((options[:namespace] || Origen.app.namespace).to_s.underscore, "#{name.to_s.underscore}.rb")
156
164
  end
157
165
  end
158
166
 
@@ -173,8 +181,11 @@ module Origen
173
181
  line << ", #{pkg_meta}" unless pkg_meta == ''
174
182
  Array(options[:attributes]).each do |attr|
175
183
  unless (v = pin.send(attr)).nil?
176
- if v.is_a?(Numeric)
184
+ case v
185
+ when Numeric, Array, Hash
177
186
  line << ", #{attr}: #{v}"
187
+ when Symbol
188
+ line << ", #{attr}: :#{v}"
178
189
  else
179
190
  line << ", #{attr}: '#{v}'"
180
191
  end
@@ -183,8 +194,11 @@ module Origen
183
194
  unless pin.meta.empty?
184
195
  line << ', meta: { '
185
196
  line << pin.meta.map do |k, v|
186
- if v.is_a?(Numeric)
197
+ case v
198
+ when Numeric, Array, Hash
187
199
  "#{k}: #{v}"
200
+ when Symbol
201
+ "#{k}: :#{v}"
188
202
  else
189
203
  "#{k}: '#{v}'"
190
204
  end
@@ -208,9 +222,9 @@ module Origen
208
222
 
209
223
  def export_sub_block(id, block, options = {})
210
224
  indent = ' ' * (options[:indent] || 0)
211
- file = File.join(options[:file_path].sub_ext(''), "#{id}.rb")
212
- local_file = file.to_s.sub("#{export_dir}/", '')
213
- line = indent + "model.sub_block :#{id}, file: '#{local_file}', lazy: true"
225
+ file_path = File.join(Pathname.new(options[:file_path]).sub_ext(''), "#{id}.rb")
226
+ dir_path = options[:dir_path]
227
+ line = indent + "model.sub_block :#{id}, file: '#{file_path}', dir: '#{dir_path}', lazy: true"
214
228
  unless block.base_address == 0
215
229
  line << ", base_address: #{block.base_address.to_hex}"
216
230
  end
@@ -223,7 +237,7 @@ module Origen
223
237
  line << ", #{key}: #{value}"
224
238
  end
225
239
  end
226
- block.export(id, options.merge(file_path: options[:file_path].sub_ext('').to_s))
240
+ block.export(id, options.merge(file_path: file_path, dir_path: dir_path))
227
241
  line
228
242
  end
229
243
 
data/lib/origen/model.rb CHANGED
@@ -34,6 +34,7 @@ module Origen
34
34
  include Origen::Clocks
35
35
  include Origen::Model::Exporter
36
36
  include Origen::Component
37
+ include Origen::Limits
37
38
  end
38
39
 
39
40
  module ClassMethods
@@ -205,6 +206,7 @@ module Origen
205
206
  unless top_level?
206
207
  # Need to do this in case a class besides SubBlock includes Origen::Model
207
208
  obj_above_self = parent.nil? ? Origen.top_level : parent
209
+ return nil if obj_above_self.nil?
208
210
  if obj_above_self.current_mode
209
211
  _modes[obj_above_self.current_mode.id] if _modes.include? obj_above_self.current_mode.id
210
212
  end
@@ -216,6 +218,10 @@ module Origen
216
218
  # Set the current mode configuration of the current model
217
219
  def current_mode=(id)
218
220
  @current_mode = id.is_a?(ChipMode) ? id.id : id
221
+ Origen.app.listeners_for(:on_mode_changed).each do |listener|
222
+ listener.on_mode_changed(mode: @current_mode)
223
+ end
224
+ @current_mode
219
225
  end
220
226
  alias_method :mode=, :current_mode=
221
227
 
@@ -69,6 +69,18 @@ module Origen
69
69
  _parameter_sets.empty? ? false : true
70
70
  end
71
71
 
72
+ # Return value of param if it exists, nil otherwise.
73
+ def param?(name)
74
+ _param = name.to_s =~ /^params./ ? name.to_s : 'params.' + name.to_s
75
+ begin
76
+ val = eval("self.#{_param}")
77
+ rescue
78
+ nil
79
+ else
80
+ val
81
+ end
82
+ end
83
+
72
84
  # @api private
73
85
  def _parameter_current
74
86
  if path = self.class.parameters_context
@@ -109,9 +109,22 @@ module Origen
109
109
  v = tester.capture do
110
110
  store!(sync: true)
111
111
  end
112
- reverse_shift_out_with_index do |bit, i|
113
- bit.instance_variable_set('@updated_post_reset', true)
114
- bit.instance_variable_set('@data', v.first[i])
112
+ if v.first
113
+ # Serial shift
114
+ if v.size == 1
115
+ reverse_shift_out_with_index do |bit, i|
116
+ bit.instance_variable_set('@updated_post_reset', true)
117
+ bit.instance_variable_set('@data', v.first[i])
118
+ end
119
+ # Parallel shift
120
+ else
121
+ reverse_shift_out_with_index do |bit, i|
122
+ bit.instance_variable_set('@updated_post_reset', true)
123
+ bit.instance_variable_set('@data', v[i].to_i)
124
+ end
125
+ end
126
+ else
127
+ Origen.log.warning "No data was captured when attempting to sync register #{owner.name}, this is probably because the current read_register driver method does not implement store requests"
115
128
  end
116
129
  end
117
130
  if size
@@ -119,8 +119,47 @@ module Origen
119
119
  end
120
120
  end
121
121
 
122
+ # Fetches any defined remotes, regardless of whether they are dirty or not
123
+ def resolve_remotes!
124
+ resolve_remotes
125
+ process_remotes
126
+ end
127
+
122
128
  private
123
129
 
130
+ # Process each remote
131
+ def process_remotes
132
+ remotes.each do |_name, remote|
133
+ dir = workspace_of(remote)
134
+ rc_url = remote[:rc_url] || remote[:vault]
135
+ tag = remote[:tag].nil? ? Origen::VersionString.new(remote[:version]) : Origen::VersionString.new(remote[:tag])
136
+ version_file = dir.to_s + '/.current_version'
137
+ begin
138
+ if File.exist?("#{dir}/.initial_populate_successful")
139
+ FileUtils.rm_f(version_file) if File.exist?(version_file)
140
+ rc = RevisionControl.new remote: rc_url, local: dir
141
+ rc.send rc.remotes_method, version: prefix_tag(tag), force: true
142
+ File.open(version_file, 'w') do |f|
143
+ f.write tag
144
+ end
145
+ else
146
+ rc = RevisionControl.new remote: rc_url, local: dir
147
+ rc.send rc.remotes_method, version: prefix_tag(tag), force: true
148
+ FileUtils.touch "#{dir}/.initial_populate_successful"
149
+ File.open(version_file, 'w') do |f|
150
+ f.write tag
151
+ end
152
+ end
153
+ rescue Origen::GitError, Origen::DesignSyncError, Origen::PerforceError => e
154
+ # If Git failed in the remote, its usually easy to see what the problem is, but now *where* it is.
155
+ # This will prepend the failing remote along with the error from the revision control system,
156
+ # then rethrow the error
157
+ e.message.prepend "When updating remotes for #{remote[:importer].name}: "
158
+ raise e
159
+ end
160
+ end
161
+ end
162
+
124
163
  # Returns the name of the given import (a lower cased symbol)
125
164
  def name_of(remote)
126
165
  dir_defined?(remote)
@@ -243,6 +282,11 @@ module Origen
243
282
  # * If multiple versions of the same remote are found the most
244
283
  # recent one wins.
245
284
  def add_remote(new)
285
+ # Cannot have both a tag and a version defined for a remote
286
+ if ([:tag, :version] - new.keys).empty?
287
+ Origen.log.error('Cannot define both a tag and a version for a remote!')
288
+ fail
289
+ end
246
290
  name = name_of(new)
247
291
  # If the current remote has been imported by one of it's dev dependencies
248
292
  # then always use the local workspace
@@ -289,28 +333,27 @@ module Origen
289
333
  end
290
334
  if remote[:path]
291
335
  create_symlink(remote[:path], dir)
292
-
293
336
  else
294
337
  rc_url = remote[:rc_url] || remote[:vault]
295
- tag = Origen::VersionString.new(remote[:version])
338
+ tag = remote[:tag].nil? ? Origen::VersionString.new(remote[:version]) : Origen::VersionString.new(remote[:tag])
296
339
  version_file = dir.to_s + '/.current_version'
297
340
  begin
298
341
  if File.exist?("#{dir}/.initial_populate_successful")
299
342
  FileUtils.rm_f(version_file) if File.exist?(version_file)
300
343
  rc = RevisionControl.new remote: rc_url, local: dir
301
- rc.checkout version: prefix_tag(tag), force: true
344
+ rc.send rc.remotes_method, version: prefix_tag(tag), force: true
302
345
  File.open(version_file, 'w') do |f|
303
346
  f.write tag
304
347
  end
305
348
  else
306
349
  rc = RevisionControl.new remote: rc_url, local: dir
307
- rc.checkout version: prefix_tag(tag), force: true
350
+ rc.send rc.remotes_method, version: prefix_tag(tag), force: true
308
351
  FileUtils.touch "#{dir}/.initial_populate_successful"
309
352
  File.open(version_file, 'w') do |f|
310
353
  f.write tag
311
354
  end
312
355
  end
313
- rescue Origen::GitError, Origen::DesignSyncError => e
356
+ rescue Origen::GitError, Origen::DesignSyncError, Origen::PerforceError => e
314
357
  # If Git failed in the remote, its usually easy to see what the problem is, but now *where* it is.
315
358
  # This will prepend the failing remote along with the error from the revision control system,
316
359
  # then rethrow the error
@@ -16,6 +16,8 @@ module Origen
16
16
  attr_reader :remote
17
17
  # Returns a pointer to the local location (a Pathname object)
18
18
  attr_reader :local
19
+ # Method to use by Origen::RemoteManager to handle fetching a remote file
20
+ attr_reader :remotes_method
19
21
 
20
22
  # rubocop:disable Lint/UnusedMethodArgument
21
23
 
@@ -27,6 +29,7 @@ module Origen
27
29
  end
28
30
  @remote = Pathname.new(options[:remote])
29
31
  @local = Pathname.new(options[:local]).expand_path
32
+ @remotes_method = :checkout
30
33
  initialize_local_dir(options)
31
34
  end
32
35
 
@@ -192,6 +195,12 @@ module Origen
192
195
  is_a?(Git) # :-)
193
196
  end
194
197
 
198
+ # Returns true if the revision controller object uses Perforce
199
+ def p4?
200
+ is_a?(Perforce)
201
+ end
202
+ alias_method :perforce?, :p4?
203
+
195
204
  # Returns true if the revision controller object uses Subversion
196
205
  def svn?
197
206
  is_a?(Subversion) # :-)
@@ -0,0 +1,110 @@
1
+ require 'P4'
2
+ module Origen
3
+ module RevisionControl
4
+ class Perforce < Base
5
+ # P4 session
6
+ attr_reader :p4
7
+
8
+ # e.g. myfile.txt
9
+ attr_accessor :remote_file
10
+
11
+ def initialize(options = {})
12
+ super
13
+ @remotes_method = :print
14
+ @p4 = P4.new
15
+ @p4.maxresults = 1
16
+ parse_remote
17
+ @p4.connect
18
+ unless @p4.connected?
19
+ Origen.log.error("Could not connect to port #{@p4.port} on client #{@p4.client}!")
20
+ fail
21
+ end
22
+ @p4.run_login
23
+ end
24
+
25
+ # Downloads a file to a local directory, no workspace/client is created. Perfect
26
+ # for read-only access like an application remote
27
+ def print(options = {})
28
+ options = {
29
+ verbose: true,
30
+ version: 'Latest'
31
+ }.merge(options)
32
+ cmd = [__method__.to_s, '-o', "#{@local}/#{@remote_file.to_s.split('/')[-1]}", @remote_file.to_s]
33
+ run cmd
34
+ end
35
+
36
+ def checkout(path = nil, options = {})
37
+ not_yet_supported(__method__)
38
+ end
39
+
40
+ def root
41
+ not_yet_supported(__method__)
42
+ end
43
+
44
+ def current_branch
45
+ not_yet_supported(__method__)
46
+ end
47
+
48
+ def diff_cmd
49
+ not_yet_supported(__method__)
50
+ end
51
+
52
+ def unmanaged
53
+ not_yet_supported(__method__)
54
+ end
55
+
56
+ def local_modifications
57
+ not_yet_supported(__method__)
58
+ end
59
+
60
+ def changes
61
+ not_yet_supported(__method__)
62
+ end
63
+
64
+ def checkin
65
+ not_yet_supported(__method__)
66
+ end
67
+
68
+ def build
69
+ not_yet_supported(__method__)
70
+ end
71
+
72
+ private
73
+
74
+ # Needs to be in the form of an Array with command, as a Sting, as the first argument
75
+ # e.g. ["print", "-o", "pins/myfile.txt", "//depot/myprod/main/doc/pinout/myfile.txt"]
76
+ def run(cmd)
77
+ p4.run cmd
78
+ end
79
+
80
+ def not_yet_supported(m)
81
+ Origen.log.warn("The method #{m} is not currently supported by the Perforce API")
82
+ nil
83
+ end
84
+
85
+ def parse_remote
86
+ (@p4.port, @remote_file) = @remote.to_s.match(/^p4\:\/\/(\S+\:\d+)(.*)/).captures unless @remote.nil?
87
+ end
88
+
89
+ def configure_client(options)
90
+ unless options.include? :local
91
+ Origen.log.error('Need options[:local] to know how to configure the Perforce client!')
92
+ fail
93
+ end
94
+ client_name = "#{Origen.app.name}_#{User.current.id}_#{Time.now.to_i}"
95
+ begin
96
+ client_spec = @p4.fetch_client
97
+ client_spec['Root'] = options[:local].to_s
98
+ client_spec['Client'] = client_name
99
+ client_spec['View'] = ["#{@remote_file} //#{client_name}/#{@remote_file.split('/')[-1]}"]
100
+ client_spec['Host'] = nil
101
+ @p4.save_client(client_spec)
102
+ @p4.client = client_name
103
+ rescue P4Exception
104
+ @p4.errors.each { |e| Origen.log.error e }
105
+ raise
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -4,6 +4,7 @@ module Origen
4
4
  autoload :DesignSync, 'origen/revision_control/design_sync'
5
5
  autoload :Git, 'origen/revision_control/git'
6
6
  autoload :Subversion, 'origen/revision_control/subversion'
7
+ autoload :Perforce, 'origen/revision_control/perforce'
7
8
 
8
9
  IGNORE_DIRS = %w(
9
10
  .ws .lsf log output web coverage .ref .yardoc .collection .bin
@@ -36,6 +37,8 @@ module Origen
36
37
  DesignSync.new(options)
37
38
  when options[:remote] =~ /git/
38
39
  Git.new(options)
40
+ when options[:remote] =~ /^p4/
41
+ Perforce.new(options)
39
42
  else
40
43
  fail "Could not work out the revision control system for: #{options[:remote]}"
41
44
  end
@@ -1,8 +1,6 @@
1
1
  module Origen
2
2
  module Specs
3
3
  module Checkers
4
- require 'nokogiri'
5
-
6
4
  # rubocop:disable Style/RescueModifier:
7
5
  def name_audit(name)
8
6
  return name if name.nil?
@@ -74,7 +72,6 @@ module Origen
74
72
  def evaluate_limit(limit)
75
73
  return limit if limit.is_a?(Numeric)
76
74
  return nil if limit.nil?
77
- limit = limit.to_s if [Nokogiri::XML::NodeSet, Nokogiri::XML::Text, Nokogiri::XML::Element].include? limit.class
78
75
  if limit.is_a? Symbol
79
76
  limit = ':' + limit.to_s
80
77
  else
@@ -364,11 +364,13 @@ module Origen
364
364
 
365
365
  def materialize
366
366
  file = attributes.delete(:file)
367
+ dir = attributes.delete(:dir) || owner.send(:export_dir)
367
368
  block = owner.send(:instantiate_sub_block, name, klass, attributes)
368
369
  if file
369
- require File.join(owner.send(:export_dir), file)
370
+ require File.join(dir, file)
370
371
  block.extend owner.send(:export_module_names_from_path, file).join('::').constantize
371
372
  end
373
+ block.owner = owner
372
374
  block
373
375
  end
374
376
 
@@ -1,6 +1,9 @@
1
+ require 'origen/limits'
1
2
  module Origen
2
3
  module Tests
3
4
  class Test
5
+ include Limits
6
+
4
7
  attr_accessor :id, :owner, :description, :conditions, :platforms
5
8
 
6
9
  def initialize(id, options = {}, &block)
data/lib/origen.rb CHANGED
@@ -68,6 +68,7 @@ unless defined? RGen::ORIGENTRANSITION
68
68
  autoload :Clocks, 'origen/clocks'
69
69
  autoload :Value, 'origen/value'
70
70
  autoload :OrgFile, 'origen/org_file'
71
+ autoload :Limits, 'origen/limits'
71
72
 
72
73
  attr_reader :switch_user
73
74
 
@@ -79,6 +80,7 @@ unless defined? RGen::ORIGENTRANSITION
79
80
  end
80
81
  end
81
82
 
83
+ class PerforceError < OrigenError; status_code(11); end
82
84
  class GitError < OrigenError; status_code(11); end
83
85
  class DesignSyncError < OrigenError; status_code(12); end
84
86
  class RevisionControlUninitializedError < OrigenError; status_code(13); end
@@ -1,6 +1,7 @@
1
1
  # Misc
2
2
  .irb_history
3
3
  .byebug_history
4
+ /config/waves/**/DVEfiles
4
5
 
5
6
  # Editor cruft
6
7
  *.swp
@@ -31,6 +32,8 @@
31
32
  /.bin
32
33
  /.session
33
34
  /tmp
35
+ /waves
36
+ /simulation
34
37
 
35
38
  # Cruft from other revision control systems
36
39
  .SYNC
@@ -4,7 +4,7 @@ module Origen
4
4
  module Export1
5
5
  module Block1
6
6
  def self.extended(model)
7
- model.sub_block :x, file: 'origen/export1/block1/x.rb', lazy: true, base_address: 0x40000000
7
+ model.sub_block :x, file: 'origen/export1/block1/x.rb', dir: '/home/stephen/Code/github/origen/vendor/lib/models', lazy: true, base_address: 0x40000000
8
8
 
9
9
  end
10
10
  end
@@ -66,8 +66,10 @@ module Origen
66
66
  model.add_ground_pin :gnd2
67
67
  model.add_ground_pin :gnd3
68
68
  model.add_ground_pin_group :gnd, :gnd1, :gnd2, :gnd3
69
+ model.add_virtual_pin :relay1
70
+ model.add_virtual_pin :relay2, packages: { bga: {} }
69
71
 
70
- model.sub_block :block1, file: 'origen/export1/block1.rb', lazy: true
72
+ model.sub_block :block1, file: 'origen/export1/block1.rb', dir: '/home/stephen/Code/github/origen/vendor/lib/models', lazy: true
71
73
 
72
74
  end
73
75
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: origen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.0
4
+ version: 0.32.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen McGinty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-12 00:00:00.000000000 Z
11
+ date: 2018-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.7'
83
- - !ruby/object:Gem::Dependency
84
- name: nokogiri
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '='
88
- - !ruby/object:Gem::Version
89
- version: 1.7.2
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '='
95
- - !ruby/object:Gem::Version
96
- version: 1.7.2
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: rspec
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -366,6 +352,26 @@ dependencies:
366
352
  - - "~>"
367
353
  - !ruby/object:Gem::Version
368
354
  version: 0.8.1
355
+ - !ruby/object:Gem::Dependency
356
+ name: p4ruby
357
+ requirement: !ruby/object:Gem::Requirement
358
+ requirements:
359
+ - - "~>"
360
+ - !ruby/object:Gem::Version
361
+ version: '2015.2'
362
+ - - ">="
363
+ - !ruby/object:Gem::Version
364
+ version: 2015.2.1313860
365
+ type: :runtime
366
+ prerelease: false
367
+ version_requirements: !ruby/object:Gem::Requirement
368
+ requirements:
369
+ - - "~>"
370
+ - !ruby/object:Gem::Version
371
+ version: '2015.2'
372
+ - - ">="
373
+ - !ruby/object:Gem::Version
374
+ version: 2015.2.1313860
369
375
  description:
370
376
  email:
371
377
  - stephen.f.mcginty@gmail.com
@@ -494,6 +500,9 @@ files:
494
500
  - lib/origen/generator/stage.rb
495
501
  - lib/origen/global_app.rb
496
502
  - lib/origen/global_methods.rb
503
+ - lib/origen/limits.rb
504
+ - lib/origen/limits/limit.rb
505
+ - lib/origen/limits/limit_set.rb
497
506
  - lib/origen/location.rb
498
507
  - lib/origen/location/base.rb
499
508
  - lib/origen/location/map.rb
@@ -553,6 +562,7 @@ files:
553
562
  - lib/origen/revision_control/base.rb
554
563
  - lib/origen/revision_control/design_sync.rb
555
564
  - lib/origen/revision_control/git.rb
565
+ - lib/origen/revision_control/perforce.rb
556
566
  - lib/origen/revision_control/subversion.rb
557
567
  - lib/origen/ruby_version_check.rb
558
568
  - lib/origen/site_config.rb
@@ -640,7 +650,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
640
650
  version: 1.8.11
641
651
  requirements: []
642
652
  rubyforge_project:
643
- rubygems_version: 2.6.7
653
+ rubygems_version: 2.6.8
644
654
  signing_key:
645
655
  specification_version: 4
646
656
  summary: The Semiconductor Developer's Kit