origen_sim 0.12.0 → 0.13.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.
Files changed (35) hide show
  1. checksums.yaml +5 -5
  2. data/config/application.rb +17 -2
  3. data/config/shared_commands.rb +7 -0
  4. data/config/version.rb +1 -1
  5. data/ext/bridge.c +13 -3
  6. data/lib/origen_sim.rb +10 -0
  7. data/lib/origen_sim/artifacts.rb +101 -0
  8. data/lib/origen_sim/commands/build.rb +41 -9
  9. data/lib/origen_sim/heartbeat.rb +13 -1
  10. data/lib/origen_sim/origen_testers/api.rb +90 -7
  11. data/lib/origen_sim/simulation.rb +49 -4
  12. data/lib/origen_sim/simulator.rb +171 -19
  13. data/lib/origen_sim/stderr_reader.rb +19 -13
  14. data/lib/origen_sim/stdout_reader.rb +22 -16
  15. data/lib/origen_sim/tester.rb +110 -1
  16. data/lib/origen_sim_dev/dut.rb +5 -0
  17. data/pattern/test.rb +143 -0
  18. data/templates/empty.gtkw +19 -1
  19. data/templates/empty.rc +86 -0
  20. data/templates/empty.svcf +79 -9
  21. data/templates/empty.tcl +37 -9
  22. data/templates/origen_guides/simulation/app.md.erb +131 -0
  23. data/templates/origen_guides/simulation/artifacts.md.erb +115 -0
  24. data/templates/origen_guides/simulation/compiling.md.erb +190 -0
  25. data/templates/origen_guides/simulation/debugging.md.erb +135 -0
  26. data/templates/origen_guides/simulation/environment.md.erb +217 -0
  27. data/templates/origen_guides/simulation/flows.md.erb +69 -0
  28. data/templates/origen_guides/simulation/howitworks.md.erb +64 -0
  29. data/templates/origen_guides/simulation/introduction.md.erb +35 -0
  30. data/templates/origen_guides/simulation/log.md.erb +118 -0
  31. data/templates/origen_guides/simulation/patterns.md.erb +193 -0
  32. data/templates/probe.tcl.erb +3 -0
  33. data/templates/rtl_v/origen.v.erb +19 -3
  34. data/templates/web/layouts/_guides.html.erb +15 -0
  35. metadata +18 -5
@@ -91,15 +91,46 @@ module OrigenSim
91
91
  # heartbeats.
92
92
  def start_heartbeat
93
93
  @heartbeat = @server_heartbeat.accept
94
- @heartbeat_thread = Heartbeat.new(@heartbeat)
94
+ if Heartbeat::THREADSAFE
95
+ @heartbeat_thread = Heartbeat.new(@heartbeat)
96
+ else
97
+ @heartbeat_pid = fork do
98
+ loop do
99
+ begin
100
+ @heartbeat.write("OK\n")
101
+ rescue Errno::EPIPE => e
102
+ if monitor_running?
103
+ Origen.log.error 'Communication with the simulation monitor has been lost (though it seems to still be running)!'
104
+ else
105
+ Origen.log.error 'The simulation monitor has stopped unexpectedly!'
106
+ end
107
+ sleep 2 # To make sure that any log output from the simulator is captured before we pull the plug
108
+ exit 1
109
+ end
110
+ sleep 5
111
+ end
112
+ end
113
+ end
95
114
  end
96
115
 
97
116
  def stop_heartbeat
98
- @heartbeat_thread.stop
117
+ if Heartbeat::THREADSAFE
118
+ @heartbeat_thread.stop
119
+ else
120
+ Process.kill('SIGHUP', @heartbeat_pid)
121
+
122
+ # Ensure that the process has stopped before closing the IO pipes
123
+ begin
124
+ Process.waitpid(@heartbeat_pid)
125
+ rescue Errno::ECHILD
126
+ # Heartbeat process has already stopped, so ignore this.
127
+ end
128
+ end
99
129
  end
100
130
 
101
131
  # Open the communication channels with the simulator
