Class: OrigenSim::Tester

Inherits:
Object
  • Object
show all
Includes:
OrigenTesters::VectorBasedTester
Defined in:
lib/origen_sim/tester.rb

Overview

Responsible for interfacing the simulator with Origen

Constant Summary collapse

TEST_PROGRAM_GENERATOR =
OrigenSim::Generator

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, &block) ⇒ Tester

Returns a new instance of Tester.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/origen_sim/tester.rb', line 10

def initialize(options = {}, &block)
  # Use Origen's collector to allow options to be set either from the options hash, or from the block
  if block_given?
    opts = Origen::Utility.collector(hash: options, merge_method: :keep_hash, &block).to_hash
  else
    opts = options
  end

  simulator.configure(opts, &block)
  @comment_buffer = []
  @last_comment_size = 0
  @execution_time_in_ns = 0
  super()
end

Instance Attribute Details

#out_of_bounds_handlerObject

Returns the value of attribute out_of_bounds_handler.



8
9
10
# File 'lib/origen_sim/tester.rb', line 8

def out_of_bounds_handler
  @out_of_bounds_handler
end

Instance Method Details

#c1(msg, options = {}) ⇒ Object



116
117
118
119
120
121
122
# File 'lib/origen_sim/tester.rb', line 116

def c1(msg, options = {})
  if @step_comment_on
    PatSeq.add_thread(msg) unless options[:no_thread_id]
    simulator.log msg
    @comment_buffer << msg
  end
end

#captureObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/origen_sim/tester.rb', line 36

def capture
  simulator.sync do
    @sync_pins = []
    @sync_cycles = 0
    yield
  end
  @sync_pins.map do |pin|
    if @sync_cycles.size == 1
      b = simulator.peek("#{simulator.testbench_top}.pins.#{pin.id}.sync_memory")[0]
      if b.is_a?(Integer)
        b
      else
        Origen.log.warning "The data captured on pin #{pin.id} was undefined (X or Z), the captured value is not correct!"
        0
      end
    else
      val = 0
      mem = simulator.peek("#{simulator.testbench_top}.pins.#{pin.id}.sync_memory")
      @sync_cycles.times do |i|
        b = mem[i]
        if b.is_a?(Integer)
          val |= b << i
        else
          Origen.log.warning "The data captured on cycle #{i} of pin #{pin.id} was undefined (X or Z), the captured value is not correct!"
        end
      end
      val
    end
  end
end

#cycle_countObject



465
466
467
# File 'lib/origen_sim/tester.rb', line 465

def cycle_count
  simulator.simulation.cycle_count
end

#dut_versionObject



29
30
31
# File 'lib/origen_sim/tester.rb', line 29

def dut_version
  simulator.dut_version
end

#flush(*args) ⇒ Object

Flush any buffered simulation output, this should cause live waveviewers to reflect the latest state and the console and log files to update



80
81
82
# File 'lib/origen_sim/tester.rb', line 80

def flush(*args)
  simulator.flush(*args)
end

#force(*args) ⇒ Object

Shorthand for simulator.force



489
490
491
# File 'lib/origen_sim/tester.rb', line 489

def force(*args)
  simulator.force(*args)
end

#handshake(options = {}) ⇒ Object



33
34
# File 'lib/origen_sim/tester.rb', line 33

def handshake(options = {})
end

#log_file_written(path) ⇒ Object



275
276
277
# File 'lib/origen_sim/tester.rb', line 275

def log_file_written(path)
  simulator.simulation.log_files << path if simulator.simulation
end

#loop_vectors(name, number_of_loops, options = {}) ⇒ Object Also known as: loop_vector



130
131
132
133
134
# File 'lib/origen_sim/tester.rb', line 130

def loop_vectors(name, number_of_loops, options = {})
  number_of_loops.times do
    yield
  end
end

#match(pin, state, timeout_in_cycles, options = {}) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/origen_sim/tester.rb', line 177

