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 Method Summary collapse

Constructor Details

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

Returns a new instance of Tester.



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

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 Method Details

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



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

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



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/origen_sim/tester.rb', line 34

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



422
423
424
# File 'lib/origen_sim/tester.rb', line 422

def cycle_count
  simulator.simulation.cycle_count
end

#dut_versionObject



27
28
29
# File 'lib/origen_sim/tester.rb', line 27

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



62
63
64
# File 'lib/origen_sim/tester.rb', line 62

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

#force(*args) ⇒ Object

Shorthand for simulator.force



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

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

#handshake(options = {}) ⇒ Object



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

def handshake(options = {})
end

#log_file_written(path) ⇒ Object



241
242
243
# File 'lib/origen_sim/tester.rb', line 241

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



112
113
114
115
116
# File 'lib/origen_sim/tester.rb', line 112

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

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



159
160
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
# File 'lib/origen_sim/tester.rb', line 159

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



197
198
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
# File 'lib/origen_sim/tester.rb', line 197

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



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

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

#peek_real(*args) ⇒ Object

Shorthand for simulator.peek_real



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

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

#poke(*args) ⇒ Object

Shorthand for simulator.poke



431
432
433
# File 'lib/origen_sim/tester.rb', line 431

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

#push_vector(options) ⇒ Object

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



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

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



418
419
420
# File 'lib/origen_sim/tester.rb', line 418

def read_reg_cycles
  @read_reg_cycles
end

#read_reg_meta_supplied=(val) ⇒ Object



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

def read_reg_meta_supplied=(val)
  @read_reg_meta_supplied = val
end

#read_reg_open?Boolean

Returns:

  • (Boolean)


414
415
416
# File 'lib/origen_sim/tester.rb', line 414

def read_reg_open?
  @read_reg_open
end

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



245
246
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
# File 'lib/origen_sim/tester.rb', line 245

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[:received], error[:expected]]
                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, received, expected|
              if received == -1
                reg_or_val[position].unknown = true
              else
                reg_or_val[position].data = received
              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, received, expected|
              reg_or_val[position].data = expected
            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, received, expected|
            if received == -1
              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



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

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

#set_timeset(name, period_in_ns) ⇒ Object



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

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



23
24
25
# File 'lib/origen_sim/tester.rb', line 23

def simulator
  OrigenSim.simulator
end

#ss(msg = nil) ⇒ Object



106
107
108
109
110
# File 'lib/origen_sim/tester.rb', line 106

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

#startObject

Start the simulator



50
51
52
# File 'lib/origen_sim/tester.rb', line 50

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


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

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



56
57
58
# File 'lib/origen_sim/tester.rb', line 56

def sync_up
  simulator.sync_up
end

#wait(*args) ⇒ Object



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

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