add first cut of verilator simulation, over from microwatt
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 14 Feb 2022 11:33:20 +0000 (11:33 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 14 Feb 2022 11:33:20 +0000 (11:33 +0000)
Makefile [new file with mode: 0644]
src/ls2.py
verilator/microwatt-verilator.cpp [new file with mode: 0644]
verilator/uart-verilator.c [new file with mode: 0644]
verilator/uart-verilator.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a132858
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,82 @@
+CFLAGS=-O3 -Wall
+CXXFLAGS=-g -g
+
+YOSYS     ?= yosys
+NEXTPNR   ?= nextpnr-ecp5
+ECPPACK   ?= ecppack
+OPENOCD   ?= openocd
+
+all = ls2_verilator
+
+all: $(all)
+
+uart_files = $(wildcard ../uart16550/rtl/verilog/*.v)
+
+# Verilator sim
+VERILATOR_ROOT=$(shell verilator -getenv VERILATOR_ROOT 2>/dev/null)
+ifeq (, $(VERILATOR_ROOT))
+$(soc_dram_tbs):
+       $(error "Verilator is required to make this target !")
+else
+
+VERILATOR_CFLAGS=-O3
+VERILATOR_FLAGS=-O3
+
+endif
+
+# Hello world
+MEMORY_SIZE=8192
+RAM_INIT_FILE=hello_world/hello_world.hex
+SIM_MAIN_BRAM=false
+
+# Micropython
+#MEMORY_SIZE=393216
+#RAM_INIT_FILE=micropython/firmware.hex
+
+# Linux
+#MEMORY_SIZE=536870912
+#RAM_INIT_FILE=dtbImage.microwatt.hex
+#SIM_MAIN_BRAM=false
+SIM_BRAM_CHAINBOOT=6291456 # 0x600000
+
+FPGA_TARGET ?= VERILATOR
+
+ifeq ($(FPGA_TARGET), verilator)
+RESET_LOW=true
+CLK_INPUT=50000000
+CLK_FREQUENCY=50000000
+clkgen=fpga/clk_gen_bypass.vhd
+endif
+
+ls2.v: src/ls2.py
+       python3 src/ls2.py sim
+
+# Need to investigate why yosys is hitting verilator warnings,
+# and eventually turn on -Wall
+microwatt-verilator: ls2.v \
+                     verilator/microwatt-verilator.cpp \
+                     verilator/uart-verilator.c
+       verilator -O3 -CFLAGS "-DCLK_FREQUENCY=$(CLK_FREQUENCY) -I../verilator" \
+    --assert \
+       --top-module top \
+    --cc ls2.v \
+    --exe verilator/microwatt-verilator.cpp verilator/uart-verilator.c \
+    -o $@ -I../uart16550/rtl/verilog \
+       -Wno-fatal -Wno-CASEOVERLAP -Wno-UNOPTFLAT \
+           -Wno-BLKANDNBLK \
+           -Wno-COMBDLY  \
+           -Wno-CASEINCOMPLETE \
+           -Wno-WIDTH \
+        --savable \
+        --trace \
+       #    --unroll-count 256 \
+       #    --output-split 5000 \
+       #    --output-split-cfuncs 500 \
+       #    --output-split-ctrace 500 \
+       make -C obj_dir -f Vtop.mk
+       @cp -f obj_dir/microwatt-verilator microwatt-verilator
+
+clean:
+       rm -fr obj_dir microwatt-verilator ls2.v
+
+.PHONY: all clean
index c39ca5d3156602891a6f83e81d79e9160068fc54..84724019ef39849faf518e86006f2911de347d16 100644 (file)
@@ -223,6 +223,6 @@ if __name__ == "__main__":
     else:
         # for now, generate verilog
         vl = verilog.convert(soc, ports=soc.ports())
-        with open("test_ls2.v", "w") as f:
+        with open("ls2.v", "w") as f:
             f.write(vl)
 
diff --git a/verilator/microwatt-verilator.cpp b/verilator/microwatt-verilator.cpp
new file mode 100644 (file)
index 0000000..4df3084
--- /dev/null
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+#include "Vtop.h"
+#include "verilated.h"
+#include "verilated_vcd_c.h"
+
+/*
+ * Current simulation time
+ * This is a 64-bit integer to reduce wrap over issues and
+ * allow modulus.  You can also use a double, if you wish.
+ */
+vluint64_t main_time = 0;
+
+/*
+ * Called by $time in Verilog
+ * converts to double, to match
+ * what SystemC does
+ */
+double sc_time_stamp(void)
+{
+       return main_time;
+}
+
+#if VM_TRACE
+VerilatedVcdC *tfp;
+#endif
+
+void tick(Vtop *top)
+{
+       top->clk = 1;
+       top->eval();
+#if VM_TRACE
+       if (tfp)
+               tfp->dump((double) main_time);
+#endif
+       main_time++;
+
+       top->clk = 0;
+       top->eval();
+#if VM_TRACE
+       if (tfp)
+               tfp->dump((double) main_time);
+#endif
+       main_time++;
+}
+
+void uart_tx(unsigned char tx);
+unsigned char uart_rx(void);
+
+int main(int argc, char **argv)
+{
+       Verilated::commandArgs(argc, argv);
+
+       // init top verilog instance
+       Vtop* top = new Vtop;
+
+#if VM_TRACE
+       // init trace dump
+       Verilated::traceEverOn(true);
+       tfp = new VerilatedVcdC;
+       top->trace(tfp, 99);
+       tfp->open("microwatt-verilator.vcd");
+#endif
+
+       // Reset
+       top->rst = 0;
+       for (unsigned long i = 0; i < 5; i++)
+               tick(top);
+       top->rst = 1;
+
+       while(!Verilated::gotFinish()) {
+               tick(top);
+
+               uart_tx(top->tx_o);
+               top->rx_i = uart_rx();
+       }
+
+#if VM_TRACE
+       tfp->close();
+       delete tfp;
+#endif
+
+       delete top;
+}
diff --git a/verilator/uart-verilator.c b/verilator/uart-verilator.c
new file mode 100644 (file)
index 0000000..8492a11
--- /dev/null
@@ -0,0 +1,253 @@
+#include <signal.h>
+#include <poll.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <termios.h>
+#include <stdlib.h>
+
+/* Should we exit simulation on ctrl-c or pass it through? */
+#define EXIT_ON_CTRL_C
+
+#define BAUD 115200
+/* Round to nearest */
+#define BITWIDTH ((CLK_FREQUENCY+(BAUD/2))/BAUD)
+
+/*
+ * Our UART uses 16x oversampling, so at 50 MHz and 115200 baud
+ * each sample is: 50000000/(115200*16) = 27 clock cycles. This
+ * means each bit is off by 0.47% so for 8 bits plus a start and
+ * stop bit the errors add to be 4.7%.
+ */
+static double error = 0.05;
+
+enum state {
+       IDLE, START_BIT, BITS, STOP_BIT, ERROR
+};
+
+static enum state tx_state = IDLE;
+static unsigned long tx_countbits;
+static unsigned char tx_bits;
+static unsigned char tx_byte;
+static unsigned char tx_prev;
+
+/*
+ * Return an error if the transition is not close enough to the start or
+ * the end of an expected bit.
+ */
+static bool is_error(unsigned long bits)
+{
+       double e = 1.0 * tx_countbits / BITWIDTH;
+
+       if ((e <= (1.0-error)) && (e >= error))
+               return true;
+
+       return false;
+}
+
+void uart_tx(unsigned char tx)
+{
+       switch (tx_state) {
+               case IDLE:
+                       if (tx == 0) {
+                               tx_state = START_BIT;
+                               tx_countbits = BITWIDTH;
+                               tx_bits = 0;
+                               tx_byte = 0;
+                       }
+                       break;
+
+               case START_BIT:
+                       tx_countbits--;
+                       if (tx == 1) {
+                               if (is_error(tx_countbits)) {
+                                       printf("START_BIT error %ld %ld\n", BITWIDTH, tx_countbits);
+                                       tx_countbits = BITWIDTH*2;
+                                       tx_state = ERROR;
+                                       break;
+                               }
+                       }
+
+                       if (tx_countbits == 0) {
+                               tx_state = BITS;
+                               tx_countbits = BITWIDTH;
+                       }
+                       break;
+
+               case BITS:
+                       tx_countbits--;
+                       if (tx_countbits == BITWIDTH/2) {
+                               tx_byte = tx_byte | (tx << tx_bits);
+                               tx_bits = tx_bits + 1;
+                       }
+
+                       if (tx != tx_prev) {
+                               if (is_error(tx_countbits)) {
+                                       printf("BITS error %ld %ld\n", BITWIDTH, tx_countbits);
+                                       tx_countbits = BITWIDTH*2;
+                                       tx_state = ERROR;
+                                       break;
+                               }
+                       }
+
+                       if (tx_countbits == 0) {
+                               if (tx_bits == 8) {
+                                       tx_state = STOP_BIT;
+                               }
+                               tx_countbits = BITWIDTH;
+                       }
+                       break;
+
+               case STOP_BIT:
+                       tx_countbits--;
+
+                       if (tx == 0) {
+                               if (is_error(tx_countbits)) {
+                                       printf("STOP_BIT error %ld %ld\n", BITWIDTH, tx_countbits);
+                                       tx_countbits = BITWIDTH*2;
+                                       tx_state = ERROR;
+                                       break;
+                               }
+                               /* Go straight to idle */
+                               write(STDOUT_FILENO, &tx_byte, 1);
+                               tx_state = IDLE;
+                       }
+
+                       if (tx_countbits == 0) {
+                               write(STDOUT_FILENO, &tx_byte, 1);
+                               tx_state = IDLE;
+                       }
+                       break;
+
+               case ERROR:
+                       tx_countbits--;
+                       if (tx_countbits == 0) {
+                               tx_state = IDLE;
+                       }
+
+                       break;
+       }
+
+       tx_prev = tx;
+}
+
+static struct termios oldt;
+
+static void disable_raw_mode(void)
+{
+       tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
+}
+
+static void enable_raw_mode(void)
+{
+       static bool initialized = false;
+
+       if (!initialized) {
+               static struct termios newt;
+
+               tcgetattr(STDIN_FILENO, &oldt);
+               newt = oldt;
+               cfmakeraw(&newt);
+#ifdef EXIT_ON_CTRL_C
+               newt.c_lflag |= ISIG;
+#endif
+               tcsetattr(STDIN_FILENO, TCSANOW, &newt);
+               initialized = true;
+               atexit(disable_raw_mode);
+       }
+}
+
+static int nonblocking_read(unsigned char *c)
+{
+       int ret;
+       unsigned long val = 0;
+       struct pollfd fdset[1];
+
+       enable_raw_mode();
+
+       memset(fdset, 0, sizeof(fdset));
+
+       fdset[0].fd = STDIN_FILENO;
+       fdset[0].events = POLLIN;
+
+       ret = poll(fdset, 1, 0);
+       if (ret == 0)
+               return false;
+
+       ret = read(STDIN_FILENO, &val, 1);
+       if (ret != 1) {
+               fprintf(stderr, "%s: read of stdin returns %d\n", __func__, ret);
+               exit(1);
+       }
+
+       if (ret == 1) {
+               *c = val;
+               return true;
+       } else {
+               return false;
+       }
+}
+
+static enum state rx_state = IDLE;
+static unsigned char rx_char;
+static unsigned long rx_countbits;
+static unsigned char rx_bit;
+static unsigned char rx = 1;
+
+/* Avoid calling poll() too much */
+#define RX_INTERVAL 10000
+static unsigned long rx_sometimes;
+
+unsigned char uart_rx(void)
+{
+       unsigned char c;
+
+       switch (rx_state) {
+               case IDLE:
+                       if (rx_sometimes++ >= RX_INTERVAL) {
+                               rx_sometimes = 0;
+
+                               if (nonblocking_read(&c)) {
+                                       rx_state = START_BIT;
+                                       rx_char = c;
+                                       rx_countbits = BITWIDTH;
+                                       rx_bit = 0;
+                                       rx = 0;
+                               }
+                       }
+
+                       break;
+
+               case START_BIT:
+                       rx_countbits--;
+                       if (rx_countbits == 0) {
+                               rx_state = BITS;
+                               rx_countbits = BITWIDTH;
+                               rx = rx_char & 1;
+                       }
+                       break;
+
+               case BITS:
+                       rx_countbits--;
+                       if (rx_countbits == 0) {
+                               rx_bit = rx_bit + 1;
+                               if (rx_bit == 8) {
+                                       rx = 1;
+                                       rx_state = STOP_BIT;
+                               } else {
+                                       rx = (rx_char >> rx_bit) & 1;
+                               }
+                               rx_countbits = BITWIDTH;
+                       }
+                       break;
+
+               case STOP_BIT:
+                       rx_countbits--;
+                       if (rx_countbits == 0) {
+                               rx_state = IDLE;
+                       }
+                       break;
+       }
+
+       return rx;
+}
diff --git a/verilator/uart-verilator.h b/verilator/uart-verilator.h
new file mode 100644 (file)
index 0000000..10ae8aa
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Our UART uses 16x oversampling, so at 50 MHz and 115200 baud
+ * each sample is: 50000000/(115200*16) = 27 clock cycles. This
+ * means each bit is off by 0.47% so for 8 bits plus a start and
+ * stop bit the errors add to be 4.7%.
+ */
+
+enum state {
+    IDLE, START_BIT, BITS, STOP_BIT, ERROR
+};
+
+struct uart_tx_state {
+    double error = 0.05;
+
+    enum state tx_state = IDLE;
+    unsigned long tx_countbits;
+    unsigned char tx_bits;
+    unsigned char tx_byte;
+    unsigned char tx_prev;
+
+    enum state rx_state = IDLE;
+    unsigned char rx_char;
+    unsigned long rx_countbits;
+    unsigned char rx_bit;
+    unsigned char rx = 1;
+};
+
+void uart_tx(unsigned char tx);
+unsigned char uart_rx(void);
+struct uart_tx_state * uart_get_state(void);
+void uart_restore(struct uart_tx_state *);
+