[opentitantool] Print the verilator stdout for easier debug
1. Spawn a thread to consume and log the verilator stdout.
Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index 99f2f93..086d5e8 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -80,6 +80,7 @@
"src/transport/ultradebug/spi.rs",
"src/transport/ultradebug/uart.rs",
"src/transport/verilator/mod.rs",
+ "src/transport/verilator/stdout.rs",
"src/transport/verilator/subprocess.rs",
"src/transport/verilator/transport.rs",
"src/transport/verilator/uart.rs",
diff --git a/sw/host/opentitanlib/src/transport/verilator/mod.rs b/sw/host/opentitanlib/src/transport/verilator/mod.rs
index 8da586f..90f9297 100644
--- a/sw/host/opentitanlib/src/transport/verilator/mod.rs
+++ b/sw/host/opentitanlib/src/transport/verilator/mod.rs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
+mod stdout;
pub mod subprocess;
pub mod transport;
pub mod uart;
diff --git a/sw/host/opentitanlib/src/transport/verilator/stdout.rs b/sw/host/opentitanlib/src/transport/verilator/stdout.rs
new file mode 100644
index 0000000..2223574
--- /dev/null
+++ b/sw/host/opentitanlib/src/transport/verilator/stdout.rs
@@ -0,0 +1,45 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use anyhow::Result;
+use std::io::Read;
+use std::process::ChildStdout;
+use std::sync::{Arc, Mutex};
+
+// The whole point of this module being named `stdout.rs` is so that the log message
+// looks like `[<timestamp> INFO ...verilator::stdout] <message>`.
+
+// Accumulates output from verilator's `stdout`.
+pub fn accumulate(stdout: ChildStdout, accumulator: Arc<Mutex<String>>) {
+ if let Err(e) = worker(stdout, accumulator) {
+ log::error!("accumulate error: {:?}", e);
+ }
+}
+
+fn worker(mut stdout: ChildStdout, accumulator: Arc<Mutex<String>>) -> Result<()> {
+ let mut s = String::default();
+ loop {
+ read(&mut stdout, &mut s)?;
+ let mut lines = s.split('\n').collect::<Vec<&str>>();
+ let next = if !s.ends_with('\n') {
+ // If we didn't read a complete line at the end, save it for the
+ // next read.
+ lines.pop()
+ } else {
+ None
+ };
+ for line in lines {
+ log::info!("{}", line);
+ }
+ accumulator.lock().unwrap().push_str(&s);
+ s = next.unwrap_or("").to_string();
+ }
+}
+
+fn read(stdout: &mut ChildStdout, s: &mut String) -> Result<()> {
+ let mut buf = [0u8; 256];
+ let n = stdout.read(&mut buf)?;
+ s.push_str(&String::from_utf8_lossy(&buf[..n]));
+ Ok(())
+}
diff --git a/sw/host/opentitanlib/src/transport/verilator/subprocess.rs b/sw/host/opentitanlib/src/transport/verilator/subprocess.rs
index fad3a7e..ae5ddfa 100644
--- a/sw/host/opentitanlib/src/transport/verilator/subprocess.rs
+++ b/sw/host/opentitanlib/src/transport/verilator/subprocess.rs
@@ -2,14 +2,15 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
-use crate::util::file;
-use anyhow::Result;
-use log::info;
+use anyhow::{anyhow, Result};
use regex::Regex;
-use std::io::{ErrorKind, Read};
-use std::process::{Child, ChildStdout, Command, Stdio};
+use std::io::ErrorKind;
+use std::process::{Child, Command, Stdio};
+use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
+use crate::transport::verilator::stdout;
+
/// Verilator startup options.
pub struct Options {
/// The verilator executable.
@@ -26,8 +27,7 @@
pub struct Subprocess {
child: Child,
- stdout: ChildStdout,
- accumulated_output: String,
+ accumulated_output: Arc<Mutex<String>>,
}
impl Subprocess {
@@ -48,31 +48,24 @@
args.extend_from_slice(&options.extra_args);
command.args(&args[..]);
- info!("Spawning verilator: {:?} {:?}", options.executable, args);
+ log::info!("Spawning verilator: {:?} {:?}", options.executable, args);
let mut child = command
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?;
+ let accumulator: Arc<Mutex<String>> = Default::default();
let stdout = child.stdout.take().unwrap();
+ let a = Arc::clone(&accumulator);
+ std::thread::spawn(move || stdout::accumulate(stdout, a));
+
Ok(Subprocess {
child,
- stdout,
- accumulated_output: String::default(),
+ accumulated_output: accumulator,
})
}
- /// Accumulates output from verilator's `stdout`.
- pub fn accumulate(&mut self, timeout: Duration) -> Result<()> {
- let mut buf = [0u8; 256];
- file::wait_read_timeout(&self.stdout, timeout)?;
- let n = self.stdout.read(&mut buf)?;
- let s = String::from_utf8_lossy(&buf[..n]);
- self.accumulated_output.push_str(&s);
- Ok(())
- }
-
/// Finds a string within the verilator output.
/// It is assumed that the [`Regex`] `re` has exactly one capture group.
pub fn find(&mut self, re: &Regex, timeout: Duration) -> Result<String> {
@@ -82,28 +75,24 @@
// and thus expect a capture length of two.
assert_eq!(re.captures_len(), 2);
let deadline = Instant::now() + timeout;
- loop {
- if let Some(captures) = re.captures(&self.accumulated_output) {
- let val = captures.get(1).expect("expected a capture");
- return Ok(val.as_str().to_owned());
- } else {
- self.accumulate(deadline.saturating_duration_since(Instant::now()))?;
+ while deadline > Instant::now() {
+ {
+ let a = self.accumulated_output.lock().unwrap();
+ if let Some(captures) = re.captures(&a.as_str()) {
+ let val = captures.get(1).expect("expected a capture");
+ return Ok(val.as_str().to_owned());
+ }
}
+ std::thread::sleep(Duration::from_millis(50));
}
+ Err(anyhow!("Timed out"))
}
/// Kill the verilator subprocess.
pub fn kill(&mut self) -> Result<()> {
match self.child.kill() {
- Ok(_) => Ok(()),
- Err(error) => {
- if error.kind() == ErrorKind::InvalidInput {
- // Don't care if the child has already exited.
- Ok(())
- } else {
- Err(error.into())
- }
- }
+ Err(error) if error.kind() != ErrorKind::InvalidInput => Err(error.into()),
+ _ => Ok(()),
}
}
}