def match(pin, state, timeout_in_cycles, options = {})
  if dut_version <= '0.12.0'
    OrigenSim.error "Use of match loops requires a DUT model compiled with OrigenSim version > 0.12.0, the current dut was compiled with #{dut_version}"
  end
  expected_val = state == :high ? 1 : 0
  if options[:pin2]
    expected_val2 = options[:state2] == :high ? 1 : 0
  end
  matched = false
  start_cycle = cycle_count
  resolution = match_loop_resolution(timeout_in_cycles, options)
  until matched || cycle_count > start_cycle + timeout_in_cycles
    resolution.cycles
    current_val = simulator.peek("dut.#{pin.rtl_name}").to_i
    if options[:pin2]
      current_val2 = simulator.peek("dut.#{options[:pin2].rtl_name}").to_i
      matched = current_val == expected_val || current_val2 == expected_val2
    else
      matched = current_val == expected_val
    end
  end
  # Final assertion to make the pattern fail if the loop timed out
  unless matched
    pin.restore_state do
      pin.assert!(expected_val)
    end
    if options[:pin2]
      options[:pin2].restore_state do
        options[:pin2].assert!(expected_val2)
      end
    end
  end
end

#match_block(timeout_in_cycles, options = {}, &block) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/origen_sim/tester.rb', line 211

def match_block(timeout_in_cycles, options = {}, &block)
  if dut_version <= '0.12.0'
    OrigenSim.error "Use of match loops requires a DUT model compiled with OrigenSim version > 0.12.0, the current dut was compiled with #{dut_version}"
  end
  match_conditions = Origen::Utility::BlockArgs.new
  fail_conditions = Origen::Utility::BlockArgs.new
  if block.arity > 0
    block.call(match_conditions, fail_conditions)
  else
    match_conditions.add(&block)
  end
  matched = false
  start_cycle = cycle_count
  resolution = match_loop_resolution(timeout_in_cycles, options)
  simulator.match_loop do
    until matched || cycle_count > start_cycle + timeout_in_cycles
      resolution.cycles
      # Consider the match resolved if any condition can execute without generating errors
      matched = match_conditions.any? do |condition|
        e = simulator.match_errors
        condition.call
        e == simulator.match_errors
      end
    end
  end
  # Final execution to make the pattern fail if the loop timed out
  unless matched
    if fail_conditions.instance_variable_get(:@block_args).empty?
      match_conditions.each(&:call)
    else
      fail_conditions.each(&:call)
    end
  end
end

#match_loop_resolution(timeout_in_cycles, options) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/origen_sim/tester.rb', line 247

def match_loop_resolution(timeout_in_cycles, options)
  if options[:resolution]
    if options[:resolution].is_a?(Hash)
      return time_to_cycles(options[:resolution])
    else
      return options[:resolution]
    end
  else
    # Used to use the supplied timeout / 10, thinking that the supplied number would be
    # roughly how long it would take. However, found that when users didn't know the timeout
    # they would just put in really large numbers, like 1sec, which would mean we would not
    # check until 100ms for an operation that might be done after 100us.
    # So now if the old default comes out less than the new one, then use it, otherwise use
    # the newer more fine-grained default.
    old_default = timeout_in_cycles / 10
    new_default = time_to_cycles(time_in_us: 100)
    return old_default < new_default ? old_default : new_default
  end
end

#peek(*args) ⇒ Object

Shorthand for simulator.peek



479
480
481
# File 'lib/origen_sim/tester.rb', line 479

def peek(*args)
  simulator.peek(*args)
end

#peek_real(*args) ⇒ Object

Shorthand for simulator.peek_real



484
485
486
# File 'lib/origen_sim/tester.rb', line 484

def peek_real(*args)
  simulator.peek_real(*args)
end

#poke(*args) ⇒ Object

Shorthand for simulator.poke



474
475
476
# File 'lib/origen_sim/tester.rb', line 474

def poke(*args)
  simulator.poke(*args)
end

#push_vector(options) ⇒ Object

This method intercepts vector data from Origen, removes white spaces and compresses repeats



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/origen_sim/tester.rb', line 95

def push_vector(options)
  if simulator.simulation.max_errors_exceeded
    fail Origen::Generator::AbortError, 'The max error count has been exceeded in the simulation'
  else
    unless options[:timeset]
      puts 'No timeset defined!'
      puts 'Add one to your top level startup method or target like this:'
      puts 'tester.set_timeset("nvmbist", 40)   # Where 40 is the period in ns'
      exit 1
    end
    flush_comments unless @comment_buffer.empty?
    repeat = options[:repeat] || 1
    simulator.cycle(repeat)
    @execution_time_in_ns += repeat * tester.timeset.period_in_ns
    if @after_next_vector
      @after_next_vector.call(@after_next_vector_args)
      @after_next_vector = nil
    end
  end
end

#read_reg_cyclesObject



461
462
463
# File 'lib/origen_sim/tester.rb', line 461

