xilinx IP 支持 | 增加全局 conf 属性

This commit is contained in:
锦恢 2024-11-11 21:06:12 +08:00
parent f055b2bbc3
commit cbddc07bdc
8 changed files with 253 additions and 87 deletions

View File

@ -1,9 +1,10 @@
use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, sync::{Arc, RwLock}}; use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, str::FromStr, sync::{Arc, RwLock}};
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::Url;
use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize}; use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize, to_escape_path};
use super::hdlparam::FastHdlparam; use super::hdlparam::FastHdlparam;
@ -59,7 +60,17 @@ impl CacheManager {
} }
} }
pub fn is_big_file(&self, path: &PathBuf) -> bool { pub fn is_big_file(path: &PathBuf) -> bool {
if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) {
return size >= 1024;
}
false
}
pub fn uri_is_big_file(uri: &Url) -> bool {
let path = PathBuf::from_str(uri.path()).unwrap();
let path = to_escape_path(&path);
if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) { if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) {
return size >= 1024; return size >= 1024;
} }
@ -83,7 +94,7 @@ impl CacheManager {
/// * 如果本来就没有缓存 /// * 如果本来就没有缓存
/// * 缓冲的 version 对不上 /// * 缓冲的 version 对不上
pub fn try_get_fast_cache(&self, path: &PathBuf) -> CacheResult<FastHdlparam> { pub fn try_get_fast_cache(&self, path: &PathBuf) -> CacheResult<FastHdlparam> {
if !self.is_big_file(path) { if !CacheManager::is_big_file(path) {
return CacheResult::NotNeedCache return CacheResult::NotNeedCache
} }
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();

View File

@ -171,6 +171,31 @@ impl Port {
port_desc_array.push(self.width.to_string()); port_desc_array.push(self.width.to_string());
} }
port_desc_array.push(self.name.to_string());
let port_desc = port_desc_array.join(" ");
port_desc
}
pub fn vhdl_to_vlog_description(&self) -> String {
let mut port_desc_array = Vec::<String>::new();
let dir_type = match self.dir_type.as_str() {
"in" => "input",
"out" => "output",
_ => &self.dir_type
};
port_desc_array.push(dir_type.to_string());
if self.net_type != "unknown" {
port_desc_array.push(self.net_type.to_string());
}
if self.signed != "unsigned" {
port_desc_array.push("signed".to_string());
}
if self.width != "1" {
port_desc_array.push(self.width.to_string());
}
port_desc_array.push(self.name.to_string()); port_desc_array.push(self.name.to_string());
let port_desc = port_desc_array.join(" "); let port_desc = port_desc_array.join(" ");
port_desc port_desc

View File

@ -207,6 +207,8 @@ pub fn goto_module_declaration_definition(
token_name: &str token_name: &str
) -> Option<GotoDefinitionResponse> { ) -> Option<GotoDefinitionResponse> {
let hdl_param = server.srcs.hdl_param.clone(); let hdl_param = server.srcs.hdl_param.clone();
info!("get into goto_module_declaration_definition");
if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) { if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) {
match file_type.as_str() { match file_type.as_str() {
"common" => { "common" => {
@ -280,6 +282,7 @@ fn goto_ip_module_declaration_definition(
let target_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap(); let target_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap();
let target_range = module.range.clone(); let target_range = module.range.clone();
info!("target range: {:?}", target_range);
let target_range = target_range.to_lsp_range(); let target_range = target_range.to_lsp_range();
let link = vec![LocationLink { let link = vec![LocationLink {

View File

@ -433,6 +433,8 @@ fn hover_ip_module_declaration(
if def_path_buf.exists() { if def_path_buf.exists() {
// TODO: 当前工具链只支持 Xilinx 下的工具链,所以此处的代码是 vhdl 的代码 // TODO: 当前工具链只支持 Xilinx 下的工具链,所以此处的代码是 vhdl 的代码
// 如果未来需要支持其他的工具链,则需要从 server 下读取对应的变量
let path_uri = Url::from_file_path(def_path.to_string()).unwrap().to_string(); let path_uri = Url::from_file_path(def_path.to_string()).unwrap().to_string();
let def_row = module.range.start.line; let def_row = module.range.start.line;
let def_col = module.range.start.character; let def_col = module.range.start.character;
@ -467,7 +469,7 @@ fn hover_ip_module_declaration(
markdowns.push(MarkedString::String("---".to_string())); markdowns.push(MarkedString::String("---".to_string()));
let language_id = get_language_id_by_path_str(def_path); let language_id = get_language_id_by_path_str(def_path);
let module_profile = make_module_profile_code(&module); let module_profile = make_entity_profile_code(&module);
let profile_markdown = LanguageString { let profile_markdown = LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
value: module_profile value: module_profile
@ -596,11 +598,109 @@ pub fn make_module_profile_code(module: &crate::core::hdlparam::Module) -> Strin
codes.push(format!("\t{port_desc},")); codes.push(format!("\t{port_desc},"));
} }
} }
codes.push(format!(")"));
} else {
codes.push(format!(")"));
} }
codes.push(format!(");"));
let profile_string = codes.join("\n"); let profile_string = codes.join("\n");
profile_string profile_string
} }
/// vhdl 的 entity 的 profile
pub fn make_entity_profile_code(module: &crate::core::hdlparam::Module) -> String {
let mut codes = Vec::<String>::new();
// param 其实就是 generic
let param_num = module.params.len();
let port_num = module.ports.len();
codes.push(format!("entity {} is", module.name));
// 缩进字符
if module.params.len() > 0 {
codes.push("\tgeneric (".to_string());
let max_param_name_length = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0);
let max_param_type_length = module.params.iter().map(|param| param.net_type.len()).max().unwrap_or(0);
for (i, param) in module.params.iter().enumerate() {
let mut param_desc_array = Vec::<String>::new();
let param_name_align_spaces = " ".repeat(max_param_name_length - param.name.len() + 1);
param_desc_array.push(format!("{}{}:", param.name, param_name_align_spaces));
let param_type_align_spaces = " ".repeat(max_param_type_length - param.net_type.len() + 1);
param_desc_array.push(format!("{}{}", param.net_type, param_type_align_spaces));
if param.init != "unknown" {
param_desc_array.push(format!(":= {}", param.init.to_string()));
}
let param_desc = param_desc_array.join(" ");
if i == param_num - 1 {
codes.push(format!("\t\t{param_desc}"));
} else {
codes.push(format!("\t\t{param_desc};"));
}
}
codes.push("\t);".to_string());
}
if module.ports.len() > 0 {
codes.push("\tport (".to_string());
let net_mapper = |net_type: &str| {
if net_type == "unknown" {
0
} else {
net_type.len()
}
};
let width_mapper = |width: &str| {
if width == "1" {
0
} else {
width.len() + 5
}
};
let max_port_length = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0);
let max_net_length = module.ports.iter().map(|port| net_mapper(&port.net_type)).max().unwrap_or(0);
// let max_width_length = module.ports.iter().map(|port| width_mapper(&port.width)).max().unwrap_or(0);
let max_dir_length = module.ports.iter().map(|port| port.dir_type.len()).max().unwrap_or(0);
for (i, port) in module.ports.iter().enumerate() {
let mut port_desc_array = Vec::<String>::new();
// port 的名字
let port_name_align_spaces = " ".repeat(max_port_length - port.name.len() + 1);
port_desc_array.push(format!("{}{}: ", port.name, port_name_align_spaces));
// in, out, inout
let port_dir_align_spaces = " ".repeat(max_dir_length - port.dir_type.len() + 1);
port_desc_array.push(format!("{}{}", port.dir_type, port_dir_align_spaces));
// std_logic, signed, unsigned 等等
if port.net_type != "unknown" {
port_desc_array.push(port.net_type.to_string());
}
// (57 downto 0)
if port.width != "1" {
// 内部的 width 统一表示成 [57:0] 这样的格式,需要转换一下
let width_string = port.width.replace("[", "(").replace("]", ")").replace(":", " downto ");
port_desc_array.push(width_string);
}
let port_desc = port_desc_array.join("");
if i == port_num - 1 {
codes.push(format!("\t\t{port_desc}"));
} else {
codes.push(format!("\t\t{port_desc};"));
}
}
codes.push("\t);".to_string());
}
codes.push(format!("end entity {};", module.name));
let profile_string = codes.join("\n");
profile_string
}

View File

@ -6,7 +6,7 @@ use log::info;
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use super::to_escape_path; use super::{get_language_id_by_path_str, to_escape_path};
pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> { pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
let uri = &params.text_document.uri; let uri = &params.text_document.uri;
@ -95,7 +95,6 @@ fn find_instport_inlay_hints_position(
let end_offset = position_to_index(rope, range.end); let end_offset = position_to_index(rope, range.end);
let instport_text = rope.slice(start_offset .. end_offset); let instport_text = rope.slice(start_offset .. end_offset);
let instport_text = instport_text.to_string(); let instport_text = instport_text.to_string();
info!("{:?}", instport_text);
if let Some(offset) = instport_text.find("(") { if let Some(offset) = instport_text.find("(") {
let target_offset = start_offset + offset + 1; let target_offset = start_offset + offset + 1;
@ -112,12 +111,13 @@ fn make_instport_hints(
instance: &core::hdlparam::Instance, instance: &core::hdlparam::Instance,
rope: &Rope rope: &Rope
) -> Vec<InlayHint> { ) -> Vec<InlayHint> {
let hdl_param = server.srcs.hdl_param.clone();
let mut hints = Vec::<InlayHint>::new(); let mut hints = Vec::<InlayHint>::new();
let define_module = server.srcs.hdl_param.find_module_by_name(&instance.inst_type); let module_context = hdl_param.find_module_context_by_name(&instance.inst_type);
if define_module.is_none() { if module_context.is_none() {
return hints; return hints;
} }
let define_module = define_module.unwrap(); let (define_module, file_type, def_path) = module_context.unwrap();
// 制作 port name 到 port 的映射 // 制作 port name 到 port 的映射
let mut port_map = HashMap::<String, &core::hdlparam::Port>::new(); let mut port_map = HashMap::<String, &core::hdlparam::Port>::new();
for port in &define_module.ports { for port in &define_module.ports {
@ -132,15 +132,18 @@ fn make_instport_hints(
} }
let port_info = port.unwrap(); let port_info = port.unwrap();
let instport_range = port_assigment.range.to_lsp_range(); let instport_range = port_assigment.range.to_lsp_range();
info!("inst name: {:?}, range: {:?}", instance.name, instport_range);
if let Some(hint_position) = find_instport_inlay_hints_position(rope, &instport_range) { if let Some(hint_position) = find_instport_inlay_hints_position(rope, &instport_range) {
let port_desc = MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```verilog\n{}\n```", port_info.to_description())
};
let label = { let label = {
let dir_type = port_info.dir_type.to_string(); let dir_type = port_info.dir_type.to_string();
// vhdl 转成 verilog
let dir_type = match dir_type.as_str() {
"out" => "output",
"in" => "input",
_ => &dir_type
};
if dir_type == "output" { if dir_type == "output" {
format!("{}", dir_type) format!("{}", dir_type)
} else { } else {
@ -148,6 +151,28 @@ fn make_instport_hints(
} }
}; };
let language_id = get_language_id_by_path_str(&def_path);
let port_desc_value = match file_type.as_str() {
"common" => {
format!("```{}\n{}\n```", language_id, port_info.to_description())
}
"ip" => {
// TODO: 支持更多的 IP
format!("```verilog\n{}\n```", port_info.vhdl_to_vlog_description())
}
"primitives" => {
format!("```{}\n{}\n```", language_id, port_info.to_description())
}
_ => {
format!("```{}\n{}\n```", language_id, port_info.to_description())
}
};
let port_desc = MarkupContent {
kind: MarkupKind::Markdown,
value: port_desc_value
};
let hint = InlayHint { let hint = InlayHint {
position: hint_position, position: hint_position,

View File

@ -255,6 +255,23 @@ fn do_vhdl_fast(
}; };
hdl_param.update_fast(path.to_string(), ip_fast.clone()); hdl_param.update_fast(path.to_string(), ip_fast.clone());
return Ok(ip_fast); return Ok(ip_fast);
} else if let Some(design_file) = vhdl_parse(&pathbuf) {
if let Some(mut fast) = make_fast_from_design_file(&design_file) {
fast.file_type = file_type.to_string();
// IP 不需要内部的 instance其实现是加密的只需要暴露 module 的接口即可
// 所以此处需要清空所有的 module 中的 instance
for module in &mut fast.content {
module.instances.clear();
}
hdl_param.update_fast(path.to_string(), fast.clone());
return Ok(fast);
}
} else {
return Err(tower_lsp::jsonrpc::Error {
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
data: None
});
} }
}, },
_ => {} _ => {}
@ -264,9 +281,6 @@ fn do_vhdl_fast(
// 没有特殊情况,则正常解析并写入 // 没有特殊情况,则正常解析并写入
if let Some(design_file) = vhdl_parse(&pathbuf) { if let Some(design_file) = vhdl_parse(&pathbuf) {
if let Some(mut fast) = make_fast_from_design_file(&design_file) { if let Some(mut fast) = make_fast_from_design_file(&design_file) {
if path.contains("ip") {
info!("vhdl fast: {:?}", fast);
}
fast.file_type = file_type.to_string(); fast.file_type = file_type.to_string();
hdl_param.update_fast(path.to_string(), fast.clone()); hdl_param.update_fast(path.to_string(), fast.clone());
return Ok(fast); return Ok(fast);

View File

@ -69,29 +69,15 @@ pub enum LogLevel {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ProjectConfig { pub struct ProjectConfig {
// if true, recursively search the working directory for files to run diagnostics on pub workspace_folder: Option<Url>,
pub auto_search_workdir: bool, pub tool_chain: String
// list of directories with header files
pub include_dirs: Vec<String>,
// list of directories to recursively search for SystemVerilog/Verilog sources
pub source_dirs: Vec<String>,
// config options for verible tools
pub verible: Verible,
// config options for verilator tools
pub verilator: Verilator,
// log level
pub log_level: LogLevel,
} }
impl Default for ProjectConfig { impl Default for ProjectConfig {
fn default() -> Self { fn default() -> Self {
ProjectConfig { ProjectConfig {
auto_search_workdir: true, workspace_folder: None,
include_dirs: Vec::new(), tool_chain: "xilinx".to_string()
source_dirs: Vec::new(),
verible: Verible::default(),
verilator: Verilator::default(),
log_level: LogLevel::Info,
} }
} }
} }
@ -170,15 +156,23 @@ impl Default for VeribleFormat {
#[tower_lsp::async_trait] #[tower_lsp::async_trait]
impl LanguageServer for Backend { impl LanguageServer for Backend {
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> { async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
self.server.srcs.init();
// 申明 LSP 的基本信息和提供的能力 // 申明 LSP 的基本信息和提供的能力
let server_info = Some(ServerInfo { let server_info = Some(ServerInfo {
name: "Digital IDE 专用 LSP 后端服务器".to_string(), name: "Digital IDE 专用 LSP 后端服务器".to_string(),
version: Some("0.4.0".to_string()) version: Some("0.4.0".to_string())
}); });
let root_uri = &params.root_uri;
if root_uri.is_none() {
self.client.show_message(MessageType::ERROR, "LSP 启动失败,原因: 没有找到启动的 workspaceFolder").await;
return Ok(InitializeResult::default());
}
let mut configure = self.server.conf.write().unwrap();
configure.workspace_folder = root_uri.clone();
info!("当前客户端配置 workspaceFolder: {:?}", params.root_uri);
let text_document_sync = TextDocumentSyncCapability::Options( let text_document_sync = TextDocumentSyncCapability::Options(
TextDocumentSyncOptions { TextDocumentSyncOptions {
open_close: Some(true), open_close: Some(true),
@ -239,18 +233,30 @@ impl LanguageServer for Backend {
} }
async fn did_open(&self, params: DidOpenTextDocumentParams) { async fn did_open(&self, params: DidOpenTextDocumentParams) {
let diagnostics = self.server.did_open(params); // // 如果文件太大则显示错误
self.client // if CacheManager::uri_is_big_file(&params.text_document.uri) {
.publish_diagnostics( // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务")
diagnostics.uri, // .await;
diagnostics.diagnostics, // } else {
diagnostics.version, let diagnostics = self.server.did_open(params);
) self.client
.await; .publish_diagnostics(
diagnostics.uri,
diagnostics.diagnostics,
diagnostics.version,
)
.await;
// }
} }
async fn did_change(&self, params: DidChangeTextDocumentParams) { async fn did_change(&self, params: DidChangeTextDocumentParams) {
self.server.did_change(params); // // 如果文件太大则显示错误
// if CacheManager::uri_is_big_file(&params.text_document.uri) {
// // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务")
// // .await;
// } else {
self.server.did_change(params);
// }
} }
async fn did_delete_files(&self, params: DeleteFilesParams) { async fn did_delete_files(&self, params: DeleteFilesParams) {
@ -258,14 +264,18 @@ impl LanguageServer for Backend {
} }
async fn did_save(&self, params: DidSaveTextDocumentParams) { async fn did_save(&self, params: DidSaveTextDocumentParams) {
let diagnostics = self.server.did_save(params); // if CacheManager::uri_is_big_file(&params.text_document.uri) {
self.client
.publish_diagnostics( // } else {
diagnostics.uri, let diagnostics = self.server.did_save(params);
diagnostics.diagnostics, self.client
diagnostics.version, .publish_diagnostics(
) diagnostics.uri,
.await; diagnostics.diagnostics,
diagnostics.version,
)
.await;
// }
} }
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {

View File

@ -1,3 +1,4 @@
use crate::core::cache_storage::CacheManager;
use crate::core::hdlparam::HdlParam; use crate::core::hdlparam::HdlParam;
use crate::core::sv_parser::make_fast_from_syntaxtree; use crate::core::sv_parser::make_fast_from_syntaxtree;
use crate::core::vhdl_parser::make_fast_from_design_file; use crate::core::vhdl_parser::make_fast_from_design_file;
@ -237,35 +238,10 @@ impl Sources {
fast_sync_controller: Arc::new(RwLock::new(HashMap::<String, Arc<RwLock<bool>>>::new())) fast_sync_controller: Arc::new(RwLock::new(HashMap::<String, Arc<RwLock<bool>>>::new()))
} }
} }
pub fn init(&self) {
let mut paths: Vec<PathBuf> = Vec::new();
for path in &*self.include_dirs.read().unwrap() {
paths.push(path.clone());
}
for path in &*self.source_dirs.read().unwrap() {
paths.push(path.clone());
}
// find and add all source/header files recursively from configured include and source directories
let src_paths = find_src_paths(&paths);
for path in src_paths {
let url = unwrap_result!(Url::from_file_path(&path));
let text = unwrap_result!(fs::read_to_string(&path));
let doc = TextDocumentItem::new(
url,
"systemverilog".to_string(),
-1,
text,
);
self.add(doc);
}
}
/// 增加一个 hdl 文件,并为该文件添加单独的解析线程 /// 增加一个 hdl 文件,并为该文件添加单独的解析线程
pub fn add(&self, doc: TextDocumentItem) { pub fn add(&self, doc: TextDocumentItem) {
// use a condvar to synchronize the parse thread // 对于当前的文件增加一个解析线程,不断进行解析和同步
// the valid bool decides whether or not the file
// needs to be re-parsed
#[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516 #[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516
let valid_parse = Arc::new((Mutex::new(false), Condvar::new())); let valid_parse = Arc::new((Mutex::new(false), Condvar::new()));
let valid_parse2 = valid_parse.clone(); let valid_parse2 = valid_parse.clone();
@ -689,8 +665,10 @@ pub fn vhdl_parser_pipeline(
return; return;
} }
let mut file = source_handle.write().unwrap(); // TODO: 通过更加精细的方法获取下面的变量
let mut file = source_handle.write().unwrap();
let mut scope_tree = if let Some(design_file) = vhdl_parse(&escape_path) { let mut scope_tree = if let Some(design_file) = vhdl_parse(&escape_path) {
//let mut design_files = design_file_handle.write().unwrap(); //let mut design_files = design_file_handle.write().unwrap();