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.
- checksums.yaml +7 -0
- data/config/application.rb +109 -0
- data/config/boot.rb +24 -0
- data/config/commands.rb +74 -0
- data/config/shared_commands.rb +12 -0
- data/config/version.rb +8 -0
- data/lib/origen_sim/commands/build.rb +105 -0
- data/lib/origen_sim/flow.rb +13 -0
- data/lib/origen_sim/generator.rb +35 -0
- data/lib/origen_sim/origen/pins/pin.rb +93 -0
- data/lib/origen_sim/origen/top_level.rb +22 -0
- data/lib/origen_sim/origen_testers/api.rb +11 -0
- data/lib/origen_sim/simulator.rb +589 -0
- data/lib/origen_sim/tester.rb +113 -0
- data/lib/origen_sim.rb +27 -0
- data/lib/origen_sim_dev/dut.rb +101 -0
- data/lib/tasks/origen_sim.rake +6 -0
- data/pattern/test.rb +84 -0
- data/program/p1.rb +10 -0
- data/templates/empty.svcf +100 -0
- data/templates/probe.tcl.erb +15 -0
- data/templates/rtl_v/origen.v.erb +194 -0
- data/templates/web/index.md.erb +37 -0
- data/templates/web/layouts/_basic.html.erb +13 -0
- data/templates/web/partials/_navbar.html.erb +20 -0
- data/templates/web/release_notes.md.erb +5 -0
- metadata +96 -0
@@ -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
|