From f5a155944859c68d6bd75309568a92cb4ca35329 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Mon, 14 Feb 2022 11:33:20 +0000 Subject: [PATCH] add first cut of verilator simulation, over from microwatt --- Makefile | 82 ++++++++++ src/ls2.py | 2 +- verilator/microwatt-verilator.cpp | 83 ++++++++++ verilator/uart-verilator.c | 253 ++++++++++++++++++++++++++++++ verilator/uart-verilator.h | 32 ++++ 5 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 verilator/microwatt-verilator.cpp create mode 100644 verilator/uart-verilator.c create mode 100644 verilator/uart-verilator.h diff --git a/Makefile b/Makefile new file mode 100644 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 diff --git a/src/ls2.py b/src/ls2.py index c39ca5d..8472401 100644 --- a/src/ls2.py +++ b/src/ls2.py @@ -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 index 0000000..4df3084 --- /dev/null +++ b/verilator/microwatt-verilator.cpp @@ -0,0 +1,83 @@ +#include +#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 index 0000000..8492a11 --- /dev/null +++ b/verilator/uart-verilator.c @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include +#include +#include + +/* 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 index 0000000..10ae8aa --- /dev/null +++ b/verilator/uart-verilator.h @@ -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 *); + -- 2.30.2