From: Florent Kermarrec Date: Tue, 3 Mar 2015 16:35:52 +0000 (+0100) Subject: mibuild/sim: create server.py and server_tb (Proof of concept OK with flterm) X-Git-Tag: 24jan2021_ls180~2099^2~217 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=991572f4fe836a17a771b5b0a415d947b9e64c12;p=litex.git mibuild/sim: create server.py and server_tb (Proof of concept OK with flterm) Using a server allow us to create a virtual UART (and ethernet TAP in the future). 1) start the server 2) start flterm on the virtual serial port created by the server 3) run the simulation This will enable us to do serialboot and netboot in simulation. This will also enable prototyping ethernet for ARTIQ in simulation. --- diff --git a/mibuild/sim/console_tb.cpp b/mibuild/sim/console_tb.cpp new file mode 100644 index 00000000..02e875da --- /dev/null +++ b/mibuild/sim/console_tb.cpp @@ -0,0 +1,162 @@ +// This file is Copyright (c) 2015 Florent Kermarrec +// License: BSD + +#include + +#include "Vdut.h" +#include "verilated.h" +#include "verilated_vcd_c.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int trace = 0; + +struct termios orig_termios; + +void reset_terminal_mode(void) +{ + tcsetattr(0, TCSANOW, &orig_termios); +} + +void set_conio_terminal_mode(void) +{ + struct termios new_termios; + + /* take two copies - one for now, one for later */ + tcgetattr(0, &orig_termios); + memcpy(&new_termios, &orig_termios, sizeof(new_termios)); + + /* register cleanup handler, and set the new terminal mode */ + atexit(reset_terminal_mode); + cfmakeraw(&new_termios); + tcsetattr(0, TCSANOW, &new_termios); +} + +int kbhit(void) +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + return select(1, &fds, NULL, NULL, &tv); +} + +int getch(void) +{ + int r; + unsigned char c; + if ((r = read(0, &c, sizeof(c))) < 0) { + return r; + } else { + return c; + } +} + +vluint64_t main_time = 0; +double sc_time_stamp() +{ + return main_time; +} + +Vdut* dut; +VerilatedVcdC* tfp; +unsigned int tick; + +/* ios */ + +int console_service() +{ + /* fpga --> console */ + SERIAL_SOURCE_ACK = 1; + if(SERIAL_SOURCE_STB == 1) { + if (SERIAL_SOURCE_DATA == '\n') + putchar('\r'); + putchar(SERIAL_SOURCE_DATA); + fflush(stdout); + } + + /* console --> fpga */ + SERIAL_SINK_STB = 0; + if (tick%(1000) == 0) { + if(kbhit()) { + char c = getch(); + if (c == 27 && !kbhit()) { + printf("\r\n"); + return -1; + } else { + SERIAL_SINK_STB = 1; + SERIAL_SINK_DATA = c; + } + } + } + return 0; +} + +void sim_tick() +{ + SYS_CLK = tick%2; + dut->eval(); + if (trace) + tfp->dump(tick); + tick++; +} + +void sim_init() +{ + int i; + tick = 0; +#ifdef SYS_RST + SYS_RST = 1; + SYS_CLK = 0; + for (i=0; i<8; i++) + sim_tick(); + SYS_RST = 0; +#endif +} + +int main(int argc, char **argv, char **env) +{ + + clock_t start; + clock_t end; + float speed; + + set_conio_terminal_mode(); + + Verilated::commandArgs(argc, argv); + dut = new Vdut; + + Verilated::traceEverOn(true); + tfp = new VerilatedVcdC; + dut->trace(tfp, 99); + tfp->open("dut.vcd"); + + start = clock(); + sim_init(); + bool run = true; + while(run) { + sim_tick(); + if (SYS_CLK) { + if (console_service() != 0) + run = false; + } + } + end = clock(); + + speed = (tick/2)/((end-start)/CLOCKS_PER_SEC); + + printf("average speed: %3.3f MHz\n\r", speed/1000000); + + tfp->close(); + + exit(0); +} diff --git a/mibuild/sim/dut_tb.cpp b/mibuild/sim/dut_tb.cpp deleted file mode 100644 index 02e875da..00000000 --- a/mibuild/sim/dut_tb.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// This file is Copyright (c) 2015 Florent Kermarrec -// License: BSD - -#include - -#include "Vdut.h" -#include "verilated.h" -#include "verilated_vcd_c.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int trace = 0; - -struct termios orig_termios; - -void reset_terminal_mode(void) -{ - tcsetattr(0, TCSANOW, &orig_termios); -} - -void set_conio_terminal_mode(void) -{ - struct termios new_termios; - - /* take two copies - one for now, one for later */ - tcgetattr(0, &orig_termios); - memcpy(&new_termios, &orig_termios, sizeof(new_termios)); - - /* register cleanup handler, and set the new terminal mode */ - atexit(reset_terminal_mode); - cfmakeraw(&new_termios); - tcsetattr(0, TCSANOW, &new_termios); -} - -int kbhit(void) -{ - struct timeval tv = { 0L, 0L }; - fd_set fds; - FD_ZERO(&fds); - FD_SET(0, &fds); - return select(1, &fds, NULL, NULL, &tv); -} - -int getch(void) -{ - int r; - unsigned char c; - if ((r = read(0, &c, sizeof(c))) < 0) { - return r; - } else { - return c; - } -} - -vluint64_t main_time = 0; -double sc_time_stamp() -{ - return main_time; -} - -Vdut* dut; -VerilatedVcdC* tfp; -unsigned int tick; - -/* ios */ - -int console_service() -{ - /* fpga --> console */ - SERIAL_SOURCE_ACK = 1; - if(SERIAL_SOURCE_STB == 1) { - if (SERIAL_SOURCE_DATA == '\n') - putchar('\r'); - putchar(SERIAL_SOURCE_DATA); - fflush(stdout); - } - - /* console --> fpga */ - SERIAL_SINK_STB = 0; - if (tick%(1000) == 0) { - if(kbhit()) { - char c = getch(); - if (c == 27 && !kbhit()) { - printf("\r\n"); - return -1; - } else { - SERIAL_SINK_STB = 1; - SERIAL_SINK_DATA = c; - } - } - } - return 0; -} - -void sim_tick() -{ - SYS_CLK = tick%2; - dut->eval(); - if (trace) - tfp->dump(tick); - tick++; -} - -void sim_init() -{ - int i; - tick = 0; -#ifdef SYS_RST - SYS_RST = 1; - SYS_CLK = 0; - for (i=0; i<8; i++) - sim_tick(); - SYS_RST = 0; -#endif -} - -int main(int argc, char **argv, char **env) -{ - - clock_t start; - clock_t end; - float speed; - - set_conio_terminal_mode(); - - Verilated::commandArgs(argc, argv); - dut = new Vdut; - - Verilated::traceEverOn(true); - tfp = new VerilatedVcdC; - dut->trace(tfp, 99); - tfp->open("dut.vcd"); - - start = clock(); - sim_init(); - bool run = true; - while(run) { - sim_tick(); - if (SYS_CLK) { - if (console_service() != 0) - run = false; - } - } - end = clock(); - - speed = (tick/2)/((end-start)/CLOCKS_PER_SEC); - - printf("average speed: %3.3f MHz\n\r", speed/1000000); - - tfp->close(); - - exit(0); -} diff --git a/mibuild/sim/server.py b/mibuild/sim/server.py new file mode 100644 index 00000000..99627a90 --- /dev/null +++ b/mibuild/sim/server.py @@ -0,0 +1,103 @@ +# This file is Copyright (c) 2015 Florent Kermarrec +# License: BSD + +import socket +import os +import pty +import time +import threading + +messages= { + "EXIT": 0, + "ACK": 1, + "ERROR": 2, + "UART": 3 +} + +class PacketTooLarge(Exception): + pass + +class VerilatorServer: + def __init__(self, sockaddr="simsocket"): + self.sockaddr = sockaddr + self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) + self._cleanup_file() + self.socket.bind(self.sockaddr) + self.socket.listen(1) + + master, slave = pty.openpty() + self.serial = master + self.serial_name = os.ttyname(slave) + + self.ack = False + + self._print_banner() + + def _print_banner(self): + print("Mibuild simulation server") + print("sockaddr: {}".format(self.sockaddr)) + print("serial: {}".format(self.serial_name)) + + def _cleanup_file(self): + try: + os.remove(self.sockaddr) + except OSError: + pass + + def accept(self): + self.conn, addr = self.socket.accept() + + def send(self, packet): + self.conn.send(packet) + + def recv(self): + maxlen = 2048 + packet = self.conn.recv(maxlen) + if len(packet) < 1: + return None + if len(packet) >= maxlen: + raise PacketTooLarge + return packet + + def close(self): + if hasattr(self, "conn"): + self.conn.shutdown(socket.SHUT_RDWR) + self.conn.close() + if hasattr(self, "socket"): + self.socket.shutdown(socket.SHUT_RDWR) + self.socket.close() + self._cleanup_file() + +# XXX proof of concept +server = VerilatorServer() +server.accept() +print("Connection accepted") + +def read(): + while True: + packet = server.recv() + if packet is not None: + if packet[0] == messages["UART"]: + c = bytes(chr(packet[1]).encode('utf-8')) + os.write(server.serial, c) + + elif packet[0] == messages["ACK"]: + server.ack = True + +def write(): + while True: + for c in list(os.read(server.serial, 100)): + packet = [messages["UART"], c] + server.send(bytes(packet)) + while not server.ack: + pass + server.ack = False + +readthread = threading.Thread(target=read, daemon=True) +readthread.start() + +writethread = threading.Thread(target=write, daemon=True) +writethread.start() + +while True: + time.sleep(1) diff --git a/mibuild/sim/server_tb.cpp b/mibuild/sim/server_tb.cpp new file mode 100644 index 00000000..977206f9 --- /dev/null +++ b/mibuild/sim/server_tb.cpp @@ -0,0 +1,202 @@ +// This file is Copyright (c) 2015 Florent Kermarrec +// License: BSD + +#include + +#include "Vdut.h" +#include "verilated.h" +#include "verilated_vcd_c.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int trace = 0; + +vluint64_t main_time = 0; +double sc_time_stamp() +{ + return main_time; +} + +Vdut* dut; +VerilatedVcdC* tfp; + +/* ios */ + +#define MAX_LEN 2048 + +enum { + MESSAGE_EXIT = 0, + MESSAGE_ACK, + MESSAGE_ERROR, + MESSAGE_UART +}; + +struct sim { + int socket; + bool run; + + unsigned int tick; + clock_t start; + clock_t end; + float speed; + + char txbuffer[MAX_LEN]; + char rxbuffer[MAX_LEN]; + + char rx_serial_stb; + char rx_serial_data; + char rx_serial_presented; +}; + +int sim_connect(struct sim *s, const char *sockaddr) +{ + struct sockaddr_un addr; + + s->socket = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if(s->socket < 0) { + return -1; + } + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, sockaddr); + if(connect(s->socket, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + close(s->socket); + return -1; + } + + return 0; +} + +int sim_send(struct sim *s, char *buffer, int len) +{ + send(s->socket, s->txbuffer, len, 0); + return 0; +} + +void sim_receive_process(struct sim *s, char * buffer, int len) { + int i; + switch(buffer[0]) { + case MESSAGE_EXIT: + s->run = false; + break; + case MESSAGE_UART: + i = 0; + for(i=0; irx_serial_stb = 1; + s->rx_serial_data = buffer[i+1]; + while (s->rx_serial_presented == 0); + s->rx_serial_presented = 0; + } + s->rx_serial_stb = 0; + break; + default: + break; + } +} + +void *sim_receive(void *s_void) +{ + struct sim *s = (sim *) s_void; + int rxlen; + while(1) + { + rxlen = recv(s->socket, s->rxbuffer, MAX_LEN, 0); + if (rxlen > 0) + sim_receive_process(s, s->rxbuffer, rxlen); + s->txbuffer[0] = MESSAGE_ACK; + sim_send(s, s->txbuffer, 1); + } +} + +void sim_destroy(struct sim *s) +{ + close(s->socket); + free(s); +} + +int console_service(struct sim *s) +{ + /* fpga --> console */ + SERIAL_SOURCE_ACK = 1; + if(SERIAL_SOURCE_STB == 1) { + s->txbuffer[0] = MESSAGE_UART; + s->txbuffer[1] = SERIAL_SOURCE_DATA; + sim_send(s, s->txbuffer, 2); + } + + /* console --> fpga */ + SERIAL_SINK_STB = s->rx_serial_stb; + SERIAL_SINK_DATA = s->rx_serial_data; + if (s->rx_serial_stb) + s->rx_serial_presented = 1; + + return 0; +} + +void sim_tick(struct sim *s) +{ + SYS_CLK = s->tick%2; + dut->eval(); + if (trace) + tfp->dump(s->tick); + s->tick++; + s->end = clock(); +} + +void sim_init(struct sim *s) +{ + int i; + s->tick = 0; +#ifdef SYS_RST + SYS_RST = 1; + SYS_CLK = 0; + for (i=0; i<8; i++) + sim_tick(s); + SYS_RST = 0; +#endif + s->start = clock(); +} + +int main(int argc, char **argv, char **env) +{ + Verilated::commandArgs(argc, argv); + dut = new Vdut; + + Verilated::traceEverOn(true); + tfp = new VerilatedVcdC; + dut->trace(tfp, 99); + tfp->open("dut.vcd"); + + struct sim s; + sim_init(&s); + sim_connect(&s, "../../migen/mibuild/sim/simsocket"); // XXX use args + + pthread_t sim_receive_thread; + + pthread_create(&sim_receive_thread, NULL, sim_receive, &s); + + s.run = true; + while(s.run) { + sim_tick(&s); + if (SYS_CLK) { + if (console_service(&s) != 0) + s.run = false; + } + } + + tfp->close(); + pthread_cancel(sim_receive_thread); + sim_destroy(&s); + + exit(0); +} diff --git a/mibuild/sim/verilator.py b/mibuild/sim/verilator.py index 20afa8de..f72e8db3 100644 --- a/mibuild/sim/verilator.py +++ b/mibuild/sim/verilator.py @@ -49,24 +49,23 @@ def _build_tb(platform, template): f.close() tools.write_to_file("dut_tb.cpp", content) -def _build_sim(platform, build_name, include_paths, template_file, trace, verbose): +def _build_sim(platform, build_name, include_paths, sim_path, dut, verbose): include = "" for path in include_paths: include += "-I"+path+" " build_script_contents = """# Autogenerated by mibuild rm -rf obj_dir/ -verilator {disable_warnings} -O3 --cc dut.v --exe dut_tb.cpp {trace} {include} +verilator {disable_warnings} -O3 --cc dut.v --exe dut_tb.cpp -LDFLAGS "-lpthread" -trace {include} make -j -C obj_dir/ -f Vdut.mk Vdut """.format( disable_warnings="-Wno-fatal", - trace="-trace" if trace else "", include=include) build_script_file = "build_" + build_name + ".sh" tools.write_to_file(build_script_file, build_script_contents, force_unix=True) - _build_tb(platform, os.path.join("../", template_file)) # XXX + _build_tb(platform, os.path.join("../", sim_path, dut + ".cpp")) # XXX if verbose: r = subprocess.call(["bash", build_script_file]) else: @@ -84,10 +83,10 @@ def _run_sim(build_name): raise OSError("Subprocess failed") class VerilatorPlatform(GenericPlatform): - # XXX fix template_file - def build(self, soc, build_dir="build", build_name="top", run=True, trace=True, - template_file="../migen/mibuild/sim/dut_tb.cpp", - verbose=False): + # XXX fir sim_path + def build(self, soc, build_dir="build", build_name="top", + sim_path="../migen/mibuild/sim/", dut="console_tb", + run=True, verbose=False): tools.mkdir_noerror(build_dir) os.chdir(build_dir) @@ -106,7 +105,7 @@ class VerilatorPlatform(GenericPlatform): if path not in include_paths: include_paths.append(path) include_paths += self.verilog_include_paths - _build_sim(self, build_name, include_paths, template_file, trace, verbose) + _build_sim(self, build_name, include_paths, sim_path, dut, verbose) if run: _run_sim(build_name)