mirror of https://github.com/llvm/circt.git
[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:
parent
938ad62122
commit
5b9abe4088
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue