From 4db68370294ed826131a1a93860e7cfa67f14358 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Tue, 10 Dec 2024 23:17:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20modelsim=20|=20vivado=20?= =?UTF-8?q?=E7=9A=84=E8=AF=8A=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/diagnostics/iverilog.rs | 4 +- src/diagnostics/modelsim.rs | 66 +++++++++++++++++++- src/diagnostics/verible.rs | 115 +++++++++++++++-------------------- src/diagnostics/verilator.rs | 69 +++++++++++++++------ src/diagnostics/vivado.rs | 67 +++++++++++++++++++- 5 files changed, 229 insertions(+), 92 deletions(-) diff --git a/src/diagnostics/iverilog.rs b/src/diagnostics/iverilog.rs index 17715cb..df649f2 100644 --- a/src/diagnostics/iverilog.rs +++ b/src/diagnostics/iverilog.rs @@ -6,9 +6,9 @@ use serde::{Deserialize, Serialize}; use ropey::Rope; use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; -use crate::{diagnostics::find_non_whitespace_indices, server::LspServer, sources::LSPSupport, utils::command::is_command_valid}; +use crate::{diagnostics::find_non_whitespace_indices, server::LspServer}; -use super::{AbstractLinterConfiguration, LinterStatus}; +use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct IverilogConfiguration { diff --git a/src/diagnostics/modelsim.rs b/src/diagnostics/modelsim.rs index 8b692e1..b75e972 100644 --- a/src/diagnostics/modelsim.rs +++ b/src/diagnostics/modelsim.rs @@ -1,10 +1,13 @@ +use std::process::{Command, Stdio}; + #[allow(unused)] use log::info; +use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::{server::LspServer, utils::command::is_command_valid}; -use super::{AbstractLinterConfiguration, LinterStatus}; +use crate::{diagnostics::find_non_whitespace_indices, server::LspServer}; +use super::AbstractLinterConfiguration; use ropey::Rope; -use tower_lsp::lsp_types::{Diagnostic, Url}; +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; #[derive(Debug, Serialize, Deserialize)] pub struct ModelsimConfiguration { @@ -48,10 +51,67 @@ impl AbstractLinterConfiguration for ModelsimConfiguration { fn provide_diagnostics( &self, uri: &Url, + #[allow(unused)] rope: &Rope, + #[allow(unused)] server: &LspServer ) -> Option> { let mut diagnostics = Vec::::new(); + let invoke_name = self.get_invoke_name(); + let pathbuf = uri.to_file_path().unwrap(); + let path_string = pathbuf.to_str().unwrap(); + + let child = Command::new(&invoke_name) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .args(&self.linter.args) + .arg(path_string) + .spawn() + .ok()?; + + let output = child.wait_with_output().ok()?; + let output_string = String::from_utf8(output.stdout).ok()?; + + info!("vivado linter: {:?}, output:\n{}", path_string, output_string); + + static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); + let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"ERROR: \[VRFC \d+-\d+\] (?P.+) \[(?P.+):(?P\d+)\]").unwrap() }); + + for error_line in output_string.lines() { + let caps = match regex.captures(error_line) { + Some(caps) => caps, + None => continue + }; + + let error_description = caps.name("description").unwrap().as_str(); + let error_no = caps.name("line").unwrap().as_str(); + let error_no = match error_no.parse::() { + // Mentor Modelsim vlog 在报告错误和警告时,行数是从 1 开始计数的。 + Ok(no) => no - 1, + Err(_) => 0 + }; + + if let Some((start_char, end_char)) = find_non_whitespace_indices(rope, error_no) { + let range = Range { + start: Position { line: error_no as u32, character: start_char as u32 }, + end: Position { line: error_no as u32, character: end_char as u32 } + }; + + let diagnostic = Diagnostic { + range, + code: None, + severity: Some(DiagnosticSeverity::ERROR), + source: Some("Digital IDE: modelsim".to_string()), + message: error_description.to_string(), + related_information: None, + tags: None, + code_description: None, + data: None + }; + diagnostics.push(diagnostic); + } + } Some(diagnostics) } diff --git a/src/diagnostics/verible.rs b/src/diagnostics/verible.rs index 44d7fa6..308d683 100644 --- a/src/diagnostics/verible.rs +++ b/src/diagnostics/verible.rs @@ -1,14 +1,11 @@ +use log::info; use serde::{Deserialize, Serialize}; use regex::Regex; use ropey::Rope; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; - -use crate::{server::LspServer, utils::command::is_command_valid}; - -use super::{AbstractLinterConfiguration, LinterStatus}; - - +use crate::server::LspServer; +use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct VeribleConfiguration { @@ -58,65 +55,6 @@ impl Default for VeribleFormat { } - - -/// syntax checking using verible-verilog-syntax -fn verible_syntax( - rope: &Rope, - verible_syntax_path: &str, - verible_syntax_args: &[String], -) -> Option> { - let mut child = Command::new(verible_syntax_path) - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .args(verible_syntax_args) - .arg("-") - .spawn() - .ok()?; - - static RE: std::sync::OnceLock = std::sync::OnceLock::new(); - let re = RE.get_or_init(|| { - Regex::new( - r"^.+:(?P\d*):(?P\d*)(?:-(?P\d*))?:\s(?P.*)\s.*$", - ) - .unwrap() - }); - // write file to stdin, read output from stdout - rope.write_to(child.stdin.as_mut()?).ok()?; - - let output = child.wait_with_output().ok()?; - if !output.status.success() { - let mut diags: Vec = Vec::new(); - let raw_output = String::from_utf8(output.stdout).ok()?; - for error in raw_output.lines() { - let caps = re.captures(error)?; - let line: u32 = caps.name("line")?.as_str().parse().ok()?; - let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?; - let endcol: Option = match caps.name("endcol").map(|e| e.as_str().parse()) { - Some(Ok(e)) => Some(e), - None => None, - Some(Err(_)) => return None, - }; - let start_pos = Position::new(line - 1, startcol - 1); - let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1); - diags.push(Diagnostic::new( - Range::new(start_pos, end_pos), - Some(DiagnosticSeverity::ERROR), - None, - Some("verible".to_string()), - caps.name("message")?.as_str().to_string(), - None, - None, - )); - } - Some(diags) - } else { - None - } -} - - impl AbstractLinterConfiguration for VeribleConfiguration { fn new(language_id: &str, invoker: &str, args: Vec) -> Self { VeribleConfiguration { @@ -131,9 +69,56 @@ impl AbstractLinterConfiguration for VeribleConfiguration { uri: &Url, #[allow(unused)] rope: &Rope, + #[allow(unused)] server: &LspServer ) -> Option> { let mut diagnostics = Vec::::new(); + + let invoke_name = self.get_invoke_name(); + let pathbuf = uri.to_file_path().unwrap(); + let path_string = pathbuf.to_str().unwrap(); + + let child = Command::new(&invoke_name) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .args(&self.linter.args) + .arg(path_string) + .spawn() + .ok()?; + + let output = child.wait_with_output().ok()?; + let output_string = String::from_utf8(output.stdout).ok()?; + + info!("verible linter: {:?}, output:\n{}", path_string, output_string); + + static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); + let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"^.+:(?P\d*):(?P\d*)(?:-(?P\d*))?:\s(?P.*)\s.*$").unwrap() }); + + for error_line in output_string.lines() { + let caps = regex.captures(error_line)?; + let line: u32 = caps.name("line")?.as_str().parse().ok()?; + let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?; + let endcol: Option = match caps.name("endcol").map(|e| e.as_str().parse()) { + Some(Ok(e)) => Some(e), + None => None, + Some(Err(_)) => return None, + }; + let start_pos = Position::new(line - 1, startcol - 1); + let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1); + let diagnostic = Diagnostic { + range: Range::new(start_pos, end_pos), + severity: Some(DiagnosticSeverity::ERROR), + code: None, + source: Some("Digital IDE: verible".to_string()), + message: caps.name("message")?.as_str().to_string(), + related_information: None, + tags: None, + code_description: None, + data: None + }; + diagnostics.push(diagnostic); + } Some(diagnostics) } diff --git a/src/diagnostics/verilator.rs b/src/diagnostics/verilator.rs index 7fa7858..2dd8f40 100644 --- a/src/diagnostics/verilator.rs +++ b/src/diagnostics/verilator.rs @@ -2,14 +2,10 @@ use log::info; use serde::{Deserialize, Serialize}; use regex::Regex; use ropey::Rope; -use std::path::PathBuf; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; - use crate::server::LspServer; -use crate::utils::command::is_command_valid; - -use super::{AbstractLinterConfiguration, LinterStatus}; +use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct VerilatorConfiguration { @@ -83,12 +79,12 @@ impl AbstractLinterConfiguration for VerilatorConfiguration { let output = child.wait_with_output().ok()?; let output_string = String::from_utf8(output.stderr).ok()?; + let lint_level = server.srcs.get_lsp_configuration_string_value("digital-ide.function.lsp.linter.linter-level").unwrap_or("error".to_string()); info!("verilator linter: {:?}, output:\n{}", path_string, output_string); static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"%(?PError|Warning)(-(?P[A-Z0-9_]+))?: (?P[^:]+):(?P\d+):((?P\d+):)? ?(?P.*)").unwrap() }); - // verilator 错误输出样例 // %Warning-IMPLICIT: library/Apply/Comm/FDE/AGC/AGC.v:23:12: Signal definition not found, creating implicitly: 'r_rms' // : ... Suggested alternative: 'ref_rms' @@ -116,31 +112,46 @@ impl AbstractLinterConfiguration for VerilatorConfiguration { } for error_tuple in error_tuples { - let captures = match regex.captures(error_tuple.0.as_str()) { + // 对于 NOTFOUNDMODULE 进行过滤 + // 如果存在于 fast 中,则直接跳过 + if let Some(module_name) = match_not_module_found(error_tuple.0.as_str()) { + if server.srcs.contains_module(&module_name) { + continue; + } + } + + // 使用模板进行解析 + let caps = match regex.captures(error_tuple.0.as_str()) { Some(caps) => caps, None => continue }; // 因为 verilator 会递归检查,因此报错信息中可能会出现非本文件内的信息 - if captures.name("filepath")?.as_str().replace("\\", "/") != path_string { + if caps.name("filepath")?.as_str().replace("\\", "/") != path_string { continue; } - // 对于 NOTFOUNDMODULE - - let line: u32 = captures.name("line")?.as_str().to_string().parse().ok()?; - let col: u32 = captures.name("col").map_or("1", |m| m.as_str()).parse().ok()?; + let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?; + let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?; // verilator 的诊断索引都是 one index 的 let pos = Position::new(line - 1, col - 1); - let severity = verilator_severity(captures.name("severity")?.as_str()); + let severity = verilator_severity(caps.name("severity")?.as_str()); let message = match severity { - Some(DiagnosticSeverity::ERROR) => captures.name("message")?.as_str().to_string(), - Some(DiagnosticSeverity::WARNING) => format!( - "{}: {}", - captures.name("warning_type")?.as_str(), - captures.name("message")?.as_str() - ), + Some(DiagnosticSeverity::ERROR) => { + caps.name("message")?.as_str().to_string() + }, + Some(DiagnosticSeverity::WARNING) => { + // 如果诊断等级为 error ,也就是只显示错误,那么直接跳过 + if lint_level == "error" { + continue; + } + format!( + "{}: {}", + caps.name("warning_type")?.as_str(), + caps.name("message")?.as_str() + ) + }, _ => "".to_string(), }; @@ -175,4 +186,24 @@ impl AbstractLinterConfiguration for VerilatorConfiguration { self.linter.exe_name.to_string() } } +} + + +/// %Warning-DECLFILENAME: /home/dide/project/Digital-Test/DIDEtemp/user/sim/FFT/FFT_IFFT_tb.v:82:5: Filename 'FFT_IFFT_tb' does not match NOTFOUNDMODULE name: 'FFT_IFFT' +/// 匹配最后一个 module 的 name +fn match_not_module_found(error_line: &str) -> Option { + if let Some(start_index) = error_line.find("NOTFOUNDMODULE") { + let mut strings = "".to_string(); + for char in error_line.chars().skip(start_index).skip_while(|x| x.to_string() != "'") { + strings.push(char); + } + + info!("strings: {:?}", strings); + if strings.starts_with("'") && strings.ends_with("'") { + strings = strings[1 .. strings.len() - 1].to_string(); + } + return Some(strings); + } + + None } \ No newline at end of file diff --git a/src/diagnostics/vivado.rs b/src/diagnostics/vivado.rs index 79e6699..f30dc57 100644 --- a/src/diagnostics/vivado.rs +++ b/src/diagnostics/vivado.rs @@ -1,11 +1,14 @@ +use std::process::{Command, Stdio}; + #[allow(unused)] use log::info; +use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::{server::LspServer, utils::command::is_command_valid}; +use crate::{diagnostics::find_non_whitespace_indices, server::LspServer}; use ropey::Rope; -use tower_lsp::lsp_types::{Diagnostic, Url}; +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; -use super::{AbstractLinterConfiguration, LinterStatus}; +use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct VivadoConfiguration { @@ -48,9 +51,67 @@ impl AbstractLinterConfiguration for VivadoConfiguration { uri: &Url, #[allow(unused)] rope: &Rope, + #[allow(unused)] server: &LspServer ) -> Option> { let mut diagnostics = Vec::::new(); + + let invoke_name = self.get_invoke_name(); + let pathbuf = uri.to_file_path().unwrap(); + let path_string = pathbuf.to_str().unwrap(); + + let child = Command::new(&invoke_name) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .args(&self.linter.args) + .arg(path_string) + .spawn() + .ok()?; + + let output = child.wait_with_output().ok()?; + let output_string = String::from_utf8(output.stdout).ok()?; + + info!("vivado linter: {:?}, output:\n{}", path_string, output_string); + + static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); + let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"ERROR: \[VRFC \d+-\d+\] (?P.+) \[(?P.+):(?P\d+)\]").unwrap() }); + + for error_line in output_string.lines() { + let caps = match regex.captures(error_line) { + Some(caps) => caps, + None => continue + }; + + let error_description = caps.name("description").unwrap().as_str(); + let error_no = caps.name("line").unwrap().as_str(); + let error_no = match error_no.parse::() { + // Xilinx Vivado xvlog 在报告错误和警告时,行数是从 1 开始计数的。 + Ok(no) => no - 1, + Err(_) => 0 + }; + + if let Some((start_char, end_char)) = find_non_whitespace_indices(rope, error_no) { + let range = Range { + start: Position { line: error_no as u32, character: start_char as u32 }, + end: Position { line: error_no as u32, character: end_char as u32 } + }; + + let diagnostic = Diagnostic { + range, + code: None, + severity: Some(DiagnosticSeverity::ERROR), + source: Some("Digital IDE: vivado".to_string()), + message: error_description.to_string(), + related_information: None, + tags: None, + code_description: None, + data: None + }; + diagnostics.push(diagnostic); + } + } + Some(diagnostics) }