origen_link 0.4.2 → 0.4.3

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.
@@ -1,25 +1,25 @@
1
- require 'origen_link/vector_based'
2
-
3
- module OrigenLink
4
- module Test
5
- class VectorBased < ::OrigenLink::VectorBased
6
- attr_accessor :message, :microcodestr, :test_response
7
-
8
- def send_cmd(cmdstr, argstr)
9
- @message = cmdstr + ':' + argstr
10
- @test_response
11
- end
12
-
13
- def send_batch(vector_batch)
14
- @test_response
15
- end
16
-
17
- def setup_cmd_response_logger(command, response)
18
- end
19
-
20
- def microcode(msg)
21
- @microcodestr = @microcodestr + msg
22
- end
23
- end
24
- end
25
- end
1
+ require 'origen_link/vector_based'
2
+
3
+ module OrigenLink
4
+ module Test
5
+ class VectorBased < ::OrigenLink::VectorBased
6
+ attr_accessor :message, :microcodestr, :test_response
7
+
8
+ def send_cmd(cmdstr, argstr)
9
+ @message = cmdstr + ':' + argstr
10
+ @test_response
11
+ end
12
+
13
+ def send_batch(vector_batch)
14
+ @test_response
15
+ end
16
+
17
+ def setup_cmd_response_logger(command, response)
18
+ end
19
+
20
+ def microcode(msg)
21
+ @microcodestr = @microcodestr + msg
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,524 +1,526 @@
1
- require 'etc'
2
- require 'origen_testers'
3
- require 'origen_link/server_com'
4
- require 'origen_link/capture_support'
5
- require 'origen_link/configuration_commands'
6
- require 'origen_link/callback_handlers'
7
- module OrigenLink
8
- # OrigenLink::VectorBased
9
- # This class describes the OrigenLink app plug-in. Vector data that Origen
10
- # generates is intercepted and sent to a debug device (typically will be a Udoo
11
- # Neo - www.udoo.org). The debug device can be any device that is able to serve
12
- # a TCP socket, recieve and interpret the command set used by this class and send
13
- # the expected responses.
14
- #
15
- class VectorBased
16
- include OrigenTesters::VectorBasedTester
17
- include ServerCom
18
- include CaptureSupport
19
- include ConfigurationCommands
20
-
21
- # The number of cycles that fail
22
- attr_accessor :fail_count
23
- # The number of vector cycles generated
24
- attr_reader :vector_count
25
- # The accumulated total time spent communicating with the server
26
- attr_reader :total_comm_time
27
- # The accumulated total time spent establishing the server connection
28
- attr_reader :total_connect_time
29
- # The accumulated total time spent transmitting to the server app
30
- attr_reader :total_xmit_time
31
- # The accumulated total time spent receiving from the server app
32
- attr_reader :total_recv_time
33
- # The accumulated total number of packets sent to the server
34
- attr_reader :total_packets
35
- # The accumulated number of times push_vector was called with the present tset and pin info
36
- attr_reader :vector_repeatcount
37
- # The look up of programmed tsets. Names are converted to a unique number identifier
38
- attr_reader :tsets_programmed
39
- # Data captured using tester.capture
40
- attr_reader :captured_data
41
- # Array of vectors waiting to be sent to the sever
42
- attr_reader :vector_batch
43
- # Used with capture
44
- attr_reader :store_pins_batch
45
- # Array of comments received through push_comment
46
- attr_reader :comment_batch
47
- # The name of the user running OrigenLink
48
- attr_reader :user_name
49
- # Indicates that communication has been initiated with the server
50
- attr_reader :initial_comm_sent
51
-
52
- def initialize(address, port, options = {})
53
- @address = address
54
- @port = port
55
- @fail_count = 0
56
- @vector_count = 0
57
- @previous_vectordata = ''
58
- @previous_tset = ''
59
- @vector_repeatcount = 0
60
- @total_comm_time = 0
61
- @total_connect_time = 0
62
- @total_xmit_time = 0
63
- @total_recv_time = 0
64
- @total_packets = 0
65
- @max_packet_time = 0
66
- @max_receive_time = 0
67
- @tsets_programmed = {}
68
- @tsets_warned = {}
69
- @tset_count = 1
70
- @store_pins = []
71
- @captured_data = []
72
- # A tester seems to be unable to register as a callback handler, so for now instantiating a
73
- # dedicated object to implement the handlers related to this tester
74
- CallbackHandlers.new
75
- @vector_batch = []
76
- @store_pins_batch = {}
77
- @comment_batch = {}
78
- @batch_vectors = true
79
- @pattern_link_messages = []
80
- @pattern_comments = {}
81
- @user_name = Etc.getlogin
82
- @initial_comm_sent = false
83
- @initial_vector_pushed = false
84
- @pinorder = ''
85
- @pinmap_hash = {}
86
- @batched_setup_cmds = []
87
-
88
- # check the server version against the plug-in version
89
- response = send_cmd('version', '')
90
- response = 'Error' if response.nil? # prevent run time error in regression tests
91
- response.chomp!
92
- server_version = response.split(':')[1]
93
- server_version = '?.?.? - 0.2.0 or earlier' if response =~ /Error/
94
- app_version = OrigenLink::VERSION # Origen.app(:origen_link).version
95
- Origen.log.info("Plug-in link version: #{app_version}, Server link version: #{server_version}")
96
- unless app_version == server_version
97
- Origen.log.warn('Server version and plug-in link versions do not match')
98
- end
99
- end
100
-
101
- # warn but don't fail if an api for another tester is not implmented
102
- def method_missing(m, *args, &block)
103
- Origen.log.warn "#{m} is not implemented by origen_link and will be ignored"
104
- end
105
-
106
- # push_comment
107
- # This method intercepts comments so they can be correctly placed in the output file
108
- # when vector batching is used
109
- def push_comment(msg)
110
- if @batch_vectors
111
- key = @vector_batch.length
112
- if @comment_batch.key?(key)
113
- @comment_batch[key] = @comment_batch[key] + "\n" + msg
114
- else
115
- @comment_batch[key] = msg
116
- end
117
- pattern_key = @pattern_link_messages.length + key
118
- @pattern_comments[pattern_key] = @comment_batch[key]
119
- else
120
- microcode msg
121
- end
122
- end
123
-
124
- # ordered_pins(options = {})
125
- # expand pin groups to their component pins after the pin ordering is completed
126
- # OrigenLink always operates on individual pins. This saves other methods
127
- # from each needing to handle pins and/or groups of pins.
128
- def ordered_pins(options = {})
129
- result = super
130
- groups = []
131
- result.each { |p| groups << p if p.size > 1 }
132
- groups.each do |group|
133
- # locate this group in the result array
134
- i = result.index(group)
135
- result.delete_at(i)
136
- dut.pins(group.id).map.each do |sub_pin|
137
- result.insert(i, sub_pin)
138
- i += 1
139
- end
140
- end
141
-
142
- if @pinmap.nil?
143
- # create the pinmap if pin metadata was provided
144
- pinarr = []
145
- result.each do |pin|
146
- if pin.meta.key?(:link_io)
147
- pinarr << pin.name.to_s
148
- pinarr << pin.meta[:link_io].to_s
149
- end
150
- end
151
- self.pinmap = pinarr.join(',') unless pinarr.size == 0
152
- end
153
-
154
- result
155
- end
156
-
157
- # fix_ordered_pins(options)
158
- # This method is called the first time push_vector is called.
159
- #
160
- # This method will create the pinmap from pin meta data if needed.
161
- #
162
- # This method will remove any pin data that doesn't correspond
163
- # to a pin in the link pinmap and remove those pins from the
164
- # @ordered_pins_cache to prevent them from being rendered
165
- # on the next cycle.
166
- # This will prevent unwanted behavior. The link server
167
- # expects only pin data for pins in the pinmap.
168
- def fix_ordered_pins(options)
169
- # remove non-mapped pins from the ordered pins cache - prevents them appearing in future push_vector calls
170
- orig_size = @ordered_pins_cache.size
171
- @ordered_pins_cache.delete_if { |p| !@pinmap_hash[p.name.to_s] }
172
- Origen.log.debug('OrigenLink removed non-mapped pins from the cached pin order array') unless orig_size == @ordered_pins_cache.size
173
- # update pin values for the current push_vector call
174
- vals = []
175
- @ordered_pins_cache.each { |p| vals << p.to_vector }
176
- options[:pin_vals] = vals.join('')
177
- options
178
- end
179
-
180
- # push_vector
181
- # This method intercepts vector data from Origen, removes white spaces and compresses repeats
182
- def push_vector(options)
183
- unless @initial_vector_pushed
184
- if @pinmap.nil?
185
- Origen.log.error('OrigenLink: pinmap has not been setup, use tester.pinmap= to initialize a pinmap')
186
- else
187
- Origen.log.debug('OrigenLink: executing pattern with pinmap:' + @pinmap.to_s)
188
- end
189
-
190
- # remove pins not in the link pinmap
191
- options = fix_ordered_pins(options)
192
-
193
- # now send any configuration commands that were saved prior to pinmap setup (clears all server configs)
194
- @batched_setup_cmds.each do |cmd|
195
- response = send_cmd(cmd[0], cmd[1])
196
- setup_cmd_response_logger(cmd[0], response)
197
- end
198
-
199
- @initial_vector_pushed = true
200
- end
201
- set_pinorder if @pinorder == ''
202
- programmed_data = options[:pin_vals].gsub(/\s+/, '')
203
- unless options[:timeset]
204
- puts 'No timeset defined!'
205
- puts 'Add one to your top level startup method or target like this:'
206
- puts 'tester.set_timeset("nvmbist", 40) # Where 40 is the period in ns'
207
- exit 1
208
- end
209
- tset = options[:timeset].name
210
- local_repeat = options[:repeat].nil? ? 1 : options[:repeat]
211
- if @vector_count > 0
212
- # compressing repeats as we go
213
- if (programmed_data == @previous_vectordata) && (@previous_tset == tset) && @store_pins.empty?
214
- @vector_repeatcount += local_repeat
215
- else
216
- # all repeats of the previous vector have been counted
217
- # time to flush. Don't panic though! @previous_vectordata
218
- # is what gets flushed. programmed_data is passed as an
219
- # arg to be set as the new @previous_vectordata
220
- flush_vector(programmed_data, tset, local_repeat)
221
- end
222
- else
223
- # if this is the first vector of the pattern, insure variables are initialized
224
- @previous_vectordata = programmed_data
225
- @previous_tset = tset
226
- @vector_repeatcount = 1
227
- end # if vector_count > 0
228
- @vector_count += 1
229
- end
230
-
231
- # flush_vector
232
- # Just as the name suggests, this method "flushes" a vector. This is necessary because
233
- # of repeat compression (a vector isn't sent until different vector data is encountered)
234
- def flush_vector(programmed_data = '', tset = '', local_repeat = 1)
235
- # prevent server crash when vector_flush is used during debug
236
- unless @previous_vectordata == ''
237
- if @vector_repeatcount > 1
238
- repeat_prefix = "repeat#{@vector_repeatcount},"
239
- else
240
- repeat_prefix = ''
241
- end
242
- if @tsets_programmed[@previous_tset]
243
- tset_prefix = "tset#{@tsets_programmed[@previous_tset]},"
244
- else
245
- # The hash of programmed tsets does not contain this tset
246
- # Check the timing api to see if there is timing info there
247
- # and send the timing info to the link server
248
- if dut.respond_to?(:timeset)
249
- tset_prefix = process_timeset(tset)
250
- else
251
- tset_warning(tset)
252
- tset_prefix = ''
253
- end
254
- end
255
-
256
- if @batch_vectors
257
- @vector_batch << 'pin_cycle:' + tset_prefix + repeat_prefix + @previous_vectordata
258
- # store capture pins for batch processing
259
- unless @store_pins.empty?
260
- @store_pins_batch[@vector_batch.length - 1] = @store_pins
261
- end
262
- else
263
- process_vector_response(send_cmd('pin_cycle', tset_prefix + repeat_prefix + @previous_vectordata))
264
- end
265
-
266
- # make sure that only requested vectors are stored when batching is enabled
267
- @store_pins = []
268
- end
269
-
270
- @vector_repeatcount = local_repeat
271
- @previous_vectordata = programmed_data
272
- @previous_tset = tset
273
- end
274
-
275
- # synchronize
276
- # This method will synchronize the DUT state with Origen. All generated
277
- # vectors are sent to the DUT for execution and the responses are processed
278
- def synchronize(output_file = '')
279
- flush_vector
280
- if @batch_vectors
281
- process_response(send_batch(@vector_batch), output_file)
282
- end
283
- @vector_batch = []
284
- @store_pins_batch.clear
285
- @comment_batch.clear
286
- end
287
-
288
- # process_response
289
- # This method will process a server response. Send log info to the output,
290
- # keep track of fail count and captured data
291
- def process_response(response, output_file = '')
292
- if response.is_a?(Array)
293
- # if called from finalize_pattern -> synchronize, open the output_file and store results
294
- output_obj = nil
295
- output_obj = File.open(output_file, 'a+') unless output_file == ''
296
-
297
- # in case there were only comments and no vectors, place comments (if any)
298
- microcode @comment_batch[0] if response.size == 0
299
-
300
- response.each_index do |index|
301
- # restore store pins state for processing
302
- if @store_pins_batch.key?(index)
303
- @store_pins = @store_pins_batch[index]
304
- else
305
- @store_pins = []
306
- end
307
- process_vector_response(response[index], output_obj)
308
- if @comment_batch.key?(index)
309
- if output_file == ''
310
- microcode @comment_batch[index]
311
- else
312
- # get the header placed correctly, the below code doesn't work
313
- # if index == response.length - 1
314
- # output_obj.puts 'last comment'
315
- # output_obj.lineno = 0
316
- # end
317
- output_obj.puts(@comment_batch[index])
318
- end
319
- end
320
- end
321
- output_obj.close unless output_file == ''
322
- else
323
- process_vector_response(response)
324
- end
325
- end
326
-
327
- # process_vector_response
328
- # This method exists to prevent code duplication when handling an array of
329
- # batched responses versus a single response string.
330
- def process_vector_response(vector_response, output_obj = nil)
331
- msg = ''
332
- unless @store_pins.empty?
333
- msg = " (Captured #{@store_pins.map(&:name).join(', ')})\n"
334
- capture_data(vector_response)
335
- vector_response.strip!
336
- # vector_response += msg
337
- end
338
- vector_cycles = vector_response.split(/\s+/)
339
- expected_msg = ''
340
- expected_msg = ' ' + vector_cycles.pop if vector_cycles[vector_cycles.length - 1] =~ /Expected/
341
- pfstatus = vector_cycles[0].chr
342
- vector_cycles[0] = vector_cycles[0].byteslice(2, vector_cycles[0].length - 2)
343
-
344
- vector_cycles.each do |cycle|
345
- thiscyclefail = false
346
- bad_pin_data = false
347
- if pfstatus == 'F'
348
- # check to see if this cycle failed
349
- 0.upto(cycle.length - 1) do |index|
350
- bad_pin_data = true if (cycle[index] == 'W')
351
- thiscyclefail = true if (cycle[index] == 'H') && (expected_msg[expected_msg.length - cycle.length + index] == 'L')
352
- thiscyclefail = true if (cycle[index] == 'L') && (expected_msg[expected_msg.length - cycle.length + index] == 'H')
353
- end
354
- end
355
- if thiscyclefail
356
- expected_msg_prnt = expected_msg
357
- prepend = 'F:'
358
- else
359
- expected_msg_prnt = ''
360
- prepend = 'P:'
361
- end
362
- if bad_pin_data
363
- expected_msg_prnt = ' ' + 'W indicates no operation, check timeset definition'
364
- prepend = 'F:'
365
- end
366
-
367
- if output_obj.nil?
368
- microcode prepend + cycle + expected_msg_prnt + msg
369
- else
370
- output_obj.puts(prepend + cycle + expected_msg_prnt + msg)
371
- end
372
- end
373
-
374
- unless vector_response.chr == 'P'
375
- # TODO: Put this back with an option to disable, based on a serial or parallel interface being used
376
- # microcode 'E:' + @previous_vectordata + ' //expected data for previous vector'
377
- @fail_count += 1
378
- end
379
- end
380
-
381
- # initialize_pattern
382
- # This method initializes variables at the start of a pattern.
383
- # It is called automatically when pattern generation starts.
384
- def initialize_pattern
385
- @fail_count = 0
386
- @vector_count = 0
387
- @vector_batch.delete_if { true }
388
- @store_pins_batch.clear
389
- @comment_batch.clear
390
- @pattern_link_messages.delete_if { true }
391
- @pattern_comments.clear
392
-
393
- @total_packets = 0
394
- @total_comm_time = 0
395
- @total_connect_time = 0
396
- @total_xmit_time = 0
397
- @total_recv_time = 0
398
-
399
- # moved to push_vector to allow auto-pinmap
400
- # if @pinmap.nil?
401
- # Origen.log.error('OrigenLink: pinmap has not been setup, use tester.pinmap= to initialize a pinmap')
402
- # else
403
- # Origen.log.debug('OrigenLink: executing pattern with pinmap:' + @pinmap.to_s)
404
- # end
405
- end
406
-
407
- # finalize_pattern
408
- # This method flushes the final vector. Then, it logs success or failure of the
409
- # pattern execution along with execution time information.
410
- def finalize_pattern(output_file)
411
- Origen.log.debug('Pattern generation completed. Sending all stored vector data')
412
- synchronize(output_file)
413
- send_cmd('', 'session_end')
414
- # for debug, report communication times
415
- Origen.log.debug("total communication time: #{@total_comm_time}")
416
- Origen.log.debug("total connect time: #{@total_connect_time}")
417
- Origen.log.debug("total transmit time: #{@total_xmit_time}")
418
- Origen.log.debug("total receive time: #{@total_recv_time}")
419
- Origen.log.debug("total packets: #{@total_packets}")
420
- Origen.log.debug("total time per packet: #{@total_comm_time / @total_packets}")
421
- Origen.log.debug("connect time per packet: #{@total_connect_time / @total_packets}")
422
- Origen.log.debug("transmit time per packet: #{@total_xmit_time / @total_packets}")
423
- Origen.log.debug("receive time per packet: #{@total_recv_time / @total_packets}")
424
- Origen.log.debug("max packet time: #{@max_packet_time}")
425
- Origen.log.debug("max duration command - #{@longest_packet}")
426
- Origen.log.debug("max receive time: #{@max_receive_time}")
427
- if @fail_count == 0
428
- # Origen.log.success("PASS - pattern execution passed (#{@vector_count} vectors pass)")
429
- Origen.app.stats.report_pass
430
- else
431
- # Origen.log.error("FAIL - pattern execution failed (#{@fail_count} failures)")
432
- Origen.app.stats.report_fail
433
- end
434
- commands_file = Origen.app.current_job.output_file.split('.')[0] + '_link_cmds.txt'
435
- File.open(commands_file, 'w') do |file|
436
- file.puts("pin_assign:#{@pinmap}")
437
- file.puts("pin_patternorder:#{@pinorder}")
438
- @pattern_link_messages.each_index do |index|
439
- file.puts(@pattern_link_messages[index])
440
- file.puts(@pattern_comments[index]) if @pattern_comments.key?(index)
441
- end
442
- file.puts(':session_end')
443
- end
444
- end
445
-
446
- # to_s
447
- # returns 'OrigenLink::VectorBased'
448
- #
449
- # No longer a use for this. Use tester.link?
450
- def to_s
451
- 'OrigenLink::VectorBased'
452
- end
453
-
454
- # transaction
455
- # returns true/false indicating whether the transaction passed
456
- # true = pass
457
- # false = fail
458
- #
459
- # @example
460
- # if !tester.transaction {dut.reg blah blah}
461
- # puts 'transaction failed'
462
- # end
463
- def transaction
464
- if block_given?
465
- synchronize
466
- transaction_fail_count = @fail_count
467
- yield
468
- synchronize
469
- transaction_fail_count = @fail_count - transaction_fail_count
470
- if transaction_fail_count == 0
471
- true
472
- else
473
- false
474
- end
475
- else
476
- true
477
- end
478
- end
479
-
480
- # wait a fixed amount of time
481
- # This method will run the Sleep method to wait rather than generating cycles.
482
- # Cycle execution time is not predictable with OrigenLink.
483
- #
484
- # If you want cycles to be generated by this method include apply_cycles: true
485
- # in the options hash
486
- #
487
- # This method currently doesn't handle match blocks.
488
- def wait(options = {})
489
- options = {
490
- cycles: 0,
491
- time_in_cycles: 0,
492
- time_in_us: 0,
493
- time_in_ns: 0,
494
- time_in_ms: 0,
495
- time_in_s: 0,
496
- match: false, # Set to true to invoke a match loop where the supplied delay
497
- apply_cycles: false
498
- # will become the timeout duration
499
- }.merge(options)
500
-
501
- fail 'wait(match: true) is not yet implemented on Link' if options[:match]
502
-
503
- time_delay = 0
504
- time_delay += (options[:time_in_s])
505
- time_delay += (options[:time_in_ms]) * 0.001
506
- time_delay += (options[:time_in_us]) * 0.000001
507
- time_delay += (options[:time_in_ns]) * 0.000000001
508
- time_delay += (options[:cycles] + options[:time_in_cycles]) * current_period_in_ns * 0.000000001
509
-
510
- if options[:apply_cycles]
511
- cycles = time_delay / 0.00003 # cycle execution time is unpredictable, but ~= 30us
512
- if options[:cycles] > 0 || options[:time_in_cycles] > 0
513
- options[:repeat] = options[:cycles] + options[:time_in_cycles]
514
- else
515
- options[:repeat] = (cycles > 0) ? cycles : 1
516
- end
517
- cycle(options)
518
- else
519
- synchronize # ensure all generated cycles are executed before delay is run
520
- sleep time_delay
521
- end
522
- end
523
- end
524
- end
1
+ require 'etc'
2
+ require 'origen_testers'
3
+ require 'origen_link/server_com'
4
+ require 'origen_link/capture_support'
5
+ require 'origen_link/configuration_commands'
6
+ require 'origen_link/callback_handlers'
7
+ module OrigenLink
8
+ # OrigenLink::VectorBased
9
+ # This class describes the OrigenLink app plug-in. Vector data that Origen
10
+ # generates is intercepted and sent to a debug device (typically will be a Udoo
11
+ # Neo - www.udoo.org). The debug device can be any device that is able to serve
12
+ # a TCP socket, recieve and interpret the command set used by this class and send
13
+ # the expected responses.
14
+ #
15
+ class VectorBased
16
+ include OrigenTesters::VectorBasedTester
17
+ include ServerCom
18
+ include CaptureSupport
19
+ include ConfigurationCommands
20
+
21
+ # The number of cycles that fail
22
+ attr_accessor :fail_count
23
+ # The number of vector cycles generated
24
+ attr_reader :vector_count
25
+ # The accumulated total time spent communicating with the server
26
+ attr_reader :total_comm_time
27
+ # The accumulated total time spent establishing the server connection
28
+ attr_reader :total_connect_time
29
+ # The accumulated total time spent transmitting to the server app
30
+ attr_reader :total_xmit_time
31
+ # The accumulated total time spent receiving from the server app
32
+ attr_reader :total_recv_time
33
+ # The accumulated total number of packets sent to the server
34
+ attr_reader :total_packets
35
+ # The accumulated number of times push_vector was called with the present tset and pin info
36
+ attr_reader :vector_repeatcount
37
+ # The look up of programmed tsets. Names are converted to a unique number identifier
38
+ attr_reader :tsets_programmed
39
+ # Data captured using tester.capture
40
+ attr_reader :captured_data
41
+ # Array of vectors waiting to be sent to the sever
42
+ attr_reader :vector_batch
43
+ # Used with capture
44
+ attr_reader :store_pins_batch
45
+ # Array of comments received through push_comment
46
+ attr_reader :comment_batch
47
+ # The name of the user running OrigenLink
48
+ attr_reader :user_name
49
+ # Indicates that communication has been initiated with the server
50
+ attr_reader :initial_comm_sent
51
+
52
+ def initialize(address, port, options = {})
53
+ @address = address
54
+ @port = port
55
+ @fail_count = 0
56
+ @vector_count = 0
57
+ @previous_vectordata = ''
58
+ @previous_tset = ''
59
+ @vector_repeatcount = 0
60
+ @total_comm_time = 0
61
+ @total_connect_time = 0
62
+ @total_xmit_time = 0
63
+ @total_recv_time = 0
64
+ @total_packets = 0
65
+ @max_packet_time = 0
66
+ @max_receive_time = 0
67
+ @tsets_programmed = {}
68
+ @tsets_warned = {}
69
+ @tset_count = 1
70
+ @store_pins = []
71
+ @captured_data = []
72
+ # A tester seems to be unable to register as a callback handler, so for now instantiating a
73
+ # dedicated object to implement the handlers related to this tester
74
+ CallbackHandlers.new
75
+ @vector_batch = []
76
+ @store_pins_batch = {}
77
+ @comment_batch = {}
78
+ @batch_vectors = true
79
+ @pattern_link_messages = []
80
+ @pattern_comments = {}
81
+ @user_name = Etc.getlogin
82
+ @initial_comm_sent = false
83
+ @initial_vector_pushed = false
84
+ @pinorder = ''
85
+ @pinmap_hash = {}
86
+ @batched_setup_cmds = []
87
+
88
+ # check the server version against the plug-in version
89
+ response = send_cmd('version', '')
90
+ response = 'Error' if response.nil? # prevent run time error in regression tests
91
+ response.chomp!
92
+ server_version = response.split(':')[1]
93
+ server_version = '?.?.? - 0.2.0 or earlier' if response =~ /Error/
94
+ app_version = OrigenLink::VERSION # Origen.app(:origen_link).version
95
+ Origen.log.info("Plug-in link version: #{app_version}, Server link version: #{server_version}")
96
+ unless app_version == server_version
97
+ Origen.log.warn('Server version and plug-in link versions do not match')
98
+ end
99
+ end
100
+
101
+ # warn but don't fail if an api for another tester is not implmented
102
+ def method_missing(m, *args, &block)
103
+ Origen.log.warn "#{m} is not implemented by origen_link and will be ignored"
104
+ end
105
+
106
+ # push_comment
107
+ # This method intercepts comments so they can be correctly placed in the output file
108
+ # when vector batching is used
109
+ def push_comment(msg)
110
+ if @batch_vectors
111
+ key = @vector_batch.length
112
+ if @comment_batch.key?(key)
113
+ @comment_batch[key] = @comment_batch[key] + "\n" + msg
114
+ else
115
+ @comment_batch[key] = msg
116
+ end
117
+ pattern_key = @pattern_link_messages.length + key
118
+ @pattern_comments[pattern_key] = @comment_batch[key]
119
+ else
120
+ microcode msg
121
+ end
122
+ end
123
+
124
+ # ordered_pins(options = {})
125
+ # expand pin groups to their component pins after the pin ordering is completed
126
+ # OrigenLink always operates on individual pins. This saves other methods
127
+ # from each needing to handle pins and/or groups of pins.
128
+ def ordered_pins(options = {})
129
+ result = super
130
+ groups = []
131
+ result.each { |p| groups << p if p.size > 1 }
132
+ groups.each do |group|
133
+ # locate this group in the result array
134
+ i = result.index(group)
135
+ result.delete_at(i)
136
+ dut.pins(group.id).map.each do |sub_pin|
137
+ result.insert(i, sub_pin)
138
+ i += 1
139
+ end
140
+ end
141
+
142
+ if @pinmap.nil?
143
+ # create the pinmap if pin metadata was provided
144
+ pinarr = []
145
+ result.each do |pin|
146
+ if pin.meta.key?(:link_io)
147
+ pinarr << pin.name.to_s
148
+ pinarr << pin.meta[:link_io].to_s
149
+ end
150
+ end
151
+ self.pinmap = pinarr.join(',') unless pinarr.size == 0
152
+ end
153
+
154
+ result
155
+ end
156
+
157
+ # fix_ordered_pins(options)
158
+ # This method is called the first time push_vector is called.
159
+ #
160
+ # This method will create the pinmap from pin meta data if needed.
161
+ #
162
+ # This method will remove any pin data that doesn't correspond
163
+ # to a pin in the link pinmap and remove those pins from the
164
+ # @ordered_pins_cache to prevent them from being rendered
165
+ # on the next cycle.
166
+ # This will prevent unwanted behavior. The link server
167
+ # expects only pin data for pins in the pinmap.
168
+ def fix_ordered_pins(options)
169
+ # remove non-mapped pins from the ordered pins cache - prevents them appearing in future push_vector calls
170
+ orig_size = @ordered_pins_cache.size
171
+ @ordered_pins_cache.delete_if { |p| !@pinmap_hash[p.name.to_s] }
172
+ Origen.log.debug('OrigenLink removed non-mapped pins from the cached pin order array') unless orig_size == @ordered_pins_cache.size
173
+ # update pin values for the current push_vector call
174
+ vals = []
175
+ @ordered_pins_cache.each { |p| vals << p.to_vector }
176
+ options[:pin_vals] = vals.join('')
177
+ options
178
+ end
179
+
180
+ # push_vector
181
+ # This method intercepts vector data from Origen, removes white spaces and compresses repeats
182
+ def push_vector(options)
183
+ unless @initial_vector_pushed
184
+ if @pinmap.nil?
185
+ Origen.log.error('OrigenLink: pinmap has not been setup, use tester.pinmap= to initialize a pinmap')
186
+ else
187
+ Origen.log.debug('OrigenLink: executing pattern with pinmap:' + @pinmap.to_s)
188
+ end
189
+
190
+ # remove pins not in the link pinmap
191
+ options = fix_ordered_pins(options)
192
+
193
+ # now send any configuration commands that were saved prior to pinmap setup (clears all server configs)
194
+ @batched_setup_cmds.each do |cmd|
195
+ response = send_cmd(cmd[0], cmd[1])
196
+ setup_cmd_response_logger(cmd[0], response)
197
+ end
198
+
199
+ @initial_vector_pushed = true
200
+ end
201
+ set_pinorder if @pinorder == ''
202
+ programmed_data = options[:pin_vals].gsub(/\s+/, '')
203
+ unless options[:timeset]
204
+ puts 'No timeset defined!'
205
+ puts 'Add one to your top level startup method or target like this:'
206
+ puts 'tester.set_timeset("nvmbist", 40) # Where 40 is the period in ns'
207
+ exit 1
208
+ end
209
+ tset = options[:timeset].name
210
+ local_repeat = options[:repeat].nil? ? 1 : options[:repeat]
211
+ if @vector_count > 0
212
+ # compressing repeats as we go
213
+ if (programmed_data == @previous_vectordata) && (@previous_tset == tset) && @store_pins.empty?
214
+ @vector_repeatcount += local_repeat
215
+ else
216
+ # all repeats of the previous vector have been counted
217
+ # time to flush. Don't panic though! @previous_vectordata
218
+ # is what gets flushed. programmed_data is passed as an
219
+ # arg to be set as the new @previous_vectordata
220
+ flush_vector(programmed_data, tset, local_repeat)
221
+ end
222
+ else
223
+ # if this is the first vector of the pattern, insure variables are initialized
224
+ @previous_vectordata = programmed_data
225
+ @previous_tset = tset
226
+ @vector_repeatcount = 1
227
+ end # if vector_count > 0
228
+ @vector_count += 1
229
+ end
230
+
231
+ # flush_vector
232
+ # Just as the name suggests, this method "flushes" a vector. This is necessary because
233
+ # of repeat compression (a vector isn't sent until different vector data is encountered)
234
+ def flush_vector(programmed_data = '', tset = '', local_repeat = 1)
235
+ # prevent server crash when vector_flush is used during debug
236
+ unless @previous_vectordata == ''
237
+ if @vector_repeatcount > 1
238
+ repeat_prefix = "repeat#{@vector_repeatcount},"
239
+ else
240
+ repeat_prefix = ''
241
+ end
242
+ if @tsets_programmed[@previous_tset]
243
+ tset_prefix = "tset#{@tsets_programmed[@previous_tset]},"
244
+ else
245
+ # The hash of programmed tsets does not contain this tset
246
+ # Check the timing api to see if there is timing info there
247
+ # and send the timing info to the link server
248
+ if dut.respond_to?(:timeset)
249
+ tset_prefix = process_timeset(tset)
250
+ else
251
+ tset_warning(tset)
252
+ tset_prefix = ''
253
+ end
254
+ end
255
+
256
+ if @batch_vectors
257
+ @vector_batch << 'pin_cycle:' + tset_prefix + repeat_prefix + @previous_vectordata
258
+ # store capture pins for batch processing
259
+ unless @store_pins.empty?
260
+ @store_pins_batch[@vector_batch.length - 1] = @store_pins
261
+ end
262
+ else
263
+ process_vector_response(send_cmd('pin_cycle', tset_prefix + repeat_prefix + @previous_vectordata))
264
+ end
265
+
266
+ # make sure that only requested vectors are stored when batching is enabled
267
+ @store_pins = []
268
+ end
269
+
270
+ @vector_repeatcount = local_repeat
271
+ @previous_vectordata = programmed_data
272
+ @previous_tset = tset
273
+ end
274
+
275
+ # synchronize
276
+ # This method will synchronize the DUT state with Origen. All generated
277
+ # vectors are sent to the DUT for execution and the responses are processed
278
+ def synchronize(output_file = '')
279
+ flush_vector
280
+ if @batch_vectors
281
+ process_response(send_batch(@vector_batch), output_file)
282
+ end
283
+ @vector_batch = []
284
+ @store_pins_batch.clear
285
+ @comment_batch.clear
286
+ end
287
+
288
+ # process_response
289
+ # This method will process a server response. Send log info to the output,
290
+ # keep track of fail count and captured data
291
+ def process_response(response, output_file = '')
292
+ if response.is_a?(Array)
293
+ # if called from finalize_pattern -> synchronize, open the output_file and store results
294
+ output_obj = nil
295
+ output_obj = File.open(output_file, 'a+') unless output_file == ''
296
+
297
+ # in case there were only comments and no vectors, place comments (if any)
298
+ microcode @comment_batch[0] if response.size == 0
299
+
300
+ response.each_index do |index|
301
+ # restore store pins state for processing
302
+ if @store_pins_batch.key?(index)
303
+ @store_pins = @store_pins_batch[index]
304
+ else
305
+ @store_pins = []
306
+ end
307
+ process_vector_response(response[index], output_obj)
308
+ if @comment_batch.key?(index)
309
+ if output_file == ''
310
+ microcode @comment_batch[index]
311
+ else
312
+ # get the header placed correctly, the below code doesn't work
313
+ # if index == response.length - 1
314
+ # output_obj.puts 'last comment'
315
+ # output_obj.lineno = 0
316
+ # end
317
+ output_obj.puts(@comment_batch[index])
318
+ end
319
+ end
320
+ end
321
+ output_obj.close unless output_file == ''
322
+ else
323
+ process_vector_response(response)
324
+ end
325
+ end
326
+
327
+ # process_vector_response
328
+ # This method exists to prevent code duplication when handling an array of
329
+ # batched responses versus a single response string.
330
+ def process_vector_response(vector_response, output_obj = nil)
331
+ msg = ''
332
+ unless @store_pins.empty?
333
+ msg = " (Captured #{@store_pins.map(&:name).join(', ')})\n"
334
+ capture_data(vector_response)
335
+ vector_response.strip!
336
+ # vector_response += msg
337
+ end
338
+ vector_cycles = vector_response.split(/\s+/)
339
+ expected_msg = ''
340
+ expected_msg = ' ' + vector_cycles.pop if vector_cycles[vector_cycles.length - 1] =~ /Expected/
341
+ pfstatus = vector_cycles[0].chr
342
+ vector_cycles[0] = vector_cycles[0].byteslice(2, vector_cycles[0].length - 2)
343
+
344
+ vector_cycles.each do |cycle|
345
+ thiscyclefail = false
346
+ bad_pin_data = false
347
+ if pfstatus == 'F'
348
+ # check to see if this cycle failed
349
+ 0.upto(cycle.length - 1) do |index|
350
+ bad_pin_data = true if (cycle[index] == 'W')
351
+ thiscyclefail = true if (cycle[index] == 'H') && (expected_msg[expected_msg.length - cycle.length + index] == 'L')
352
+ thiscyclefail = true if (cycle[index] == 'L') && (expected_msg[expected_msg.length - cycle.length + index] == 'H')
353
+ end
354
+ end
355
+ if thiscyclefail
356
+ expected_msg_prnt = expected_msg
357
+ prepend = 'F:'
358
+ else
359
+ expected_msg_prnt = ''
360
+ prepend = 'P:'
361
+ end
362
+ if bad_pin_data
363
+ expected_msg_prnt = ' ' + 'W indicates no operation, check timeset definition'
364
+ prepend = 'F:'
365
+ end
366
+
367
+ if output_obj.nil?
368
+ microcode prepend + cycle + expected_msg_prnt + msg
369
+ else
370
+ output_obj.puts(prepend + cycle + expected_msg_prnt + msg)
371
+ end
372
+ end
373
+
374
+ unless vector_response.chr == 'P'
375
+ # TODO: Put this back with an option to disable, based on a serial or parallel interface being used
376
+ # microcode 'E:' + @previous_vectordata + ' //expected data for previous vector'
377
+ @fail_count += 1
378
+ end
379
+ end
380
+
381
+ # initialize_pattern
382
+ # This method initializes variables at the start of a pattern.
383
+ # It is called automatically when pattern generation starts.
384
+ def initialize_pattern
385
+ @fail_count = 0
386
+ @vector_count = 0
387
+ @vector_batch.delete_if { true }
388
+ @store_pins_batch.clear
389
+ @comment_batch.clear
390
+ @pattern_link_messages.delete_if { true }
391
+ @pattern_comments.clear
392
+
393
+ @total_packets = 0
394
+ @total_comm_time = 0
395
+ @total_connect_time = 0
396
+ @total_xmit_time = 0
397
+ @total_recv_time = 0
398
+
399
+ # moved to push_vector to allow auto-pinmap
400
+ # if @pinmap.nil?
401
+ # Origen.log.error('OrigenLink: pinmap has not been setup, use tester.pinmap= to initialize a pinmap')
402
+ # else
403
+ # Origen.log.debug('OrigenLink: executing pattern with pinmap:' + @pinmap.to_s)
404
+ # end
405
+ end
406
+
407
+ # finalize_pattern
408
+ # This method flushes the final vector. Then, it logs success or failure of the
409
+ # pattern execution along with execution time information.
410
+ def finalize_pattern(output_file)
411
+ Origen.log.debug('Pattern generation completed. Sending all stored vector data')
412
+ synchronize(output_file)
413
+ send_cmd('', 'session_end')
414
+ # for debug, report communication times
415
+ Origen.log.debug("total communication time: #{@total_comm_time}")
416
+ Origen.log.debug("total connect time: #{@total_connect_time}")
417
+ Origen.log.debug("total transmit time: #{@total_xmit_time}")
418
+ Origen.log.debug("total receive time: #{@total_recv_time}")
419
+ Origen.log.debug("total packets: #{@total_packets}")
420
+ Origen.log.debug("total time per packet: #{@total_comm_time / @total_packets}")
421
+ Origen.log.debug("connect time per packet: #{@total_connect_time / @total_packets}")
422
+ Origen.log.debug("transmit time per packet: #{@total_xmit_time / @total_packets}")
423
+ Origen.log.debug("receive time per packet: #{@total_recv_time / @total_packets}")
424
+ Origen.log.debug("max packet time: #{@max_packet_time}")
425
+ Origen.log.debug("max duration command - #{@longest_packet}")
426
+ Origen.log.debug("max receive time: #{@max_receive_time}")
427
+ if @fail_count == 0
428
+ # Origen.log.success("PASS - pattern execution passed (#{@vector_count} vectors pass)")
429
+ Origen.app.stats.report_pass
430
+ else
431
+ # Origen.log.error("FAIL - pattern execution failed (#{@fail_count} failures)")
432
+ Origen.app.stats.report_fail
433
+ end
434
+ commands_file = Origen.app.current_job.output_file.split('.')[0] + '_link_cmds.txt'
435
+ File.open(commands_file, 'w') do |file|
436
+ file.puts("pin_assign:#{@pinmap}")
437
+ file.puts("pin_patternorder:#{@pinorder}")
438
+ @pattern_link_messages.each_index do |index|
439
+ file.puts(@pattern_link_messages[index])
440
+ file.puts(@pattern_comments[index]) if @pattern_comments.key?(index)
441
+ end
442
+ file.puts(':session_end')
443
+ end
444
+ end
445
+
446
+ # to_s
447
+ # returns 'OrigenLink::VectorBased'
448
+ #
449
+ # No longer a use for this. Use tester.link?
450
+ def to_s
451
+ 'OrigenLink::VectorBased'
452
+ end
453
+
454
+ # transaction
455
+ # returns true/false indicating whether the transaction passed
456
+ # true = pass
457
+ # false = fail
458
+ #
459
+ # @example
460
+ # if !tester.transaction {dut.reg blah blah}
461
+ # puts 'transaction failed'
462
+ # end
463
+ def transaction
464
+ if block_given?
465
+ synchronize
466
+ transaction_fail_count = @fail_count
467
+ yield
468
+ synchronize
469
+ transaction_fail_count = @fail_count - transaction_fail_count
470
+ if transaction_fail_count == 0
471
+ true
472
+ else
473
+ false
474
+ end
475
+ else
476
+ true
477
+ end
478
+ end
479
+
480
+ # wait a fixed amount of time
481
+ # This method will run the Sleep method to wait rather than generating cycles.
482
+ # Cycle execution time is not predictable with OrigenLink.
483
+ #
484
+ # If you want cycles to be generated by this method include apply_cycles: true
485
+ # in the options hash
486
+ #
487
+ # This method currently doesn't handle match blocks.
488
+ def wait(options = {})
489
+ options = {
490
+ cycles: 0,
491
+ time_in_cycles: 0,
492
+ time_in_us: 0,
493
+ time_in_ns: 0,
494
+ time_in_ms: 0,
495
+ time_in_s: 0,
496
+ match: false, # Set to true to invoke a match loop where the supplied delay
497
+ apply_cycles: false
498
+ # will become the timeout duration
499
+ }.merge(options)
500
+
501
+ fail 'wait(match: true) is not yet implemented on Link' if options[:match]
502
+
503
+ time_delay = 0
504
+ time_delay += (options[:time_in_s])
505
+ time_delay += (options[:time_in_ms]) * 0.001
506
+ time_delay += (options[:time_in_us]) * 0.000001
507
+ time_delay += (options[:time_in_ns]) * 0.000000001
508
+ time_delay += (options[:cycles] + options[:time_in_cycles]) * current_period_in_ns * 0.000000001
509
+
510
+ if options[:apply_cycles]
511
+ cycles = time_delay / 0.00003 # cycle execution time is unpredictable, but ~= 30us
512
+ if options[:cycles] > 0 || options[:time_in_cycles] > 0
513
+ options[:repeat] = options[:cycles] + options[:time_in_cycles]
514
+ else
515
+ options[:repeat] = (cycles > 0) ? cycles : 1
516
+ end
517
+ cycle(options)
518
+ else
519
+ # ensure at least 1 cycle is generated so that pin changes always get applied before the wait
520
+ cycle
521
+ synchronize # ensure all generated cycles are executed before delay is run
522
+ sleep time_delay
523
+ end
524
+ end
525
+ end
526
+ end