102
- def open(timeout)
132
+ def open(monitor_pid, timeout)
133
+ @monitor_pid = monitor_pid
103
134
  timeout_connection(timeout) do
104
135
  start_heartbeat
105
136
  @stdout = @server_stdout.accept
@@ -169,10 +200,12 @@ module OrigenSim
169
200
  @socket.close
170
201
  @stderr.close
171
202
  @stdout.close
203
+ @status.close
172
204
  File.unlink(socket_id(:heartbeat)) if File.exist?(socket_id(:heartbeat))
173
205
  File.unlink(socket_id) if File.exist?(socket_id)
174
206
  File.unlink(socket_id(:stderr)) if File.exist?(socket_id(:stderr))
175
207
  File.unlink(socket_id(:stdout)) if File.exist?(socket_id(:stdout))
208
+ File.unlink(socket_id(:status)) if File.exist?(socket_id(:status))
176
209
  end
177
210
 
178
211
  # Returns true if the simulation is running
@@ -186,8 +219,20 @@ module OrigenSim
186
219
  end
187
220
  end
188
221
 
222
+ # Returns true if the simulation monitor process (the one that receives the heartbeat
223
+ # and kills the simulation if it stops) is running
224
+ def monitor_running?
225
+ return false unless @monitor_pid
226
+ begin
227
+ Process.getpgid(@monitor_pid)
228
+ true
229
+ rescue Errno::ESRCH
230
+ false
231
+ end
232
+ end
233
+
189
234
  def socket_id(type = nil)
190
- @socket_ids[type] ||= "/tmp/#{socket_number}#{type}.sock"
235
+ @socket_ids[type] ||= "#{OrigenSim.socket_dir || '/tmp'}/#{socket_number}#{type}.sock"
191
236
  end
192
237
 
193
238
  private
@@ -1,10 +1,13 @@
1
1
  require 'origen_sim/simulation'
2
+ require 'origen_sim/artifacts'
3
+
2
4
  module OrigenSim
3
5
  # Responsible for managing and communicating with the simulator
4
6
  # process, a single instance of this class is instantiated as
5
7
  # OrigenSim.simulator
6
8
  class Simulator
7
9
  include Origen::PersistentCallbacks
10
+ include Artifacts
8
11
 
9
12
  VENDORS = [:icarus, :cadence, :synopsys, :generic]
10
13
 
@@ -116,6 +119,78 @@ module OrigenSim
116
119
  end
117
120
  @configuration = options
118
121
  @tmp_dir = nil
122
+
123
+ # Temporary workaround for bug in componentable, which is making the container a class object, instead of an
124
+ # instance object.
125
+ clear_artifacts
126
+
127
+ # Add any artifacts in the given artifact path
128
+ if Dir.exist?(default_artifact_dir)
129
+ default_artifact_dir.children.each { |a| artifact(a.basename.to_s, target: a) }
130
+ end
131
+
132
+ # Add any artifacts from the target-specific path (simulation/<target>/artifacts). Files of the same name
133
+ # will override artifacts residing in the default directory.
134
+ if Dir.exist?(target_artifact_dir)
135
+ target_artifact_dir.children.each do |a|
136
+ remove_artifact(a.basename.to_s) if has_artifact?(a.basename.to_s)
137
+ add_artifact(a.basename.to_s, target: a)
138
+ end
139
+ end
140
+
141
+ # If a user artifact path was given, add those artifacts as well, overriding any of the default and target artifacts
142
+ if user_artifact_dirs?
143
+ user_artifact_dirs.each do |d|
144
+ if Dir.exist?(d)
145
+ # Add any artifacts from any user-given paths. Files of the same name will override artifacts residing in the default directory.
146
+ d.children.each do |a|
147
+ remove_artifact(a.basename.to_s) if has_artifact?(a.basename.to_s)
148
+ add_artifact(a.basename.to_s, target: a)
149
+ end
150
+ else
151
+ Origen.app.fail! message: "Simulator configuration specified a user artifact dir at #{d} but this directory could not be found!"
152
+ end
153
+ end
154
+ end
155
+
156
+ self
157
+ end
158
+
159
+ def default_artifact_dir
160
+ # Removed this from a constant at the top of the file since it gave boot errors when the file was
161
+ # being required while Origen.app was still loading
162
+ Pathname("#{Origen.app.root}/simulation/application/artifacts")
163
+ end
164
+
165
+ def user_artifact_dirs?
166
+ @configuration.key?(:user_artifact_dirs)
167
+ end
168
+
169
+ def user_artifact_dirs
170
+ @configuration.key?(:user_artifact_dirs) ? @configuration[:user_artifact_dirs].map { |d| Pathname(d) } : nil
171
+ end
172
+
173
+ def target_artifact_dir
174
+ Pathname(@configuration[:target_artifact_dir] || "#{Origen.app.root}/simulation/#{Origen.target.name}/artifacts")
175
+ end
176
+
177
+ def artifact_run_dir
178
+ p = Pathname(@configuration[:artifact_run_dir] || './application/artifacts')
179
+ if p.absolute?
180
+ p
181
+ else
182
+ Pathname(run_dir).join(p)
183
+ end
184
+ end
185
+
186
+ def artifact_populate_method
187
+ @configuration[:artifact_populate_method] || begin
188
+ if Origen.running_on_windows?
189
+ :copy
190
+ else
191
+ :symlink
192
+ end
193
+ end
119
194
  end
