[uartdpi] Accept log file name through plusarg
The UARTDPI module simulates an UART device by creating a
pseudo-terminal (e.g. /dev/pts/N). Additionally, each written character
is also written to a log file, in our case always `uart0.log` in the
current directory.
This patch adds the ability to specify the path of the log file through
a plus argument (plusarg). The defaults remain unchanged: calling the
simulation without special arguents writes an `uart0.log` file.
As a new feature, the log file can now also be given as "-", which
writes UART logs directly to STDOUT. In this case, the running
simulation directly shows all output printed from device software, e.g.
from `LOG()` macros, making the life of software developers much easier.
To write all logs of uart0 (the one and only UART in an Earl Grey
system), use a command like the following:
```
build/lowrisc_systems_top_earlgrey_verilator_0.1/sim-verilator/Vtop_earlgrey_verilator \
--meminit=rom,build-bin/sw/device/boot_rom/boot_rom_sim_verilator.elf \
--meminit=flash,build-bin/sw/device/tests/dif_hmac_sanitytest_sim_verilator.elf \
+UARTDPI_LOG_uart0=-
```
To implement this functionality, the log writing was refactored from
SystemVerilog to C (DPI) code.
Signed-off-by: Philipp Wagner <phw@lowrisc.org>
diff --git a/doc/ug/getting_started_verilator.md b/doc/ug/getting_started_verilator.md
index d9b61a9..cbfae32 100644
--- a/doc/ug/getting_started_verilator.md
+++ b/doc/ug/getting_started_verilator.md
@@ -93,6 +93,20 @@
I00005 hello_world.c:46] The LEDs show the ASCII code of the last character.
```
+Instead of interacting with the UART through a pseudo-terminal, the UART output can be written to a log file, or to STDOUT.
+This is done by passing the `UARTDPI_LOG_uart0` plus argument ("plusarg") to the verilated simulation at runtime.
+To write all UART output to STDOUT, pass `+UARTDPI_LOG_uart0=-` to the simulation.
+To write all UART output to a file called `your-log-file.log`, pass `+UARTDPI_LOG_uart0=your-log-file.log`.
+
+A full command-line invocation of the simulation could then look like that:
+```console
+$ cd $REPO_TOP
+$ build/lowrisc_systems_top_earlgrey_verilator_0.1/sim-verilator/Vtop_earlgrey_verilator \
+ --meminit=rom,build-bin/sw/device/boot_rom/boot_rom_sim_verilator.elf \
+ --meminit=flash,build-bin/sw/device/examples/hello_world/hello_world_sim_verilator.elf \
+ +UARTDPI_LOG_uart0=-
+```
+
## Interact with GPIO
The simulation includes a DPI module to map general-purpose I/O (GPIO) pins to two POSIX FIFO files: one for input, and one for output.
diff --git a/hw/dv/dpi/uartdpi/uartdpi.c b/hw/dv/dpi/uartdpi/uartdpi.c
index 6e85aa4..4ef96f2 100644
--- a/hw/dv/dpi/uartdpi/uartdpi.c
+++ b/hw/dv/dpi/uartdpi/uartdpi.c
@@ -11,18 +11,21 @@
#endif
#include <assert.h>
+#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
-void *uartdpi_create(const char *name) {
+void *uartdpi_create(const char *name, const char *log_file_path) {
struct uartdpi_ctx *ctx =
(struct uartdpi_ctx *)malloc(sizeof(struct uartdpi_ctx));
assert(ctx);
int rv;
+ // Initialize UART pseudo-terminal
struct termios tty;
cfmakeraw(&tty);
@@ -43,6 +46,28 @@
"$ screen %s\n",
ctx->ptyname, name, ctx->ptyname);
+ // Open log file (if requested)
+ ctx->log_file = NULL;
+ bool write_log_file = strlen(log_file_path) != 0;
+ if (write_log_file) {
+ if (strcmp(log_file_path, "-") == 0) {
+ ctx->log_file = stdout;
+ printf("UART: Additionally writing all UART output to STDOUT.\n");
+
+ } else {
+ FILE *log_file;
+ log_file = fopen(log_file_path, "w");
+ if (!log_file) {
+ fprintf(stderr, "UART: Unable to open log file at %s: %s\n",
+ log_file_path, strerror(errno));
+ } else {
+ ctx->log_file = log_file;
+ printf("UART: Additionally writing all UART output to '%s'.\n",
+ log_file_path);
+ }
+ }
+ }
+
return (void *)ctx;
}
@@ -55,6 +80,15 @@
close(ctx->host);
close(ctx->device);
+ if (ctx->log_file) {
+ // Always ensure the log file is flushed (most important when writing
+ // to STDOUT)
+ fflush(ctx->log_file);
+ if (ctx->log_file != stdout) {
+ fclose(ctx->log_file);
+ }
+ }
+
free(ctx);
}
@@ -72,8 +106,15 @@
}
void uartdpi_write(void *ctx_void, char c) {
+ int rv;
+
struct uartdpi_ctx *ctx = (struct uartdpi_ctx *)ctx_void;
- int rv = write(ctx->host, &c, 1);
- assert(rv == 1 && "write() failed.");
+ rv = write(ctx->host, &c, 1);
+ assert(rv == 1 && "Write to pseudo-terminal failed.");
+
+ if (ctx->log_file) {
+ rv = fwrite(&c, sizeof(char), 1, ctx->log_file);
+ assert(rv == 1 && "Write to log file failed.");
+ }
}
diff --git a/hw/dv/dpi/uartdpi/uartdpi.h b/hw/dv/dpi/uartdpi/uartdpi.h
index 1203fee..29d50a5 100644
--- a/hw/dv/dpi/uartdpi/uartdpi.h
+++ b/hw/dv/dpi/uartdpi/uartdpi.h
@@ -4,16 +4,20 @@
#ifndef OPENTITAN_HW_DV_DPI_UARTDPI_UARTDPI_H_
#define OPENTITAN_HW_DV_DPI_UARTDPI_UARTDPI_H_
+
extern "C" {
+#include <stdio.h>
+
struct uartdpi_ctx {
char ptyname[64];
int host;
int device;
char tmp_read;
+ FILE *log_file;
};
-void *uartdpi_create(const char *name);
+void *uartdpi_create(const char *name, const char *log_file_path);
void uartdpi_close(void *ctx_void);
int uartdpi_can_read(void *ctx_void);
char uartdpi_read(void *ctx_void);
diff --git a/hw/dv/dpi/uartdpi/uartdpi.sv b/hw/dv/dpi/uartdpi/uartdpi.sv
index 203099c..38a7649 100644
--- a/hw/dv/dpi/uartdpi/uartdpi.sv
+++ b/hw/dv/dpi/uartdpi/uartdpi.sv
@@ -13,11 +13,13 @@
output logic tx_o,
input logic rx_i
);
+ // Path to a log file. Used if none is specified through the `UARTDPI_LOG_<name>` plusarg.
+ localparam string DEFAULT_LOG_FILE = {NAME, ".log"};
localparam int CYCLES_PER_SYMBOL = FREQ / BAUD;
import "DPI-C" function
- chandle uartdpi_create(input string name);
+ chandle uartdpi_create(input string name, input string log_file_path);
import "DPI-C" function
void uartdpi_close(input chandle ctx);
@@ -32,13 +34,11 @@
void uartdpi_write(input chandle ctx, int data);
chandle ctx;
- int file_handle;
- string file_name;
+ string log_file_path = DEFAULT_LOG_FILE;
initial begin
- ctx = uartdpi_create(NAME);
- $sformat(file_name, "%s.log", NAME);
- file_handle = $fopen(file_name, "w");
+ $value$plusargs({"UARTDPI_LOG_", NAME, "=%s"}, log_file_path);
+ ctx = uartdpi_create(NAME, log_file_path);
end
final begin
@@ -119,7 +119,6 @@
rxactive <= 0;
if (rx_i) begin
uartdpi_write(ctx, rxsymbol);
- $fwrite(file_handle, "%c", rxsymbol);
end
end
end
diff --git a/hw/top_earlgrey/top_earlgrey_verilator.core b/hw/top_earlgrey/top_earlgrey_verilator.core
index 1e818e2..bc1fdd2 100644
--- a/hw/top_earlgrey/top_earlgrey_verilator.core
+++ b/hw/top_earlgrey/top_earlgrey_verilator.core
@@ -57,6 +57,10 @@
datatype: bool
paramtype: vlogdefine
description: Use the OTBN model instead of the RTL implementation (development only)
+ UART_LOG_uart0:
+ datatype: string
+ paramtype: plusarg
+ description: Write a log of output from uart0 to the given log file. Use "-" for stdout.
targets:
default: &default_target