origen_link 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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