120
195
 
121
196
  # The ID assigned to the current simulation target, falls back to to the
@@ -160,7 +235,7 @@ module OrigenSim
160
235
  end
161
236
 
162
237
  def wave_config_file
163
- @wave_config_file ||= begin
238
+ @wave_config_file ||= configuration[:wave_config_file] || begin
164
239
  f = "#{wave_config_dir}/#{User.current.id}.#{wave_config_ext}"
165
240
  unless File.exist?(f)
166
241
  # Take a default wave if one has been set up
@@ -190,7 +265,11 @@ module OrigenSim
190
265
  when :cadence
191
266
  'svcf'
192
267
  when :synopsys
193
- 'tcl'
268
+ if configuration[:verdi]
269
+ 'rc'
270
+ else
271
+ 'tcl'
272
+ end
194
273
  end
195
274
  end
196
275
 
@@ -231,7 +310,11 @@ module OrigenSim
231
310
  cmd += " -nclibdirpath #{compiled_dir}"
232
311
 
233
312
  when :synopsys
234
- cmd = "#{compiled_dir}/simv +socket+#{socket_id} -vpd_file #{wave_file_basename}.vpd"
313
+ if configuration[:verdi]
314
+ cmd = "#{compiled_dir}/simv +socket+#{socket_id} +FSDB_ON +fsdbfile+#{Origen.root}/waves/#{Origen.target.name}/#{wave_file_basename}.fsdb +memcbk +vcsd"
315
+ else
316
+ cmd = "#{compiled_dir}/simv +socket+#{socket_id} -vpd_file #{wave_file_basename}.vpd"
317
+ end
235
318
 
236
319
  when :generic
237
320
  # Generic tester requires that a generic_run_command option/block be provided.
@@ -269,6 +352,10 @@ module OrigenSim
269
352
  cmd = post_process_run_cmd.call(cmd, self) if post_process_run_cmd
270
353
  fail "OrigenSim: :post_process_run_cmd returned object of class #{cmd.class}. Must return a String." unless cmd.is_a?(String)
271
354
 
355
+ # Print the command if debug is enabled
356
+ Origen.log.debug 'OrigenSim Run Command:'
357
+ Origen.log.debug cmd
358
+
272
359
  cmd
273
360
  end
274
361
 
@@ -279,7 +366,11 @@ module OrigenSim
279
366
  if Origen.app.current_job
280
367
  @last_wafe_file_basename = Pathname.new(Origen.app.current_job.output_file).basename('.*').to_s
281
368
  else
282
- @last_wafe_file_basename
369
+ if Origen.interactive?
370
+ 'interactive'
371
+ else
372
+ @last_wafe_file_basename
373
+ end
283
374
  end