def read_reg_cycles
  @read_reg_cycles
end

#read_reg_meta_supplied=(val) ⇒ Object



469
470
471
# File 'lib/origen_sim/tester.rb', line 469

def read_reg_meta_supplied=(val)
  @read_reg_meta_supplied = val
end

#read_reg_open?Boolean

Returns:

  • (Boolean)


457
458
459
# File 'lib/origen_sim/tester.rb', line 457

def read_reg_open?
  @read_reg_open
end

#read_register(reg_or_val, options = {}) ⇒ Object



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/origen_sim/tester.rb', line 279

def read_register(reg_or_val, options = {})
  # This could be called multiple times for the same transaction
  if read_reg_open?
    yield
  else
    @read_reg_meta_supplied = false
    @read_reg_open = true
    @read_reg_cycles = {}
    unless @supports_transactions_set
      @supports_transactions = dut_version > '0.15.0'
      @supports_transactions_set = true
    end

    if reg_or_val.respond_to?(:named_bits)
      bit_names = reg_or_val.named_bits.map { |name, bits| name }.uniq
      expected = bit_names.map do |name|
        bits = reg_or_val.bits(name)
        if bits.is_to_be_read?
          [name, bits.status_str(:read)]
        end
      end.compact

      # Save which bits are being read for later, the driver performing the read will
      # clear the register flags
      read_flags = reg_or_val.map(&:is_to_be_read?)
    end

    error_count = simulator.error_count

    simulator.start_read_reg_transaction if @supports_transactions

    yield

    if @supports_transactions
      errors_captured, exceeded_max_errors, errors = *(simulator.stop_read_reg_transaction)
    end

    @read_reg_open = false

    if simulator.error_count > error_count
      if @supports_transactions
        actual_data_available = true
        if exceeded_max_errors
          Origen.log.warning 'The number of errors in this transaction exceeded the capture buffer, the actual data reported here may not be accurate'
        end
        out_of_sync = simulator.simulation.cycle_count != simulator.cycle_count
        if out_of_sync
          Origen.log.warning 'Something has gone wrong and Origen and the simulator do not agree on the current cycle number, it is not possible to resolve the actual data'
          actual_data_available = false
        else
          diffs = []
          errors.each do |error|
            if c = read_reg_cycles[error[:cycle]]
              if p = c[simulator.pins_by_rtl_name[error[:pin_name]]]
                if p[:position]
                  diffs << [p[:position], error[:expected], error[:received]]
                end
              end
            end
          end
          if diffs.empty?
            if @read_reg_meta_supplied
              Origen.log.warning 'It looks like the miscompare(s) occurred on pins/cycles that are not associated with register data'
              non_data_miscompare = true
            else
              Origen.log.warning 'It looks like your current read register driver does not provide the necessary meta-data to map these errors to an actual register value'
            end
            actual_data_available = false
          end
        end
      else
        Origen.log.warning 'Your DUT needs to be compiled with a newer version of OrigenSim to support reporting of the actual read data from this failed transaction'
        actual_data_available = false
      end

      # If a register object has been supplied...
      if read_flags
        Origen.log.error "Errors occurred reading register #{reg_or_val.path}:"
        if actual_data_available
          actual = nil
          reg_or_val.preserve_flags do
            reg_or_val.each_with_index do |bit, i|
              bit.read if read_flags[i]
            end

            diffs.each do |position, expected, received|
              if position < reg_or_val.size
                if received == -1 || received == -2
                  reg_or_val[position].unknown = true
                else
                  reg_or_val[position].write(received, force: true)
                end
              else
                # This bit position is beyond the bounds of the register
                if @out_of_bounds_handler
                  @out_of_bounds_handler.call(position, received, expected, reg_or_val)
                else
                  Origen.log.error "bit[#{position}] of read operation on #{reg_or_val.path}.#{reg_or_val.name}: expected #{expected} received #{received}"
                end
              end
            end

            actual = bit_names.map do |name|
              bits = reg_or_val.bits(name)
              if bits.is_to_be_read?
                [name, bits.status_str(:read)]
              end
            end.compact

            # Put the data back so the application behaves as it would if generating
            # for a non-simulation tester target
            diffs.each do |position, expected, received|
              reg_or_val[position].write(expected, force: true) if position < reg_or_val.size
            end
          end
        end

        expected.each do |name, expected|
          msg = "#{reg_or_val.path}.#{name}: expected #{expected}"
          if actual_data_available
            received_ = nil
            actual.each do |name2, received|
              if name == name2
                received_ = received
                msg += " received #{received}"
              end
            end
            if expected == received_
              Origen.log.info msg
            else
              Origen.log.error msg
            end
          else
            # This means that the correct data was read, but errors occurred on other pins/cycles during the transaction
            msg += " received #{expected}" if non_data_miscompare
            Origen.log.error msg
          end
        end
      else
        Origen.log.error 'Errors occurred while reading a register:'
        msg = "expected #{reg_or_val.to_s(16).upcase}"
        if actual_data_available
          actual = reg_or_val
          diffs.each do |position, expected, received|
            if received == -1 || received == -2
              actual = '?' * reg_or_val.to_s(16).size
              break
            elsif received == 1
              actual |= (1 << position)
            else
              lower = actual[(position - 1)..0]
              actual = actual >> (position + 1)
              actual = actual << (position + 1)
              actual |= lower
            end
          end
          if actual.is_a?(String)
            msg += " received #{actual}"
          else
            msg += " received #{actual.to_s(16).upcase}"
          end
        else
          # This means that the correct data was read, but errors occurred on other pins/cycles during the transaction
          msg += " received #{reg_or_val.to_s(16).upcase}" if non_data_miscompare
        end
        Origen.log.error msg
      end

      Origen.log.error
      caller.each do |line|
        if Pathname.new(line.split(':').first).expand_path.to_s =~ /^#{Origen.root}(?!(\/lbin|\/vendor\/gems)).*$/
          Origen.log.error line
        end
      end
    end
  end
