[verilator] Add a mechanism for the host to drive GPIO pins.
This change refactors gpiodpi.h to create two UNIX fifos, one for
reading output pins and one for driving input pins. The input pins
are driven by writing commands of the form h0e (pulls 14th pin high)
and l12 (pulls 18th pin low); see gpiodpi.h for details.
As part of the refector, gpio_oe is now respected when printing out the
values of gpio pins: disabled pins (i.e., semanitcally "floating") are
represented with an `X` character.
Signed-off-by: Miguel Young de la Sota <mcyoung@google.com>
diff --git a/hw/dv/dpi/gpiodpi/gpiodpi.c b/hw/dv/dpi/gpiodpi/gpiodpi.c
index 1c23dff..3d2c126 100644
--- a/hw/dv/dpi/gpiodpi/gpiodpi.c
+++ b/hw/dv/dpi/gpiodpi/gpiodpi.c
@@ -21,46 +21,110 @@
#include <sys/types.h>
#include <unistd.h>
+// This file does a lot of bit setting and getting; these macros are intended to
+// make that a little more readable.
+#define GET_BIT(word, bit_idx) (((word) >> (bit_idx)) & 1)
+#define SET_BIT(word, bit_idx) ((word) |= (1 << (bit_idx)))
+#define CLR_BIT(word, bit_idx) ((word) &= ~(1 << (bit_idx)))
+
+struct gpiodpi_ctx {
+ // The number of pins we're driving.
+ int n_bits;
+
+ // The last known value of the pins, in little-endian order.
+ uint32_t driven_pin_values;
+
+ // File descriptors and paths for the device-to-host and host-to-device
+ // FIFOs.
+ int dev_to_host_fifo;
+ char dev_to_host_path[PATH_MAX];
+ int host_to_dev_fifo;
+ char host_to_dev_path[PATH_MAX];
+};
+
+/**
+ * Creates a new UNIX FIFO file at |path_buf|, and opens it with |flags|.
+ *
+ * @return a file descriptor for the fifo, or -1 if any syscall failed.
+ */
+static int open_fifo(char *path_buf, int flags) {
+ int fifo_status = mkfifo(path_buf, 0644);
+ if (fifo_status != 0) {
+ fprintf(stderr, "GPIO: Unable to create FIFO at %s: %s\n", path_buf,
+ strerror(errno));
+ return -1;
+ }
+
+ int fd = open(path_buf, flags);
+ if (fd < 0) {
+ // Delete the fifo we created; ignore errors.
+ unlink(path_buf);
+ fprintf(stderr, "GPIO: Unable to open FIFO at %s: %s\n", path_buf,
+ strerror(errno));
+ return -1;
+ }
+
+ return fd;
+}
+
+/**
+ * Print out a useage message for the GPIO interface.
+ *
+ * @arg rfifo the path to the "read" side (w.r.t the host).
+ * @arg wfifo the path to the "write" side (w.r.t the host).
+ * @arg n_bits the number of pins supported.
+ */
+static void print_usage(char *rfifo, char *wfifo, int n_bits) {
+ printf("\n");
+ printf(
+ "GPIO: FIFO pipes created at %s (read) and %s (write) for %d-bit wide "
+ "GPIO.\n",
+ rfifo, wfifo, n_bits);
+ printf(
+ "GPIO: To measure the values of the pins as driven by the device, run\n");
+ printf("$ cat %s # '0' low, '1' high, 'X' floating\n", rfifo);
+ printf("GPIO: To drive the pins, run a command like\n");
+ printf("$ echo 'h09 l31' > %s # Pull the pin 9 high, and pin 31 low.\n",
+ wfifo);
+}
+
void *gpiodpi_create(const char *name, int n_bits) {
struct gpiodpi_ctx *ctx =
- (struct gpiodpi_ctx *)calloc(1, sizeof(struct gpiodpi_ctx));
+ (struct gpiodpi_ctx *)malloc(sizeof(struct gpiodpi_ctx));
assert(ctx);
// n_bits > 32 requires more sophisticated handling of svBitVecVal which we
// currently don't do.
assert(n_bits <= 32 && "n_bits must be <= 32");
-
ctx->n_bits = n_bits;
- char cwd[PATH_MAX];
- char *cwd_rv;
- cwd_rv = getcwd(cwd, sizeof(cwd));
- assert(cwd_rv != NULL);
+ ctx->driven_pin_values = 0;
- int rv;
- rv = snprintf(ctx->fifo_pathname, PATH_MAX, "%s/%s", cwd, name);
- assert(rv <= PATH_MAX && rv > 0);
+ char cwd_buf[PATH_MAX];
+ char *cwd = getcwd(cwd_buf, sizeof(cwd_buf));
+ assert(cwd != NULL);
- rv = mkfifo(ctx->fifo_pathname, 0644); // writes are not supported currently
- if (rv != 0) {
- fprintf(stderr, "GPIO: Unable to create FIFO at %s: %s\n",
- ctx->fifo_pathname, strerror(errno));
+ int path_len;
+ path_len = snprintf(ctx->dev_to_host_path, PATH_MAX, "%s/%s-read", cwd, name);
+ assert(path_len > 0 && path_len <= PATH_MAX);
+ path_len =
+ snprintf(ctx->host_to_dev_path, PATH_MAX, "%s/%s-write", cwd, name);
+ assert(path_len > 0 && path_len <= PATH_MAX);
+
+ ctx->dev_to_host_fifo = open_fifo(ctx->dev_to_host_path, O_RDWR);
+ if (ctx->dev_to_host_fifo < 0) {
return NULL;
}
- ctx->fifo_fd = open(ctx->fifo_pathname, O_RDWR);
- if (ctx->fifo_fd < 0) {
- fprintf(stderr, "GPIO: Unable to open FIFO at %s: %s\n", ctx->fifo_pathname,
- strerror(errno));
+ ctx->host_to_dev_fifo = open_fifo(ctx->host_to_dev_path, O_RDWR);
+ if (ctx->host_to_dev_fifo < 0) {
return NULL;
}
- printf(
- "\n"
- "GPIO: FIFO pipe created at %s for %d bit wide GPIO. Run\n"
- "$ cat %s\n"
- "to observe the output.\n",
- ctx->fifo_pathname, ctx->n_bits, ctx->fifo_pathname);
+ int flags = fcntl(ctx->host_to_dev_fifo, F_GETFL, 0);
+ fcntl(ctx->host_to_dev_fifo, F_SETFL, flags | O_NONBLOCK);
+
+ print_usage(ctx->dev_to_host_path, ctx->host_to_dev_path, ctx->n_bits);
return (void *)ctx;
}
@@ -70,34 +134,126 @@
struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
assert(ctx);
- // n_bits > 32 requires more sophisticated handling of svBitVecVal which we
- // currently don't do (loop through the array of 32 bit values).
- assert(ctx->n_bits <= 32 && "n_bits must be <= 32");
-
+ // Write 0, 1, or X (when oe is not set) for each GPIO pin, in big endian
+ // order (i.e., pin 0 is the last character written). Finish it with a
+ // newline.
char gpio_str[32 + 1];
- for (int i = 0; i < ctx->n_bits; i++) {
- gpio_str[ctx->n_bits - i - 1] = !!(gpio_data[0] & (1 << i)) + '0';
+ char *pin_char = gpio_str;
+ for (int i = ctx->n_bits - 1; i >= 0; --i, ++pin_char) {
+ if (!GET_BIT(gpio_oe[0], i)) {
+ *pin_char = 'X';
+ } else if (GET_BIT(gpio_data[0], i)) {
+ *pin_char = '1';
+ } else {
+ *pin_char = '0';
+ }
+ }
+ *pin_char = '\n';
+
+ ssize_t written = write(ctx->dev_to_host_fifo, gpio_str, ctx->n_bits + 1);
+ assert(written == ctx->n_bits + 1);
+}
+
+/**
+ * Parses an unsigned decimal number from |text|, advancing it forward as
+ * necessary.
+ *
+ * Returns upon encountering any non-decimal digit.
+ */
+static uint32_t parse_dec(char **text) {
+ if (text == NULL || *text == NULL) {
+ return 0;
}
- gpio_str[ctx->n_bits] = '\n';
- ssize_t written = write(ctx->fifo_fd, gpio_str, ctx->n_bits + 1);
- assert(written == ctx->n_bits + 1);
+ uint32_t value = 0;
+ for (; **text != '\0'; ++*text) {
+ char c = **text;
+ uint32_t digit;
+ if (c >= '0' && c <= '9') {
+ digit = (c - '0');
+ } else {
+ break;
+ }
+
+ value *= 10;
+ value += digit;
+ }
+
+ return value;
+}
+
+uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe) {
+ struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
+ assert(ctx);
+
+ char gpio_str[32 + 2];
+ ssize_t read_len = read(ctx->host_to_dev_fifo, gpio_str, 32 + 1);
+ if (read_len < 0) {
+ return ctx->driven_pin_values;
+ }
+ gpio_str[read_len] = '\0';
+
+ char *gpio_text = gpio_str;
+ for (; *gpio_text != '\0'; ++gpio_text) {
+ switch (*gpio_text) {
+ case '\n':
+ case '\r':
+ case '\0':
+ goto parse_loop_end;
+ case 'l':
+ case 'L': {
+ ++gpio_text;
+ int idx = parse_dec(&gpio_text);
+ if (!GET_BIT(gpio_oe[0], idx)) {
+ fprintf(stderr,
+ "GPIO: Host tried to pull disabled pin low: pin %2d\n", idx);
+ }
+ CLR_BIT(ctx->driven_pin_values, idx);
+ break;
+ }
+ case 'h':
+ case 'H': {
+ ++gpio_text;
+ int idx = parse_dec(&gpio_text);
+ if (!GET_BIT(gpio_oe[0], idx)) {
+ fprintf(stderr,
+ "GPIO: Host tried to pull disabled pin high: pin %2d\n", idx);
+ }
+ SET_BIT(ctx->driven_pin_values, idx);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+parse_loop_end:
+ return ctx->driven_pin_values;
}
void gpiodpi_close(void *ctx_void) {
struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
- if (!ctx) {
+ if (ctx == NULL) {
return;
}
- int rv;
- rv = close(ctx->fifo_fd);
- if (rv != 0) {
- printf("GPIO: Failed to close FIFO: %s\n", strerror(errno));
- }
- rv = unlink(ctx->fifo_pathname);
- if (rv != 0) {
- printf("GPIO: Failed to unlink FIFO file at %s: %s\n", ctx->fifo_pathname,
+
+ if (close(ctx->dev_to_host_fifo) != 0) {
+ printf("GPIO: Failed to close FIFO file at %s: %s\n", ctx->dev_to_host_path,
strerror(errno));
}
+ if (close(ctx->host_to_dev_fifo) != 0) {
+ printf("GPIO: Failed to close FIFO file at %s: %s\n", ctx->host_to_dev_path,
+ strerror(errno));
+ }
+
+ if (unlink(ctx->dev_to_host_path) != 0) {
+ printf("GPIO: Failed to unlink FIFO file at %s: %s\n",
+ ctx->dev_to_host_path, strerror(errno));
+ }
+ if (unlink(ctx->host_to_dev_path) != 0) {
+ printf("GPIO: Failed to unlink FIFO file at %s: %s\n",
+ ctx->host_to_dev_path, strerror(errno));
+ }
+
free(ctx);
}
diff --git a/hw/dv/dpi/gpiodpi/gpiodpi.h b/hw/dv/dpi/gpiodpi/gpiodpi.h
index 6518e4b..4a20fbd 100644
--- a/hw/dv/dpi/gpiodpi/gpiodpi.h
+++ b/hw/dv/dpi/gpiodpi/gpiodpi.h
@@ -5,20 +5,45 @@
#ifndef GPIODPI_H_
#define GPIODPI_H_
-#include <limits.h>
#include <svdpi.h>
extern "C" {
-struct gpiodpi_ctx {
- int n_bits;
- int fifo_fd;
- char fifo_pathname[PATH_MAX];
-};
-
+/**
+ * Allocate a new GPIO DPI interface, returned as an opaque pointer.
+ *
+ * @param name a name to use when creating the inner FIFO.
+ * @param n_bits number of bits to write in each direction; this must be at
+ * most 32 bits.
+ */
void *gpiodpi_create(const char *name, int n_bits);
+
+/**
+ * Attempt to post the current GPIO state to the outside world.
+ *
+ * Intended to be called from SystemVerilog.
+ */
void gpiodpi_device_to_host(void *ctx_void, svBitVecVal *gpio_data,
svBitVecVal *gpio_oe);
+
+/**
+ * Attempt to read a GPIO command from the outside world.
+ *
+ * The commands from the host should be a space-separated sequence of high and
+ * low commands, terminated by a newline. A high command is of the form |hXX|,
+ * where XX are hex digits, pulls the XXth GPIO pin high; a low command, |lXX|,
+ * does the opposite. All other pins at left in an unspecified state. Invalid
+ * commands are ignored.
+ *
+ * Intended to be called from SystemVerilog.
+ * @return the values to pull the GPIO pins to.
+ */
+uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe);
+
+/**
+ * Relinquish resources held by a GPIO DPI interface.
+ */
void gpiodpi_close(void *ctx_void);
-}
+
+} // extern "C"
#endif // GPIODPI_H_
diff --git a/hw/dv/dpi/gpiodpi/gpiodpi.sv b/hw/dv/dpi/gpiodpi/gpiodpi.sv
index 7db7eaa..ccb44a9 100644
--- a/hw/dv/dpi/gpiodpi/gpiodpi.sv
+++ b/hw/dv/dpi/gpiodpi/gpiodpi.sv
@@ -24,12 +24,9 @@
import "DPI-C" function
void gpiodpi_close(input chandle ctx);
- export "DPI-C" function
- gpiodpi_host_to_device;
-
- function void gpiodpi_host_to_device(input [N_GPIO-1:0] gpio_p2d_set);
- gpio_p2d = gpio_p2d_set;
- endfunction
+ import "DPI-C" function
+ int gpiodpi_host_to_device_tick(input chandle ctx,
+ input [N_GPIO-1:0] gpio_en_d2p);
chandle ctx;
@@ -49,4 +46,32 @@
end
end
+ logic gpio_write_pulse;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ gpio_p2d <= '0; // default value
+ end else if (gpio_write_pulse) begin
+ gpio_p2d <= gpiodpi_host_to_device_tick(ctx, gpio_en_d2p);
+ end
+ end
+
+ // gpiodpio_host_to_device_tick() will be called every MAX_COUNT
+ // clock posedges; this should be kept reasonably high, since each
+ // tick call will perform at least one syscall.
+ localparam MAX_COUNT = 2048;
+ logic [$clog2(MAX_COUNT)-1:0] counter;
+
+ assign gpio_write_pulse = counter == MAX_COUNT -1;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ counter <= '0;
+ end else if (gpio_write_pulse) begin
+ counter <= '0;
+ end else begin
+ counter <= counter + 1'b1;
+ end
+ end
+
endmodule