mirror of https://github.com/llvm/circt.git
[PyCDE] Remove a bunch of old cruft
A lot of this functionality was replaced by the ESI runtime.
This commit is contained in:
parent
d0879a7663
commit
0cd857f581
|
@ -1,102 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.13.4)
|
||||
project(esi_ram_test)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# TODO: most of this stuff should be moved to a .cmake file that is included.
|
||||
|
||||
# fetch https://github.com/veselink1/refl-cpp
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
refl-cpp
|
||||
GIT_REPOSITORY https://github.com/veselink1/refl-cpp
|
||||
GIT_TAG v0.12.4
|
||||
)
|
||||
FetchContent_MakeAvailable(refl-cpp)
|
||||
|
||||
# Assert that CIRCT_DIR is defined.
|
||||
if(NOT DEFINED CIRCT_DIR)
|
||||
message(FATAL_ERROR "CIRCT_DIR must be defined.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED PYCDE_OUT_DIR)
|
||||
message(FATAL_ERROR "PYCDE_OUT_DIR must be defined.")
|
||||
endif()
|
||||
|
||||
message(STATUS "CIRCT_DIR= ${CIRCT_DIR}")
|
||||
message(STATUS "PYCDE_OUT_DIR= ${PYCDE_OUT_DIR}")
|
||||
|
||||
set(CAPNP_SCHEMA "${PYCDE_OUT_DIR}/hw/schema.capnp")
|
||||
set(ESI_CPP_API "${PYCDE_OUT_DIR}/hw/ESISystem.h")
|
||||
set(ESI_HW_INCLUDE_DIR "${PYCDE_OUT_DIR}/hw")
|
||||
|
||||
# Ensure that the above files are present
|
||||
if(NOT EXISTS ${CAPNP_SCHEMA})
|
||||
message(FATAL_ERROR "CAPNP_SCHEMA not found: ${CAPNP_SCHEMA}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS ${ESI_CPP_API})
|
||||
message(FATAL_ERROR "ESI_CPP_API not found: ${ESI_CPP_API}")
|
||||
endif()
|
||||
|
||||
|
||||
if(DEFINED CAPNP_PATH)
|
||||
set(ENV{PKG_CONFIG_PATH}
|
||||
"${CAPNP_PATH}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
||||
find_package(CapnProto CONFIG PATHS ${CAPNP_PATH})
|
||||
else()
|
||||
set(ENV{PKG_CONFIG_PATH}
|
||||
"${CIRCT_DIR}/ext/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
||||
find_package(CapnProto CONFIG PATHS "${CIRCT_DIR}/ext")
|
||||
endif()
|
||||
|
||||
if (NOT CapnProto_FOUND)
|
||||
message(FATAL_ERROR "Cap'n Proto not found.")
|
||||
endif()
|
||||
|
||||
# Move schema to the build directory - required by capnp_generate_cpp.
|
||||
set(CAPNPC_SRC_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/capnp_generated)
|
||||
set(CAPNPC_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/capnp_generated)
|
||||
file(COPY ${CAPNP_SCHEMA} DESTINATION ${CAPNPC_OUTPUT_DIR})
|
||||
get_filename_component(CAPNP_SCHEMA_BASENAME ${CAPNP_SCHEMA} NAME)
|
||||
set(COPIED_CAPNP_SCHEMA ${CAPNP_OUTDIR}/${CAPNP_SCHEMA_BASENAME})
|
||||
capnp_generate_cpp(
|
||||
ESI_RAM_SRCS
|
||||
ESI_RAM_HDRS
|
||||
${CAPNPC_SRC_PREFIX}/${CAPNP_SCHEMA_BASENAME}
|
||||
)
|
||||
|
||||
message(STATUS "CAPNP_OUTDIR= ${CAPNP_OUTDIR}")
|
||||
add_executable(esi_ram_test
|
||||
esi_ram.cpp
|
||||
${ESI_RAM_SRCS}
|
||||
${ESI_RAM_HDRS}
|
||||
)
|
||||
target_link_libraries(esi_ram_test
|
||||
${CAPNP_LIBRARIES}
|
||||
refl-cpp
|
||||
)
|
||||
target_include_directories(esi_ram_test PUBLIC
|
||||
# Include the copied ESI C++ runtime headers.
|
||||
${PYCDE_OUT_DIR}/runtime/cpp/include)
|
||||
|
||||
message("ESI_RAM_SRCS: ${ESI_RAM_SRCS}")
|
||||
message("ESI_RAM_HDRS: ${ESI_RAM_HDRS}")
|
||||
|
||||
target_include_directories(
|
||||
esi_ram_test
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CAPNP_INCLUDE_DIRS}
|
||||
${CAPNP_OUTDIR}
|
||||
${CIRCT_DIR}/include
|
||||
${ESI_HW_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
esi_ram_test
|
||||
PUBLIC
|
||||
-DESI_COSIM_CAPNP_H=\"${CAPNPC_OUTPUT_DIR}/schema.capnp.h\"
|
||||
)
|
|
@ -1,138 +0,0 @@
|
|||
// REQUIRES: esi-cosim
|
||||
// XFAIL: *
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Create ESI system
|
||||
// RUN: rm -rf %t
|
||||
// RUN: %PYTHON% %S/../esi_ram.py %t 2>&1
|
||||
|
||||
// Build the project using the CMakeLists.txt from this directory. Just move
|
||||
// everything to the output folder in the build directory; this is very convenient
|
||||
// if we want to run the build manually afterwards.
|
||||
// RUN: cp %s %t
|
||||
// RUN: cp %S/CMakeLists.txt %t
|
||||
// RUN: cmake -S %t \
|
||||
// RUN: -B %t/build \
|
||||
// RUN: -DCIRCT_DIR=%CIRCT_SOURCE% \
|
||||
// RUN: -DPYCDE_OUT_DIR=%t
|
||||
// RUN: cmake --build %t/build
|
||||
|
||||
// Run test
|
||||
// ... can't glob *.sv because PyCDE always includes driver.sv, but that's not the
|
||||
// top that we want to use. Just delete it.
|
||||
// RUN: rm %t/hw/driver.sv
|
||||
// RUN: esi-cosim-runner.py --tmpdir=%t \
|
||||
// RUN: --no-aux-files \
|
||||
// RUN: --schema %t/hw/schema.capnp \
|
||||
// RUN: --exec %t/build/esi_ram_test \
|
||||
// RUN: %t/hw/*.sv
|
||||
|
||||
// To run this test manually:
|
||||
// 1. run `ninja check-pycde-integration` (this will create the output folder, run PyCDE, ...)
|
||||
// 2. navigate to %t
|
||||
// 3. In a separate terminal, run esi-cosim-runner.py in server only mode:
|
||||
// - cd %t
|
||||
// - esi-cosim-runner.py --tmpdir=$(pwd) --schema=$(pwd)/hw/schema.capnp --server-only $(pwd)/hw/top.sv $(pwd)
|
||||
// 4. In another terminal, run the test executable. When running esi-cosim-runner, it'll print the $port which
|
||||
// the test executable should connect to.
|
||||
// - cd %t/build
|
||||
// - ./esi_ram_test localhost:$port ../hw/schema.capn
|
||||
|
||||
// clang-format on
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esi/backends/capnp.h"
|
||||
|
||||
#include ESI_COSIM_CAPNP_H
|
||||
|
||||
#include "ESISystem.h"
|
||||
|
||||
using namespace esi;
|
||||
using namespace runtime;
|
||||
|
||||
template <typename T>
|
||||
int logTestFailure(T expected, T actual, int testID) {
|
||||
std::cerr << "Test " << testID << " failed: expected " << expected << ", got "
|
||||
<< actual << std::endl;
|
||||
return testID;
|
||||
}
|
||||
|
||||
template <typename TBackend>
|
||||
int runTest(TBackend &backend) {
|
||||
// Connect the ESI system to the provided backend.
|
||||
esi::runtime::top top(backend);
|
||||
|
||||
auto write_cmd =
|
||||
ESITypes::Struct16871797234873963366{.address = 2, .data = 42};
|
||||
|
||||
auto loopback_result = (*top.bsp->loopback)(write_cmd);
|
||||
if (loopback_result != write_cmd)
|
||||
return logTestFailure(write_cmd, loopback_result, 1);
|
||||
|
||||
auto read_result = (*top.bsp->read)(2);
|
||||
if (read_result != ESITypes::I64(0))
|
||||
return logTestFailure(ESITypes::I64(0), read_result, 2);
|
||||
|
||||
read_result = (*top.bsp->read)(3);
|
||||
if (read_result != ESITypes::I64(0))
|
||||
return logTestFailure(ESITypes::I64(0), read_result, 3);
|
||||
|
||||
(*top.bsp->write)(write_cmd);
|
||||
read_result = (*top.bsp->read)(2);
|
||||
if (read_result != ESITypes::I64(42))
|
||||
return logTestFailure(ESITypes::I64(42), read_result, 4);
|
||||
|
||||
read_result = (*top.bsp->read)(3);
|
||||
if (read_result != ESITypes::I64(42))
|
||||
return logTestFailure(ESITypes::I64(42), read_result, 5);
|
||||
|
||||
// Re-write a 0 to the memory (mostly for debugging purposes to allow us to
|
||||
// keep the server alive and rerun the test).
|
||||
write_cmd = ESITypes::Struct16871797234873963366{.address = 2, .data = 0};
|
||||
(*top.bsp->write)(write_cmd);
|
||||
read_result = (*top.bsp->read)(2);
|
||||
if (read_result != ESITypes::I64(0))
|
||||
return logTestFailure(ESITypes::I64(0), read_result, 6);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int run_cosim_test(const std::string &host, unsigned port) {
|
||||
// Run test with cosimulation backend.
|
||||
esi::runtime::cosim::CapnpBackend cosim(host, port);
|
||||
return runTest(cosim);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::string rpchostport;
|
||||
if (argc != 3) {
|
||||
// Schema not currently used but required by the ESI cosim tester
|
||||
std::cerr
|
||||
<< "usage: esi_ram_test {rpc hostname}:{rpc port} {path to schema}"
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
rpchostport = argv[1];
|
||||
|
||||
// Parse the RPC host and port from the command line.
|
||||
auto colon = rpchostport.find(':');
|
||||
if (colon == std::string::npos) {
|
||||
std::cerr << "Invalid RPC host:port string: " << rpchostport << std::endl;
|
||||
return 1;
|
||||
}
|
||||
auto host = rpchostport.substr(0, colon);
|
||||
auto port = stoi(rpchostport.substr(colon + 1));
|
||||
|
||||
auto res = run_cosim_test(host, port);
|
||||
if (res != 0) {
|
||||
std::cerr << "Test failed with error code " << res << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Test passed" << std::endl;
|
||||
return 0;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
config.suffixes.add('.cpp')
|
|
@ -8,10 +8,6 @@
|
|||
|
||||
include(AddMLIRPython)
|
||||
|
||||
# Some of the PyCDE CMake code which is only used in Capnp builds uses newer
|
||||
# CMake features.
|
||||
cmake_minimum_required(VERSION 3.21.0)
|
||||
|
||||
add_compile_definitions("MLIR_PYTHON_PACKAGE_PREFIX=pycde.circt.")
|
||||
|
||||
declare_mlir_python_sources(PyCDESources
|
||||
|
@ -39,19 +35,13 @@ declare_mlir_python_sources(PyCDESources
|
|||
signals.py
|
||||
ndarray.py
|
||||
esi.py
|
||||
esi_api.py
|
||||
esi_runtime_common.py
|
||||
fsm.py
|
||||
testing.py
|
||||
|
||||
esi_api.py.j2
|
||||
Makefile.cosim
|
||||
|
||||
bsp/__init__.py
|
||||
bsp/common.py
|
||||
bsp/cosim.py
|
||||
bsp/xrt.py
|
||||
bsp/EsiXrtPython.cpp
|
||||
bsp/Makefile.xrt.mk
|
||||
bsp/xrt_package.tcl
|
||||
bsp/xrt_api.py
|
||||
|
@ -126,32 +116,3 @@ foreach(CFile IN LISTS CollateralFiles)
|
|||
COMPONENT PyCDE
|
||||
)
|
||||
endforeach()
|
||||
|
||||
install(TARGETS circt-std-sim-drivers
|
||||
PUBLIC_HEADER DESTINATION python_packages/pycde/collateral
|
||||
COMPONENT PyCDE
|
||||
)
|
||||
if(ESI_COSIM)
|
||||
add_dependencies(PyCDE EsiCosimDpiServer)
|
||||
set_property(TARGET EsiCosimDpiServer PROPERTY INSTALL_RPATH "$ORIGIN")
|
||||
install(TARGETS EsiCosimDpiServer
|
||||
RUNTIME_DEPENDENCY_SET EsiCosimDpiServer_RUNTIME_DEPS
|
||||
DESTINATION python_packages/pycde/collateral
|
||||
COMPONENT PyCDE
|
||||
)
|
||||
install(RUNTIME_DEPENDENCY_SET EsiCosimDpiServer_RUNTIME_DEPS
|
||||
DESTINATION python_packages/pycde/collateral
|
||||
PRE_EXCLUDE_REGEXES .*
|
||||
PRE_INCLUDE_REGEXES capnp kj
|
||||
COMPONENT PyCDE
|
||||
)
|
||||
install(TARGETS MtiPli
|
||||
DESTINATION python_packages/pycde/collateral
|
||||
COMPONENT PyCDE
|
||||
)
|
||||
install(FILES
|
||||
"$<TARGET_PROPERTY:EsiCosimCapnp,BINARY_DIR>/CosimDpi.capnp"
|
||||
DESTINATION python_packages/pycde/collateral/runtime
|
||||
COMPONENT PyCDE
|
||||
)
|
||||
endif()
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||
mkfile_dir := $(dir $(mkfile_path))
|
||||
|
||||
VSIM_PATH := $(which vsim)
|
||||
|
||||
ifneq ($(VSIM_PATH),)
|
||||
QUESTA_PATH := $(basename $(VSIM_PATH))
|
||||
endif
|
||||
|
||||
ifneq ($(QUESTA_PATH),)
|
||||
run_questa:
|
||||
$(QUESTA_PATH)/vlog hw/*.sv
|
||||
$(QUESTA_PATH)/vsim driver -c -sv_lib hw/libEsiCosimDpiServer -do "run -all; quit"
|
||||
endif
|
||||
|
||||
VERILATOR_PATH := $(which verilator)
|
||||
ifneq ($(VERILATOR_PATH),)
|
||||
SV_SRCS = $(shell ls hw/*.sv | grep -v driver.sv)
|
||||
VERILATOR_SRCS = $(SV_SRCS) $(mkfile_dir)/hw/*.so hw/*.cpp
|
||||
run_verilator:
|
||||
$(VERILATOR_PATH) --cc --top-module top -sv --build --exe --assert $(VERILATOR_SRCS)
|
||||
LD_LIBRARY_PATH=hw obj_dir/Vtop
|
||||
endif
|
|
@ -1,58 +0,0 @@
|
|||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
// pybind11 includes
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/stl.h"
|
||||
namespace py = pybind11;
|
||||
|
||||
// XRT includes
|
||||
#include "experimental/xrt_bo.h"
|
||||
#include "experimental/xrt_device.h"
|
||||
#include "experimental/xrt_ip.h"
|
||||
#include "experimental/xrt_xclbin.h"
|
||||
|
||||
// We don't want to clutter up the symbol space any more than necessary, so use
|
||||
// an anonymous namespace.
|
||||
namespace {
|
||||
|
||||
uint32_t MagicNumOffset = 16;
|
||||
uint32_t MagicNumberLo = 0xE5100E51;
|
||||
uint32_t MagicNumberHi = 0x207D98E5;
|
||||
uint32_t ExpectedVersionNumber = 0;
|
||||
|
||||
class Accelerator {
|
||||
xrt::device m_device;
|
||||
xrt::ip m_ip;
|
||||
|
||||
public:
|
||||
Accelerator(const std::string &xclbin_path, const std::string kernel_name) {
|
||||
m_device = xrt::device(0);
|
||||
auto uuid = m_device.load_xclbin(xclbin_path);
|
||||
m_ip = xrt::ip(m_device, uuid, kernel_name);
|
||||
|
||||
// Check that this is actually an ESI system.
|
||||
uint32_t magicLo = m_ip.read_register(MagicNumOffset);
|
||||
uint32_t magicHi = m_ip.read_register(MagicNumOffset + 4);
|
||||
if (magicLo != MagicNumberLo || magicHi != MagicNumberHi)
|
||||
throw std::runtime_error("Accelerator is not an ESI system");
|
||||
|
||||
// Check version is one we understand.
|
||||
if (version() != ExpectedVersionNumber)
|
||||
std::cerr
|
||||
<< "[ESI] Warning: accelerator ESI version may not be compatible\n";
|
||||
}
|
||||
|
||||
uint32_t version() { return m_ip.read_register(MagicNumOffset + 8); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_MODULE(esiXrtPython, m) {
|
||||
py::class_<Accelerator>(m, "Accelerator")
|
||||
.def(py::init<const std::string &, const std::string &>())
|
||||
.def("version", &Accelerator::version);
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
# See https://llvm.org/LICENSE.txt for license information.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
||||
|
||||
from io import FileIO
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
from typing import Dict, List
|
||||
|
||||
__dir__ = pathlib.Path(__file__).parent
|
||||
|
||||
|
||||
def _camel_to_snake(camel: str):
|
||||
if camel.upper() == camel:
|
||||
return camel.lower()
|
||||
return re.sub(r'(?<!^)(?=[A-Z])', '_', camel).lower()
|
||||
|
||||
|
||||
def _get_ports_for_clients(clients):
|
||||
# Assemble lists of clients for each service port.
|
||||
ports = {}
|
||||
for client in clients:
|
||||
port = client['port']['inner']
|
||||
if port not in ports:
|
||||
ports[port] = []
|
||||
ports[port].append(client)
|
||||
return ports
|
||||
|
||||
|
||||
class SoftwareApiBuilder:
|
||||
"""Parent class for all software API builders. Defines an interfaces and tries
|
||||
to encourage code sharing and API consistency (between languages)."""
|
||||
|
||||
class Module:
|
||||
"""Bookkeeping about modules."""
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.instances: Dict[str, SoftwareApiBuilder.Module] = {}
|
||||
self.services: List[Dict] = []
|
||||
|
||||
def __init__(self, services_json: str):
|
||||
"""Read in the system descriptor and set up bookkeeping structures."""
|
||||
self.services = json.loads(services_json)
|
||||
self.types: Dict[str, Dict] = {}
|
||||
self.modules: Dict[str, SoftwareApiBuilder.Module] = {}
|
||||
|
||||
# Get all the modules listed in the service hierarchy. Populate their
|
||||
# 'instances' properly.
|
||||
for top in self.services["top_levels"]:
|
||||
top_mod = self._get_module(top["module"][1:])
|
||||
for svc in top["services"]:
|
||||
parent: SoftwareApiBuilder.Module = top_mod
|
||||
for inner_ref in [
|
||||
(inst["outer_sym"], inst["inner"]) for inst in svc["instance_path"]
|
||||
]:
|
||||
m = self._get_module(inner_ref[0])
|
||||
parent.instances[inner_ref[1]] = m
|
||||
parent = m
|
||||
|
||||
# For any modules which have services, add them as appropriate.
|
||||
for mod in self.services["modules"]:
|
||||
m = self._get_module(mod["symbol"])
|
||||
for svc in mod["services"]:
|
||||
m.services.append(svc)
|
||||
|
||||
def _get_module(self, mod_sym: str):
|
||||
"""Get a module adding an entry if it doesn't exist."""
|
||||
if mod_sym not in self.modules:
|
||||
self.modules[mod_sym] = SoftwareApiBuilder.Module(mod_sym)
|
||||
return self.modules[mod_sym]
|
||||
|
||||
def build(self, os: FileIO, tmpl_file: str):
|
||||
"""Output the API (in a pre-determined order) via callbacks. Encourages some
|
||||
level of consistency between language APIs."""
|
||||
|
||||
env = Environment(loader=FileSystemLoader(str(__dir__)),
|
||||
undefined=StrictUndefined)
|
||||
env.globals.update(camel_to_snake=_camel_to_snake,
|
||||
get_ports_for_clients=_get_ports_for_clients,
|
||||
get_type_name=self.get_type_name,
|
||||
type_str_of=self.get_str_type)
|
||||
|
||||
template = env.get_template(tmpl_file)
|
||||
top_levels = [
|
||||
self._get_module(t["module"][1:]) for t in self.services["top_levels"]
|
||||
]
|
||||
os.write(
|
||||
template.render(services=self.services,
|
||||
modules=self.modules.values(),
|
||||
types=self.types,
|
||||
tops=top_levels))
|
||||
|
||||
def get_type_name(self, type: Dict):
|
||||
"""Create a name for 'type', record it, and return it."""
|
||||
if "capnp_name" in type:
|
||||
name = type["capnp_name"]
|
||||
else:
|
||||
name = "".join([c if c.isalnum() else '_' for c in type["mlir_name"]])
|
||||
self.types[name] = type
|
||||
return name
|
||||
|
||||
def get_str_type(self, type: Dict):
|
||||
assert False, "unimplemented"
|
||||
|
||||
|
||||
class PythonApiBuilder(SoftwareApiBuilder):
|
||||
|
||||
def __init__(self, services_json: str):
|
||||
super().__init__(services_json)
|
||||
|
||||
def build(self, system_name: str, sw_dir: pathlib.Path):
|
||||
"""Emit a Python ESI runtime library into 'output_dir'."""
|
||||
libdir = sw_dir / system_name
|
||||
if not libdir.exists():
|
||||
libdir.mkdir()
|
||||
|
||||
common_file = libdir / "common.py"
|
||||
shutil.copy(__dir__ / "esi_runtime_common.py", common_file)
|
||||
|
||||
# Emit the system-specific API.
|
||||
main = libdir / "__init__.py"
|
||||
super().build(main.open("w"), "esi_api.py.j2")
|
||||
|
||||
def get_str_type(self, type_dict: Dict):
|
||||
"""Get a Python code string instantiating 'type'."""
|
||||
|
||||
def py_type(type: Dict):
|
||||
dialect = type["dialect"]
|
||||
mn: str = type["mnemonic"]
|
||||
if dialect == "esi" and mn == "channel":
|
||||
return py_type(type["inner"])
|
||||
if dialect == "builtin":
|
||||
if mn.startswith("i") or mn.startswith("ui"):
|
||||
width = int(mn.strip("ui"))
|
||||
signed = False
|
||||
elif mn.startswith("si"):
|
||||
width = int(mn.strip("si"))
|
||||
signed = True
|
||||
if width == 0:
|
||||
return "VoidType()"
|
||||
return f"IntType({width}, {signed})"
|
||||
elif dialect == "hw":
|
||||
if mn == "struct":
|
||||
fields = [
|
||||
f"('{x['name']}', {py_type(x['type'])})" for x in type["fields"]
|
||||
]
|
||||
fields_str = ", ".join(fields)
|
||||
return "StructType([" + fields_str + "])"
|
||||
|
||||
assert False, "unimplemented type"
|
||||
|
||||
return py_type(type_dict["type_desc"])
|
|
@ -1,147 +0,0 @@
|
|||
{# This template is not generated. #}
|
||||
# Generated ESI runtime API.
|
||||
|
||||
from .common import *
|
||||
|
||||
{# Function to list port parameters for the service declaration
|
||||
constructors. #}
|
||||
{%- macro port_list(ports) -%}
|
||||
{% for port in ports -%}
|
||||
{{-port.name}}_ports: typing.List[Port]{% if not loop.last %}, {% endif %}
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
{# List of service declaration classes. E.g.:
|
||||
```
|
||||
class HostComms:
|
||||
|
||||
def __init__(self, to_host_ports: typing.List[Port], from_host_ports: typing.List[Port], req_resp_ports: typing.List[Port]):
|
||||
self.to_host = to_host_ports
|
||||
self.from_host = from_host_ports
|
||||
self.req_resp = req_resp_ports
|
||||
|
||||
def to_host_read_any(self):
|
||||
for p in self.to_host:
|
||||
rc = p.read(block=False)
|
||||
if rc is not None:
|
||||
return rc
|
||||
return None
|
||||
|
||||
def req_resp_read_any(self):
|
||||
for p in self.req_resp:
|
||||
rc = p.read(block=False)
|
||||
if rc is not None:
|
||||
return rc
|
||||
return None
|
||||
```
|
||||
#}
|
||||
{%- for svc in services.declarations %}
|
||||
class {{ svc['name' ]}}:
|
||||
|
||||
def __init__(self, {{ port_list(svc.ports) }}):
|
||||
{%- for port in svc.ports %}
|
||||
self.{{port.name}} = {{port.name}}_ports
|
||||
{%- endfor %}
|
||||
pass
|
||||
|
||||
{% for port in svc.ports if port['to-server-type'] is defined -%}
|
||||
def {{port.name}}_read_any(self):
|
||||
for p in self.{{port.name}}:
|
||||
rc = p.read(blocking_timeout=None)
|
||||
if rc is not None:
|
||||
return rc
|
||||
return None
|
||||
|
||||
{% endfor -%}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{# "Namespace" for design modules. Section looks roughly like:
|
||||
```
|
||||
class DesignModules:
|
||||
|
||||
class top:
|
||||
|
||||
def __init__(self):
|
||||
self.mid = DesignModules.Mid()
|
||||
|
||||
|
||||
class Mid:
|
||||
|
||||
def __init__(self):
|
||||
from_host = [
|
||||
ReadPort(['Producer', 'loopback_in'], write_type=ESITypes.I32)
|
||||
]
|
||||
to_host = [
|
||||
WritePort(['Consumer', 'loopback_out'], read_type=ESITypes.I32)
|
||||
]
|
||||
req_resp = [
|
||||
ReadPort(['LoopbackInOut', 'loopback_inout'], write_type=ESITypes.I32)
|
||||
WritePort(['LoopbackInOut', 'loopback_inout'], read_type=ESITypes.I16)
|
||||
]
|
||||
self.host_comms = HostComms(from_host_ports=from_host, to_host_ports=to_host, req_resp_ports=req_resp)
|
||||
```
|
||||
#}
|
||||
class DesignModules:
|
||||
{% for mod in modules %}
|
||||
class {{mod.name}}:
|
||||
|
||||
def __init__(self, backend):
|
||||
self._backend = backend
|
||||
{%- for inst_name, child_mod in mod.instances.items() %}
|
||||
self.{{camel_to_snake(inst_name)}} = DesignModules.{{child_mod.name}}(backend.get_child("{{inst_name}}"))
|
||||
{%- endfor %}
|
||||
{%- for svc in mod.services %}
|
||||
{%- set ports = get_ports_for_clients(svc.clients) -%}
|
||||
{% for port_name, port_clients in ports.items() %}
|
||||
{{port_name}} = [
|
||||
{% for pc in port_clients -%}
|
||||
{% if pc.to_client_type is defined and pc.to_server_type is defined -%}
|
||||
ReadWritePort({{pc.client_name}}, backend, "{{svc.impl_type}}", read_type=ESITypes.{{get_type_name(pc.to_server_type)}}, write_type=ESITypes.{{get_type_name(pc.to_client_type)}})
|
||||
{%- elif pc.to_client_type is defined -%}
|
||||
WritePort({{pc.client_name}}, backend, "{{svc.impl_type}}", write_type=ESITypes.{{get_type_name(pc.to_client_type)}})
|
||||
{%- elif pc.to_server_type is defined -%}
|
||||
ReadPort({{pc.client_name}}, backend, "{{svc.impl_type}}", read_type=ESITypes.{{get_type_name(pc.to_server_type)}})
|
||||
{%- endif -%}
|
||||
{%- if not loop.last %}, {% endif %}
|
||||
{%- if not loop.last %}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
]
|
||||
{%- endfor %}
|
||||
self.{{camel_to_snake(svc.service)}} = {{svc.service}}(
|
||||
{%- for pn in ports.keys() -%}{{pn}}_ports={{pn}}{% if not loop.last %}, {% endif %}{% endfor -%}
|
||||
)
|
||||
{%- endfor %}
|
||||
pass
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{# Types section. Example:`
|
||||
```
|
||||
class ESITypes:
|
||||
|
||||
I32 = IntType(32, False)
|
||||
I16 = IntType(16, False)
|
||||
```
|
||||
#}
|
||||
class ESITypes:
|
||||
{% for type_name, type_dict in types.items() %}
|
||||
{{type_name}} = {{type_str_of(type_dict)}}
|
||||
{%- if type_dict.capnp_name is defined %}
|
||||
{{type_name}}.capnp_name = "{{type_dict.capnp_name}}"
|
||||
{% endif -%}
|
||||
{%- endfor %}
|
||||
pass
|
||||
|
||||
{# Instantiate the top modules. E.g.:
|
||||
|
||||
```
|
||||
top = DesignModules.top()
|
||||
```
|
||||
|
||||
#}
|
||||
{% for top in tops %}
|
||||
{{top.name}} = lambda backend: DesignModules.{{top.name}}(backend.get_child("{{top.name}}"))
|
||||
{%- endfor %}
|
|
@ -1,324 +0,0 @@
|
|||
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
# See https://llvm.org/LICENSE.txt for license information.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
import capnp
|
||||
import os
|
||||
from pathlib import Path
|
||||
import time
|
||||
import typing
|
||||
|
||||
|
||||
class Type:
|
||||
|
||||
def __init__(self, width, type_id: typing.Optional[int] = None):
|
||||
self.type_id = type_id
|
||||
self.width = width
|
||||
|
||||
def is_valid(self, obj) -> bool:
|
||||
"""Is a Python object compatible with HW type."""
|
||||
assert False, "unimplemented"
|
||||
|
||||
|
||||
class VoidType(Type):
|
||||
|
||||
def __init__(self, type_id: typing.Optional[int] = None):
|
||||
super().__init__(0, type_id)
|
||||
|
||||
def is_valid(self, obj) -> bool:
|
||||
return obj is None
|
||||
|
||||
|
||||
class IntType(Type):
|
||||
|
||||
def __init__(self,
|
||||
width: int,
|
||||
signed: bool,
|
||||
type_id: typing.Optional[int] = None):
|
||||
super().__init__(width, type_id)
|
||||
self.signed = signed
|
||||
|
||||
def is_valid(self, obj) -> bool:
|
||||
if self.width == 0:
|
||||
return obj is None
|
||||
if not isinstance(obj, int):
|
||||
return False
|
||||
if obj >= 2**self.width:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return ("" if self.signed else "u") + \
|
||||
f"int{self.width}"
|
||||
|
||||
|
||||
class StructType(Type):
|
||||
|
||||
def __init__(self,
|
||||
fields: typing.List[typing.Tuple[str, Type]],
|
||||
type_id: typing.Optional[int] = None):
|
||||
self.fields = fields
|
||||
width = sum([ftype.width for (_, ftype) in self.fields])
|
||||
super().__init__(width, type_id)
|
||||
|
||||
def is_valid(self, obj) -> bool:
|
||||
fields_count = 0
|
||||
if isinstance(obj, dict):
|
||||
for (fname, ftype) in self.fields:
|
||||
if fname not in obj:
|
||||
return False
|
||||
if not ftype.is_valid(obj[fname]):
|
||||
return False
|
||||
fields_count += 1
|
||||
if fields_count != len(obj):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Port:
|
||||
|
||||
def __init__(self,
|
||||
client_path: typing.List[str],
|
||||
backend,
|
||||
impl_type: str,
|
||||
read_type: typing.Optional[Type] = None,
|
||||
write_type: typing.Optional[Type] = None):
|
||||
# If a backend doesn't support a particular implementation type, just skip
|
||||
# it. We don't want to error out on services which aren't being used.
|
||||
if backend.supports_impl(impl_type):
|
||||
self._backend = backend.get_port(client_path, read_type, write_type)
|
||||
else:
|
||||
self._backend = None
|
||||
self.client_path = client_path
|
||||
self.read_type = read_type
|
||||
self.write_type = write_type
|
||||
|
||||
|
||||
class WritePort(Port):
|
||||
|
||||
def write(self, msg=None) -> bool:
|
||||
assert self.write_type is not None, "Expected non-None write_type"
|
||||
if not self.write_type.is_valid(msg):
|
||||
raise ValueError(f"'{msg}' cannot be converted to '{self.write_type}'")
|
||||
if self._backend is None:
|
||||
raise ValueError("Backend does not support implementation of port")
|
||||
return self._backend.write(msg)
|
||||
|
||||
|
||||
class ReadPort(Port):
|
||||
|
||||
def read(self, blocking_timeout: typing.Optional[float] = 1.0):
|
||||
if self._backend is None:
|
||||
raise ValueError("Backend does not support implementation of port")
|
||||
return self._backend.read(blocking_timeout)
|
||||
|
||||
|
||||
class ReadWritePort(Port):
|
||||
|
||||
def __call__(self,
|
||||
msg=None,
|
||||
blocking_timeout: typing.Optional[float] = 1.0) -> typing.Any:
|
||||
"""Send a message and wait for a response. If 'timeout' is exceeded while
|
||||
waiting for a response, there may well be one coming. It is the caller's
|
||||
responsibility to clear the response channel before sending another request
|
||||
so as to ensure correlation between request and response.
|
||||
|
||||
Intended for blocking or synchronous interfaces."""
|
||||
|
||||
if not self.write(msg):
|
||||
raise RuntimeError(f"Could not send message '{msg}'")
|
||||
return self.read(blocking_timeout)
|
||||
|
||||
def write(self, msg=None) -> bool:
|
||||
assert self.write_type is not None, "Expected non-None write_type"
|
||||
if not self.write_type.is_valid(msg):
|
||||
raise ValueError(f"'{msg}' cannot be converted to '{self.write_type}'")
|
||||
return self._backend.write(msg)
|
||||
|
||||
def read(self, blocking_timeout: typing.Optional[float] = 1.0):
|
||||
return self._backend.read(blocking_timeout)
|
||||
|
||||
|
||||
class _CosimNode:
|
||||
"""Provides a capnp-based co-simulation backend."""
|
||||
|
||||
def __init__(self, root, prefix: typing.List[str]):
|
||||
self._root: Cosim = root
|
||||
self._endpoint_prefix = prefix
|
||||
|
||||
def supports_impl(self, impl_type: str) -> bool:
|
||||
"""The cosim backend only supports cosim connectivity implementations."""
|
||||
return impl_type == "cosim"
|
||||
|
||||
def get_child(self, child_name: str):
|
||||
"""When instantiating a child instance, get the backend node with which it
|
||||
is associated."""
|
||||
child_path = self._endpoint_prefix + [child_name]
|
||||
return _CosimNode(self._root, child_path)
|
||||
|
||||
def get_port(self,
|
||||
client_path: typing.List[str],
|
||||
read_type: typing.Optional[Type] = None,
|
||||
write_type: typing.Optional[Type] = None):
|
||||
"""When building a service port, get the backend port which it should use
|
||||
for interactions."""
|
||||
path = ".".join(self._endpoint_prefix) + "." + "_".join(client_path)
|
||||
ep = self._root._open_endpoint(
|
||||
path,
|
||||
write_type=write_type.type_id if write_type is not None else None,
|
||||
read_type=read_type.type_id if read_type is not None else None)
|
||||
return _CosimPort(self, ep, read_type, write_type)
|
||||
|
||||
|
||||
class Cosim(_CosimNode):
|
||||
"""Connect to a Cap'N Proto RPC co-simulation and provide a cosim backend
|
||||
service."""
|
||||
|
||||
def __init__(self, schemaPath, hostPort):
|
||||
"""Load the schema and connect to the RPC server"""
|
||||
self._schema = capnp.load(schemaPath)
|
||||
self._rpc_client = capnp.TwoPartyClient(hostPort)
|
||||
self._cosim = self._rpc_client.bootstrap().cast_as(
|
||||
self._schema.CosimDpiServer)
|
||||
|
||||
# Find the simulation prefix and use it in our parent constructor.
|
||||
ifaces = self.list()
|
||||
prefix = [] if len(ifaces) == 0 else ifaces[0].endpointID.split(".")[:1]
|
||||
super().__init__(self, prefix)
|
||||
|
||||
def load_package(path: os.PathLike):
|
||||
"""Load a cosim connection from something running out of 'path' package dir.
|
||||
Reads and parses 'cosim.cfg' from that directory to get the connection
|
||||
information. Loads the capnp schema from the 'runtime' directory in that
|
||||
package path."""
|
||||
path = Path(path)
|
||||
simcfg = path / "cosim.cfg"
|
||||
if not simcfg.exists():
|
||||
simcfg = Path.cwd() / "cosim.cfg"
|
||||
if not simcfg.exists():
|
||||
raise RuntimeError("Could not find simulation connection file")
|
||||
port_lines = filter(lambda x: x.startswith("port:"),
|
||||
simcfg.open().readlines())
|
||||
port = int(list(port_lines)[0].split(":")[1])
|
||||
return Cosim(os.path.join(path, "runtime", "schema.capnp"),
|
||||
f"{os.uname()[1]}:{port}")
|
||||
|
||||
def list(self):
|
||||
"""List the available interfaces"""
|
||||
return self._cosim.list().wait().ifaces
|
||||
|
||||
def _open_endpoint(self, epid: str, write_type=None, read_type=None):
|
||||
"""Open the endpoint, optionally checking the send and recieve types"""
|
||||
for iface in self.list():
|
||||
if iface.endpointID == epid:
|
||||
# Optionally check that the type IDs match.
|
||||
if write_type is not None:
|
||||
assert iface.sendTypeID == write_type.schema.node.id
|
||||
else:
|
||||
assert write_type is None
|
||||
if read_type is not None:
|
||||
assert iface.recvTypeID == read_type.schema.node.id
|
||||
else:
|
||||
assert read_type is None
|
||||
|
||||
openResp = self._cosim.open(iface).wait()
|
||||
assert openResp.iface is not None
|
||||
return openResp.iface
|
||||
assert False, f"Could not find specified EndpointID: {epid}"
|
||||
|
||||
|
||||
class _CosimPort:
|
||||
"""Cosim backend for service ports. This is where the real meat is buried."""
|
||||
|
||||
class _TypeConverter:
|
||||
"""Parent class for Capnp type converters."""
|
||||
|
||||
def __init__(self, schema, esi_type: Type):
|
||||
self.esi_type = esi_type
|
||||
assert hasattr(esi_type, "capnp_name")
|
||||
if not hasattr(schema, esi_type.capnp_name):
|
||||
raise ValueError("Cosim does not support non-capnp types.")
|
||||
self.capnp_type = getattr(schema, esi_type.capnp_name)
|
||||
|
||||
class _VoidConverter(_TypeConverter):
|
||||
"""Convert python ints to and from capnp messages."""
|
||||
|
||||
def write(self, py_int: None):
|
||||
return self.capnp_type.new_message()
|
||||
|
||||
def read(self, capnp_resp) -> None:
|
||||
return capnp_resp.as_struct(self.capnp_type)
|
||||
|
||||
class _IntConverter(_TypeConverter):
|
||||
"""Convert python ints to and from capnp messages."""
|
||||
|
||||
def write(self, py_int: int):
|
||||
return self.capnp_type.new_message(i=py_int)
|
||||
|
||||
def read(self, capnp_resp) -> int:
|
||||
return capnp_resp.as_struct(self.capnp_type).i
|
||||
|
||||
class _StructConverter(_TypeConverter):
|
||||
"""Convert python ints to and from capnp messages."""
|
||||
|
||||
def write(self, py_dict: dict):
|
||||
return self.capnp_type.new_message(**py_dict)
|
||||
|
||||
def read(self, capnp_resp) -> int:
|
||||
capnp_msg = capnp_resp.as_struct(self.capnp_type)
|
||||
ret = {}
|
||||
for (fname, _) in self.esi_type.fields:
|
||||
if hasattr(capnp_msg, fname):
|
||||
ret[fname] = getattr(capnp_msg, fname)
|
||||
return ret
|
||||
|
||||
# Lookup table for getting the correct type converter for a given type.
|
||||
ConvertLookup = {
|
||||
VoidType: _VoidConverter,
|
||||
IntType: _IntConverter,
|
||||
StructType: _StructConverter
|
||||
}
|
||||
|
||||
def __init__(self, node: _CosimNode, endpoint,
|
||||
read_type: typing.Optional[Type],
|
||||
write_type: typing.Optional[Type]):
|
||||
self._endpoint = endpoint
|
||||
schema = node._root._schema
|
||||
# For each type, lookup the type converter and store that instead of the
|
||||
# type itself.
|
||||
if read_type is not None:
|
||||
converter = _CosimPort.ConvertLookup[type(read_type)]
|
||||
self._read_convert = converter(schema, read_type)
|
||||
if write_type is not None:
|
||||
converter = _CosimPort.ConvertLookup[type(write_type)]
|
||||
self._write_convert = converter(schema, write_type)
|
||||
|
||||
def write(self, msg) -> bool:
|
||||
"""Write a message to this port."""
|
||||
self._endpoint.send(self._write_convert.write(msg)).wait()
|
||||
return True
|
||||
|
||||
def read(self, blocking_time: typing.Optional[float]):
|
||||
"""Read a message from this port. If 'blocking_timeout' is None, return
|
||||
immediately. Otherwise, wait up to 'blocking_timeout' for a message. Returns
|
||||
the message if found, None if no message was read."""
|
||||
|
||||
if blocking_time is None:
|
||||
# Non-blocking.
|
||||
recvResp = self._endpoint.recv(False).wait()
|
||||
else:
|
||||
# Blocking. Since our cosim rpc server doesn't currently support blocking
|
||||
# reads, use polling instead.
|
||||
e = time.time() + blocking_time
|
||||
recvResp = None
|
||||
while recvResp is None or e > time.time():
|
||||
recvResp = self._endpoint.recv(False).wait()
|
||||
if recvResp.hasData:
|
||||
break
|
||||
else:
|
||||
time.sleep(0.001)
|
||||
if not recvResp.hasData:
|
||||
return None
|
||||
assert recvResp.resp is not None
|
||||
return self._read_convert.read(recvResp.resp)
|
|
@ -15,7 +15,6 @@ from .types import TypeAlias
|
|||
from . import circt
|
||||
from .circt import ir, passmanager
|
||||
from .circt.dialects import esi, hw, msft
|
||||
from .esi_api import PythonApiBuilder
|
||||
|
||||
from contextvars import ContextVar
|
||||
from collections.abc import Iterable
|
||||
|
|
Loading…
Reference in New Issue