[ESI] [Cosim] Pull everything together into one simple test (#468)

Adds a simple loopback test and changes/fixes to enable it.

- Uses all the lowering passes to get everything in the RTL dialect.
- Uses circt-translate --emit-verilog to emit verilog.
- Uses circt-translate -emit-esi-capnp to get RPC schema.
- Runs it via esi-cosim-runner.py.
This commit is contained in:
John Demme 2021-01-17 19:42:52 -08:00 committed by GitHub
parent 938ad62122
commit 5b9abe4088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 104 additions and 84 deletions

View File

@ -16,7 +16,7 @@
@0xe642127a31681ef6;
# The primary interface exposed by an ESI cosim simulation.
interface CosimDpiServer {
interface CosimDpiServer @0x85e029b5352bcdb5 {
# List all the registered endpoints.
list @0 () -> (ifaces :List(EsiDpiInterfaceDesc));
# Open one of them. Specify both the send and recv data types if want type
@ -25,7 +25,7 @@ interface CosimDpiServer {
}
# Description of a registered endpoint.
struct EsiDpiInterfaceDesc {
struct EsiDpiInterfaceDesc @0xd2584d2506f01c8c {
# Capn'Proto ID of the struct type being sent _to_ the simulator.
sendTypeID @0 :UInt64;
# Capn'Proto ID of the struct type being sent _from_ the simulator.
@ -35,7 +35,7 @@ struct EsiDpiInterfaceDesc {
}
# Interactions with an open endpoint. Optionally typed.
interface EsiDpiEndpoint(SendMsgType, RecvMsgType) {
interface EsiDpiEndpoint @0xfb0a36bf859be47b (SendMsgType, RecvMsgType) {
# Send a message to the endpoint.
send @0 (msg :SendMsgType);
# Recieve a message from the endpoint. Non-blocking.

View File

@ -0,0 +1,12 @@
// REQUIRES: esi-cosim
// RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-rtl | circt-translate --emit-verilog > %t1.sv
// RUN: circt-translate %s -emit-esi-capnp -verify-diagnostics > %t2.capnp
// RUN: esi-cosim-runner.py --schema %t2.capnp %s %t1.sv
// PY: import loopback as test
// PY: rpc = test.LoopbackTester(rpcschemapath, simhostport)
// PY: rpc.test_i32(25)
rtl.module @top(%clk:i1, %rstn:i1) -> () {
%cosimRecv = esi.cosim %clk, %rstn, %bufferedResp, 1 {name="TestEP"} : !esi.channel<i32> -> !esi.channel<i32>
%bufferedResp = esi.buffer %clk, %rstn, %cosimRecv {stages=1} : i32
}

View File

@ -34,14 +34,7 @@ class LoopbackTester:
assert openResp.iface is not None
return openResp.iface
def write(self, ep):
r = random.randrange(0, 2**24)
data = r.to_bytes(3, 'big')
print(f'Sending: {binascii.hexlify(data)}')
ep.send(self.dpi.UntypedData.new_message(data=data)).wait()
return data
def read(self, ep):
def readMsg(self, ep, expectedType):
while True:
recvResp = ep.recv(False).wait()
if recvResp.hasData:
@ -49,31 +42,41 @@ class LoopbackTester:
else:
time.sleep(0.1)
assert recvResp.resp is not None
dataMsg = recvResp.resp.as_struct(self.dpi.UntypedData)
return recvResp.resp.as_struct(expectedType)
def test_i32(self, num_msgs):
ep = self.openEP()
for _ in range(num_msgs):
data = random.randint(0, 2**32)
print(f"Sending {data}")
ep.send(self.dpi.TYi32.new_message(i=data))
result = self.readMsg(ep, self.dpi.TYi32)
print(f"Got {result}")
assert (result.i == data)
def write_3bytes(self, ep):
r = random.randrange(0, 2**24)
data = r.to_bytes(3, 'big')
print(f'Sending: {binascii.hexlify(data)}')
ep.send(self.dpi.UntypedData.new_message(data=data)).wait()
return data
def read_3bytes(self, ep):
dataMsg = self.readMsg(ep, self.dpi.UntypedData)
data = dataMsg.data
print(binascii.hexlify(data))
return data
def write_read(self):
ep = self.openEP()
print("Testing writes")
dataSent = self.write(ep)
print()
print("Testing reads")
dataRecv = self.read(ep)
ep.close().wait()
assert dataSent == dataRecv
def write_read_many(self, num_msgs=50):
def test_3bytes(self, num_msgs=50):
ep = self.openEP()
print("Testing writes")
dataSent = list()
for _ in range(num_msgs):
dataSent.append(self.write(ep))
dataSent.append(self.write_3bytes(ep))
print()
print("Testing reads")
dataRecv = list()
for _ in range(num_msgs):
dataRecv.append(self.read(ep))
dataRecv.append(self.read_3bytes(ep))
ep.close().wait()
assert dataSent == dataRecv

View File

@ -1,10 +1,10 @@
// REQUIRES: esi-cosim
// RUN: esi-cosim-runner.py %s
// RUN: esi-cosim-runner.py %s %s
// PY: import loopback as test
// PY: rpc = test.LoopbackTester(rpcschemapath, simhostport)
// PY: rpc.test_list()
// PY: rpc.test_open_close()
// PY: rpc.write_read_many(5)
// PY: rpc.test_3bytes(5)
import Cosim_DpiPkg::*;

View File

@ -416,10 +416,6 @@ Value TypeSchemaImpl::buildDecoder(OpBuilder &b, Value clk, Value valid,
result = fieldValue;
}
asserts.create<sv::FWriteOp>(
loc, "typeAndOffset: %h, dataSize: %h, ptrSize: %h, decodedData: %h\n",
ValueRange{typeAndOffset, dataSectionSize, ptrSectionSize, result});
// All that just to decode an int! (But it'll pay off as we progress.)
return result;
}

View File

@ -28,7 +28,7 @@ class CosimTestRunner:
"""The main class responsible for running a cosim test. We use a separate
class to allow for per-test mutable state variables."""
def __init__(self, testFile, addlArgs):
def __init__(self, testFile, schema, addlArgs):
"""Parse a test file. Look for comments we recognize anywhere in the
file. Assemble a list of sources."""
@ -40,12 +40,17 @@ class CosimTestRunner:
self.top = "top"
if "@ESI_COSIM_PATH@" == "" or not os.path.exists("@ESI_COSIM_PATH@"):
raise Exception("The ESI cosimulation DPI library must be enabled " +
"to run cosim tests.")
raise Exception("The ESI cosimulation DPI library must be enabled " +
"to run cosim tests.")
self.simRunScript = os.path.join(
"@CIRCT_TOOLS_DIR@", "circt-rtl-sim.py")
if schema == "":
schema = os.path.join(
"@CIRCT_MAIN_INCLUDE_DIR@", "circt", "Dialect", "ESI",
"cosim", "CosimDpi.capnp")
self.schema = schema
fileReader = open(self.file, "r")
sources = []
@ -63,17 +68,16 @@ class CosimTestRunner:
self.runs.append(m.group(1).strip())
fileReader.close()
if len(sources) == 0:
self.sources = [self.file]
else:
self.sources = [(src if os.path.isabs(src) else os.path.join(
self.srcdir, src)) for src in self.sources]
self.sources = [(src if os.path.isabs(src) else os.path.join(
self.srcdir, src)) for src in self.sources]
# Include the cosim DPI SystemVerilog files.
cosimInclude = os.path.join(
"@CIRCT_MAIN_INCLUDE_DIR@", "circt", "Dialect", "ESI", "cosim")
esiInclude = os.path.join(
"@CIRCT_MAIN_INCLUDE_DIR@", "circt", "Dialect", "ESI")
cosimInclude = os.path.join(esiInclude, "cosim")
self.sources.insert(0, os.path.join(cosimInclude, "Cosim_DpiPkg.sv"))
self.sources.insert(1, os.path.join(cosimInclude, "Cosim_Endpoint.sv"))
self.sources.insert(2, os.path.join(esiInclude, "ESIPrimitives.sv"))
self.sources.append("@ESI_COSIM_PATH@")
def compile(self):
@ -82,7 +86,9 @@ class CosimTestRunner:
# Run the simulation compilation step. Requires a simulator to be
# installed and working.
# os.makedirs(self.execdir, exist_ok=True)
cmd = [self.simRunScript, "--no-run"] + self.args + self.sources
cmd = [self.simRunScript, "--no-run",
"--objdir", os.path.basename(self.file)] \
+ self.sources + self.args
print("Running: " + " ".join(cmd))
vrun = subprocess.run(
cmd,
@ -117,9 +123,7 @@ class CosimTestRunner:
"srcfile": self.file,
# 'rpcSchemaPath' points to the CapnProto schema for RPC and is
# the one that nearly all scripts are going to need.
"rpcschemapath": os.path.join(
"@CIRCT_MAIN_INCLUDE_DIR@", "circt", "Dialect", "ESI",
"cosim", "CosimDpi.capnp")
"rpcschemapath": self.schema
}
script.writelines(f"{name} = \"{value}\"\n" for (
name, value) in vars.items())
@ -148,22 +152,24 @@ class CosimTestRunner:
simEnv = os.environ.copy()
simEnv["COSIM_PORT"] = str(port)
simProc = subprocess.Popen(
[self.simRunScript] + self.args + self.sources,
stdout=simStdout, stderr=simStderr, env=simEnv, preexec_fn=os.setsid)
[self.simRunScript, "--objdir", os.path.basename(self.file)]
+ self.sources + self.args,
stdout=simStdout, stderr=simStderr, env=simEnv,
preexec_fn=os.setsid)
simStderr.close()
simStdout.close()
# Wait for the simulation to start accepting RPC connections.
checkCount = 0
while not isPortOpen(port):
checkCount += 1
if checkCount > 200:
print("Cosim RPC port never opened")
return 1
if simProc.poll() != None:
print("Simulation exited early")
return 1
time.sleep(0.05)
checkCount += 1
if checkCount > 200:
print("Cosim RPC port never opened")
return 1
if simProc.poll() != None:
print("Simulation exited early")
return 1
time.sleep(0.05)
# Run the test script.
testStdout = open("test_stdout.log", "w")
@ -189,49 +195,52 @@ class CosimTestRunner:
logs += f"---- Test process exit code: {testProc.returncode}\n"
passed = testProc.returncode == 0 and not err
if not passed:
print(logs)
print(logs)
return 0 if passed else 1
def readLogs(self):
"""Read the log files from the simulation and the test script. Only add
the stderr logs if they contain something. Also return a flag
indicating that one of the stderr logs has content."""
"""Read the log files from the simulation and the test script. Only add
the stderr logs if they contain something. Also return a flag
indicating that one of the stderr logs has content."""
foundErr = False
ret = "----- Simulation stdout -----\n"
with open("sim_stdout.log") as f:
ret += f.read()
foundErr = False
ret = "----- Simulation stdout -----\n"
with open("sim_stdout.log") as f:
ret += f.read()
with open("sim_stderr.log") as f:
stderr = f.read()
if stderr != "":
ret += "\n----- Simulation stderr -----\n"
ret += stderr
foundErr = True
with open("sim_stderr.log") as f:
stderr = f.read()
if stderr != "":
ret += "\n----- Simulation stderr -----\n"
ret += stderr
foundErr = True
ret += "\n----- Test stdout -----\n"
with open("test_stdout.log") as f:
ret += f.read()
ret += "\n----- Test stdout -----\n"
with open("test_stdout.log") as f:
ret += f.read()
with open("test_stderr.log") as f:
stderr = f.read()
if stderr != "":
ret += "\n----- Test stderr -----\n"
ret += stderr
foundErr = True
with open("test_stderr.log") as f:
stderr = f.read()
if stderr != "":
ret += "\n----- Test stderr -----\n"
ret += stderr
foundErr = True
return (foundErr, ret)
return (foundErr, ret)
def isPortOpen(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex(('127.0.0.1', port))
sock.close()
return True if result == 0 else False
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex(('127.0.0.1', port))
sock.close()
return True if result == 0 else False
def __main__(args):
argparser = argparse.ArgumentParser(
description="RTL cosimulation runner for ESI")
argparser.add_argument("--schema", default="",
help="The schema file to use.")
argparser.add_argument("source",
help="The source run spec file")
argparser.add_argument("addlArgs", nargs=argparse.REMAINDER,
@ -243,7 +252,7 @@ def __main__(args):
return
args = argparser.parse_args(args[1:])
runner = CosimTestRunner(args.source, args.addlArgs)
runner = CosimTestRunner(args.source, args.schema, args.addlArgs)
rc = runner.compile()
if rc != 0:
return rc