284
375
  end
285
376
  end
@@ -308,12 +399,26 @@ module OrigenSim
308
399
  when :synopsys
309
400
  edir = Pathname.new(wave_config_dir).relative_path_from(Pathname.pwd)
310
401
  cmd = "cd #{edir} && "
311
- cmd += configuration[:dve] || 'dve'
312
- dir = Pathname.new(wave_dir).relative_path_from(edir.expand_path)
313
- cmd += " -vpd #{dir}/#{wave_file_basename}.vpd"
314
- f = Pathname.new(wave_config_file).relative_path_from(edir.expand_path)
315
- cmd += " -session #{f}"
316
- cmd += ' &'
402
+ if configuration[:verdi]
403
+ unless ENV['VCS_HOME'] && ENV['LD_LIBRARY_PATH']
404
+ puts 'Please make sure the VCS_HOME and LD_LIBRARY PATH are setup correctly before using Verdi'
405
+ end
406
+ edir = Pathname.new(wave_config_dir).relative_path_from(Pathname.pwd)
407
+ cmd = "cd #{edir} && "
408
+ cmd += configuration[:verdi] || 'verdi'
409
+ dir = Pathname.new(wave_dir).relative_path_from(edir.expand_path)
410
+ cmd += " -ssz -dbdir #{Origen.root}/simulation/#{Origen.target.name}/synopsys/simv.daidir/ -ssf #{dir}/#{wave_file_basename}.fsdb"
411
+ f = Pathname.new(wave_config_file).relative_path_from(edir.expand_path)
412
+ cmd += " -sswr #{f}"
413
+ cmd += ' &'
414
+ else
415
+ cmd += configuration[:dve] || 'dve'
416
+ dir = Pathname.new(wave_dir).relative_path_from(edir.expand_path)
417
+ cmd += " -vpd #{dir}/#{wave_file_basename}.vpd"
418
+ f = Pathname.new(wave_config_file).relative_path_from(edir.expand_path)
419
+ cmd += " -session #{f}"
420
+ cmd += ' &'
421
+ end
317
422
 
318
423
  when :generic
319
424
  # Since this could be anything, the simulator will need to set this up. But, once it is, we can print it here.
@@ -359,6 +464,9 @@ module OrigenSim
359
464
 
360
465
  fetch_simulation_objects
361
466
 
467
+ artifact.clean
468
+ artifact.populate
469
+
362
470
  cmd = run_cmd + ' & echo \$!'
363
471
 
