origen_sim 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,589 @@
1
+ require 'socket'
2
+ module OrigenSim
3
+ # Responsible for managing and communicating with the simulator
4
+ # process, a single instance of this class is instantiated as
5
+ # OrigenSim.simulator
6
+ class Simulator
7
+ include Origen::PersistentCallbacks
8
+
9
+ VENDORS = [:icarus, :cadence]
10
+
11
+ attr_reader :socket, :failed, :configuration
12
+ alias_method :config, :configuration
13
+
14
+ # When set to true the simulator will log all messages it receives, note that
15
+ # this must be run in conjunction with -d supplied to the Origen command to actually
16
+ # see the messages
17
+ def log_messages=(val)
18
+ if val
19
+ put('d^1')
20
+ else
21
+ put('d^0')
22
+ end
23
+ end
24
+
25
+ def configure(options)
26
+ fail 'A vendor must be supplied, e.g. OrigenSim::Tester.new(vendor: :icarus)' unless options[:vendor]
27
+ unless VENDORS.include?(options[:vendor])
28
+ fail "Unknown vendor #{options[:vendor]}, valid values are: #{VENDORS.map { |v| ':' + v.to_s }.join(', ')}"
29
+ end
30
+ unless options[:rtl_top]
31
+ fail "The name of the file containing the DUT top-level must be supplied, e.g. OrigenSim::Tester.new(rtl_top: 'my_ip')" unless options[:rtl_top]
32
+ end
33
+ @configuration = options
34
+ @tmp_dir = nil
35
+ end
36
+
37
+ # The ID assigned to the current simulation target, falls back to to the
38
+ # Origen target name if an :id option is not supplied when instantiating
39
+ # the tester
40
+ def id
41
+ config[:id] || Origen.target.name
42
+ end
43
+
44
+ def tmp_dir
45
+ @tmp_dir ||= begin
46
+ d = "#{Origen.root}/tmp/origen_sim/#{id}/#{config[:vendor]}"
47
+ FileUtils.mkdir_p(d)
48
+ d
49
+ end
50
+ end
51
+
52
+ def artifacts_dir
53
+ @artifacts_dir ||= begin
54
+ d = "#{Origen.root}/simulation/#{id}/artifacts"
55
+ FileUtils.mkdir_p(d)
56
+ d
57
+ end
58
+ end
59
+
60
+ # Returns the directory where the compiled simulation object lives, this should
61
+ # be checked into your Origen app's repository
62
+ def compiled_dir
63
+ @compiled_dir ||= begin
64
+ d = "#{Origen.root}/simulation/#{id}/#{config[:vendor]}"
65
+ FileUtils.mkdir_p(d)
66
+ d
67
+ end
68
+ end
69
+
70
+ def wave_dir
71
+ @wave_dir ||= begin
72
+ d = "#{Origen.root}/waves/#{id}"
73
+ FileUtils.mkdir_p(d)
74
+ d
75
+ end
76
+ end
77
+
78
+ def pid_dir
79
+ @pid_dir ||= begin
80
+ d = "#{Origen.root}/tmp/origen_sim/pids"
81
+ FileUtils.mkdir_p(d)
82
+ d
83
+ end
84
+ end
85
+
86
+ def wave_config_dir
87
+ @wave_config_dir ||= begin
88
+ d = "#{Origen.root}/config/waves/#{id}"
89
+ FileUtils.mkdir_p(d)
90
+ d
91
+ end
92
+ end
93
+
94
+ def wave_config_file
95
+ @wave_config_file ||= begin
96
+ f = "#{wave_config_dir}/#{User.current.id}.svcf"
97
+ unless File.exist?(f)
98
+ # Take a default wave if one has been set up
99
+ d = "#{wave_config_dir}/default.svcf"
100
+ if File.exist?(d)
101
+ FileUtils.cp(d, f)
102
+ else
103
+ # Otherwise seed it with the latest existing setup by someone else
104
+ d = Dir.glob("#{wave_config_dir}/*.svcf").max { |a, b| File.ctime(a) <=> File.ctime(b) }
105
+ if d
106
+ FileUtils.cp(d, f)
107
+ else
108
+ # We tried our best, start from scratch
109
+ d = "#{Origen.root!}/templates/empty.svcf"
110
+ FileUtils.cp(d, f)
111
+ end
112
+ end
113
+ end
114
+ f
115
+ end
116
+ end
117
+
118
+ def run_cmd
119
+ case config[:vendor]
120
+ when :icarus
121
+ cmd = configuration[:vvp] || 'vvp'
122
+ cmd += " -M#{compiled_dir} -morigen #{compiled_dir}/dut.vvp +socket+#{socket_id}"
123
+
124
+ when :cadence
125
+ input_file = "#{tmp_dir}/#{id}.tcl"
126
+ unless File.exist?(input_file)
127
+ Origen.app.runner.launch action: :compile,
128
+ files: "#{Origen.root!}/templates/probe.tcl.erb",
129
+ output: tmp_dir,
130
+ check_for_changes: false,
131
+ quiet: true,
132
+ options: { dir: wave_dir, force: config[:force], setup: config[:setup] },
133
+ output_file_name: "#{id}.tcl"
134
+ end
135
+ wave_dir # Ensure this exists since it won't be referenced above if the
136
+ # input file is already generated
137
+
138
+ cmd = configuration[:irun] || 'irun'
139
+ cmd += " -r origen -snapshot origen +socket+#{socket_id}"
140
+ cmd += " -input #{input_file}"
141
+ cmd += " -nclibdirpath #{compiled_dir}"
142
+ end
143
+ cmd
144
+ end
145
+
146
+ def view_wave_command
147
+ cmd = nil
148
+ case config[:vendor]
149
+ when :cadence
150
+ edir = Pathname.new(wave_config_dir).relative_path_from(Pathname.pwd)
151
+ cmd = "cd #{edir} && "
152
+ cmd += configuration[:simvision] || 'simvision'
153
+ dir = Pathname.new(wave_dir).relative_path_from(edir.expand_path)
154
+ cmd += " #{dir}/#{id}.dsn #{dir}/#{id}.trn"
155
+ f = Pathname.new(wave_config_file).relative_path_from(edir.expand_path)
156
+ cmd += " -input #{f} &"
157
+ end
158
+ cmd
159
+ end
160
+
161
+ def run_dir
162
+ case config[:vendor]
163
+ when :icarus
164
+ wave_dir
165
+ else
166
+ tmp_dir
167
+ end
168
+ end
169
+
170
+ def link_artifacts
171
+ done = "#{run_dir}/.artifacts_linked"
172
+ unless File.exist?(done)
173
+ Dir.foreach(artifacts_dir) do |item|
174
+ next if item == '.' || item == '..'
175
+ FileUtils.ln_s("#{artifacts_dir}/#{item}", "#{run_dir}/#{item}") unless File.exist?("#{run_dir}/#{item}")
176
+ end
177
+ FileUtils.touch(done)
178
+ end
179
+ end
180
+
181
+ # Starts up the simulator process
182
+ def start
183
+ server = UNIXServer.new(socket_id)
184
+ verbose = Origen.debugger_enabled?
185
+ link_artifacts
186
+ cmd = run_cmd + ' & echo \$!'
187
+
188
+ launch_simulator = %(
189
+ require 'open3'
190
+
191
+ Dir.chdir '#{run_dir}' do
192
+ Open3.popen3('#{cmd}') do |stdin, stdout, stderr, thread|
193
+ pid = stdout.gets.strip
194
+ File.open '#{pid_dir}/#{socket_number}', 'w' do |f|
195
+ f.puts pid
196
+ end
197
+ threads = []
198
+ threads << Thread.new do
199
+ until (line = stdout.gets).nil?
200
+ puts line if #{verbose ? 'true' : 'false'}
201
+ end
202
+ end
203
+ threads << Thread.new do
204
+ until (line = stderr.gets).nil?
205
+ puts line
206
+ end
207
+ end
208
+ threads.each(&:join)
209
+ end
210
+ end
211
+ )
212
+
213
+ simulator_parent_process = spawn("ruby -e \"#{launch_simulator}\"")
214
+ Process.detach(simulator_parent_process)
215
+
216
+ timeout_connection(config[:startup_timeout] || 15) do
217
+ @socket = server.accept
218
+ @connection_established = true
219
+ if @connection_timed_out
220
+ @failed_to_start = true
221
+ Origen.log.error 'Simulator failed to respond'
222
+ @failed = true
223
+ exit
224
+ end
225
+ end
226
+ data = get
227
+ unless data.strip == 'READY!'
228
+ @failed_to_start = true
229
+ fail "The simulator didn't start properly!"
230
+ end
231
+ @enabled = true
232
+ # Tick the simulation on, this seems to be required since any VPI puts operations before
233
+ # the simulation has started are not applied.
234
+ # Note that this is not setting a tester timeset, so the application will still have to
235
+ # do that before generating any vectors.
236
+ set_period(100)
237
+ cycle(1)
238
+ Origen.listeners_for(:simulation_startup).each(&:simulation_startup)
239
+ end
240
+
241
+ # Returns the pid of the simulator process
242
+ def pid
243
+ return @pid if @pid_set
244
+ @pid = File.readlines(pid_file).first.strip.to_i if File.exist?(pid_file)
245
+ @pid ||= 0
246
+ @pid_set = true
247
+ @pid
248
+ end
249
+
250
+ def pid_file
251
+ "#{Origen.root}/tmp/origen_sim/pids/#{socket_number}"
252
+ end
253
+
254
+ # Returns true if the simulator process is running
255
+ def running?
256
+ return false if pid == 0
257
+ begin
258
+ Process.getpgid(pid)
259
+ true
260
+ rescue Errno::ESRCH
261
+ false
262
+ end
263
+ end
264
+
265
+ # Send the given message string to the simulator
266
+ def put(msg)
267
+ socket.write(msg + "\n") if socket
268
+ end
269
+
270
+ # Get a message from the simulator, will block until one
271
+ # is received
272
+ def get
273
+ socket.readline
274
+ end
275
+
276
+ # This will be called at the end of every pattern, make
277
+ # sure the simulator is not running behind before potentially
278
+ # moving onto another pattern
279
+ def pattern_generated(path)
280
+ sync_up if simulation_tester?
281
+ end
282
+
283
+ # Called before every pattern is generated, but we only use it the
284
+ # first time it is called to kick off the simulator process if the
285
+ # current tester is an OrigenSim::Tester
286
+ def before_pattern(name)
287
+ if simulation_tester?
288
+ unless @enabled
289
+ # When running pattern back-to-back, only want to launch the simulator the
290
+ # first time
291
+ unless socket
292
+ start
293
+ end
294
+ end
295
+ # Set the current pattern name in the simulation
296
+ put("a^#{name.sub(/\..*/, '')}")
297
+ @pattern_count ||= 0
298
+ if @pattern_count > 0
299
+ c = error_count
300
+ if c > 0
301
+ Origen.log.error "The simulation currently has #{c} error(s)!"
302
+ else
303
+ Origen.log.success 'There are no simulation errors yet!'
304
+ end
305
+ end
306
+ @pattern_count += 1
307
+ end
308
+ end
309
+
310
+ def write_comment(comment)
311
+ # Not sure what the limiting factor here is, the comment memory in the test bench should
312
+ # be able to handle 1024 / 8 length strings, but any bigger than this hangs the simulation
313
+ comment = comment[0..96]
314
+ put("c^#{comment}")
315
+ end
316
+
317
+ # Applies the current state of all pins to the simulation
318
+ def put_all_pin_states
319
+ dut.rtl_pins.each do |name, pin|
320
+ pin.reset_simulator_state
321
+ pin.update_simulation
322
+ end
323
+ end
324
+
325
+ # This will be called automatically whenever tester.set_timeset
326
+ # has been called
327
+ def on_timeset_changed
328
+ # Important that this is done first, since it is used to clear the pin
329
+ # and wave definitions in the bridge
330
+ set_period(dut.current_timeset_period)
331
+ # Clear pins and waves
332
+ define_pins
333
+ define_waves
334
+ # Apply the pin reset values / re-apply the existing states
335
+ put_all_pin_states
336
+ end
337
+
338
+ # Tells the simulator about the pins in the current device so that it can
339
+ # set up internal handles to efficiently access them
340
+ def define_pins
341
+ dut.rtl_pins.each_with_index do |(name, pin), i|
342
+ unless pin.tie_off
343
+ pin.simulation_index = i
344
+ put("0^#{pin.id}^#{i}^#{pin.drive_wave.index}^#{pin.compare_wave.index}")
345
+ end
346
+ end
347
+ end
348
+
349
+ def wave_to_str(wave)
350
+ wave.evaluated_events.map do |time, data|
351
+ time = time * (config[:time_factor] || 1)
352
+ if data == :x
353
+ data = 'X'
354
+ elsif data == :data
355
+ data = wave.drive? ? 'D' : 'C'
356
+ end
357
+ if data == 'C'
358
+ "#{time}_#{data}_#{time + (config[:time_factor] || 1)}_X"
359
+ else
360
+ "#{time}_#{data}"
361
+ end
362
+ end.join('_')
363
+ end
364
+
365
+ def define_waves
366
+ dut.timeset.drive_waves.each_with_index do |wave, i|
367
+ put("6^#{i}^0^#{wave_to_str(wave)}")
368
+ end
369
+ dut.timeset.compare_waves.each_with_index do |wave, i|
370
+ put("6^#{i}^1^#{wave_to_str(wave)}")
371
+ end
372
+ end
373
+
374
+ def end_simulation
375
+ put('8^')
376
+ end
377
+
378
+ def set_period(period_in_ns)
379
+ period_in_ns = period_in_ns * (config[:time_factor] || 1)
380
+ put("1^#{period_in_ns}")
381
+ end
382
+
383
+ def cycle(number_of_cycles)
384
+ put("3^#{number_of_cycles}")
385
+ end
386
+
387
+ # Blocks the Origen process until the simulator indicates that it has
388
+ # processed all operations up to this point
389
+ def sync_up
390
+ put('7^')
391
+ data = get
392
+ unless data.strip == 'OK!'
393
+ fail 'Origen and the simulator are out of sync!'
394
+ end
395
+ end
396
+
397
+ # Returns the current simulation error count
398
+ def error_count
399
+ peek('origen.debug.errors')
400
+ end
401
+
402
+ # Returns the current value of the given net, or nil if the given path does not
403
+ # resolve to a valid node
404
+ def peek(net)
405
+ # The Verilog spec does not specify that underlying VPI put method should
406
+ # handle a part select, so some simulators do not handle it. Therefore we
407
+ # deal with it here to ensure cross simulator compatibility.
408
+
409
+ # http://rubular.com/r/eTVGzrYmXQ
410
+ if net =~ /(.*)\[(\d+):?(\.\.)?(\d*)\]$/
411
+ net = Regexp.last_match(1)
412
+ msb = Regexp.last_match(2).to_i
413
+ lsb = Regexp.last_match(4)
414
+ lsb = lsb.empty? ? nil : lsb.to_i
415
+ end
416
+
417
+ sync_up
418
+ put("9^#{clean(net)}")
419
+
420
+ m = get.strip
421
+ if m == 'FAIL'
422
+ return nil
423
+ else
424
+ m = m.to_i
425
+ if msb
426
+ # Setting a range of bits
427
+ if lsb
428
+ m[msb..lsb]
429
+ else
430
+ m[msb]
431
+ end
432
+ else
433
+ m
434
+ end
435
+ end
436
+ end
437
+
438
+ # Forces the given value to the given net.
439
+ # Note that no error checking is done and no error will be communicated if an illegal
440
+ # net is supplied. The user should follow up with a peek if they want to verify that
441
+ # the poke was applied.
442
+ def poke(net, value)
443
+ # The Verilog spec does not specify that underlying VPI put method should
444
+ # handle a part select, so some simulators do not handle it. Therefore we
445
+ # deal with it here to ensure cross simulator compatibility.
446
+
447
+ # http://rubular.com/r/eTVGzrYmXQ
448
+ if net =~ /(.*)\[(\d+):?(\.\.)?(\d*)\]$/
449
+ path = Regexp.last_match(1)
450
+ msb = Regexp.last_match(2).to_i
451
+ lsb = Regexp.last_match(4)
452
+ lsb = lsb.empty? ? nil : lsb.to_i
453
+
454
+ v = peek(path)
455
+ return nil unless v
456
+
457
+ # Setting a range of bits
458
+ if lsb
459
+ upper = v >> (msb + 1)
460
+ # Make sure value does not overflow
461
+ value = value[(msb - lsb)..0]
462
+ if lsb == 0
463
+ value = (upper << (msb + 1)) | value
464
+ else
465
+ lower = v[(lsb - 1)..0]
466
+ value = (upper << (msb + 1)) |
467
+ (value << lsb) | lower
468
+ end
469
+
470
+ # Setting a single bit
471
+ else
472
+ if msb == 0
473
+ upper = v >> 1
474
+ value = (upper << 1) | value[0]
475
+ else
476
+ lower = v[(msb - 1)..0]
477
+ upper = v >> (msb + 1)
478
+ value = (upper << (msb + 1)) |
479
+ (value[0] << msb) | lower
480
+ end
481
+ end
482
+ net = path
483
+ end
484
+
485
+ sync_up
486
+ put("b^#{clean(net)}^#{value}")
487
+ end
488
+
489
+ def interactive_shutdown
490
+ @interactive_mode = true
491
+ end
492
+
493
+ # Stop the simulator
494
+ def stop
495
+ Origen.listeners_for(:simulation_shutdown).each(&:simulation_shutdown)
496
+ ended = Time.now
497
+ end_simulation
498
+ # Give the simulator a few seconds to shut down, otherwise kill it
499
+ sleep 1 while running? && (Time.now - ended) < 5
500
+ Process.kill(15, pid) if running?
501
+ sleep 0.1
502
+ # Leave the pid file around if we couldn't verify the simulator
503
+ # has stopped
504
+ FileUtils.rm_f(pid_file) if File.exist?(pid_file) && !running?
505
+ @socket.close if @socket
506
+ File.unlink(socket_id) if File.exist?(socket_id)
507
+ end
508
+
509
+ def on_origen_shutdown
510
+ if @enabled
511
+ unless @interactive_mode
512
+ Origen.log.debug 'Shutting down simulator...'
513
+ unless @failed_to_start
514
+ c = error_count
515
+ if c > 0
516
+ @failed = true
517
+ Origen.log.error "The simulation failed with #{c} errors!"
518
+ end
519
+ end
520
+ end
521
+ stop
522
+ unless @interactive_mode
523
+ if failed
524
+ Origen.app.stats.report_fail
525
+ else
526
+ Origen.app.stats.report_pass
527
+ end
528
+ end
529
+ if view_wave_command
530
+ puts
531
+ puts 'To view the simulation run the following command:'
532
+ puts
533
+ puts " #{view_wave_command}"
534
+ puts
535
+ end
536
+ end
537
+ ensure
538
+ Process.kill(15, pid) if @enabled && running?
539
+ end
540
+
541
+ def socket_id
542
+ @socket_id ||= "/tmp/#{socket_number}.sock"
543
+ end
544
+
545
+ def socket_number
546
+ @socket_number ||= (Process.pid.to_s + Time.now.to_f.to_s).sub('.', '')
547
+ end
548
+
549
+ def simulation_tester?
550
+ (tester && tester.is_a?(OrigenSim::Tester))
551
+ end
552
+
553
+ def timeout_connection(wait_in_s)
554
+ @connection_timed_out = false
555
+ t = Thread.new do
556
+ sleep wait_in_s
557
+ # If the Verilog process has not established a connection yet, then make one to
558
+ # release our process and then exit
559
+ unless @connection_established
560
+ @connection_timed_out = true
561
+ UNIXSocket.new(socket_id).puts(message)
562
+ end
563
+ end
564
+ yield
565
+ end
566
+
567
+ def sync
568
+ put('f')
569
+ @sync_active = true
570
+ yield
571
+ put('g')
572
+ @sync_active = false
573
+ end
574
+
575
+ def sync_active?
576
+ @sync_active
577
+ end
578
+
579
+ private
580
+
581
+ def clean(net)
582
+ if net =~ /^dut\./
583
+ "origen.#{net}"
584
+ else
585
+ net
586
+ end
587
+ end
588
+ end
589
+ end
@@ -0,0 +1,113 @@
1
+ module OrigenSim
2
+ # Responsible for interfacing the simulator with Origen
3
+ class Tester
4
+ include OrigenTesters::VectorBasedTester
5
+
6
+ TEST_PROGRAM_GENERATOR = OrigenSim::Generator
7
+
8
+ def initialize(options = {})
9
+ simulator.configure(options)
10
+ super()
11
+ end
12
+
13
+ def simulator
14
+ OrigenSim.simulator
15
+ end
16
+
17
+ def handshake(options = {})
18
+ end
19
+
20
+ def capture
21
+ simulator.sync do
22
+ @sync_pins = []
23
+ @sync_cycles = 0
24
+ yield
25
+ end
26
+ @sync_pins.map { |pin| simulator.peek("origen.pins.#{pin.id}.sync_memory[#{@sync_cycles - 1}:0]") }
27
+ end
28
+
29
+ # Start the simulator
30
+ def start
31
+ simulator.start
32
+ end
33
+
34
+ # Blocks the Origen process until the simulator indicates that it has
35
+ # processed all operations up to this point
36
+ def sync_up
37
+ simulator.sync_up
38
+ end
39
+
40
+ def set_timeset(name, period_in_ns)
41
+ super
42
+ # Need to remove this once OrigenTesters does it
43
+ dut.timeset = name
44
+ dut.current_timeset_period = period_in_ns
45
+
46
+ # Now update the simulator with the new waves
47
+ simulator.on_timeset_changed
48
+ end
49
+
50
+ # This method intercepts vector data from Origen, removes white spaces and compresses repeats
51
+ def push_vector(options)
52
+ unless options[:timeset]
53
+ puts 'No timeset defined!'
54
+ puts 'Add one to your top level startup method or target like this:'
55
+ puts '$tester.set_timeset("nvmbist", 40) # Where 40 is the period in ns'
56
+ exit 1
57
+ end
58
+ simulator.cycle(options[:repeat] || 1)
59
+ if @after_next_vector
60
+ @after_next_vector.call(@after_next_vector_args)
61
+ @after_next_vector = nil
62
+ end
63
+ end
64
+
65
+ def c1(msg, options = {})
66
+ simulator.write_comment(msg) if @step_comment_on
67
+ end
68
+
69
+ def loop_vectors(name, number_of_loops, options = {})
70
+ number_of_loops.times do
71
+ yield
72
+ end
73
+ end
74
+ alias_method :loop_vector, :loop_vectors
75
+
76
+ # Capture the next vector generated
77
+ #
78
+ # This method applies a store request to the next vector to be generated,
79
+ # note that is does not actually generate a new vector.
80
+ #
81
+ # The captured data is added to the captured_data array.
82
+ #
83
+ # This method is intended to be used by pin drivers, see the #capture method for the application
84
+ # level API.
85
+ #
86
+ # @example
87
+ # tester.store_next_cycle
88
+ # tester.cycle # This is the vector that will be captured
89
+ def store_next_cycle(*pins)
90
+ options = pins.last.is_a?(Hash) ? pins.pop : {}
91
+ pins = dut.rtl_pins.values if pins.empty?
92
+ if simulator.sync_active?
93
+ pins.each do |pin|
94
+ @sync_cycles += 1
95
+ @sync_pins << pin unless @sync_pins.include?(pin)
96
+ end
97
+ end
98
+ pins.each(&:capture)
99
+ # A store request is only valid for one cycle, this tells the simulator
100
+ # to stop after the next vector is generated
101
+ after_next_vector do
102
+ pins.each { |pin| simulator.put("h^#{pin.simulation_index}") }
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def after_next_vector(*args, &block)
109
+ @after_next_vector = block
110
+ @after_next_vector_args = args
111
+ end
112
+ end
113
+ end