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



100
101
102
103
104
105
106
# File 'lib/origen_sim/tester.rb', line 100

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
# 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
      simulator.peek("#{simulator.testbench_top}.pins.#{pin.id}.sync_memory")[0]
    else
      simulator.peek("#{simulator.testbench_top}.pins.#{pin.id}.sync_memory").to_i[(@sync_cycles - 1)..0]
    end
  end
end

#cycle_countObject



433
434
435
# File 'lib/origen_sim/tester.rb', line 433

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



64
65
66
# File 'lib/origen_sim/tester.rb', line 64

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

#force(*args) ⇒ Object

Shorthand for simulator.force



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

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



243
244
245
# File 'lib/origen_sim/tester.rb', line 243

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



114
115
116
117
118
# File 'lib/origen_sim/tester.rb', line 114

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

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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/origen_sim/tester.rb', line 161

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
  timed_out = true
  10.times do
    (timeout_in_cycles / 10).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
      if current_val == expected_val || current_val2 == expected_val2
        timed_out = false
        break
      end
    else
      if current_val == expected_val
        timed_out = false
        break
      end
    end
  end
  # Final assertion to make the pattern fail if the loop timed out
  if timed_out
    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



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/origen_sim/tester.rb', line 199

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
  timed_out = true
  simulator.match_loop do
    10.times do
      (timeout_in_cycles / 10).cycles
      # Consider the match resolved if any condition can execute without generating errors
      if match_conditions.any? do |condition|
        e = simulator.match_errors
        condition.call
        e == simulator.match_errors
      end
        timed_out = false
        break
      end
    end
  end
  # Final execution to make the pattern fail if the loop timed out
  if timed_out
    if fail_conditions.instance_variable_get(:@block_args).empty?
      match_conditions.each(&:call)
    else
      fail_conditions.each(&:call)
    end
  end
end

#peek(*args) ⇒ Object

Shorthand for simulator.peek



447
448
449
# File 'lib/origen_sim/tester.rb', line 447

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

#peek_real(*args) ⇒ Object

Shorthand for simulator.peek_real



452
453
454
# File 'lib/origen_sim/tester.rb', line 452

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

#poke(*args) ⇒ Object

Shorthand for simulator.poke



442
443
444
# File 'lib/origen_sim/tester.rb', line 442

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

#push_vector(options) ⇒ Object

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



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/origen_sim/tester.rb', line 79

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



429
430
431
# File 'lib/origen_sim/tester.rb', line 429

def read_reg_cycles
  @read_reg_cycles
end

#read_reg_meta_supplied=(val) ⇒ Object



437
438
439
# File 'lib/origen_sim/tester.rb', line 437

def read_reg_meta_supplied=(val)
  @read_reg_meta_supplied = val
end

#read_reg_open?Boolean

Returns:

  • (Boolean)


425
426
427
# File 'lib/origen_sim/tester.rb', line 425

def read_reg_open?
  @read_reg_open
end

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



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
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
# File 'lib/origen_sim/tester.rb', line 247

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



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

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

#set_timeset(name, period_in_ns) ⇒ Object



68
69
70
71
72
73
74
75
76
# File 'lib/origen_sim/tester.rb', line 68

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



108
109
110
111
112
# File 'lib/origen_sim/tester.rb', line 108

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

#startObject

Start the simulator



52
53
54
# File 'lib/origen_sim/tester.rb', line 52

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


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/origen_sim/tester.rb', line 134

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



58
59
60
# File 'lib/origen_sim/tester.rb', line 58

def sync_up
  simulator.sync_up
end

#wait(*args) ⇒ Object



235
236
237
238
239
240
241
# File 'lib/origen_sim/tester.rb', line 235

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