use tower_lsp::lsp_types::*; /// 文档:IEEE 1364-2005 /// author: LSTM-Kirigaya /// date: 2024.12.13 pub const VLOG_DIRECTIVES: &[(&str, &str, &str)] = &[ // 1800-2009 22.13 `__FILE__ and `__LINE__ ( "__FILE__", "`__FILE__", r#"用于获取当前文件名 ```verilog // 文件名: /path/to/your/project/test.v `timescale 1ns/1ps module test; initial begin // 输出 当前文件名:/path/to/your/project/test.v $display("当前文件名: %s", `__FILE__); end endmodule ``` "#), ( "__LINE__", "`__LINE__", r#"用于获取当前行号 ```verilog // 文件名: /path/to/your/project/test.v `timescale 1ns/1ps module test; initial begin // 使用 __FILE__ 和 __LINE__ 宏输出当前文件名和行号 $display("当前文件名: %s, 当前行号: %0d", `__FILE__, `__LINE__); end endmodule ``` "#), // 19.1 `celldefine and `endcelldefine ( "celldefine", "`celldefine\n$1\n`endcelldefine", r#"开始定义单元格 `celldefine` 和 `endcelldefine` 是用于定义单元库模块的编译指令。它们通常用于定义可重用的硬件模块(如标准单元库),并控制这些模块的编译行为。 ```verilog // 使用 celldefine 和 endcelldefine 定义一个单元库模块 `celldefine module my_cell ( input a, input b, output out ); assign out = a & b; // 简单的与门逻辑 endmodule `endcelldefine // 测试模块 module test; reg a, b; wire out; // 实例化单元库模块 my_cell uut ( .a(a), .b(b), .out(out) ); initial begin // 初始化输入 a = 0; b = 0; // 仿真逻辑 #10; a = 1; #10; b = 1; #10; $display("输出结果: %b", out); end endmodule ``` 使用 `celldefine` 的优势: - 编译器可以将 `celldefine` 模块视为标准单元库的一部分,从而进行更高效的编译优化。例如,编译器可以提前处理这些模块的逻辑,减少仿真时的计算开销。 - 在大型设计中,标准单元库模块可能会被多次实例化。使用 celldefine 可以避免重复编译相同的模块,从而减少编译时间和内存占用。 "#), ( "endcelldefine", "`endcelldefine", "结束单元格定义" ), // 19.2 `default_nettype ( "default_nettype", "`default_nettype $1", "设置默认的网络类型" ), // 19.3 `define and `undef ( "define", "`define $1 $2", r#"定义一个宏 ### 语法 ```verilog `define MACRO_NAME value ``` ### 示例 ```verilog // 使用 `define 定义常量和代码片段 `define PORT_NUM 8 // 定义常量 PORT_NUM 为 8 `define ADD_ONE(x) (x + 1) // 定义带参数的宏 ADD_ONE module test; reg [(`PORT_NUM - 1):0] data; // 使用常量 PORT_NUM integer value; initial begin // 使用宏 ADD_ONE value = 5; $display("原始值: %0d", value); value = `ADD_ONE(value); $display("加一后的值: %0d", value); // 使用常量 PORT_NUM data = 8'hFF; $display("数据宽度: %0d 位", `PORT_NUM); $display("数据值: %h", data); end endmodule ``` "#), ( "undef", "`undef $1", "取消定义一个宏" ), // 1800-2009 22.5 `define, `undef and `undefineall ( "undefineall", "`undefineall", "取消定义所有宏" ), // 19.4 `ifdef, `else, `elsif, `endif, `ifndef ( "ifdef", "`ifdef $1\n\t//ifdef\n\t$2\n`else\n//else\n\t$3\n`endif", r#"ifdef、else 和 endif 是条件编译指令,用于根据宏定义的存在与否来选择性地编译代码。它们通常用于实现条件编译逻辑,以便在不同的编译环境下生成不同的代码。 ### 语法 ```verilog `ifdef MACRO_NAME // 如果 MACRO_NAME 已定义,编译此段代码 `else // 如果 MACRO_NAME 未定义,编译此段代码 `endif ``` ### 示例 ```verilog // 使用 `ifdef 和 `else 进行条件编译 `define DEBUG // 定义 DEBUG 宏 module test; reg [7:0] data; initial begin data = 8'hFF; // 使用 `ifdef 和 `else 进行条件编译 `ifdef DEBUG $display("调试模式: 数据值 = %h", data); `else $display("发布模式: 数据值 = %h", data); `endif end endmodule ``` 假设上述代码保存在文件 test.v 中,仿真输出可能如下: ``` 调试模式: 数据值 = FF ``` "# ), ( "else", "`else", "条件编译的 else 分支" ), ( "elsif", "`elsif $1", "条件编译的 elsif 分支" ), ( "endif", "`endif", "结束条件编译" ), ( "ifndef", "`ifndef $1", "如果未定义宏,则编译代码" ), // 19.5 `include ( "include", "`include $1", "包含一个外部文件" ), // 19.6 `resetall ( "resetall", "`resetall", // resetall 也常用于开始 // 1834-2005中指出,建议的用法是将 `resetall 放在每个源文本文件的开头,后面紧跟着文件中所需的指令。 // 通常可用于源文本开头,避免该文件受到其他文件代码的影响。或放置在末尾,避免编译器设置的进一步传递 r#"`resetall` 用于重置所有编译器设置为默认值。通常可用于源文本开头,避免该文件受到其他文件代码的影响。或放置在末尾,避免编译器设置的进一步传递。 1834-2005中指出,建议的用法是将 `resetall 放在每个源文本文件的开头,后面紧跟着文件中所需的指令。 ```verilog `timescale 1ns/1ps // 设置时间单位和精度 module test; // 一些 verilog 代码 endmodule `resetall // 重置所有编译器设置,上文的 timescale 设置自此开始无效 ``` "# ), // 19.7 `line ( "line", "`line $1 $2 $3", r#"`line` 用于指定当前代码的行号和文件名。它通常用于调试或日志记录,以便在仿真或编译时能够追踪代码的来源。 ### 语法 ```verilog `line line_number "filename" level ``` ### 示例 ```verilog // 使用 `line 指令指定行号和文件名 `line 10 "example.v" 0 module test; initial begin $display("当前行号: %0d", `__LINE__); $display("当前文件名: %s", `__FILE__); end endmodule ``` "# ), // 19.8 `timescale ( "timescale", "`timescale ${1:1ns}/${2:1ps}", // 原文是The time_precision argument shall be at least as precise as the time_unit argument; it cannot specify a longer unit of time than time_unit. // 也即是,仿真时间单位必须大于等于时间精度 // `timescale 1ns/1ns不会报错,但是`timescale 1ps/1ns会报错 // [Synth 8-522] parsing error: error in timescale or timeprecision statement. must be at least as precise as r#"设置时间单位和精度,设置时间单位和精度,格式为 `timescale 仿真时间单位/时间精度,仿真时间单位 必须大于等于 时间精度,例如 ```verilog `timescale 1ns/1ps module test; initial begin $display("当前时间单位: %0t", $time); // 输出当前时间 #10; // 延迟 10 纳秒 $display("延迟 10 纳秒后的时间: %0t", $time); end endmodule ``` 如果延迟是 #10.5,仿真器会将其解释为 10.5 纳秒,但精度会精确到皮秒级别。 在编译过程中,`timescale` 指令影响这一编译器指令后面所有模块中的时延值,直至遇到另一个 `timescale` 指令 `resetall` 指令。 "#), // 19.9 `unconnected_drive and `nounconnected_drive ( "unconnected_drive", "`unconnected_drive ${1:pull1}\n$2\n`nounconnected_drive\n", r#"`nounconnected_drive` 和 `unconnected_drive` 是配合使用的编译指令,用于控制未连接端口的驱动行为。它们通常成对出现,分别用于禁用和启用未连接端口的驱动处理。 ### 语法 ```verilog `unconnected_drive (pull1 | pull0) // 启用未连接端口的驱动,若为 pull1,则默认驱动为高电平,若为 pull0,则默认为低电平 // 你的 verilog 代码 `nounconnected_drive // 禁用未连接端口的驱动 ``` ### 示例 ```verilog // 使用 `line 指令指定行号和文件名 `unconnected_drive pull1 module my_and(y, a, b); output y; input a, b; assign y = a & b; endmodule module test(y,b); output y; input b; my_and ul(y, ,b); endmodule `nounconnected_drive ``` "# ), ( "nounconnected_drive", "`nounconnected_drive", r#"`nounconnected_drive` 和 `unconnected_drive` 是配合使用的编译指令,用于控制未连接端口的驱动行为。它们通常成对出现,分别用于禁用和启用未连接端口的驱动处理。 ### 语法 ```verilog `unconnected_drive (pull1 | pull0) // 启用未连接端口的驱动,若为 pull1,则默认驱动为高电平,若为 pull0,则默认为低电平 // 你的 verilog 代码 `nounconnected_drive // 禁用未连接端口的驱动 ``` "# ), // 19.10 `pragma ( "pragma", "`pragma $1", "编译指示,用于传递编译器指令" ), // 19.11 `begin_keywords, `end_keywords ( "begin_keywords", "`begin_keywords\n$1\n`end_keywords ", // begin_keywords并不是指定Verilog的编译版本,而是指定使用的Verilog关键字版本 r#"`begin_keywords` 和 `end_keywords` 是用于指定 Verilog 关键字版本的编译指令。它们允许设计者在同一个文件中使用不同版本的 Verilog 关键字语法,从而实现兼容性或逐步迁移到新版本。 `begin_keywords` 支持以下 Verilog 版本: - `1364-1995`:Verilog-1995 标准。 - `1364-2001`:Verilog-2001 标准。 - `1364-2005`:Verilog-2005 标准。 - `1800-2009`:SystemVerilog-2009 标准。 - `1800-2012`:SystemVerilog-2012 标准。 ```verilog // 使用 begin_keywords 和 end_keywords 指定 Verilog 版本 `begin_keywords "1364-2001" // 指定 Verilog-2001 语法 module my_module; reg [7:0] data; initial begin data = 8'hFF; // 使用 Verilog-2001 的语法 $display("Verilog-2001 语法: data = %h", data); end endmodule `end_keywords // 结束 Verilog-2001 语法 // 恢复默认的 Verilog 语法 module another_module; reg [7:0] data; initial begin data = 8'hAA; // 使用默认的 Verilog 语法 $display("默认语法: data = %h", data); end endmodule ``` "#), ( "end_keywords", "`end_keywords", "结束关键字版本指定" ), // Annex D Compiler directives ( "default_decay_time", "`default_decay_time $1", r#"`default_decay_time` 用于设置电荷衰减时间(decay time)的默认值。它通常用于模拟电荷存储元件(如电容或寄生电容)的行为。 ### 语法 ```verilog `default_decay_time time_value ``` ``` "# ), ( "default_trireg_strength", "`default_trireg_strength $1", r#"`default_trireg_strength ` 用于设置三态寄存器(trireg)的默认驱动强度(strength)。三态寄存器是一种特殊的寄存器,可以存储电荷,并且在未被驱动时会衰减。"# ), ( "delay_mode_distributed", "`delay_mode_distributed", r#"`delay_mode_distributed` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟分布方式。"# ), ( "delay_mode_path", "`delay_mode_path", r#"`delay_mode_path` 用于设置延迟模式的类型。它通常用于控制路径延迟(path delay)的计算方式,特别是在时序逻辑电路中。"# ), ( "delay_mode_unit", "`delay_mode_unit", r#"`delay_mode_unit` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟计算方式,特别是在仿真中需要精确控制延迟时。"# ), ( "delay_mode_zero", "`delay_mode_zero", r#"`delay_mode_zero` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟计算方式,特别是在仿真中需要忽略延迟时。"# ), ]; fn make_function_profile( description: &str ) -> MarkupContent { MarkupContent { kind: MarkupKind::Markdown, value: format!("{}", description) } } pub fn provide_vlog_directives_completions() -> Vec { let mut completion_items: Vec = Vec::new(); for (label, snippet_code, description) in VLOG_DIRECTIVES { let function_profile = make_function_profile(description); let label_details = CompletionItemLabelDetails { description: Some("directive".to_string()), ..Default::default() }; completion_items.push(CompletionItem { label: label.to_string(), detail: Some("directive".to_string()), label_details: Some(label_details), documentation: Some(Documentation::MarkupContent(function_profile)), kind: Some(CompletionItemKind::FUNCTION), insert_text: Some(snippet_code.to_string()), insert_text_format: Some(InsertTextFormat::SNIPPET), insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION), ..CompletionItem::default() }); } completion_items }