364
472
  launch_simulator = %(
@@ -448,10 +556,10 @@ module OrigenSim
448
556
 
449
557
  Origen.log.debug 'Starting the simulation monitor...'
450
558
 
451
- simulator_parent_process = spawn("ruby -e \"#{launch_simulator}\"")
452
- Process.detach(simulator_parent_process)
559
+ monitor_pid = spawn("ruby -e \"#{launch_simulator}\"")
560
+ Process.detach(monitor_pid)
453
561
 
454
- simulation.open(config[:startup_timeout] || 60) # This will block until the simulation process has started
562
+ simulation.open(monitor_pid, config[:startup_timeout] || 60) # This will block until the simulation process has started
455
563
 
456
564
  # The VPI extension will send 'READY!' when it starts, make sure we get it before proceeding
457
565
  data = get
@@ -460,6 +568,8 @@ module OrigenSim
460
568
  simulation.log_results
461
569
  exit # Assume it is not worth trying another pattern in this case, some kind of environment/config issue
462
570
  end
571
+ Origen.log.info "OrigenSim version #{Origen.app!.version}"
572
+ Origen.log.info "OrigenSim DUT version #{dut_version}"
463
573
  # Tick the simulation on, this seems to be required since any VPI puts operations before
464
574
  # the simulation has started are not applied.
465
575
  # Note that this is not setting a tester timeset, so the application will still have to
@@ -472,6 +582,14 @@ module OrigenSim
472
582
  # Send the given message string to the simulator
473
583
  def put(msg)
474
584
  simulation.socket.write(msg + "\n")
585
+ rescue Errno::EPIPE => e
586
+ if simulation.running?
587
+ Origen.log.error 'Communication with the simulator has been lost (though it seems to still be running)!'
588
+ else
589
+ Origen.log.error 'The simulator has stopped unexpectedly!'
590
+ end
591
+ sleep 2 # To make sure that any log output from the simulator is captured before we pull the plug
592
+ exit 1
475
593
  end
476
594
 
477
595
  # Get a message from the simulator, will block until one
@@ -536,11 +654,16 @@ module OrigenSim
536
654
  end
537
655
  end
538
656
 
539
- def write_comment(comment)
657
+ def write_comment(line, comment)
658
+ return if line >= OrigenSim::NUMBER_OF_COMMENT_LINES
540
659
  # Not sure what the limiting factor here is, the comment memory in the test bench should
541
660
  # be able to handle 1024 / 8 length strings, but any bigger than this hangs the simulation
542
- comment = comment[0..96]
543
- put("c^#{comment}")
661
+ comment = comment ? comment[0..96] : ''
662
+ if dut_version > '0.12.1'
663
+ put("c^#{line}^#{comment} ") # Space at the end is important so that an empty comment is communicated properly
664
+ else
665
+ put("c^#{comment} ")
666
+ end
544
667
  end
545
668
 
546
669
  # Applies the current state of all pins to the simulation
@@ -624,6 +747,17 @@ module OrigenSim
624
747
  end
625
748
  end
626
749
 
750
+ # Flush any buffered simulation output, this should cause live wave viewers to
751
+ # reflect the latest state
752
+ def flush
753
+ if dut_version > '0.12.0'
754
+ put('j^')
755
+ sync_up
756
+ else
757
+ OrigenSim.error "Use of flush requires a DUT model compiled with OrigenSim version > 0.12.0, the current dut was compiled with #{dut_version}"
758
+ end
759
+ end
760
+
627
761
  def error(message)
628
762
  simulation.logged_errors = true
629
763
  Origen.log.error message
@@ -816,11 +950,29 @@ module OrigenSim
816
950
  # Returns the version of Origen Sim that the current DUT object was compiled with
817
951
  def dut_version
818
952
  @dut_version ||= begin
819
- put('i^')
820
- Origen::VersionString.new(get.strip)
953
+ # Allow configs to force a dut version, this is to allow backwards compatibility with very early
954
+ # compiled duts which do not support the command to get it from the compiled object
955
+ config[:dut_version] || begin
956
+ put('i^')
957
+ Origen::VersionString.new(get.strip)
958
+ end
821
959
  end
822
960
  end
823
961
 
962
+ # Any vectors executed within the given block will increment the match_errors counter
963
+ # rather than the errors counter.
964
+ # The match_errors counter will be returned to 0 at the end.
965
+ def match_loop
966
+ poke("#{testbench_top}.pins.match_loop", 1)
967
+ yield
968
+ poke("#{testbench_top}.pins.match_loop", 0)
969
+ poke("#{testbench_top}.pins.match_errors", 0)
970
+ end
971
+
972
+ def match_errors
973
+ peek("#{testbench_top}.pins.match_errors").to_i
974
+ end
975
+
824
976
  private
825
977
 
826
978
  # Pre 0.8.0 the simulator represented the time in ns instead of ps
@@ -830,7 +982,7 @@ module OrigenSim
830
982
 
831
983
  def clean(net)
832
984
  if net =~ /^dut\./
833
- "origen.#{net}"
985
+ "#{testbench_top}.#{net}"
834
986
  else
835
987
  net
836
988
  end
@@ -1,5 +1,4 @@
1
1
  require 'thread'
2
- require 'io/wait'
3
2
  module OrigenSim
4
3
  class StderrReader < Thread
5
4
  attr_reader :socket, :logged_errors
@@ -9,20 +8,27 @@ module OrigenSim
9
8
  @continue = true
10
9
  @logged_errors = false
11
10
  super do
12
- while @continue
13
- while @socket.ready?
14
- line = @socket.gets.chomp
15
- if OrigenSim.fail_on_stderr && !line.empty? &&
16
- !OrigenSim.stderr_string_exceptions.any? { |s| line =~ /#{s}/ }
17
- # We're failing on stderr, so print its results and log as errors if its not an exception.
18
- @logged_errors = true
19
- Origen.log.error "(STDERR): #{line}"
20
- elsif OrigenSim.verbose?
21
- Origen.log.info line
22
- else
23
- Origen.log.debug line
11
+ begin
12
+ while @continue
13
+ line = @socket.gets
14
+ if line
15
+ line = line.chomp
16
+ if OrigenSim.fail_on_stderr && !line.empty? &&
17
+ !OrigenSim.stderr_string_exceptions.any? { |s| s.is_a?(Regexp) ? s.match?(line) : line =~ /#{s}/i }
18
+ # We're failing on stderr, so print its results and log as errors if its not an exception.
19
+ @logged_errors = true
20
+ Origen.log.error "(STDERR): #{line}"
21
+ elsif OrigenSim.verbose?
22
+ Origen.log.info line
23
+ else
24
+ Origen.log.debug line
25
+ end
24
26
  end
25
27
  end
28
+ rescue IOError => e
29
+ unless e.message =~ /stream closed/
30
+ raise e
31
+ end
26
32
  end
27
33
  end
28
34
  end
@@ -1,5 +1,4 @@
1
1
  require 'thread'
2
- require 'io/wait'
3
2
  module OrigenSim
4
3
  class StdoutReader < Thread
5
4
  attr_reader :socket, :logged_errors
@@ -9,25 +8,32 @@ module OrigenSim
9
8
  @continue = true
10
9
  @logged_errors = false
11
10
  super do
12
- while @continue
13
- while @socket.ready?
14
- line = @socket.gets.chomp
15
- if OrigenSim.error_strings.any? { |s| line =~ /#{s}/ } &&
16
- !OrigenSim.error_string_exceptions.any? { |s| line =~ /#{s}/ }
17
- @logged_errors = true
18
- Origen.log.error "(STDOUT): #{line}"
19
- elsif OrigenSim.warning_strings.any? { |s| line =~ /#{s}/ } &&
20
- !OrigenSim.warning_string_exceptions.any? { |s| line =~ /#{s}/ }
21
- Origen.log.warn line
22
- else
23
- if OrigenSim.verbose? ||
24
- OrigenSim.log_strings.any? { |s| line =~ /#{s}/ }
25
- Origen.log.info line
11
+ begin
12
+ while @continue
13
+ line = @socket.gets
14
+ if line
15
+ line = line.chomp
16
+ if OrigenSim.error_strings.any? { |s| s.is_a?(Regexp) ? s.match?(line) : line =~ /#{s}/i } &&
17
+ !OrigenSim.error_string_exceptions.any? { |s| s.is_a?(Regexp) ? s.match?(line) : line =~ /#{s}/i }
18
+ @logged_errors = true
19
+ Origen.log.error "(STDOUT): #{line}"
20
+ elsif OrigenSim.warning_strings.any? { |s| s.is_a?(Regexp) ? s.match?(line) : line =~ /#{s}/i } &&
21
+ !OrigenSim.warning_string_exceptions.any? { |s| s.is_a?(Regexp) ? s.match?(line) : line =~ /#{s}/i }
22
+ Origen.log.warn line
26
23
  else
27
- Origen.log.debug line
24
+ if OrigenSim.verbose? ||
25
+ OrigenSim.log_strings.any? { |s| s.is_a?(Regexp) ? s.match?(line) : line =~ /#{s}/i }
26
+ Origen.log.info line
27
+ else
28
+ Origen.log.debug line
29
+ end
28
30
  end
29
31
  end
30
32
  end
33
+ rescue IOError => e
34
+ unless e.message =~ /stream closed/
35
+ raise e
36
+ end
31
37
  end
32
38
  end
33
39
  end