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