Class: OrigenSim::Simulation

Inherits:
Object
  • Object
show all
Defined in:
lib/origen_sim/simulation.rb

Overview

Responsible for managing each individual simulation that is run in an Origen thread e.g. If multiple patterns are run in separate simulations, then one instance of this class will exist for each one.

It is primarily responsible for all communications with the simulation and capturing log output and errors.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id, view_wave_command) ⇒ Simulation

Returns a new instance of Simulation.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/origen_sim/simulation.rb', line 30

def initialize(id, view_wave_command)
  @id = id
  @view_wave_command = view_wave_command
  @completed_cleanly = false
  @failed_to_start = false
  @logged_errors = false
  @ended = false
  @error_count = 0
  @cycle_count = 0
  @socket_ids = {}
  @log_files = []
  @max_errors_exceeded = false

  # Socket used to send Origen -> Verilog commands
  @server = UNIXServer.new(socket_id)

  # Socket used to capture STDOUT from the simulator
  @server_stdout = UNIXServer.new(socket_id(:stdout))
  # Socket used to capture STDERR from the simulator
  @server_stderr = UNIXServer.new(socket_id(:stderr))
  # Socket used to send a heartbeat pulse from Origen to process running the simulator
  @server_heartbeat = UNIXServer.new(socket_id(:heartbeat))
  # Socket used to receive status updates from the process running the simulator
  @server_status = UNIXServer.new(socket_id(:status))
end

Instance Attribute Details

#completed_cleanlyObject

Returns the value of attribute completed_cleanly.



16
17
18
# File 'lib/origen_sim/simulation.rb', line 16

def completed_cleanly
  @completed_cleanly
end

#endedObject

Returns false when the simulation is running and will be set to true once all instructions have been sent and executed by the simulator and immediately before the end_simulation instruction is sent to the simulator.



24
25
26
# File 'lib/origen_sim/simulation.rb', line 24

def ended
  @ended
end

#error_countObject

Returns the value of attribute error_count.



16
17
18
# File 'lib/origen_sim/simulation.rb', line 16

def error_count
  @error_count
end

#failed_to_startObject

Returns the value of attribute failed_to_start.



16
17
18
# File 'lib/origen_sim/simulation.rb', line 16

def failed_to_start
  @failed_to_start
end

#idObject (readonly)

Returns the value of attribute id.



14
15
16
# File 'lib/origen_sim/simulation.rb', line 14

def id
  @id
end

#log_filesObject (readonly)

Returns the value of attribute log_files.



26
27
28
# File 'lib/origen_sim/simulation.rb', line 26

def log_files
  @log_files
end

#logged_errorsObject

Returns the value of attribute logged_errors.



16
17
18
# File 'lib/origen_sim/simulation.rb', line 16

def logged_errors
  @logged_errors
end

#max_errors_exceededObject

Returns the value of attribute max_errors_exceeded.



28
29
30
# File 'lib/origen_sim/simulation.rb', line 28

def max_errors_exceeded
  @max_errors_exceeded
end

#pidObject

Returns the value of attribute pid.



17
18
19
# File 'lib/origen_sim/simulation.rb', line 17

def pid
  @pid
end

#socketObject (readonly)

Returns the communication socket used for sending commands to the Origen VPI running in the simulation process



20
21
22
# File 'lib/origen_sim/simulation.rb', line 20

def socket
  @socket
end

#view_wave_commandObject (readonly)

Returns the value of attribute view_wave_command.



14
15
16
# File 'lib/origen_sim/simulation.rb', line 14

def view_wave_command
  @view_wave_command
end

Instance Method Details

#abort_connectionObject



236
237
238
239
240
241
242
243
# File 'lib/origen_sim/simulation.rb', line 236

def abort_connection
  # If the Verilog process has not established a connection yet, then make one to
  # release our process and then exit
  unless @connection_established
    @connection_aborted = true
    UNIXSocket.new(socket_id).puts("Time out\n")
  end
end

#closeObject

Close all communication channels with the simulator



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/origen_sim/simulation.rb', line 246

