mirror of https://github.com/llvm/circt.git
[circt-verilog-lsp-server] Add Verilog Language Server (#8234)
This is a first commit to add a Language Server Protocol (LSP) implementation for Verilog/SystemVerilog using the slang frontend. This enables IDE features like syntax error checking and diagnostics. The server is built as circt-verilog-lsp-server and integrates with CIRCT's existing Verilog import capabilities. It leverages MLIR's LSP server support libraries for the protocol implementation.
This commit is contained in:
parent
c7c7352daf
commit
6128e12465
|
@ -0,0 +1,30 @@
|
|||
# slang uses exceptions
|
||||
set(LLVM_REQUIRES_EH ON)
|
||||
set(LLVM_REQUIRES_RTTI ON)
|
||||
|
||||
# For ABI compatibility, define the DEBUG macro in debug builds. Slang sets this
|
||||
# internally. If we don't set this here as well, header-defined things like the
|
||||
# destructor of `Driver`, which is generated in ImportVerilog's compilation
|
||||
# unit, will destroy a different set of fields than what was potentially built
|
||||
# or modified by code compiled in the Slang compilation unit.
|
||||
add_compile_definitions($<$<CONFIG:Debug>:DEBUG>)
|
||||
|
||||
# Disable some compiler warnings caused by slang headers such that the
|
||||
# `ImportVerilog` build doesn't spew out a ton of warnings that are not related
|
||||
# to CIRCT.
|
||||
if (MSVC)
|
||||
# No idea what to put here
|
||||
else ()
|
||||
# slang uses exceptions; we intercept these in ImportVerilog
|
||||
add_compile_options(-fexceptions)
|
||||
add_compile_options(-frtti)
|
||||
# slang has some classes with virtual funcs but non-virtual destructor.
|
||||
add_compile_options(-Wno-non-virtual-dtor)
|
||||
# some other warnings we've seen
|
||||
add_compile_options(-Wno-c++98-compat-extra-semi)
|
||||
add_compile_options(-Wno-ctad-maybe-unsupported)
|
||||
add_compile_options(-Wno-cast-qual)
|
||||
# visitor switch statements cover all cases but have default
|
||||
add_compile_options(-Wno-covered-switch-default)
|
||||
endif ()
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Main entry function for circt-verilog-lsp-server for when built as standalone
|
||||
// binary.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef CIRCT_TOOLS_CIRCT_VERILOG_LSP_SERVER_CIRCTVERILOGLSPSERVERMAIN_H
|
||||
#define CIRCT_TOOLS_CIRCT_VERILOG_LSP_SERVER_CIRCTVERILOGLSPSERVERMAIN_H
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace llvm {
|
||||
struct LogicalResult;
|
||||
} // namespace llvm
|
||||
|
||||
namespace mlir {
|
||||
namespace lsp {
|
||||
class JSONTransport;
|
||||
} // namespace lsp
|
||||
} // namespace mlir
|
||||
|
||||
namespace circt {
|
||||
namespace lsp {
|
||||
struct VerilogServerOptions {
|
||||
VerilogServerOptions(const std::vector<std::string> &libDirs)
|
||||
: libDirs(libDirs) {}
|
||||
/// Additional list of RTL directories to search.
|
||||
const std::vector<std::string> &libDirs;
|
||||
};
|
||||
// namespace lsp
|
||||
|
||||
/// Implementation for tools like `circt-verilog-lsp-server`.
|
||||
llvm::LogicalResult
|
||||
CirctVerilogLspServerMain(const VerilogServerOptions &options,
|
||||
mlir::lsp::JSONTransport &transport);
|
||||
|
||||
} // namespace lsp
|
||||
} // namespace circt
|
||||
|
||||
#endif // CIRCT_TOOLS_CIRCT_VERILOG_LSP_SERVER_CIRCTVERILOGLSPSERVERMAIN_H
|
|
@ -1,32 +1,4 @@
|
|||
# slang uses exceptions
|
||||
set(LLVM_REQUIRES_EH ON)
|
||||
set(LLVM_REQUIRES_RTTI ON)
|
||||
|
||||
# For ABI compatibility, define the DEBUG macro in debug builds. Slang sets this
|
||||
# internally. If we don't set this here as well, header-defined things like the
|
||||
# destructor of `Driver`, which is generated in ImportVerilog's compilation
|
||||
# unit, will destroy a different set of fields than what was potentially built
|
||||
# or modified by code compiled in the Slang compilation unit.
|
||||
add_compile_definitions($<$<CONFIG:Debug>:DEBUG>)
|
||||
|
||||
# Disable some compiler warnings caused by slang headers such that the
|
||||
# `ImportVerilog` build doesn't spew out a ton of warnings that are not related
|
||||
# to CIRCT.
|
||||
if (MSVC)
|
||||
# No idea what to put here
|
||||
else ()
|
||||
# slang uses exceptions; we intercept these in ImportVerilog
|
||||
add_compile_options(-fexceptions)
|
||||
add_compile_options(-frtti)
|
||||
# slang has some classes with virtual funcs but non-virtual destructor.
|
||||
add_compile_options(-Wno-non-virtual-dtor)
|
||||
# some other warnings we've seen
|
||||
add_compile_options(-Wno-c++98-compat-extra-semi)
|
||||
add_compile_options(-Wno-ctad-maybe-unsupported)
|
||||
add_compile_options(-Wno-cast-qual)
|
||||
# visitor switch statements cover all cases but have default
|
||||
add_compile_options(-Wno-covered-switch-default)
|
||||
endif ()
|
||||
include(SlangCompilerOptions)
|
||||
|
||||
add_circt_translation_library(CIRCTImportVerilog
|
||||
Expressions.cpp
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
add_subdirectory(circt-bmc)
|
||||
add_subdirectory(circt-lec)
|
||||
add_subdirectory(rtgtool)
|
||||
|
||||
if(CIRCT_SLANG_FRONTEND_ENABLED)
|
||||
add_subdirectory(circt-verilog-lsp-server)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
add_subdirectory(Utils)
|
||||
add_subdirectory(VerilogServerImpl)
|
||||
add_circt_library(CIRCTVerilogLspServerLib
|
||||
CirctVerilogLspServerMain.cpp
|
||||
LSPServer.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${MLIR_MAIN_INCLUDE_DIR}/circt/Tools/circt-verilog-lsp-server
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
CIRCTVerilogLspServerImpl
|
||||
MLIRLspServerSupportLib
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/Tools/circt-verilog-lsp-server/CirctVerilogLspServerMain.h"
|
||||
#include "LSPServer.h"
|
||||
#include "VerilogServerImpl/VerilogServer.h"
|
||||
#include "mlir/Tools/lsp-server-support/Transport.h"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace mlir::lsp;
|
||||
|
||||
llvm::LogicalResult circt::lsp::CirctVerilogLspServerMain(
|
||||
const circt::lsp::VerilogServerOptions &options,
|
||||
mlir::lsp::JSONTransport &transport) {
|
||||
circt::lsp::VerilogServer server(options);
|
||||
return circt::lsp::runVerilogLSPServer(server, transport);
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "LSPServer.h"
|
||||
#include "VerilogServerImpl/VerilogServer.h"
|
||||
#include "mlir/Tools/lsp-server-support/Protocol.h"
|
||||
#include "mlir/Tools/lsp-server-support/Transport.h"
|
||||
#include <optional>
|
||||
|
||||
#define DEBUG_TYPE "circt-verilog-lsp-server"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace mlir::lsp;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// LSPServer
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
struct LSPServer {
|
||||
LSPServer(circt::lsp::VerilogServer &server, JSONTransport &transport)
|
||||
: server(server), transport(transport) {}
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Initialization
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
void onInitialize(const InitializeParams ¶ms,
|
||||
Callback<llvm::json::Value> reply);
|
||||
void onInitialized(const InitializedParams ¶ms);
|
||||
void onShutdown(const NoParams ¶ms, Callback<std::nullptr_t> reply);
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Document Change
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
void onDocumentDidOpen(const DidOpenTextDocumentParams ¶ms);
|
||||
void onDocumentDidClose(const DidCloseTextDocumentParams ¶ms);
|
||||
void onDocumentDidChange(const DidChangeTextDocumentParams ¶ms);
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Fields
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
circt::lsp::VerilogServer &server;
|
||||
JSONTransport &transport;
|
||||
|
||||
/// An outgoing notification used to send diagnostics to the client when they
|
||||
/// are ready to be processed.
|
||||
OutgoingNotification<PublishDiagnosticsParams> publishDiagnostics;
|
||||
|
||||
/// Used to indicate that the 'shutdown' request was received from the
|
||||
/// Language Server client.
|
||||
bool shutdownRequestReceived = false;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Initialization
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
void LSPServer::onInitialize(const InitializeParams ¶ms,
|
||||
Callback<llvm::json::Value> reply) {
|
||||
// Send a response with the capabilities of this server.
|
||||
llvm::json::Object serverCaps{
|
||||
{"textDocumentSync",
|
||||
llvm::json::Object{
|
||||
{"openClose", true},
|
||||
{"change", (int)TextDocumentSyncKind::Incremental},
|
||||
{"save", true},
|
||||
}}};
|
||||
|
||||
llvm::json::Object result{
|
||||
{{"serverInfo", llvm::json::Object{{"name", "circt-verilog-lsp-server"},
|
||||
{"version", "0.0.1"}}},
|
||||
{"capabilities", std::move(serverCaps)}}};
|
||||
reply(std::move(result));
|
||||
}
|
||||
void LSPServer::onInitialized(const InitializedParams &) {}
|
||||
void LSPServer::onShutdown(const NoParams &, Callback<std::nullptr_t> reply) {
|
||||
shutdownRequestReceived = true;
|
||||
reply(nullptr);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Document Change
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
void LSPServer::onDocumentDidOpen(const DidOpenTextDocumentParams ¶ms) {
|
||||
PublishDiagnosticsParams diagParams(params.textDocument.uri,
|
||||
params.textDocument.version);
|
||||
server.addDocument(params.textDocument.uri, params.textDocument.text,
|
||||
params.textDocument.version, diagParams.diagnostics);
|
||||
|
||||
// Publish any recorded diagnostics.
|
||||
publishDiagnostics(diagParams);
|
||||
}
|
||||
|
||||
void LSPServer::onDocumentDidClose(const DidCloseTextDocumentParams ¶ms) {
|
||||
std::optional<int64_t> version =
|
||||
server.removeDocument(params.textDocument.uri);
|
||||
if (!version)
|
||||
return;
|
||||
|
||||
// Empty out the diagnostics shown for this document. This will clear out
|
||||
// anything currently displayed by the client for this document (e.g. in the
|
||||
// "Problems" pane of VSCode).
|
||||
publishDiagnostics(
|
||||
PublishDiagnosticsParams(params.textDocument.uri, *version));
|
||||
}
|
||||
|
||||
void LSPServer::onDocumentDidChange(const DidChangeTextDocumentParams ¶ms) {
|
||||
PublishDiagnosticsParams diagParams(params.textDocument.uri,
|
||||
params.textDocument.version);
|
||||
server.updateDocument(params.textDocument.uri, params.contentChanges,
|
||||
params.textDocument.version, diagParams.diagnostics);
|
||||
|
||||
// Publish any recorded diagnostics.
|
||||
publishDiagnostics(diagParams);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Entry Point
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
LogicalResult circt::lsp::runVerilogLSPServer(VerilogServer &server,
|
||||
JSONTransport &transport) {
|
||||
LSPServer lspServer(server, transport);
|
||||
MessageHandler messageHandler(transport);
|
||||
|
||||
// Initialization
|
||||
messageHandler.method("initialize", &lspServer, &LSPServer::onInitialize);
|
||||
messageHandler.notification("initialized", &lspServer,
|
||||
&LSPServer::onInitialized);
|
||||
messageHandler.method("shutdown", &lspServer, &LSPServer::onShutdown);
|
||||
|
||||
// Document Changes
|
||||
messageHandler.notification("textDocument/didOpen", &lspServer,
|
||||
&LSPServer::onDocumentDidOpen);
|
||||
messageHandler.notification("textDocument/didClose", &lspServer,
|
||||
&LSPServer::onDocumentDidClose);
|
||||
messageHandler.notification("textDocument/didChange", &lspServer,
|
||||
&LSPServer::onDocumentDidChange);
|
||||
|
||||
// Diagnostics
|
||||
lspServer.publishDiagnostics =
|
||||
messageHandler.outgoingNotification<PublishDiagnosticsParams>(
|
||||
"textDocument/publishDiagnostics");
|
||||
|
||||
// Run the main loop of the transport.
|
||||
if (llvm::Error error = transport.run(messageHandler)) {
|
||||
Logger::error("Transport error: {0}", error);
|
||||
llvm::consumeError(std::move(error));
|
||||
return failure();
|
||||
}
|
||||
|
||||
return success(lspServer.shutdownRequestReceived);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LIB_CIRCT_TOOLS_CIRCT_VERILOG_LSP_LSPSERVER_H
|
||||
#define LIB_CIRCT_TOOLS_CIRCT_VERILOG_LSP_LSPSERVER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace llvm {
|
||||
struct LogicalResult;
|
||||
} // namespace llvm
|
||||
namespace mlir {
|
||||
namespace lsp {
|
||||
class JSONTransport;
|
||||
} // namespace lsp
|
||||
} // namespace mlir
|
||||
|
||||
namespace circt {
|
||||
namespace lsp {
|
||||
class VerilogServer;
|
||||
|
||||
/// Run the main loop of the LSP server using the given Verilog server and
|
||||
/// transport.
|
||||
llvm::LogicalResult runVerilogLSPServer(VerilogServer &server,
|
||||
mlir::lsp::JSONTransport &transport);
|
||||
|
||||
} // namespace lsp
|
||||
} // namespace circt
|
||||
|
||||
#endif // LIB_CIRCT_TOOLS_CIRCT_VERILOG_LSP_LSPSERVER_H
|
|
@ -0,0 +1,16 @@
|
|||
##===----------------------------------------------------------------------===//
|
||||
##
|
||||
## This directory contains the utilities for the Verilog LSP server. slang
|
||||
## currently requires RTTI but LLVM may not be built with RTTI. In that case
|
||||
## functions within VerilogServer.cpp use RTTI of LLVM, it will cause
|
||||
## compilation error. To avoid this, we specialize these functions in this
|
||||
## directory.
|
||||
##
|
||||
##===----------------------------------------------------------------------===//
|
||||
|
||||
add_circt_library(CIRCTVerilogLspServerUtils
|
||||
LSPUtils.cpp
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRLspServerSupportLib
|
||||
)
|
|
@ -0,0 +1,26 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file contains utilities for CIRCT Verilog LSP server.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "LSPUtils.h"
|
||||
#include "mlir/Tools/lsp-server-support/Logging.h"
|
||||
|
||||
void circt::lsp::Logger::error(Twine message) {
|
||||
mlir::lsp::Logger::error("{}", message);
|
||||
}
|
||||
|
||||
void circt::lsp::Logger::info(Twine message) {
|
||||
mlir::lsp::Logger::info("{}", message);
|
||||
}
|
||||
|
||||
void circt::lsp::Logger::debug(Twine message) {
|
||||
mlir::lsp::Logger::debug("{}", message);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef CIRCT_SUPPORT_LSPUTILS_H
|
||||
#define CIRCT_SUPPORT_LSPUTILS_H
|
||||
|
||||
#include "circt/Support/LLVM.h"
|
||||
namespace circt {
|
||||
|
||||
namespace lsp {
|
||||
namespace Logger {
|
||||
// These are specialization of logger functions. Slang requires RTTI but
|
||||
// usually LLVM is not built with RTTI. So it causes compilation error when
|
||||
// these functions are used in `VerilogServerImpl`.
|
||||
void error(Twine message);
|
||||
void info(Twine message);
|
||||
void debug(Twine message);
|
||||
} // namespace Logger
|
||||
} // namespace lsp
|
||||
} // namespace circt
|
||||
#endif // CIRCT_SUPPORT_LSPUTILS_H
|
|
@ -0,0 +1,18 @@
|
|||
# Include the Slang compiler options
|
||||
include(SlangCompilerOptions)
|
||||
|
||||
add_circt_library(CIRCTVerilogLspServerImpl
|
||||
VerilogServer.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${MLIR_MAIN_INCLUDE_DIR}/circt/Tools/crct-verilog-lsp-server
|
||||
|
||||
DEPENDS
|
||||
slang_slang
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRLspServerSupportLib
|
||||
CIRCTVerilogLspServerUtils
|
||||
PRIVATE
|
||||
slang_slang
|
||||
)
|
|
@ -0,0 +1,351 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements the VerilogServer class, which is responsible for
|
||||
// managing the state of the Verilog server. VerilogServer keeps track of the
|
||||
// contents of all open text documents, and each document has a slang
|
||||
// compilation result.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "VerilogServer.h"
|
||||
#include "../Utils/LSPUtils.h"
|
||||
|
||||
#include "circt/Support/LLVM.h"
|
||||
#include "circt/Tools/circt-verilog-lsp-server/CirctVerilogLspServerMain.h"
|
||||
#include "mlir/Tools/lsp-server-support/Logging.h"
|
||||
#include "mlir/Tools/lsp-server-support/Protocol.h"
|
||||
#include "slang/ast/Compilation.h"
|
||||
#include "slang/diagnostics/DiagnosticClient.h"
|
||||
#include "slang/diagnostics/Diagnostics.h"
|
||||
#include "slang/driver/Driver.h"
|
||||
#include "slang/syntax/SyntaxTree.h"
|
||||
#include "slang/text/SourceLocation.h"
|
||||
#include "slang/text/SourceManager.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
using namespace mlir;
|
||||
using namespace mlir::lsp;
|
||||
|
||||
using namespace circt::lsp;
|
||||
using namespace circt;
|
||||
|
||||
static mlir::lsp::DiagnosticSeverity
|
||||
getSeverity(slang::DiagnosticSeverity severity) {
|
||||
switch (severity) {
|
||||
case slang::DiagnosticSeverity::Fatal:
|
||||
case slang::DiagnosticSeverity::Error:
|
||||
return mlir::lsp::DiagnosticSeverity::Error;
|
||||
case slang::DiagnosticSeverity::Warning:
|
||||
return mlir::lsp::DiagnosticSeverity::Warning;
|
||||
case slang::DiagnosticSeverity::Ignored:
|
||||
case slang::DiagnosticSeverity::Note:
|
||||
return mlir::lsp::DiagnosticSeverity::Information;
|
||||
}
|
||||
llvm_unreachable("all slang diagnostic severities should be handled");
|
||||
return mlir::lsp::DiagnosticSeverity::Error;
|
||||
}
|
||||
namespace {
|
||||
|
||||
// A global context carried around by the server.
|
||||
struct VerilogServerContext {
|
||||
VerilogServerContext(const VerilogServerOptions &options)
|
||||
: options(options) {}
|
||||
const VerilogServerOptions &options;
|
||||
};
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VerilogDocument
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// This class represents all of the information pertaining to a specific
|
||||
/// Verilog document.
|
||||
class LSPDiagnosticClient;
|
||||
struct VerilogDocument {
|
||||
VerilogDocument(VerilogServerContext &globalContext,
|
||||
const mlir::lsp::URIForFile &uri, StringRef contents,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics);
|
||||
VerilogDocument(const VerilogDocument &) = delete;
|
||||
VerilogDocument &operator=(const VerilogDocument &) = delete;
|
||||
|
||||
const mlir::lsp::URIForFile &getURI() const { return uri; }
|
||||
|
||||
llvm::SourceMgr &getSourceMgr() { return sourceMgr; }
|
||||
llvm::SmallDenseMap<uint32_t, uint32_t> &getBufferIDMap() {
|
||||
return bufferIDMap;
|
||||
}
|
||||
|
||||
const slang::SourceManager &getSlangSourceManager() const {
|
||||
return driver.sourceManager;
|
||||
}
|
||||
|
||||
// Return LSP location from slang location.
|
||||
mlir::lsp::Location getLspLocation(slang::SourceLocation loc) const;
|
||||
|
||||
private:
|
||||
// A map from slang buffer ID to the corresponding buffer ID in the LLVM
|
||||
// source manager.
|
||||
llvm::SmallDenseMap<uint32_t, uint32_t> bufferIDMap;
|
||||
|
||||
// The compilation result.
|
||||
FailureOr<std::unique_ptr<slang::ast::Compilation>> compilation;
|
||||
|
||||
// The slang driver.
|
||||
slang::driver::Driver driver;
|
||||
|
||||
// The LLVM source manager.
|
||||
llvm::SourceMgr sourceMgr;
|
||||
|
||||
// The URI of the document.
|
||||
mlir::lsp::URIForFile uri;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// LSPDiagnosticClient
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
/// A converter that can be plugged into a slang `DiagnosticEngine` as a
|
||||
/// client that will map slang diagnostics to LSP diagnostics.
|
||||
class LSPDiagnosticClient : public slang::DiagnosticClient {
|
||||
const VerilogDocument &document;
|
||||
std::vector<mlir::lsp::Diagnostic> &diags;
|
||||
|
||||
public:
|
||||
LSPDiagnosticClient(const VerilogDocument &document,
|
||||
std::vector<mlir::lsp::Diagnostic> &diags)
|
||||
: document(document), diags(diags) {}
|
||||
|
||||
void report(const slang::ReportedDiagnostic &slangDiag) override;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void LSPDiagnosticClient::report(const slang::ReportedDiagnostic &slangDiag) {
|
||||
auto loc = document.getLspLocation(slangDiag.location);
|
||||
// Show only the diagnostics in the current file.
|
||||
if (loc.uri != document.getURI())
|
||||
return;
|
||||
auto &mlirDiag = diags.emplace_back();
|
||||
mlirDiag.severity = getSeverity(slangDiag.severity);
|
||||
mlirDiag.range = loc.range;
|
||||
mlirDiag.source = "slang";
|
||||
mlirDiag.message = slangDiag.formattedMessage;
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VerilogDocument
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
VerilogDocument::VerilogDocument(
|
||||
VerilogServerContext &context, const mlir::lsp::URIForFile &uri,
|
||||
StringRef contents, std::vector<mlir::lsp::Diagnostic> &diagnostics)
|
||||
: uri(uri) {
|
||||
unsigned int bufferId;
|
||||
if (auto memBufferOwn =
|
||||
llvm::MemoryBuffer::getMemBufferCopy(contents, uri.file())) {
|
||||
|
||||
bufferId = sourceMgr.AddNewSourceBuffer(std::move(memBufferOwn), SMLoc());
|
||||
} else {
|
||||
circt::lsp::Logger::error(
|
||||
Twine("Failed to create memory buffer for file ") + uri.file());
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the set of include directories for this file.
|
||||
llvm::SmallString<32> uriDirectory(uri.file());
|
||||
llvm::sys::path::remove_filename(uriDirectory);
|
||||
|
||||
std::vector<std::string> libDirs;
|
||||
libDirs.push_back(uriDirectory.str().str());
|
||||
libDirs.insert(libDirs.end(), context.options.libDirs.begin(),
|
||||
context.options.libDirs.end());
|
||||
|
||||
// Populate source managers.
|
||||
const llvm::MemoryBuffer *memBuffer = sourceMgr.getMemoryBuffer(bufferId);
|
||||
|
||||
driver.options.libDirs = libDirs;
|
||||
// Assign text to slang.
|
||||
auto slangBuffer =
|
||||
driver.sourceManager.assignText(uri.file(), memBuffer->getBuffer());
|
||||
driver.buffers.push_back(slangBuffer);
|
||||
bufferIDMap[slangBuffer.id.getId()] = bufferId;
|
||||
|
||||
auto diagClient = std::make_shared<LSPDiagnosticClient>(*this, diagnostics);
|
||||
driver.diagEngine.addClient(diagClient);
|
||||
|
||||
if (!driver.parseAllSources()) {
|
||||
circt::lsp::Logger::error(Twine("Failed to parse Verilog file ") +
|
||||
uri.file());
|
||||
return;
|
||||
}
|
||||
|
||||
compilation = driver.createCompilation();
|
||||
for (auto &diag : (*compilation)->getAllDiagnostics())
|
||||
driver.diagEngine.issue(diag);
|
||||
}
|
||||
|
||||
mlir::lsp::Location
|
||||
VerilogDocument::getLspLocation(slang::SourceLocation loc) const {
|
||||
if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) {
|
||||
const auto &slangSourceManager = getSlangSourceManager();
|
||||
auto line = slangSourceManager.getLineNumber(loc) - 1;
|
||||
auto column = slangSourceManager.getColumnNumber(loc) - 1;
|
||||
auto it = bufferIDMap.find(loc.buffer().getId());
|
||||
// Check if the current buffer is the main file.
|
||||
if (it != bufferIDMap.end() && it->second == sourceMgr.getMainFileID())
|
||||
return mlir::lsp::Location(uri, mlir::lsp::Range(Position(line, column)));
|
||||
|
||||
// Otherwise, construct URI from slang source manager.
|
||||
auto fileName = slangSourceManager.getFileName(loc);
|
||||
auto loc = mlir::lsp::URIForFile::fromFile(
|
||||
slangSourceManager.makeAbsolutePath(fileName));
|
||||
if (auto e = loc.takeError())
|
||||
return mlir::lsp::Location();
|
||||
return mlir::lsp::Location(loc.get(),
|
||||
mlir::lsp::Range(Position(line, column)));
|
||||
}
|
||||
|
||||
return mlir::lsp::Location();
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VerilogTextFile
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
namespace {
|
||||
/// This class represents a text file containing one or more Verilog
|
||||
/// documents.
|
||||
class VerilogTextFile {
|
||||
public:
|
||||
VerilogTextFile(VerilogServerContext &globalContext,
|
||||
const mlir::lsp::URIForFile &uri, StringRef fileContents,
|
||||
int64_t version,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics);
|
||||
|
||||
/// Return the current version of this text file.
|
||||
int64_t getVersion() const { return version; }
|
||||
|
||||
/// Update the file to the new version using the provided set of content
|
||||
/// changes. Returns failure if the update was unsuccessful.
|
||||
LogicalResult
|
||||
update(const mlir::lsp::URIForFile &uri, int64_t newVersion,
|
||||
ArrayRef<mlir::lsp::TextDocumentContentChangeEvent> changes,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics);
|
||||
|
||||
private:
|
||||
/// Initialize the text file from the given file contents.
|
||||
void initialize(const mlir::lsp::URIForFile &uri, int64_t newVersion,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics);
|
||||
|
||||
VerilogServerContext &context;
|
||||
|
||||
/// The full string contents of the file.
|
||||
std::string contents;
|
||||
|
||||
/// The version of this file.
|
||||
int64_t version = 0;
|
||||
|
||||
/// The chunks of this file. The order of these chunks is the order in which
|
||||
/// they appear in the text file.
|
||||
std::unique_ptr<VerilogDocument> document;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
VerilogTextFile::VerilogTextFile(
|
||||
VerilogServerContext &context, const mlir::lsp::URIForFile &uri,
|
||||
StringRef fileContents, int64_t version,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics)
|
||||
: context(context), contents(fileContents.str()) {
|
||||
initialize(uri, version, diagnostics);
|
||||
}
|
||||
|
||||
LogicalResult VerilogTextFile::update(
|
||||
const mlir::lsp::URIForFile &uri, int64_t newVersion,
|
||||
ArrayRef<mlir::lsp::TextDocumentContentChangeEvent> changes,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics) {
|
||||
if (failed(mlir::lsp::TextDocumentContentChangeEvent::applyTo(changes,
|
||||
contents))) {
|
||||
circt::lsp::Logger::error(Twine("Failed to update contents of ") +
|
||||
uri.file());
|
||||
return failure();
|
||||
}
|
||||
|
||||
// If the file contents were properly changed, reinitialize the text file.
|
||||
initialize(uri, newVersion, diagnostics);
|
||||
return success();
|
||||
}
|
||||
|
||||
void VerilogTextFile::initialize(
|
||||
const mlir::lsp::URIForFile &uri, int64_t newVersion,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics) {
|
||||
version = newVersion;
|
||||
document =
|
||||
std::make_unique<VerilogDocument>(context, uri, contents, diagnostics);
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VerilogServer::Impl
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct circt::lsp::VerilogServer::Impl {
|
||||
explicit Impl(const VerilogServerOptions &options) : context(options) {}
|
||||
|
||||
/// The files held by the server, mapped by their URI file name.
|
||||
llvm::StringMap<std::unique_ptr<VerilogTextFile>> files;
|
||||
|
||||
VerilogServerContext context;
|
||||
};
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// VerilogServer
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
circt::lsp::VerilogServer::VerilogServer(const VerilogServerOptions &options)
|
||||
: impl(std::make_unique<Impl>(options)) {}
|
||||
circt::lsp::VerilogServer::~VerilogServer() = default;
|
||||
|
||||
void circt::lsp::VerilogServer::addDocument(
|
||||
const URIForFile &uri, StringRef contents, int64_t version,
|
||||
std::vector<mlir::lsp::Diagnostic> &diagnostics) {
|
||||
impl->files[uri.file()] = std::make_unique<VerilogTextFile>(
|
||||
impl->context, uri, contents, version, diagnostics);
|
||||
}
|
||||
|
||||
void circt::lsp::VerilogServer::updateDocument(
|
||||
const URIForFile &uri,
|
||||
ArrayRef<mlir::lsp::TextDocumentContentChangeEvent> changes,
|
||||
int64_t version, std::vector<mlir::lsp::Diagnostic> &diagnostics) {
|
||||
// Check that we actually have a document for this uri.
|
||||
auto it = impl->files.find(uri.file());
|
||||
if (it == impl->files.end())
|
||||
return;
|
||||
|
||||
// Try to update the document. If we fail, erase the file from the server. A
|
||||
// failed updated generally means we've fallen out of sync somewhere.
|
||||
if (failed(it->second->update(uri, version, changes, diagnostics)))
|
||||
impl->files.erase(it);
|
||||
}
|
||||
|
||||
std::optional<int64_t>
|
||||
circt::lsp::VerilogServer::removeDocument(const URIForFile &uri) {
|
||||
auto it = impl->files.find(uri.file());
|
||||
if (it == impl->files.end())
|
||||
return std::nullopt;
|
||||
|
||||
int64_t version = it->second->getVersion();
|
||||
impl->files.erase(it);
|
||||
return version;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Main entry function for circt-verilog-lsp-server for when built as standalone
|
||||
// binary.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LIB_CIRCT_TOOLS_CIRCT_VERILOG_LSP_SERVER_VERILOGSERVER_H_
|
||||
#define LIB_CIRCT_TOOLS_CIRCT_VERILOG_LSP_SERVER_VERILOGSERVER_H_
|
||||
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace mlir {
|
||||
namespace lsp {
|
||||
struct Diagnostic;
|
||||
struct TextDocumentContentChangeEvent;
|
||||
class URIForFile;
|
||||
} // namespace lsp
|
||||
} // namespace mlir
|
||||
|
||||
namespace circt {
|
||||
namespace lsp {
|
||||
struct VerilogServerOptions;
|
||||
using TextDocumentContentChangeEvent =
|
||||
mlir::lsp::TextDocumentContentChangeEvent;
|
||||
using URIForFile = mlir::lsp::URIForFile;
|
||||
using Diagnostic = mlir::lsp::Diagnostic;
|
||||
|
||||
/// This class implements all of the Verilog related functionality necessary for
|
||||
/// a language server. This class allows for keeping the Verilog specific logic
|
||||
/// separate from the logic that involves LSP server/client communication.
|
||||
class VerilogServer {
|
||||
public:
|
||||
VerilogServer(const circt::lsp::VerilogServerOptions &options);
|
||||
~VerilogServer();
|
||||
|
||||
/// Add the document, with the provided `version`, at the given URI. Any
|
||||
/// diagnostics emitted for this document should be added to `diagnostics`.
|
||||
void addDocument(const URIForFile &uri, llvm::StringRef contents,
|
||||
int64_t version, std::vector<Diagnostic> &diagnostics);
|
||||
|
||||
/// Update the document, with the provided `version`, at the given URI. Any
|
||||
/// diagnostics emitted for this document should be added to `diagnostics`.
|
||||
void updateDocument(const URIForFile &uri,
|
||||
llvm::ArrayRef<TextDocumentContentChangeEvent> changes,
|
||||
int64_t version, std::vector<Diagnostic> &diagnostics);
|
||||
|
||||
/// Remove the document with the given uri. Returns the version of the removed
|
||||
/// document, or std::nullopt if the uri did not have a corresponding document
|
||||
/// within the server.
|
||||
std::optional<int64_t> removeDocument(const URIForFile &uri);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace lsp
|
||||
} // namespace circt
|
||||
|
||||
#endif // LIB_CIRCT_TOOLS_CIRCT_VERILOG_LSP_SERVER_VERILOGSERVER_H_
|
|
@ -48,6 +48,7 @@ endif()
|
|||
|
||||
if(CIRCT_SLANG_FRONTEND_ENABLED)
|
||||
list(APPEND CIRCT_TEST_DEPENDS circt-verilog)
|
||||
list(APPEND CIRCT_TEST_DEPENDS circt-verilog-lsp-server)
|
||||
endif()
|
||||
|
||||
add_lit_testsuite(check-circt "Running the CIRCT regression tests"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// RUN: circt-verilog-lsp-server -lit-test < %s | FileCheck %s
|
||||
// REQUIRES: slang
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"verilog","capabilities":{},"trace":"off"}}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
|
||||
"uri":"test:///diagnostic.sv",
|
||||
"languageId":"verilog",
|
||||
"version":1,
|
||||
"text":"module foo()\n wire bar;\nendmodule"
|
||||
}}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK-NEXT: "diagnostics": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "message": "expected ';'",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "character": 12,
|
||||
// CHECK-NEXT: "line": 0
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "start": {
|
||||
// CHECK-NEXT: "character": 12,
|
||||
// CHECK-NEXT: "line": 0
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "severity": 1,
|
||||
// CHECK-NEXT: "source": "slang"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "message": "unused net 'bar'",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "character": 6,
|
||||
// CHECK-NEXT: "line": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "start": {
|
||||
// CHECK-NEXT: "character": 6,
|
||||
// CHECK-NEXT: "line": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "severity": 2,
|
||||
// CHECK-NEXT: "source": "slang"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ],
|
||||
// CHECK-NEXT: "uri": "test:///diagnostic.sv",
|
||||
// CHECK-NEXT: "version": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
// -----
|
||||
{"jsonrpc":"2.0","id":7,"method":"shutdown"}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -0,0 +1,38 @@
|
|||
// RUN: circt-verilog-lsp-server -lit-test < %s | FileCheck %s
|
||||
// RUN: circt-verilog-lsp-server --libdir %S/include -lit-test < %s | FileCheck %s --check-prefix=CHECK-INCLUDE
|
||||
// REQUIRES: slang
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"verilog","capabilities":{},"trace":"off"}}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
|
||||
"uri":"test:///diagnostic.sv",
|
||||
"languageId":"verilog",
|
||||
"version":1,
|
||||
"text":"module Foo();\n Bar bar();\nendmodule"
|
||||
}}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK-NEXT: "diagnostics": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "message": "unknown module 'Bar'",
|
||||
// CHECK-NEXT: "range": {
|
||||
// CHECK-NEXT: "end": {
|
||||
// CHECK-NEXT: "character": 1,
|
||||
// CHECK-NEXT: "line": 1
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "start": {
|
||||
// CHECK-NEXT: "character": 1,
|
||||
// CHECK-NEXT: "line": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "severity": 1,
|
||||
// CHECK-NEXT: "source": "slang"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ],
|
||||
// CHECK-NEXT: "uri": "test:///diagnostic.sv",
|
||||
// CHECK-NEXT: "version": 1
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-INCLUDE-NOT: unknown module 'Bar'
|
||||
// -----
|
||||
{"jsonrpc":"2.0","id":7,"method":"shutdown"}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -0,0 +1,2 @@
|
|||
module Bar();
|
||||
endmodule
|
|
@ -0,0 +1,26 @@
|
|||
// RUN: circt-verilog-lsp-server -lit-test < %s | FileCheck %s
|
||||
// REQUIRES: slang
|
||||
// Test initialize request parameters with rootUri
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}}
|
||||
// CHECK: "id": 0,
|
||||
// CHECK-NEXT: "jsonrpc": "2.0",
|
||||
// CHECK-NEXT: "result": {
|
||||
// CHECK-NEXT: "capabilities": {
|
||||
// CHECK-NEXT: "textDocumentSync": {
|
||||
// CHECK-NEXT: "change": 2,
|
||||
// CHECK-NEXT: "openClose": true,
|
||||
// CHECK-NEXT: "save": true
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "serverInfo": {
|
||||
// CHECK-NEXT: "name": "circt-verilog-lsp-server",
|
||||
// CHECK-NEXT: "version": "{{.*}}"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
// -----
|
||||
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|
||||
// CHECK: "id": 3,
|
||||
// CHECK-NEXT: "jsonrpc": "2.0",
|
||||
// CHECK-NEXT: "result": null
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -0,0 +1 @@
|
|||
config.excludes = ["include"]
|
|
@ -0,0 +1,35 @@
|
|||
// RUN: circt-verilog-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
// REQUIRES: slang
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"verilog","capabilities":{},"trace":"off"}}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
|
||||
"uri":"test:///foo.sv",
|
||||
"languageId":"verilog",
|
||||
"version":1,
|
||||
"text":"module Foo(); "
|
||||
}}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK: "diagnostics": [
|
||||
// CHECK: "message": "expected 'endmodule'",
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{
|
||||
"uri":"test:///foo.sv",
|
||||
"version":2
|
||||
}, "contentChanges": [{
|
||||
"range":{
|
||||
"start":{"line":0,"character":14},
|
||||
"end":{"line":0,"character":14}
|
||||
},
|
||||
"text": " endmodule"
|
||||
}]}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK-NEXT: "diagnostics": [],
|
||||
// CHECK-NEXT: "uri": "test:///foo.sv",
|
||||
// CHECK-NEXT: "version": 2
|
||||
// CHECK-NEXT: }
|
||||
// -----
|
||||
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -0,0 +1,29 @@
|
|||
// RUN: circt-verilog-lsp-server -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
// REQUIRES: slang
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"verilog","capabilities":{},"trace":"off"}}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{
|
||||
"uri":"test:///foo.sv",
|
||||
"languageId":"verilog",
|
||||
"version":1,
|
||||
"text":"module Foo(); "
|
||||
}}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK: "diagnostics": [
|
||||
// CHECK: "message": "expected 'endmodule'",
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{
|
||||
"uri":"test:///foo.sv",
|
||||
"version":2
|
||||
}}}
|
||||
// CHECK: "method": "textDocument/publishDiagnostics",
|
||||
// CHECK-NEXT: "params": {
|
||||
// CHECK-NEXT: "diagnostics": [],
|
||||
// CHECK-NEXT: "uri": "test:///foo.sv",
|
||||
// CHECK-NEXT: "version": 1
|
||||
// CHECK-NEXT: }
|
||||
// -----
|
||||
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|
||||
// -----
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -22,7 +22,7 @@ config.name = 'CIRCT'
|
|||
config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell)
|
||||
|
||||
# suffixes: A list of file extensions to treat as test files.
|
||||
config.suffixes = ['.td', '.mlir', '.ll', '.fir', '.sv']
|
||||
config.suffixes = ['.td', '.mlir', '.ll', '.fir', '.sv', '.test']
|
||||
|
||||
# test_source_root: The root path where tests are located.
|
||||
config.test_source_root = os.path.dirname(__file__)
|
||||
|
@ -91,5 +91,6 @@ if config.scheduling_or_tools != "":
|
|||
if config.slang_frontend_enabled:
|
||||
config.available_features.add('slang')
|
||||
tools.append('circt-verilog')
|
||||
tools.append('circt-verilog-lsp-server')
|
||||
|
||||
llvm_config.add_tool_substitutions(tools, tool_dirs)
|
||||
|
|
|
@ -20,4 +20,5 @@ add_subdirectory(py-split-input-file)
|
|||
|
||||
if(CIRCT_SLANG_FRONTEND_ENABLED)
|
||||
add_subdirectory(circt-verilog)
|
||||
add_subdirectory(circt-verilog-lsp-server)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
set(libs
|
||||
CIRCTVerilogLspServerLib
|
||||
)
|
||||
|
||||
add_circt_tool(circt-verilog-lsp-server circt-verilog-lsp-server.cpp DEPENDS ${libs})
|
||||
target_link_libraries(circt-verilog-lsp-server PRIVATE ${libs})
|
||||
|
||||
llvm_update_compile_flags(circt-verilog-lsp-server)
|
||||
mlir_check_all_link_libraries(circt-verilog-lsp-server)
|
|
@ -0,0 +1,101 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements a utility to run CIRCT Verilog LSP server.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "circt/Tools/circt-verilog-lsp-server/CirctVerilogLspServerMain.h"
|
||||
|
||||
#include "mlir/Tools/lsp-server-support/Logging.h"
|
||||
#include "mlir/Tools/lsp-server-support/Transport.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/Program.h"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace mlir::lsp;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
//===--------------------------------------------------------------------===//
|
||||
// LSP options
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
llvm::cl::opt<Logger::Level> logLevel{
|
||||
"log",
|
||||
llvm::cl::desc("Verbosity of log messages written to stderr"),
|
||||
llvm::cl::values(
|
||||
clEnumValN(Logger::Level::Error, "error", "Error messages only"),
|
||||
clEnumValN(Logger::Level::Info, "info",
|
||||
"High level execution tracing"),
|
||||
clEnumValN(Logger::Level::Debug, "verbose", "Low level details")),
|
||||
llvm::cl::init(Logger::Level::Info),
|
||||
};
|
||||
|
||||
llvm::cl::opt<mlir::lsp::JSONStreamStyle> inputStyle{
|
||||
"input-style",
|
||||
llvm::cl::desc("Input JSON stream encoding"),
|
||||
llvm::cl::values(clEnumValN(mlir::lsp::JSONStreamStyle::Standard,
|
||||
"standard", "usual LSP protocol"),
|
||||
clEnumValN(mlir::lsp::JSONStreamStyle::Delimited,
|
||||
"delimited",
|
||||
"messages delimited by `// -----` lines, "
|
||||
"with // comment support")),
|
||||
llvm::cl::init(mlir::lsp::JSONStreamStyle::Standard),
|
||||
llvm::cl::Hidden,
|
||||
};
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Include paths
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
llvm::cl::list<std::string> libDirs{
|
||||
"y",
|
||||
llvm::cl::desc(
|
||||
"Library search paths, which will be searched for missing modules"),
|
||||
llvm::cl::value_desc("dir"), llvm::cl::Prefix};
|
||||
llvm::cl::alias libDirsLong{"libdir", llvm::cl::desc("Alias for -y"),
|
||||
llvm::cl::aliasopt(libDirs), llvm::cl::NotHidden};
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Testing
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
||||
llvm::cl::opt<bool> prettyPrint{
|
||||
"pretty",
|
||||
llvm::cl::desc("Pretty-print JSON output"),
|
||||
llvm::cl::init(false),
|
||||
};
|
||||
llvm::cl::opt<bool> litTest{
|
||||
"lit-test",
|
||||
llvm::cl::desc(
|
||||
"Abbreviation for -input-style=delimited -pretty -log=verbose. "
|
||||
"Intended to simplify lit tests"),
|
||||
llvm::cl::init(false),
|
||||
};
|
||||
|
||||
llvm::cl::ParseCommandLineOptions(argc, argv, "Verilog LSP Language Server");
|
||||
|
||||
if (litTest) {
|
||||
inputStyle = mlir::lsp::JSONStreamStyle::Delimited;
|
||||
logLevel = mlir::lsp::Logger::Level::Debug;
|
||||
prettyPrint = true;
|
||||
}
|
||||
|
||||
// Configure the logger.
|
||||
mlir::lsp::Logger::setLogLevel(logLevel);
|
||||
|
||||
// Configure the transport used for communication.
|
||||
(void)llvm::sys::ChangeStdinToBinary();
|
||||
mlir::lsp::JSONTransport transport(stdin, llvm::outs(), inputStyle,
|
||||
prettyPrint);
|
||||
|
||||
// Configure the servers and start the main language server.
|
||||
circt::lsp::VerilogServerOptions options(libDirs);
|
||||
return failed(circt::lsp::CirctVerilogLspServerMain(options, transport));
|
||||
}
|
Loading…
Reference in New Issue