esiaccel 0.1.5.dev205__cp39-cp39-win_amd64.whl → 0.1.5.dev533__cp39-cp39-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esiaccel/CosimBackend.dll +0 -0
- esiaccel/CosimBackend.lib +0 -0
- esiaccel/ESICppRuntime.dll +0 -0
- esiaccel/ESICppRuntime.lib +0 -0
- esiaccel/EsiCosimDpiServer.dll +0 -0
- esiaccel/EsiCosimDpiServer.lib +0 -0
- esiaccel/MtiPli.dll +0 -0
- esiaccel/MtiPli.lib +0 -0
- esiaccel/__init__.py +12 -3
- esiaccel/abseil_dll.dll +0 -0
- esiaccel/accelerator.py +37 -5
- esiaccel/cares.dll +0 -0
- esiaccel/codegen.py +3 -3
- esiaccel/cosim/Cosim_Endpoint.sv +0 -18
- esiaccel/cosim/driver.cpp +6 -6
- esiaccel/cosim/driver.sv +14 -0
- esiaccel/cosim/questa.py +67 -4
- esiaccel/cosim/simulator.py +185 -38
- esiaccel/cosim/verilator.py +36 -5
- esiaccel/esiCppAccel.cp312-win_amd64.pyd +0 -0
- esiaccel/esiquery.exe +0 -0
- esiaccel/include/esi/Accelerator.h +3 -16
- esiaccel/include/esi/CLI.h +5 -5
- esiaccel/include/esi/Common.h +11 -1
- esiaccel/include/esi/Context.h +17 -9
- esiaccel/include/esi/Design.h +9 -4
- esiaccel/include/esi/Manifest.h +0 -2
- esiaccel/include/esi/Ports.h +72 -15
- esiaccel/include/esi/Services.h +50 -18
- esiaccel/include/esi/Types.h +108 -31
- esiaccel/include/esi/Values.h +313 -0
- esiaccel/libcrypto-3-x64.dll +0 -0
- esiaccel/libprotobuf.dll +0 -0
- esiaccel/libssl-3-x64.dll +0 -0
- esiaccel/re2.dll +0 -0
- esiaccel/types.py +6 -4
- esiaccel/zlib1.dll +0 -0
- {esiaccel-0.1.5.dev205.dist-info → esiaccel-0.1.5.dev533.dist-info}/METADATA +1 -1
- esiaccel-0.1.5.dev533.dist-info/RECORD +54 -0
- esiaccel/esiCppAccel.cp39-win_amd64.pyd +0 -0
- esiaccel-0.1.5.dev205.dist-info/RECORD +0 -46
- {esiaccel-0.1.5.dev205.dist-info → esiaccel-0.1.5.dev533.dist-info}/WHEEL +0 -0
- {esiaccel-0.1.5.dev205.dist-info → esiaccel-0.1.5.dev533.dist-info}/entry_points.txt +0 -0
- {esiaccel-0.1.5.dev205.dist-info → esiaccel-0.1.5.dev533.dist-info}/licenses/LICENSE +0 -0
- {esiaccel-0.1.5.dev205.dist-info → esiaccel-0.1.5.dev533.dist-info}/top_level.txt +0 -0
esiaccel/CosimBackend.dll
CHANGED
|
Binary file
|
esiaccel/CosimBackend.lib
CHANGED
|
Binary file
|
esiaccel/ESICppRuntime.dll
CHANGED
|
Binary file
|
esiaccel/ESICppRuntime.lib
CHANGED
|
Binary file
|
esiaccel/EsiCosimDpiServer.dll
CHANGED
|
Binary file
|
esiaccel/EsiCosimDpiServer.lib
CHANGED
|
Binary file
|
esiaccel/MtiPli.dll
CHANGED
|
Binary file
|
esiaccel/MtiPli.lib
CHANGED
|
Binary file
|
esiaccel/__init__.py
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
|
-
from .accelerator import AcceleratorConnection
|
|
6
|
+
from .accelerator import AcceleratorConnection, Context, LogLevel
|
|
7
7
|
|
|
8
8
|
from .esiCppAccel import (AppID, Type, BundleType, ChannelType, ArrayType,
|
|
9
9
|
StructType, BitsType, UIntType, SIntType)
|
|
10
10
|
|
|
11
11
|
__all__ = [
|
|
12
|
-
"AcceleratorConnection", "AppID", "
|
|
13
|
-
"
|
|
12
|
+
"AcceleratorConnection", "AppID", "Context", "LogLevel", "Type",
|
|
13
|
+
"BundleType", "ChannelType", "ArrayType", "StructType", "BitsType",
|
|
14
|
+
"UIntType", "SIntType"
|
|
14
15
|
]
|
|
15
16
|
|
|
16
17
|
if sys.platform == "win32":
|
|
@@ -20,3 +21,11 @@ if sys.platform == "win32":
|
|
|
20
21
|
"""
|
|
21
22
|
from .utils import get_dll_dir
|
|
22
23
|
os.add_dll_directory(str(get_dll_dir()))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def connect(platform: str, connection_str: str) -> "AcceleratorConnection":
|
|
27
|
+
"""Connect to an accelerator using the specified platform and connection
|
|
28
|
+
string."""
|
|
29
|
+
ctxt = Context.default()
|
|
30
|
+
return AcceleratorConnection(ctxt,
|
|
31
|
+
ctxt.cpp_ctxt.connect(platform, connection_str))
|
esiaccel/abseil_dll.dll
ADDED
|
Binary file
|
esiaccel/accelerator.py
CHANGED
|
@@ -15,19 +15,51 @@ from typing import Dict, List, Optional
|
|
|
15
15
|
from .types import BundlePort
|
|
16
16
|
from . import esiCppAccel as cpp
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
LogLevel = cpp.LogLevel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Context:
|
|
22
|
+
"""A context for ESI accelerator connections. The underlying C++ context owns
|
|
23
|
+
everything assocated with it including types, accelerator connections, and
|
|
24
|
+
the accelerator facade/interface (aka Accelerator) itself. It must not be
|
|
25
|
+
garbage collected while any accelerators or connections that it owns are still
|
|
26
|
+
in use as they will be disconnected and destroyed when the context is
|
|
27
|
+
destroyed."""
|
|
28
|
+
|
|
29
|
+
_default: Optional["Context"] = None
|
|
30
|
+
|
|
31
|
+
def __init__(self, log_level: cpp.LogLevel = cpp.LogLevel.Warning):
|
|
32
|
+
self.cpp_ctxt = cpp.Context()
|
|
33
|
+
self.set_stdio_logger(log_level)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def default() -> "Context":
|
|
37
|
+
if Context._default is None:
|
|
38
|
+
Context._default = Context()
|
|
39
|
+
return Context._default
|
|
40
|
+
|
|
41
|
+
def set_stdio_logger(self, level: cpp.LogLevel):
|
|
42
|
+
self.cpp_ctxt.set_stdio_logger(level)
|
|
43
|
+
|
|
44
|
+
def connect(self, platform: str,
|
|
45
|
+
connection_str: str) -> "AcceleratorConnection":
|
|
46
|
+
return AcceleratorConnection(
|
|
47
|
+
self, self.cpp_ctxt.connect(platform, connection_str))
|
|
20
48
|
|
|
21
49
|
|
|
22
50
|
class AcceleratorConnection:
|
|
23
51
|
"""A connection to an ESI accelerator."""
|
|
24
52
|
|
|
25
|
-
def __init__(self,
|
|
26
|
-
|
|
53
|
+
def __init__(self, ctxt: Context, cpp_accel: cpp.AcceleratorConnection):
|
|
54
|
+
if not isinstance(ctxt, Context):
|
|
55
|
+
raise TypeError("ctxt must be a Context")
|
|
56
|
+
self.ctxt = ctxt
|
|
57
|
+
self.cpp_accel = cpp_accel
|
|
27
58
|
|
|
28
59
|
def manifest(self) -> cpp.Manifest:
|
|
29
60
|
"""Get and parse the accelerator manifest."""
|
|
30
|
-
return cpp.Manifest(
|
|
61
|
+
return cpp.Manifest(self.ctxt.cpp_ctxt,
|
|
62
|
+
self.cpp_accel.sysinfo().json_manifest())
|
|
31
63
|
|
|
32
64
|
def sysinfo(self) -> cpp.SysInfo:
|
|
33
65
|
return self.cpp_accel.sysinfo()
|
esiaccel/cares.dll
ADDED
|
Binary file
|
esiaccel/codegen.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# with the runtime, though it is intended to be extensible for other languages.
|
|
7
7
|
|
|
8
8
|
from typing import List, TextIO, Type, Optional
|
|
9
|
-
from .accelerator import AcceleratorConnection
|
|
9
|
+
from .accelerator import AcceleratorConnection, Context
|
|
10
10
|
from .esiCppAccel import ModuleInfo
|
|
11
11
|
from . import types
|
|
12
12
|
|
|
@@ -171,12 +171,12 @@ def run(generator: Type[Generator] = CppGenerator,
|
|
|
171
171
|
|
|
172
172
|
conn: AcceleratorConnection
|
|
173
173
|
if args.file is not None:
|
|
174
|
-
conn =
|
|
174
|
+
conn = Context.default().connect("trace", f"-:{args.file}")
|
|
175
175
|
elif args.platform is not None:
|
|
176
176
|
if args.connection is None:
|
|
177
177
|
print("Must specify --connection with --platform")
|
|
178
178
|
return 1
|
|
179
|
-
conn =
|
|
179
|
+
conn = Context.default().connect(args.platform, args.connection)
|
|
180
180
|
else:
|
|
181
181
|
print("Must specify either --file or --platform")
|
|
182
182
|
return 1
|
esiaccel/cosim/Cosim_Endpoint.sv
CHANGED
|
@@ -82,15 +82,6 @@ module Cosim_Endpoint_ToHost
|
|
|
82
82
|
TO_HOST_SIZE_BYTES_FLOOR_IN_BITS];
|
|
83
83
|
endgenerate
|
|
84
84
|
|
|
85
|
-
initial begin
|
|
86
|
-
$display("TO_HOST_SIZE_BITS: %d", TO_HOST_SIZE_BITS);
|
|
87
|
-
$display("TO_HOST_SIZE_BYTES: %d", TO_HOST_SIZE_BYTES);
|
|
88
|
-
$display("TO_HOST_SIZE_BITS_DIFF: %d", TO_HOST_SIZE_BITS_DIFF);
|
|
89
|
-
$display("TO_HOST_SIZE_BYTES_FLOOR: %d", TO_HOST_SIZE_BYTES_FLOOR);
|
|
90
|
-
$display("TO_HOST_SIZE_BYTES_FLOOR_IN_BITS: %d",
|
|
91
|
-
TO_HOST_SIZE_BYTES_FLOOR_IN_BITS);
|
|
92
|
-
end
|
|
93
|
-
|
|
94
85
|
endmodule
|
|
95
86
|
|
|
96
87
|
module Cosim_Endpoint_FromHost
|
|
@@ -224,13 +215,4 @@ module Cosim_Endpoint_FromHost
|
|
|
224
215
|
assign DataOutValid = DataOut_x_valid;
|
|
225
216
|
assign DataOut = DataOut_x;
|
|
226
217
|
|
|
227
|
-
initial begin
|
|
228
|
-
$display("FROM_HOST_SIZE_BITS: %d", FROM_HOST_SIZE_BITS);
|
|
229
|
-
$display("FROM_HOST_SIZE_BYTES: %d", FROM_HOST_SIZE_BYTES);
|
|
230
|
-
$display("FROM_HOST_SIZE_BITS_DIFF: %d", FROM_HOST_SIZE_BITS_DIFF);
|
|
231
|
-
$display("FROM_HOST_SIZE_BYTES_FLOOR: %d", FROM_HOST_SIZE_BYTES_FLOOR);
|
|
232
|
-
$display("FROM_HOST_SIZE_BYTES_FLOOR_IN_BITS: %d",
|
|
233
|
-
FROM_HOST_SIZE_BYTES_FLOOR_IN_BITS);
|
|
234
|
-
end
|
|
235
|
-
|
|
236
218
|
endmodule
|
esiaccel/cosim/driver.cpp
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
#include CONCAT3(V,TOP_MODULE,.h)
|
|
31
31
|
// clang-format on
|
|
32
32
|
|
|
33
|
-
#include "
|
|
33
|
+
#include "verilated_fst_c.h"
|
|
34
34
|
|
|
35
35
|
#include "signal.h"
|
|
36
36
|
#include <iostream>
|
|
@@ -64,12 +64,12 @@ int main(int argc, char **argv) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
#ifdef TRACE
|
|
67
|
-
|
|
67
|
+
VerilatedFstC *tfp = nullptr;
|
|
68
68
|
#endif
|
|
69
69
|
|
|
70
70
|
if (waveformFile) {
|
|
71
71
|
#ifdef TRACE
|
|
72
|
-
tfp = new
|
|
72
|
+
tfp = new VerilatedFstC();
|
|
73
73
|
Verilated::traceEverOn(true);
|
|
74
74
|
dut.trace(tfp, 99); // Trace 99 levels of hierarchy
|
|
75
75
|
tfp->open(waveformFile);
|
|
@@ -95,11 +95,11 @@ int main(int argc, char **argv) {
|
|
|
95
95
|
// Run for a few cycles with reset held.
|
|
96
96
|
for (timeStamp = 0; timeStamp < 8 && !Verilated::gotFinish(); timeStamp++) {
|
|
97
97
|
dut.eval();
|
|
98
|
-
dut.clk = !dut.clk;
|
|
99
98
|
#ifdef TRACE
|
|
100
99
|
if (tfp)
|
|
101
100
|
tfp->dump(timeStamp);
|
|
102
101
|
#endif
|
|
102
|
+
dut.clk = !dut.clk;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Take simulation out of reset.
|
|
@@ -108,12 +108,12 @@ int main(int argc, char **argv) {
|
|
|
108
108
|
// Run for the specified number of cycles out of reset.
|
|
109
109
|
for (; !Verilated::gotFinish() && !stopSimulation; timeStamp++) {
|
|
110
110
|
dut.eval();
|
|
111
|
-
dut.clk = !dut.clk;
|
|
112
|
-
|
|
113
111
|
#ifdef TRACE
|
|
114
112
|
if (tfp)
|
|
115
113
|
tfp->dump(timeStamp);
|
|
116
114
|
#endif
|
|
115
|
+
dut.clk = !dut.clk;
|
|
116
|
+
|
|
117
117
|
if (debugPeriod)
|
|
118
118
|
std::this_thread::sleep_for(std::chrono::milliseconds(debugPeriod));
|
|
119
119
|
}
|
esiaccel/cosim/driver.sv
CHANGED
|
@@ -18,8 +18,22 @@
|
|
|
18
18
|
`define TOP_MODULE ESI_Cosim_Top
|
|
19
19
|
`endif
|
|
20
20
|
|
|
21
|
+
// Allow reading environment variables to control waveform dumping.
|
|
22
|
+
import "DPI-C" function string getenv(input string env_name);
|
|
23
|
+
|
|
21
24
|
module driver();
|
|
22
25
|
|
|
26
|
+
// If the SAVE_WAVE environment variable is set, dump a VCD waveform to that
|
|
27
|
+
// filename.
|
|
28
|
+
initial begin
|
|
29
|
+
string save_wave = getenv("SAVE_WAVE");
|
|
30
|
+
if (save_wave != "") begin
|
|
31
|
+
$display("[driver] Saving waveform to %s", save_wave);
|
|
32
|
+
$dumpfile(save_wave);
|
|
33
|
+
$dumpvars(0, driver);
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
23
37
|
logic clk = 0;
|
|
24
38
|
logic rst = 0;
|
|
25
39
|
|
esiaccel/cosim/questa.py
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import List
|
|
7
|
+
from typing import List, Optional, Callable, Dict
|
|
8
8
|
|
|
9
|
-
from .simulator import CosimCollateralDir, Simulator
|
|
9
|
+
from .simulator import CosimCollateralDir, Simulator, SourceFiles
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Questa(Simulator):
|
|
@@ -14,6 +14,32 @@ class Questa(Simulator):
|
|
|
14
14
|
|
|
15
15
|
DefaultDriver = CosimCollateralDir / "driver.sv"
|
|
16
16
|
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
sources: SourceFiles,
|
|
20
|
+
run_dir: Path,
|
|
21
|
+
debug: bool,
|
|
22
|
+
run_stdout_callback: Optional[Callable[[str], None]] = None,
|
|
23
|
+
run_stderr_callback: Optional[Callable[[str], None]] = None,
|
|
24
|
+
compile_stdout_callback: Optional[Callable[[str], None]] = None,
|
|
25
|
+
compile_stderr_callback: Optional[Callable[[str], None]] = None,
|
|
26
|
+
make_default_logs: bool = True,
|
|
27
|
+
macro_definitions: Optional[Dict[str, str]] = None,
|
|
28
|
+
# An optional list of questa error codes to suppress
|
|
29
|
+
suppressed_questa_errors: Optional[List[int]] = None):
|
|
30
|
+
super().__init__(
|
|
31
|
+
sources=sources,
|
|
32
|
+
run_dir=run_dir,
|
|
33
|
+
debug=debug,
|
|
34
|
+
run_stdout_callback=run_stdout_callback,
|
|
35
|
+
run_stderr_callback=run_stderr_callback,
|
|
36
|
+
compile_stdout_callback=compile_stdout_callback,
|
|
37
|
+
compile_stderr_callback=compile_stderr_callback,
|
|
38
|
+
make_default_logs=make_default_logs,
|
|
39
|
+
macro_definitions=macro_definitions,
|
|
40
|
+
)
|
|
41
|
+
self.suppressed_questa_errors = suppressed_questa_errors
|
|
42
|
+
|
|
17
43
|
# Questa doesn't use stderr for error messages. Everything goes to stdout.
|
|
18
44
|
UsesStderr = False
|
|
19
45
|
|
|
@@ -23,9 +49,27 @@ class Questa(Simulator):
|
|
|
23
49
|
]
|
|
24
50
|
sources = self.sources.rtl_sources
|
|
25
51
|
sources.append(Questa.DefaultDriver)
|
|
52
|
+
|
|
53
|
+
# Format macro definition command
|
|
54
|
+
if self.macro_definitions:
|
|
55
|
+
macro_definitions_cmd = " ".join([
|
|
56
|
+
f"+define+{k}={v}" if v is not None else f"+define+{k}"
|
|
57
|
+
for k, v in self.macro_definitions.items()
|
|
58
|
+
])
|
|
59
|
+
else:
|
|
60
|
+
macro_definitions_cmd = ""
|
|
61
|
+
|
|
62
|
+
# Format error suppression command
|
|
63
|
+
if self.suppressed_questa_errors:
|
|
64
|
+
suppressed_questa_errors_cmd = " ".join(
|
|
65
|
+
[f"-suppress {ec}" for ec in self.suppressed_questa_errors])
|
|
66
|
+
else:
|
|
67
|
+
suppressed_questa_errors_cmd = ""
|
|
68
|
+
|
|
26
69
|
for src in sources:
|
|
27
|
-
cmds.append(
|
|
28
|
-
|
|
70
|
+
cmds.append(
|
|
71
|
+
f"vlog -incr +acc -sv {macro_definitions_cmd} {suppressed_questa_errors_cmd} +define+TOP_MODULE={self.sources.top}"
|
|
72
|
+
f" +define+SIMULATION {src.as_posix()}")
|
|
29
73
|
cmds.append(f"vopt -incr driver -o driver_opt +acc")
|
|
30
74
|
return cmds
|
|
31
75
|
|
|
@@ -53,9 +97,28 @@ class Questa(Simulator):
|
|
|
53
97
|
vsim,
|
|
54
98
|
"driver_opt",
|
|
55
99
|
"-batch",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
if self.debug:
|
|
103
|
+
# Create waveform dump .do file
|
|
104
|
+
wave_file = Path("wave.do")
|
|
105
|
+
with wave_file.open("w") as f:
|
|
106
|
+
f.write("log -r /*\n")
|
|
107
|
+
cmd += [
|
|
108
|
+
"-do",
|
|
109
|
+
str(wave_file.resolve()),
|
|
110
|
+
]
|
|
111
|
+
# Questa will by default log to a vsim.wlf file in the current
|
|
112
|
+
# directory.
|
|
113
|
+
print(
|
|
114
|
+
f"Debug mode enabled - waveform will be in {wave_file.resolve().parent / 'vsim.wlf'}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
cmd += [
|
|
56
118
|
"-do",
|
|
57
119
|
"run -all",
|
|
58
120
|
]
|
|
121
|
+
|
|
59
122
|
for lib in self.sources.dpi_so_paths():
|
|
60
123
|
svLib = os.path.splitext(lib)[0]
|
|
61
124
|
cmd.append("-sv_lib")
|
esiaccel/cosim/simulator.py
CHANGED
|
@@ -9,7 +9,8 @@ import socket
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import time
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Dict, List
|
|
12
|
+
from typing import Dict, List, Optional, Callable, IO
|
|
13
|
+
import threading
|
|
13
14
|
|
|
14
15
|
_thisdir = Path(__file__).parent
|
|
15
16
|
CosimCollateralDir = _thisdir
|
|
@@ -77,9 +78,15 @@ class SourceFiles:
|
|
|
77
78
|
|
|
78
79
|
class SimProcess:
|
|
79
80
|
|
|
80
|
-
def __init__(self,
|
|
81
|
+
def __init__(self,
|
|
82
|
+
proc: subprocess.Popen,
|
|
83
|
+
port: int,
|
|
84
|
+
threads: Optional[List[threading.Thread]] = None,
|
|
85
|
+
gui: bool = False):
|
|
81
86
|
self.proc = proc
|
|
82
87
|
self.port = port
|
|
88
|
+
self.threads: List[threading.Thread] = threads or []
|
|
89
|
+
self.gui = gui
|
|
83
90
|
|
|
84
91
|
def force_stop(self):
|
|
85
92
|
"""Make sure to stop the simulation no matter what."""
|
|
@@ -92,6 +99,10 @@ class SimProcess:
|
|
|
92
99
|
# If the simulation doesn't exit of its own free will, kill it.
|
|
93
100
|
self.proc.kill()
|
|
94
101
|
|
|
102
|
+
# Join reader threads (they should exit once pipes are closed).
|
|
103
|
+
for t in self.threads:
|
|
104
|
+
t.join()
|
|
105
|
+
|
|
95
106
|
|
|
96
107
|
class Simulator:
|
|
97
108
|
|
|
@@ -100,10 +111,75 @@ class Simulator:
|
|
|
100
111
|
# broken behavior by overriding this.
|
|
101
112
|
UsesStderr = True
|
|
102
113
|
|
|
103
|
-
def __init__(self,
|
|
114
|
+
def __init__(self,
|
|
115
|
+
sources: SourceFiles,
|
|
116
|
+
run_dir: Path,
|
|
117
|
+
debug: bool,
|
|
118
|
+
run_stdout_callback: Optional[Callable[[str], None]] = None,
|
|
119
|
+
run_stderr_callback: Optional[Callable[[str], None]] = None,
|
|
120
|
+
compile_stdout_callback: Optional[Callable[[str], None]] = None,
|
|
121
|
+
compile_stderr_callback: Optional[Callable[[str], None]] = None,
|
|
122
|
+
make_default_logs: bool = True,
|
|
123
|
+
macro_definitions: Optional[Dict[str, str]] = None):
|
|
124
|
+
"""Simulator base class.
|
|
125
|
+
|
|
126
|
+
Optional sinks can be provided for capturing output. If not provided,
|
|
127
|
+
the simulator will write to log files in `run_dir`.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
sources: SourceFiles describing RTL/DPI inputs.
|
|
131
|
+
run_dir: Directory where build/run artifacts are placed.
|
|
132
|
+
debug: Enable cosim debug mode.
|
|
133
|
+
run_stdout_callback: Line-based callback for runtime stdout.
|
|
134
|
+
run_stderr_callback: Line-based callback for runtime stderr.
|
|
135
|
+
compile_stdout_callback: Line-based callback for compile stdout.
|
|
136
|
+
compile_stderr_callback: Line-based callback for compile stderr.
|
|
137
|
+
make_default_logs: If True and corresponding callback is not supplied,
|
|
138
|
+
create log file and emit via internally-created callback.
|
|
139
|
+
macro_definitions: Optional dictionary of macro definitions to be defined
|
|
140
|
+
during compilation.
|
|
141
|
+
"""
|
|
104
142
|
self.sources = sources
|
|
105
143
|
self.run_dir = run_dir
|
|
106
144
|
self.debug = debug
|
|
145
|
+
self.macro_definitions = macro_definitions
|
|
146
|
+
|
|
147
|
+
# Unified list of any log file handles we opened.
|
|
148
|
+
self._default_files: List[IO[str]] = []
|
|
149
|
+
|
|
150
|
+
def _ensure_default(cb: Optional[Callable[[str], None]], filename: str):
|
|
151
|
+
"""Return (callback, file_handle_or_None) with optional file creation.
|
|
152
|
+
|
|
153
|
+
Behavior:
|
|
154
|
+
* If a callback is provided, return it unchanged with no file.
|
|
155
|
+
* If no callback and make_default_logs is False, return (None, None).
|
|
156
|
+
* If no callback and make_default_logs is True, create a log file and
|
|
157
|
+
return a writer callback plus the opened file handle.
|
|
158
|
+
"""
|
|
159
|
+
if cb is not None:
|
|
160
|
+
return cb, None
|
|
161
|
+
if not make_default_logs:
|
|
162
|
+
return None, None
|
|
163
|
+
p = self.run_dir / filename
|
|
164
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
logf = p.open("w+")
|
|
166
|
+
self._default_files.append(logf)
|
|
167
|
+
|
|
168
|
+
def _writer(line: str, _lf=logf):
|
|
169
|
+
_lf.write(line + "\n")
|
|
170
|
+
_lf.flush()
|
|
171
|
+
|
|
172
|
+
return _writer, logf
|
|
173
|
+
|
|
174
|
+
# Initialize all four (compile/run stdout/stderr) uniformly.
|
|
175
|
+
self._compile_stdout_cb, self._compile_stdout_log = _ensure_default(
|
|
176
|
+
compile_stdout_callback, 'compile_stdout.log')
|
|
177
|
+
self._compile_stderr_cb, self._compile_stderr_log = _ensure_default(
|
|
178
|
+
compile_stderr_callback, 'compile_stderr.log')
|
|
179
|
+
self._run_stdout_cb, self._run_stdout_log = _ensure_default(
|
|
180
|
+
run_stdout_callback, 'sim_stdout.log')
|
|
181
|
+
self._run_stderr_cb, self._run_stderr_log = _ensure_default(
|
|
182
|
+
run_stderr_callback, 'sim_stderr.log')
|
|
107
183
|
|
|
108
184
|
@staticmethod
|
|
109
185
|
def get_env() -> Dict[str, str]:
|
|
@@ -123,23 +199,29 @@ class Simulator:
|
|
|
123
199
|
def compile(self) -> int:
|
|
124
200
|
cmds = self.compile_commands()
|
|
125
201
|
self.run_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
202
|
+
for cmd in cmds:
|
|
203
|
+
ret = self._start_process_with_callbacks(
|
|
204
|
+
cmd,
|
|
205
|
+
env=Simulator.get_env(),
|
|
206
|
+
cwd=None,
|
|
207
|
+
stdout_cb=self._compile_stdout_cb,
|
|
208
|
+
stderr_cb=self._compile_stderr_cb,
|
|
209
|
+
wait=True)
|
|
210
|
+
if isinstance(ret, int) and ret != 0:
|
|
211
|
+
print("====== Compilation failure")
|
|
212
|
+
|
|
213
|
+
# If we have the default file loggers, print the compilation logs to
|
|
214
|
+
# console. Else, assume that the user has already captured them.
|
|
215
|
+
if self.UsesStderr:
|
|
216
|
+
if self._compile_stderr_log is not None:
|
|
217
|
+
self._compile_stderr_log.seek(0)
|
|
218
|
+
print(self._compile_stderr_log.read())
|
|
219
|
+
else:
|
|
220
|
+
if self._compile_stdout_log is not None:
|
|
221
|
+
self._compile_stdout_log.seek(0)
|
|
222
|
+
print(self._compile_stdout_log.read())
|
|
223
|
+
|
|
224
|
+
return ret
|
|
143
225
|
return 0
|
|
144
226
|
|
|
145
227
|
def run_command(self, gui: bool) -> List[str]:
|
|
@@ -148,11 +230,16 @@ class Simulator:
|
|
|
148
230
|
|
|
149
231
|
def run_proc(self, gui: bool = False) -> SimProcess:
|
|
150
232
|
"""Run the simulation process. Returns the Popen object and the port which
|
|
151
|
-
the simulation is listening on.
|
|
152
|
-
|
|
233
|
+
the simulation is listening on.
|
|
234
|
+
|
|
235
|
+
If user-provided stdout/stderr sinks were supplied in the constructor,
|
|
236
|
+
they are used. Otherwise, log files are created in `run_dir`.
|
|
237
|
+
"""
|
|
153
238
|
self.run_dir.mkdir(parents=True, exist_ok=True)
|
|
154
|
-
|
|
155
|
-
|
|
239
|
+
|
|
240
|
+
env_gui = os.environ.get("COSIM_GUI", "0")
|
|
241
|
+
if env_gui != "0":
|
|
242
|
+
gui = True
|
|
156
243
|
|
|
157
244
|
# Erase the config file if it exists. We don't want to read
|
|
158
245
|
# an old config.
|
|
@@ -168,19 +255,19 @@ class Simulator:
|
|
|
168
255
|
# Slow the simulation down to one tick per millisecond.
|
|
169
256
|
simEnv["DEBUG_PERIOD"] = "1"
|
|
170
257
|
rcmd = self.run_command(gui)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
258
|
+
# Start process with asynchronous output capture.
|
|
259
|
+
proc, threads = self._start_process_with_callbacks(
|
|
260
|
+
rcmd,
|
|
261
|
+
env=simEnv,
|
|
262
|
+
cwd=self.run_dir,
|
|
263
|
+
stdout_cb=self._run_stdout_cb,
|
|
264
|
+
stderr_cb=self._run_stderr_cb,
|
|
265
|
+
wait=False)
|
|
179
266
|
|
|
180
267
|
# Get the port which the simulation RPC selected.
|
|
181
268
|
checkCount = 0
|
|
182
269
|
while (not os.path.exists(portFileName)) and \
|
|
183
|
-
|
|
270
|
+
proc.poll() is None:
|
|
184
271
|
time.sleep(0.1)
|
|
185
272
|
checkCount += 1
|
|
186
273
|
if checkCount > 500 and not gui:
|
|
@@ -200,10 +287,66 @@ class Simulator:
|
|
|
200
287
|
checkCount += 1
|
|
201
288
|
if checkCount > 200:
|
|
202
289
|
raise Exception(f"Cosim RPC port ({port}) never opened")
|
|
203
|
-
if
|
|
290
|
+
if proc.poll() is not None:
|
|
204
291
|
raise Exception("Simulation exited early")
|
|
205
292
|
time.sleep(0.05)
|
|
206
|
-
return SimProcess(proc=
|
|
293
|
+
return SimProcess(proc=proc, port=port, threads=threads, gui=gui)
|
|
294
|
+
|
|
295
|
+
def _start_process_with_callbacks(
|
|
296
|
+
self, cmd: List[str], env: Optional[Dict[str, str]], cwd: Optional[Path],
|
|
297
|
+
stdout_cb: Optional[Callable[[str],
|
|
298
|
+
None]], stderr_cb: Optional[Callable[[str],
|
|
299
|
+
None]],
|
|
300
|
+
wait: bool) -> int | tuple[subprocess.Popen, List[threading.Thread]]:
|
|
301
|
+
"""Start a subprocess and stream its stdout/stderr to callbacks.
|
|
302
|
+
|
|
303
|
+
If wait is True, blocks until process completes and returns its exit code.
|
|
304
|
+
If wait is False, returns the Popen object (threads keep streaming).
|
|
305
|
+
"""
|
|
306
|
+
if os.name == "posix":
|
|
307
|
+
proc = subprocess.Popen(cmd,
|
|
308
|
+
stdout=subprocess.PIPE,
|
|
309
|
+
stderr=subprocess.PIPE,
|
|
310
|
+
env=env,
|
|
311
|
+
cwd=cwd,
|
|
312
|
+
text=True,
|
|
313
|
+
preexec_fn=os.setsid)
|
|
314
|
+
else: # windows
|
|
315
|
+
proc = subprocess.Popen(cmd,
|
|
316
|
+
stdout=subprocess.PIPE,
|
|
317
|
+
stderr=subprocess.PIPE,
|
|
318
|
+
env=env,
|
|
319
|
+
cwd=cwd,
|
|
320
|
+
text=True,
|
|
321
|
+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
|
322
|
+
|
|
323
|
+
def _reader(pipe, cb):
|
|
324
|
+
if pipe is None:
|
|
325
|
+
return
|
|
326
|
+
for raw in pipe:
|
|
327
|
+
if raw.endswith('\n'):
|
|
328
|
+
raw = raw[:-1]
|
|
329
|
+
if cb:
|
|
330
|
+
try:
|
|
331
|
+
cb(raw)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
print(f"Exception in simulator output callback: {e}")
|
|
334
|
+
|
|
335
|
+
threads: List[threading.Thread] = [
|
|
336
|
+
threading.Thread(target=_reader,
|
|
337
|
+
args=(proc.stdout, stdout_cb),
|
|
338
|
+
daemon=True),
|
|
339
|
+
threading.Thread(target=_reader,
|
|
340
|
+
args=(proc.stderr, stderr_cb),
|
|
341
|
+
daemon=True),
|
|
342
|
+
]
|
|
343
|
+
for t in threads:
|
|
344
|
+
t.start()
|
|
345
|
+
if wait:
|
|
346
|
+
for t in threads:
|
|
347
|
+
t.join()
|
|
348
|
+
return proc.wait()
|
|
349
|
+
return proc, threads
|
|
207
350
|
|
|
208
351
|
def run(self,
|
|
209
352
|
inner_command: str,
|
|
@@ -228,8 +371,12 @@ class Simulator:
|
|
|
228
371
|
testEnv = os.environ.copy()
|
|
229
372
|
testEnv["ESI_COSIM_PORT"] = str(simProc.port)
|
|
230
373
|
testEnv["ESI_COSIM_HOST"] = "localhost"
|
|
231
|
-
|
|
232
|
-
|
|
374
|
+
ret = subprocess.run(inner_command, cwd=os.getcwd(),
|
|
375
|
+
env=testEnv).returncode
|
|
376
|
+
if simProc.gui:
|
|
377
|
+
print("GUI mode - waiting for simulator to exit...")
|
|
378
|
+
simProc.proc.wait()
|
|
379
|
+
return ret
|
|
233
380
|
finally:
|
|
234
|
-
if simProc:
|
|
381
|
+
if simProc and simProc.proc.poll() is None:
|
|
235
382
|
simProc.force_stop()
|