def close
  return unless @opened
  stop_heartbeat
  @stdout_reader.stop
  @stderr_reader.stop
  @heartbeat.close
  @socket.close
  @stderr.close
  @stdout.close
  @status.close
  File.unlink(socket_id(:heartbeat)) if File.exist?(socket_id(:heartbeat))
  File.unlink(socket_id) if File.exist?(socket_id)
  File.unlink(socket_id(:stderr)) if File.exist?(socket_id(:stderr))
  File.unlink(socket_id(:stdout)) if File.exist?(socket_id(:stdout))
  File.unlink(socket_id(:status)) if File.exist?(socket_id(:status))
  @opened = false
end

#cycle(number_of_cycles) ⇒ Object



296
297
298
# File 'lib/origen_sim/simulation.rb', line 296

def cycle(number_of_cycles)
  @cycle_count += number_of_cycles
end

#cycle_countObject

Returns the current cycle count, this is Origen’s local count



292
293
294
# File 'lib/origen_sim/simulation.rb', line 292

def cycle_count
  @cycle_count
end

#ended_fileObject



113
114
115
116
117
118
119
# File 'lib/origen_sim/simulation.rb', line 113

def ended_file
  @ended_file ||= begin
    dir = Origen.root.join('tmp', 'origen_sim', 'ended')
    FileUtils.mkdir_p(dir.to_s)
    dir.join(socket_number).to_s
  end
end

#failed?(in_progress = false) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
64
65
# File 'lib/origen_sim/simulation.rb', line 56

def failed?(in_progress = false)
  # Exit cleanly when the simulator didn't even start, e.g. because no compiled DUT existed
  return true unless @stderr_reader
  failed = stderr_logged_errors || logged_errors || failed_to_start || error_count > 0
  if in_progress
    failed
  else
    failed || !completed_cleanly
  end
end

#log_results(in_progress = false) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/origen_sim/simulation.rb', line 75

def log_results(in_progress = false)
  if failed?(in_progress)
    if failed_to_start
      if Origen.debugger_enabled?
        Origen.log.error 'The simulation failed to get underway!'
      else
        Origen.log.error 'The simulation failed to get underway! (run again with -verbose to see why)'
      end
    else
      if in_progress
        simulator.log("The simulation has #{error_count} error#{error_count > 1 ? 's' : ''}!", :error) if error_count > 0
      else
        Origen.log.error "The simulation failed with #{error_count} errors!" if error_count > 0
        Origen.log.error "The simulation was aborted due to exceeding #{simulator.max_errors} errors!" if max_errors_exceeded
      end
      Origen.log.error 'The simulation log reported errors!' if logged_errors
      Origen.log.error 'The simulation stderr reported errors!' if stderr_logged_errors
      Origen.log.error 'The simulation exited early!' unless completed_cleanly || in_progress
    end
  else
    if in_progress
      simulator.log 'The simulation is passing!', :success
    else
      Origen.log.success 'The simulation passed!'
    end
  end
end

#monitor_running?Boolean

Returns true if the simulation monitor process (the one that receives the heartbeat and kills the simulation if it stops) is running

Returns:

  • (Boolean)


277
278
279
280
281
282
283
284
285
# File 'lib/origen_sim/simulation.rb', line 277

def monitor_running?
  return false unless @monitor_pid
  begin
    Process.getpgid(@monitor_pid)
    true
  rescue Errno::ESRCH
    false
  end
end

#open(monitor_pid, timeout) ⇒ Object

Open the communication channels with the simulator



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/origen_sim/simulation.rb', line 176

