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.
This commit is contained in:
Florent Kermarrec 2015-03-03 17:35:52 +01:00
parent f154c2e7ec
commit 991572f4fe
4 changed files with 313 additions and 9 deletions

103
mibuild/sim/server.py Normal file
View File

@ -0,0 +1,103 @@
# This file is Copyright (c) 2015 Florent Kermarrec <florent@enjoy-digital.fr>
# 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)

202
mibuild/sim/server_tb.cpp Normal file
View File

@ -0,0 +1,202 @@
// This file is Copyright (c) 2015 Florent Kermarrec <florent@enjoy-digital.fr>
// License: BSD
#include <time.h>
#include "Vdut.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <netdb.h>
#include <pthread.h>
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; i<len-1; i++) {
s->rx_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);
}

View File

@ -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)