From 3e8a6a8fc28833e8e3a11507f275b6bfab580c22 Mon Sep 17 00:00:00 2001 From: Anton Blanchard Date: Tue, 19 May 2020 15:18:42 +1000 Subject: [PATCH] Add yosys/verilator support Add a microwatt-verilator target that simulates the ghdl -> yosys -> verilog -> verilator path. A good test of ghdl/yosys synthesis. Because the everything is run through synthesis, the instruction image is baked into the build via the RAM_INIT_FILE generic. Signed-off-by: Anton Blanchard --- Makefile.synth | 9 ++ verilator/microwatt-verilator.cpp | 83 ++++++++++ verilator/uart-verilator.c | 254 ++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 verilator/microwatt-verilator.cpp create mode 100644 verilator/uart-verilator.c diff --git a/Makefile.synth b/Makefile.synth index b0fb274..19a96b8 100644 --- a/Makefile.synth +++ b/Makefile.synth @@ -60,6 +60,15 @@ all: microwatt.bit microwatt.json: $(VHDL_FILES) $(YOSYS) -m $(GHDLSYNTH) -p "ghdl --std=08 $(GHDL_IMAGE_GENERICS) $(GHDL_TARGET_GENERICS) $(VHDL_FILES) -e toplevel; synth_ecp5 -json $@" +microwatt.v: $(VHDL_FILES) + $(YOSYS) -m $(GHDLSYNTH) -p "ghdl --std=08 $(GHDL_IMAGE_GENERICS) $(GHDL_TARGET_GENERICS) $(VHDL_FILES) -e toplevel; write_verilog $@" + +microwatt-verilator: microwatt.v verilator/microwatt-verilator.cpp verilator/uart-verilator.c + #verilator -O3 -Wall --assert --cc microwatt.v --exe verilator/microwatt-verilator.cpp verilator/uart-verilator.c -o $@ -Wno-CASEOVERLAP -Wno-UNOPTFLAT #--trace + verilator -O3 --assert --cc microwatt.v --exe verilator/microwatt-verilator.cpp verilator/uart-verilator.c -o $@ -Wno-CASEOVERLAP -Wno-UNOPTFLAT #--trace + make -C obj_dir -f Vmicrowatt.mk + @cp -f obj_dir/microwatt-verilator microwatt-verilator + microwatt_out.config: microwatt.json $(LPF) $(NEXTPNR) --json $< --lpf $(LPF) --textcfg $@ $(NEXTPNR_FLAGS) --package $(PACKAGE) diff --git a/verilator/microwatt-verilator.cpp b/verilator/microwatt-verilator.cpp new file mode 100644 index 0000000..1e82820 --- /dev/null +++ b/verilator/microwatt-verilator.cpp @@ -0,0 +1,83 @@ +#include +#include "Vmicrowatt.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(Vmicrowatt *top) +{ + top->ext_clk = 1; + top->eval(); +#if VM_TRACE + if (tfp) + tfp->dump((double) main_time); +#endif + main_time++; + + top->ext_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 + Vmicrowatt* top = new Vmicrowatt; + +#if VM_TRACE + // init trace dump + Verilated::traceEverOn(true); + tfp = new VerilatedVcdC; + top->trace(tfp, 99); + tfp->open("microwatt-verilator.vcd"); +#endif + + // Reset + top->ext_rst = 0; + for (unsigned long i = 0; i < 5; i++) + tick(top); + top->ext_rst = 1; + + while(!Verilated::gotFinish()) { + tick(top); + + uart_tx(top->uart0_txd); + top->uart0_rxd = 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..8529959 --- /dev/null +++ b/verilator/uart-verilator.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include + +/* Should we exit simulation on ctrl-c or pass it through? */ +#define EXIT_ON_CTRL_C + +#define CLOCK 50000000L +#define BAUD 115200 +/* Round to nearest */ +#define BITWIDTH ((CLOCK+(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; +}