end

#release(*args) ⇒ Object

Shorthand for simulator.release



494
495
496
# File 'lib/origen_sim/tester.rb', line 494

def release(*args)
  simulator.release(*args)
end

#set_timeset(name, period_in_ns) ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/origen_sim/tester.rb', line 84

def set_timeset(name, period_in_ns)
  super
  # Need to remove this once OrigenTesters does it
  dut.timeset = name
  dut.current_timeset_period = period_in_ns

  # Now update the simulator with the new waves
  simulator.on_timeset_changed
end

#simulatorObject



25
26
27
# File 'lib/origen_sim/tester.rb', line 25

def simulator
  OrigenSim.simulator
end

#ss(msg = nil) ⇒ Object



124
125
126
127
128
# File 'lib/origen_sim/tester.rb', line 124

def ss(msg = nil)
  simulator.log '=' * 70
  super
  simulator.log '=' * 70
end

#startObject

Start the simulator



68
69
70
# File 'lib/origen_sim/tester.rb', line 68

def start
  simulator.start
end

#store_next_cycle(*pins) ⇒ Object

Capture the next vector generated

This method applies a store request to the next vector to be generated, note that is does not actually generate a new vector.

The captured data is added to the captured_data array.

This method is intended to be used by pin drivers, see the #capture method for the application level API.

Examples:

tester.store_next_cycle
tester.cycle                # This is the vector that will be captured


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/origen_sim/tester.rb', line 150

def store_next_cycle(*pins)
  options = pins.last.is_a?(Hash) ? pins.pop : {}
  if pins.empty?
    pins = dut.rtl_pins.values
  else
    pins_orig = pins.dup
    pins_orig.each do |p|
      if p.is_a? Origen::Pins::PinCollection
        pins.concat(p.map(&:id).map { |p| dut.pin(p) })
        pins.delete(p)
      end
    end
  end
  if simulator.sync_active? && @sync_cycles
    @sync_cycles += 1
    pins.each do |pin|
      @sync_pins << pin unless @sync_pins.include?(pin)
    end
  end
  pins.each(&:capture)
  # A store request is only valid for one cycle, this tells the simulator
  # to stop after the next vector is generated
  after_next_vector do
    pins.each { |pin| simulator.put("h^#{pin.simulation_index}") }
  end
end

#sync_upObject

Blocks the Origen process until the simulator indicates that it has processed all operations up to this point



74
75
76
# File 'lib/origen_sim/tester.rb', line 74

def sync_up
  simulator.sync_up
end

#wait(*args) ⇒ Object



267
268
269
270
271
272
273
# File 'lib/origen_sim/tester.rb', line 267

def wait(*args)
  super
  if Origen.running_interactively? ||
     (defined?(Byebug) && Byebug.try(:mode) == :attached)
    flush quiet: true
  end
end