def open(monitor_pid, timeout)
  @monitor_pid = monitor_pid
  timeout_connection(timeout) do
    start_heartbeat
    @stdout = @server_stdout.accept
    @stderr = @server_stderr.accept
    @status = @server_status.accept
    @stdout_reader = StdoutReader.new(@stdout, simulator)
    @stderr_reader = StderrReader.new(@stderr)
    @stdout_reader.priority = 1
    @stderr_reader.priority = 2

    Origen.log.debug 'The simulation monitor has started'
    Origen.log.debug @status.gets.chomp  # Starting simulator
    Origen.log.debug @status.gets.chomp  # Simulator has started
    response = @status.gets.chomp
    if response =~ /finished/
      abort_connection
    else
      @pid = response.to_i
    end
    # That's all status info done until the simulation process ends, start a thread
    # to wait for that in case it ends before the VPI starts
    Thread.new do
      begin
        @status.gets.chomp  # This will block until something is received
      rescue Exception => e
        Origen.log.error "Exception occurred while processing the monitor's status!"
        raise e
      ensure
        abort_connection
      end
    end
    Origen.log.debug 'Waiting for Origen VPI to start...'

    # This will block until the VPI extension is invoked and connects to the socket
    @socket = @server.accept

    @connection_established = true # Cancels timeout_connection
    if @connection_aborted
      self.failed_to_start = true
      log_results
      exit  # Assume it is not worth trying another pattern in this case, some kind of environment/config issue
    end
    Origen.log.debug 'Origen VPI has started'
  end

  @opened = true
end

#running?Boolean

Returns true if the simulation is running

Returns:

  • (Boolean)


265
266
267
268
269
270
271
272
273
# File 'lib/origen_sim/simulation.rb', line 265

def running?
  return false unless pid
  begin
    Process.getpgid(pid)
    true
  rescue Errno::ESRCH
    false
  end
end

#socket_id(type = nil) ⇒ Object



287
288
289
# File 'lib/origen_sim/simulation.rb', line 287

def socket_id(type = nil)
  @socket_ids[type] ||= "#{OrigenSim.socket_dir || '/tmp'}/#{socket_number}#{type}.sock"
end

#start_heartbeatObject

Provide a heartbeat to let the parallel Ruby process in charge of the simulator know that the master Origen process is still alive. If the Origen process crashes and leaves the simulator running, the child process will automatically reap it after a couple of missed heartbeats.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/origen_sim/simulation.rb', line 125

def start_heartbeat
  @heartbeat = @server_heartbeat.accept
  if Heartbeat::THREADSAFE
    @heartbeat_thread = Heartbeat.new(self, @heartbeat)
  else
    ended_file # Cache this file name before forking
    @heartbeat_pid = fork do
      loop do
        begin
          @heartbeat.write("OK\n")
        rescue Errno::EPIPE => e
          if File.exist?(ended_file)
            FileUtils.rm_f(ended_file)
            exit 0
          else
            if monitor_running?
              Origen.log.error 'Communication with the simulation monitor has been lost (though it seems to still be running)!'
            else
              Origen.log.error 'The simulation monitor has stopped unexpectedly!'
            end
            exit 1
          end
        end
        sleep 5
      end
    end
  end
end

#stderr_logged_errorsObject



71
72
73
# File 'lib/origen_sim/simulation.rb', line 71

def stderr_logged_errors
  @stderr_reader.logged_errors
end

#stop_heartbeatObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/origen_sim/simulation.rb', line 154

def stop_heartbeat
  if Heartbeat::THREADSAFE
    @heartbeat_thread.stop
  else
    Process.kill('SIGHUP', @heartbeat_pid)

    # Ensure that the process has stopped before closing the IO pipes
    begin
      Process.waitpid(@heartbeat_pid)
    rescue Errno::ECHILD
      # Heartbeat process has already stopped, so ignore this.
    end
    FileUtils.rm_f(ended_file) if File.exist?(ended_file)
  end
end

#time_since_last_logObject



170
171
172
173
# File 'lib/origen_sim/simulation.rb', line 170

def time_since_last_log
  [@stdout_reader.time_since_last_message,
   @stderr_reader.time_since_last_message].min
end

#timeout_connection(wait_in_s) ⇒ Object



226
227
228
229
230
231
232
233
234
# File 'lib/origen_sim/simulation.rb', line 226

def timeout_connection(wait_in_s)
  @connection_aborted = false
  @connection_established = false
  Thread.new do
    sleep wait_in_s
    abort_connection # Will do nothing if a successful connection has been made while we were waiting
  end
  yield
end