origen_sim 0.5.0

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