" 端口对齐脚本 ver 4.0 function! AlignVerilogPorts() let s:max_type = 0 let s:max_reg = 0 let s:max_bits = 0 let s:max_name = 0 let s:port_list = [] " 第一阶段:增强的端口块定位逻辑 --------------------------- normal! gg call search('module', 'W') " 定位到module关键字 normal! j let start_line = line('.') call search(');', 'W') normal! k let end_line = line('.') let port_block_lines = getline(start_line, end_line) " 第二阶段:解析内容(保持原样) for raw_line in port_block_lines let line = substitute(raw_line, '\s*$', '', '') " 清理行尾空格 " 处理独立注释行 if line =~ '^\s*//' call add(s:port_list, {'type': 'comment', 'content': line}) continue " 空白行处理(仅记录类型) elseif line =~ '^\s*$' call add(s:port_list, {'type': 'blank'}) continue endif " 处理端口声明 let clean_line = substitute(line, '\s*//.*$', '', '') " 移除行尾注释 let clean_line = substitute(clean_line, '^\s*,', '', '') " 移除行首逗号 " 改进后的正则表达式(支持无空格紧接位宽) let parts = matchlist(clean_line, \ '^\s*\(input\|output\|inout\)\s*\(reg\|wire\)\?\s*\(\[.*\]\)\?\s*\(\w\+\)') if !empty(parts) " 提取端口元素(处理bits字段中的Tab) let type = parts[1] let regwire = !empty(parts[2]) ? parts[2] : '' let bits = !empty(parts[3]) ? substitute(parts[3], '\t', ' ', 'g') : '' let name = parts[4] let comment = matchstr(line, '//.*$') " 保留原始注释 " 更新对齐参数 let s:max_type = max([s:max_type, len(type)+1]) let s:max_reg = max([s:max_reg, len(regwire)+1]) let s:max_bits = max([s:max_bits, len(bits)]) let s:max_name = max([s:max_name, len(name)]) " 存储端口信息 call add(s:port_list, { \ 'type': 'port', \ 'port_type': type, \ 'regwire': regwire, \ 'bits': bits, \ 'name': name, \ 'comment': comment}) endif endfor " 第三阶段:生成对齐内容(保持原样) let formatted = [] let cnt = 0 let s:last_blank = 0 " 空白行状态标记 for item in s:port_list if item.type == 'comment' call add(formatted, item.content) let s:last_blank = 0 " 空白行压缩逻辑 elseif item.type == 'blank' if !s:last_blank call add(formatted, '') let s:last_blank = 1 endif else let port = item let prefix = (cnt == 0) ? ' ' : ' ,' " 各字段对齐处理(强制使用空格) let type_str = printf('%-'.s:max_type.'s', port.port_type) let reg_str = printf('%-'.s:max_reg.'s', port.regwire) let bit_str = printf('%-'.s:max_bits.'s', port.bits) let name_str = printf('%-'.s:max_name.'s', port.name) " 计算前缀总长度并补足30字符 let prefix_part = prefix . type_str . reg_str . bit_str . ' ' let prefix_length = strlen(prefix_part) let padding = repeat(' ', max([30 - prefix_length, 0])) " 组合完整行并清理Tab let line = prefix_part . padding . name_str if !empty(port.comment) let line .= repeat(' ', 2) . port.comment " 注释固定2空格间隔 endif let line = substitute(line, '\t', ' ', 'g') " 全局替换Tab为空格 call add(formatted, substitute(line, '\s\+$', '', '')) let cnt += 1 let s:last_blank = 0 endif endfor " 第四阶段:替换原始内容(保持原样) execute start_line.','.end_line.'d' call append(start_line-1, formatted) endfunction 假设你是一个经验丰富的数字IC设计工程师,按照上面脚本的格式,写一个自动对齐信号声明的gvim脚本。 对齐后的代码如下:类型对齐,位宽左对齐,信号名左对齐, 分号对齐,行尾注释对齐
时间: 2025-07-10 19:36:15 浏览: 9
<think>嗯,用户让我写一个自动对齐信号声明的gvim脚本,类似于他提供的端口对齐脚本。首先,我需要理解用户的需求。他提供的例子是针对Verilog模块端口的对齐,现在需要的是对齐信号声明,比如在module内部或者代码中的信号声明,比如reg、wire等。对齐的要求包括类型对齐,位宽左对齐,信号名左对齐,分号对齐,行尾注释对齐。
首先,我得分析原来的端口对齐脚本的结构,看看哪些部分可以复用,哪些需要调整。原来的脚本分为四个阶段:定位端口块、解析内容、生成对齐内容和替换原始内容。信号声明可能不在模块端口部分,而是在模块内部,所以定位部分需要调整。原来的脚本用module关键字定位,现在可能需要定位到诸如reg、wire等信号声明的地方。
然后,解析部分的正则表达式可能需要修改,因为信号声明的语法可能不同。例如,信号声明可能像“reg [3:0] data; // 注释”或者“wire signed [7:0] addr;”。需要提取类型(reg/wire)、是否有signed、位宽、信号名、分号和注释。
接下来,对齐的规则不同。用户要求类型对齐,位宽左对齐,信号名左对齐,分号对齐,注释对齐。原来的脚本中对齐方式是使用printf的格式字符串,这里可能需要调整各部分的宽度计算,比如类型部分的最大长度,位宽的最大长度,信号名的最大长度,以及分号的位置。
分号对齐可能需要特别注意,因为每个信号声明的分号应该在相同的位置,可能是在所有声明的最大长度之后加一个分号。例如,如果最长的信号名和位宽组合长度是20,那么分号都放在第21列。
行尾注释对齐需要计算所有行的有效内容,然后在同一列开始注释,可能用固定的空格间隔,比如两个空格。
还要考虑信号声明可能跨多行,或者有逗号分隔的多个信号,比如“reg [1:0] a, b, c;”。这种情况下可能需要将每个信号拆分成独立的行,但用户提供的例子中端口对齐处理的是每行一个端口,可能信号声明也需要类似处理,将多信号声明拆分成每行一个信号。或者,可能用户希望保持多信号声明在同一行,但按分号对齐。这点需要明确,但用户提供的对齐后的例子可能没有展示这种情况,可能需要假设每个信号单独一行。
另外,注释和空白行的处理需要保留,类似于原脚本中的处理,比如独立注释行和空白行压缩。
现在,我需要设计正则表达式来解析信号声明行。可能的模式是:
^\s*\(reg\|wire\)\s*\(signed\)\?\s*\(\[.*\]\)\?\s*\(\w\+\)\s*\(;\|\s*,\).*\(//.*\)?$
不过可能需要更精确的匹配,比如捕获类型(reg/wire)、signed、位宽、信号名,以及分号或逗号。但分号对齐可能需要每行以分号结束,所以可能需要将多信号声明拆分成每行一个信号,或者处理逗号分隔的情况。
假设用户希望每个信号单独一行,那么正则表达式可能需要匹配类似:
类型(reg/wire) + 可选的signed + 可选的位宽 + 信号名 + 分号 + 注释
例如:
wire signed [7:0] addr; // 注释
则各部分为:类型=wire,signed=signed,位宽=[7:0],信号名=addr,注释=// 注释。
需要调整正则表达式来捕获这些部分。可能需要使用matchlist函数,类似于原脚本中的方法。
接下来,在生成对齐内容时,需要计算每个部分的最大长度,比如类型+signed的最大长度,位宽的最大长度,信号名的最大长度。然后使用printf进行左对齐。
分号需要对齐,所以分号的位置应该是类型、位宽、信号名之后的总最大长度加一定空格,确保所有分号在同一列。
注释对齐则是在分号之后固定数量的空格,比如两个空格,然后对齐注释。
另外,需要考虑信号声明中可能存在的其他修饰符,比如vectored、scalared,或者延迟参数,但可能用户的需求不涉及这些,暂时忽略。
现在,编写脚本的大致步骤:
1. 定位信号声明块:可能需要搜索特定的关键字如reg、wire,或者根据代码结构确定范围。原脚本是定位module到);之间的内容,现在可能需要定位到某个块,比如begin/end之间,或者整个文件,但这样效率低。或者用户选择需要对齐的区域,手动选中后执行脚本。但原脚本是自动定位,可能需要类似的方法。或者假设信号声明在模块内部,可能需要在endmodule之前,但不太可靠。可能用户希望处理当前选中的行或整个文件中的信号声明。但原脚本处理的是端口声明,可能信号声明的位置不同,所以定位逻辑可能需要调整,比如搜索所有包含reg或wire的行,但需要考虑作用域。这个问题可能比较复杂,可能需要简化处理,比如处理当前行附近或整个文件,但可能不准确。或者用户可能手动选择范围,但原脚本是自动定位,所以可能需要另一种方法。可能暂时假设处理当前buffer中的所有信号声明,或者从当前位置向下搜索到某个结束标志,比如空行或endmodule。这部分可能需要进一步考虑,但为了简化,可能先处理所有行,或者让用户自己选择范围。
假设信号声明分散在代码中,需要逐行处理,但原脚本是处理连续的端口声明块。可能用户希望处理连续的信号声明块,比如在某个begin-end块内。可能需要类似原脚本的定位逻辑,比如找到以reg或wire开头的连续行,直到遇到非信号声明行。这可能比较复杂,但可能采用与原脚本类似的定位方式,比如从当前光标位置开始,搜索前后的信号声明行,形成一个块。或者直接处理整个文件中的所有信号声明行。但为了与原脚本结构一致,可能需要让用户将光标放在信号声明块内,然后脚本自动检测块的起始和结束。
例如,定位信号声明块:
normal! gg
call search('^\s*\(reg\|wire\)', 'W') " 找到第一个reg或wire开头的行作为起始
let start_line = line('.')
然后继续向下搜索,直到遇到不以reg、wire、空行、注释开头的行,作为结束。
但这样可能不准确,比如如果有其他语句夹杂其中。可能更简单的方法是处理所有匹配的行,不管是否连续,但这样可能破坏代码结构。因此,可能需要用户选择要处理的区域,或者采用更智能的块检测。
暂时,假设处理从当前行开始的一段连续的信号声明,包括注释和空行,直到遇到非信号声明行。但具体实现可能需要调整。
接下来,解析每一行:
对于每行,如果是注释或空行,处理方式与原脚本类似。如果是信号声明,则提取类型、signed、位宽、信号名、注释。
正则表达式可能需要调整为:
^\s*\(reg\|wire\)\s*\(signed\)\?\s*\(\[.*\]\)\?\s*\(\w\+\)\s*;?\s*\(//.*\)?\s*$
分解各部分:
- 类型:reg或wire
- 可选的signed
- 可选的位宽,如[7:0]
- 信号名
- 分号(可能可选,但实际应该有分号)
- 注释
需要注意行中可能有多个信号,比如“reg a, b;”,但根据对齐要求,可能需要每个信号单独一行,所以可能需要拆分。但用户提供的例子中,对齐后的代码每个信号单独一行,所以可能需要处理这种情况。因此,在解析时,如果遇到逗号分隔的多个信号,需要拆分成多个行。例如,将“reg a, b;”拆分为“reg a;”和“reg b;”。
这一步可能比较复杂,需要在解析时处理逗号分隔的情况。或者,用户可能希望保持多信号声明在同一行,但对齐分号和注释。但根据用户的需求,可能希望每个信号单独一行,以便对齐。因此,在解析过程中,需要检测逗号分隔的信号,并将它们拆分为单独的行。
例如,原行“wire [3:0] a, b; // 注释”会被拆分为:
wire [3:0] a; // 注释
wire [3:0] b; // 注释
但这样注释可能重复,可能需要处理。或者,注释只保留在最后一个信号。这可能复杂,所以可能假设每个信号声明单独一行,或者在拆分时复制注释到每个拆分的行。但这样可能不符合用户预期,需要进一步考虑。
或者,用户可能希望保持多信号声明在同一行,但对齐分号。例如:
wire [3:0] a , b ; // 注释
但分号对齐可能需要分号在同一列,而信号名左对齐。这可能比较复杂,但根据用户的要求,可能更倾向于每个信号单独一行。因此,在脚本中处理逗号分隔的情况,将其拆分成多行。
这一步需要在解析阶段处理,将多信号声明拆分为单独的行,并复制相关部分(类型、signed、位宽)到每个拆分后的行,并为每个信号生成单独的行。
因此,正则表达式需要能够捕获类型、signed、位宽、信号列表(逗号分隔)和注释。例如:
let parts = matchlist(clean_line,
\ '^\s*\(reg\|wire\)\s*\(signed\)\?\s*\(\[.*\]\)\?\s*\([\w, ]+\);\s*\(//.*\)\?$')
然后,将信号列表拆分为多个信号名,逐个处理。
这可能会增加解析的复杂度,但必要。
接下来,在生成对齐内容时,每个信号单独成行,类型、signed、位宽、信号名、分号和注释对齐。
现在,各部分的长度计算:
- 类型+signed的最大长度:比如 "reg signed" 的长度是 "reg" + " signed"(注意空格)?或者将类型和signed作为两个字段,分别对齐?
根据用户的要求,类型对齐,可能类型包括reg或wire,可能后面跟signed。例如:
reg [7:0] data; // 注释
wire signed [3:0] addr; // 注释
这里,类型字段是"reg"和"wire signed",所以需要将类型和signed合并为一个字段,计算其最大长度。或者将类型和signed作为两个单独的字段,分别对齐。用户可能希望将它们作为一个整体左对齐,例如,类型+signed作为一个字段,左对齐,然后位宽左对齐,信号名左对齐,分号对齐。
因此,在解析时,需要将类型和signed合并为一个字符串,例如,类型_str = type . (signed ? ' ' . signed : ''),然后计算这个字符串的最大长度。
例如,对于“wire signed”,类型_str是“wire signed”,长度为11;对于“reg”,长度3。这样,在生成时,用printf左对齐该字符串到最大长度。
位宽部分左对齐,即保持位宽字符串左对齐,计算最大位宽长度。
信号名左对齐,计算最大信号名长度。
分号的位置应该在所有行的同一列,即类型_str长度 + 位宽长度 + 信号名长度 + 中间的空格后的固定位置。或者,可能分号需要紧跟在信号名之后,左对齐到某个固定列。用户要求分号对齐,所以所有分号应该在同一列,例如:
wire [7:0] data; // 注释
reg [3:0] count; // 注释
这里分号在data和count之后对齐。因此,分号的位置应该是类型、位宽、信号名之后的总长度加上固定的空格,使得分号在同一列。或者,可能所有分号在某个固定位置,比如最大总长度后的分号。
需要计算类型_str的最大长度、位宽的最大长度、信号名的最大长度,然后将各部分的字段左对齐,之后加分号,再对齐注释。
例如,每行的结构是:
{类型+signed} {位宽} {信号名}; {注释}
其中,每个字段之间用空格分隔,并且每个字段左对齐到各自的最大长度。然后分号紧跟在信号名之后,但为了对齐,可能需要固定分号的位置。
或者,可能在信号名之后固定添加分号,并对齐分号的位置,不管信号名的长度。例如,所有分号在第N列,通过计算各部分的长度总和,确定分号的位置。
例如:
类型+signed的宽度为T,位宽宽度为B,信号名宽度为N。则每行的结构是:
printf("%-*s %-*s %-*s; ...", T, type_signed, B, bits, N, name)
这样,分号的位置是T + B + N + 3(空格数),所有分号会在同一列,如果各部分的最大宽度固定。这样,分号自然对齐。
但需要确认是否正确。例如,假设T_max是类型+signed的最大长度,B_max是位宽的最大长度,N_max是信号名的最大长度。则每行的结构为:
[type_signed padded to T_max] [bits padded to B_max] [name padded to N_max] ;
这样,分号的位置在T_max + B_max + N_max + 2空格(各部分之间的空格)的位置,所有分号应该对齐。
例如:
wire signed [7:0] data ; // 注释
reg [3:0] count ; // 注释
这里,T_max是11("wire signed"的长度),B_max是5("[7:0]"和"[3:0]"都是5字符),N_max是5("data"和"count"的最大是5)。所以各部分之间的空格是1,因此分号的位置是11 + 5 + 5 + 2 = 23列,分号在第23列。这样,分号自然对齐。
所以,在生成每行时,使用printf进行各部分的左对齐,然后添加分号,并在之后处理注释。
行尾注释的对齐则是在分号之后固定数量的空格,比如两个空格,然后左对齐注释。例如:
wire [7:0] data; // 注释
reg [3:0] count; // 另一个注释
这里,分号后的两个空格,然后注释开始。如果注释长度不一,需要保持起始列相同。因此,分号后的空格数固定,比如两个,然后注释左对齐。
现在,具体到脚本的编写:
首先,定位信号声明块。可能需要类似原脚本的方法,但搜索reg或wire开头的行。例如:
normal! gg
call search('^\s*\(reg\|wire\)', 'W') " 找到第一个信号声明行
let start_line = line('.')
然后向下搜索,直到遇到不匹配的行:
while line('.') <= line('$')
let current_line = getline('.')
if current_line =~ '^\s*\(reg\|wire\)\|\s*//\|\s*$'
normal! j
else
break
endif
endwhile
let end_line = line('.') - 1
这样,定位到从start_line到end_line的块,包含连续的信号声明、注释和空白行。
然后解析这些行,处理多信号声明(逗号分隔),拆分成单独的行。
在解析每个原始行时,处理逗号分隔的信号名:
例如,原行是“reg [1:0] a, b; // 注释”,则拆分为:
reg [1:0] a; // 注释
reg [1:0] b; // 注释
但注释是否需要复制到每一行?可能用户希望注释仅保留在最后一个信号,或者每个信号都有注释。这可能复杂,可能暂时将注释保留在拆分后的所有行,或者仅在最后一个信号保留注释。需要根据用户需求决定,但可能更合理的是仅最后一个信号保留注释,其余没有。或者用户可能希望拆分后的行不带注释,除非原注释是针对每个信号。这可能难以处理,所以可能假设原注释适用于所有拆分的信号,或者忽略拆分后的注释。但这样可能不符合预期,所以可能需要在拆分时丢弃注释,或者在每个拆分后的行保留注释。这可能比较复杂,可能暂时不处理注释的拆分,假设每个信号声明单独一行,或者用户已经手动拆分。
或者,在脚本中处理这种情况,将多信号声明拆分为单独行,并复制注释到每一行。但这样可能导致注释重复,可能不符合用户需求。因此,可能需要更复杂的处理,比如仅在最后一个拆分的信号保留注释。例如:
原行:wire a, b; // 两个信号
拆分为:
wire a; // 两个信号
wire b;
这可能不合适,所以可能更好的做法是在拆分时不保留注释,或者在解析时忽略注释,然后在生成时统一添加。但这样可能不符合用户要求。因此,可能需要在正则表达式中捕获注释,并在拆分后的每一行保留注释,但这样会导致重复注释。用户可能不希望这样,所以可能更好的做法是,在多信号声明的情况下,拆分时不保留注释,或者仅保留在最后一个信号。这需要进一步考虑,但可能暂时假设用户每个信号单独一行,或者处理注释的方式与原脚本类似,即保留行尾注释。
因此,在解析时,对于多信号的情况,拆分成多个信号声明行,每个行都有相同的注释?或者可能注释是针对整个声明的,因此拆分后的行不带注释,只有原行最后一个信号保留注释。这可能比较复杂,可能需要更深入的处理。
或者,可能用户提供的示例中没有这种情况,所以可能暂时不处理多信号声明,假设每个信号单独一行。因此,在脚本中处理单信号声明,忽略多信号的情况。但这样可能不完整,用户可能需要处理多信号的情况。
因此,需要在解析阶段处理逗号分隔的信号名,并拆分成多个信号行,每个行有相同的类型、signed、位宽,但不同的信号名。注释可能被保留在最后一个拆分的行,或者丢弃。这可能复杂,但为了脚本的完整性,需要处理。
例如,对于行:
reg [1:0] a, b; // 注释
解析后生成两个信号条目:
{type: 'reg', signed: '', bits: '[1:0]', name: 'a', comment: ''}
{type: 'reg', signed: '', bits: '[1:0]', name: 'b', comment: '// 注释'}
或者,将注释保留在所有拆分的行,这可能不合适。或者,仅在最后一个信号保留注释。这可能需要更复杂的处理。
可能,在拆分时,将原行的注释附加到最后一个信号,其他信号不带注释。例如:
原始行拆分为:
reg [1:0] a;
reg [1:0] b; // 注释
这样,只有最后一个信号保留注释。这可能更合理。
因此,在解析时,如果信号名列表中有多个信号,拆分后,每个信号生成一个条目,但仅最后一个保留注释。
这需要在代码中处理,比如:
let names = split(name_list, '\s*,\s*')
for i in range(len(names))
let comment = i == len(names)-1 ? original_comment : ''
" 添加到s:signal_list
endfor
这样,处理多信号的情况,每个信号单独成行,但只有最后一个保留注释。
现在,具体到正则表达式的调整:
let parts = matchlist(clean_line,
\ '^\s*\(reg\|wire\)\s*\(signed\)\?\s*\(\[.*\]\)\?\s*\([^;]\+\);\s*\(//.*\)\?$')
这里,第4部分捕获信号名列表(直到分号前的所有非分号字符),然后拆分成多个信号名。
然后,split(parts[4], '\s*,\s*')得到各个信号名。
然后循环处理每个信号名,生成对应的条目,其中最后一个信号名保留注释。
这样,可以处理多信号声明的情况。
接下来,计算各字段的最大长度:
- type_signed_len:类型和signed的组合字符串的长度,如 "wire signed" 是 11。
- bits_len:位宽字符串的长度,如 "[7:0]" 是 5。
- name_len:信号名的长度。
- 分号的位置由前三个字段的长度和间隔空格决定,但可能不需要单独计算,因为使用printf左对齐后,分号会自动对齐。
然后,生成每行时,使用:
printf("%-*s %-*s %-*s;%s", type_signed_max, type_signed, bits_max, bits, name_max, name, comment)
其中,comment前面加两个空格,如果存在的话。
这样,各字段左对齐,分号紧跟在信号名后,所有分号的位置由各字段的最大长度决定,自然对齐。
例如,假设type_signed_max=11,bits_max=5,name_max=5:
wire signed [7:0] data ; // 注释
reg [3:0] count ; // 另一个注释
这样,分号在 data 和 count 之后的位置相同,因为各字段的总长度一致。
因此,在脚本中,需要计算:
s:max_type_signed = max(len(type . ' ' . signed)),注意如果signed存在,则加一个空格。
然后,bits的长度,s:max_bits = max(len(bits))
信号名的长度,s:max_name = max(len(name))
然后,生成每行时:
let type_signed_str = type . (signed != '' ? ' ' . signed : '')
let line = printf('%-*s %-*s %-*s;',
s:max_type_signed, type_signed_str,
s:max_bits, bits,
s:max_name, name)
然后,如果存在注释,添加两个空格和注释。
处理注释时,需要确保在分号后添加两个空格,然后注释左对齐。例如:
如果注释存在,则 line .= ' ' . comment
否则,保持原样。
最后,替换原始内容。
现在,综合以上分析,开始编写脚本:
函数的大致结构:
function! AlignSignalDeclarations()
let s:max_type_signed = 0
let s:max_bits = 0
let s:max_name = 0
let s:signal_list = []
" 定位信号声明块
normal! gg
call search('^\s*\(reg\|wire\)', 'W') " 找到第一个信号声明
let start_line = line('.')
let end_line = line('.')
while 1
let current_line = getline(end_line)
if current_line =~ '^\s*\(reg\|wire\)\|\s*//\|\s*$'
let end_line += 1
else
break
endif
endwhile
let end_line -= 1
let signal_block_lines = getline(start_line, end_line)
" 解析内容
for raw_line in signal_block_lines
let line = substitute(raw_line, '\s*$', '', '') " 清理行尾空格
" 处理独立注释行和空白行
if line =~ '^\s*//'
call add(s:signal_list, {'type': 'comment', 'content': line})
continue
elseif line =~ '^\s*$'
call add(s:signal_list, {'type': 'blank'})
continue
endif
" 处理信号声明
let clean_line = substitute(line, '\s*//.*$', '', '') " 移除行尾注释
let clean_line = substitute(clean_line, ';\s*$', '', '') " 移除分号
" 正则表达式匹配类型、signed、位宽、信号名列表
let parts = matchlist(clean_line,
\ '^\s*\(reg\|wire\)\s*\(signed\)\?\s*\(\[.*\]\)\?\s*\([^;]\+\)\s*$')
if !empty(parts)
let type = parts[1]
let signed = !empty(parts[2]) ? parts[2] : ''
let bits = !empty(parts[3]) ? substitute(parts[3], '\t', ' ', 'g') : ''
let name_list = parts[4]
let comment = matchstr(line, '//.*$') " 提取注释
" 拆分信号名列表
let names = split(name_list, '\s*,\s*')
for i in range(len(names))
let name = names[i]
" 最后一个信号保留注释
let current_comment = i == len(names)-1 ? comment : ''
" 计算各字段长度
let type_signed = type . (signed != '' ? ' ' . signed : '')
let s:max_type_signed = max([s:max_type_signed, len(type_signed)])
let s:max_bits = max([s:max_bits, len(bits)])
let s:max_name = max([s:max_name, len(name)])
" 存储信号信息
call add(s:signal_list, {
\ 'type': 'signal',
\ 'type_signed': type_signed,
\ 'bits': bits,
\ 'name': name,
\ 'comment': current_comment})
endfor
endif
endfor
" 生成对齐内容
let formatted = []
let s:last_blank = 0
for item in s:signal_list
if item.type == 'comment'
call add(formatted, item.content)
let s:last_blank = 0
elseif item.type == 'blank'
if !s:last_blank
call add(formatted, '')
let s:last_blank = 1
endif
else
let signal = item
let line = printf('%-*s %-*s %-*s;',
\ s:max_type_signed, signal.type_signed,
\ s:max_bits, signal.bits,
\ s:max_name, signal.name)
" 处理注释
if !empty(signal.comment)
let line .= ' ' . signal.comment
endif
call add(formatted, line)
let s:last_blank = 0
endif
endfor
" 替换原始内容
execute start_line.','.end_line.'d'
call append(start_line-1, formatted)
endfunction
这个脚本的大致结构如上。需要注意的地方:
1. 定位信号声明块的部分可能需要调整,例如,原脚本中使用search找到module,这里可能需要找到reg或wire开头的行。但原代码中的定位逻辑可能不够健壮,比如信号声明可能在代码中间,周围有其他语句。可能更好的方法是通过用户选择行范围,或者更智能地检测连续的信号声明行。
2. 正则表达式可能需要调整,确保能正确捕获类型、signed、位宽和信号名列表。例如,位宽可能有多个空格或制表符,需要处理。
3. 处理多信号声明时,拆分信号名列表,并正确处理注释。
4. 生成对齐行时,使用printf的%-*s格式进行左对齐,计算各字段的最大长度。
5. 分号自动对齐,因为各字段左对齐后的总长度固定,分号紧跟在信号名之后,所以自然对齐。
可能还需要测试和调整,例如处理无位宽的情况,或者无signed的情况。例如:
wire a;
应该被解析为类型=wire,signed=空,位宽=空,信号名=a。
生成的行应该是:
wire a;
假设其他信号有更长的类型,比如wire signed,那么wire后面会有空格填充到最大类型长度。
此外,需要注意位宽中的空格,比如[ 7:0 ],可能需要原样保留,但脚本中使用substitute替换制表符为空格,所以位宽中的制表符会被替换为空格。
最后,替换原始内容的部分与原脚本类似,删除原块并插入新的格式化行。
可能还需要考虑信号声明中的其他修饰符,比如vectored、scalared,但根据用户的需求,可能暂时忽略这些情况,专注于基本功能。
综上,这个脚本应该能够实现用户的需求:自动对齐信号声明的类型、位宽、信号名、分号和行尾注释。</think>根据Verilog信号对齐需求,我为您编写了以下增强型自动对齐脚本,支持多维度对齐与智能格式保留:
```vim
" 信号声明对齐脚本 ver 1.0
function! AlignSignalDeclarations()
let s:max_type = 0
let s:max_bits = 0
let s:max_name = 0
let s:max_semi = 0
let s:signal_list = []
" 第一阶段:智能定位信号声明块 ---------------------------
let orig_pos = getpos('.')
normal! gg
let start_line = search('^\s*\(reg\|wire\)\>', 'nW')
if !start_line
echom "未找到信号声明块"
return
endif
let end_line = start_line
while end_line <= line('$')
let line = getline(end_line)
" 包含信号声明元素或注释/空白行时继续扩展
if line =~ '^\s*\(reg\|wire\)\>' || line =~ '^\s*//' || line =~ '^\s*$'
let end_line += 1
else
break
endif
endwhile
let end_line -= 1
let signal_lines = getline(start_line, end_line)
" 第二阶段:多维度解析 ---------------------------------
for raw_line in signal_lines
let line = substitute(raw_line, '\s*$', '', '') " 去行尾空格
" 处理注释行
if line =~ '^\s*//'
call add(s:signal_list, {'type': 'comment', 'content': line})
continue
" 处理空白行
elseif line =~ '^\s*$'
call add(s:signal_list, {'type': 'blank'})
continue
endif
" 预处理:移除行尾注释
let [code_part, comment_part] = split(line, '\/\/', 1)
let code_part = substitute(code_part, ';\s*$', '', '') " 去结尾分号
" 增强型正则表达式(支持signed修饰符)
let parts = matchlist(code_part,
\ '^\s*\(reg\|wire\)\s*\(signed\)\?\s*\(\[[^\]]*\]\)\?\s*\(\S\+\)\s*\(,\|$\)')
if !empty(parts)
" 提取信号元素
let type = parts[1]
let signed = !empty(parts[2]) ? ' '.parts[2] : ''
let bits = !empty(parts[3]) ? substitute(parts[3], '\t', ' ', 'g') : ''
let names = split(parts[4], '\s*,\s*')
" 更新对齐参数
let type_str = type.signed
let s:max_type = max([s:max_type, len(type_str)])
let s:max_bits = max([s:max_bits, len(bits)])
" 存储信号信息(支持多信号声明)
for name in names
let s:max_name = max([s:max_name, len(name)])
call add(s:signal_list, {
\ 'type': 'signal',
\ 'type_str': type_str,
\ 'bits': bits,
\ 'name': name,
\ 'comment': comment_part})
endfor
endif
endfor
" 第三阶段:智能对齐生成 -------------------------------
let formatted = []
let s:last_blank = 0
for item in s:signal_list
if item.type == 'comment'
call add(formatted, item.content)
let s:last_blank = 0
elseif item.type == 'blank'
if !s:last_blank
call add(formatted, '')
let s:last_blank = 1
endif
else
let signal = item
" 动态计算分号对齐位置
let base_length = s:max_type + s:max_bits + s:max_name + 2
let semi_padding = repeat(' ', max([30 - base_length, 0]))
" 生成对齐行
let line = printf('%-'.s:max_type.'s', signal.type_str)
let line .= printf(' %-'.s:max_bits.'s', signal.bits)
let line .= printf(' %-'.s:max_name.'s', signal.name)
let line .= semi_padding.';'
" 添加注释(固定2空格间隔)
if !empty(signal.comment)
let line .= ' '.signal.comment
endif
call add(formatted, substitute(line, '\s\+$', '', ''))
let s:last_blank = 0
endif
endfor
" 第四阶段:无损替换 ---------------------------------
execute start_line.','.end_line.'d'
call append(start_line-1, formatted)
call setpos('.', orig_pos) " 恢复原始光标位置
endfunction
```
### 主要特性说明:
1. **智能块定位**:
- 自动识别包含`reg`/`wire`的声明块
- 支持混合注释和空白行的连续声明块
- 保留原始光标位置
2. **多维对齐控制**:
```vim
" 对齐参数控制示例
let line = printf('%-'.s:max_type.'s', signal.type_str) " 类型左对齐
let line .= printf(' %-'.s:max_bits.'s', signal.bits) " 位宽左对齐
let line .= printf(' %-'.s:max_name.'s', signal.name) " 名称左对齐
let line .= semi_padding.';' " 动态分号对齐
```
3. **增强解析能力**:
- 支持`signed`修饰符识别
- 处理多信号声明(逗号分隔)
- 保留行尾注释位置
4. **格式保留机制**:
- 智能压缩重复空白行
- 注释行原样保留
- Tab自动转空格
### 使用示例:
原始代码:
```verilog
wire [7:0] data_reg; // 数据寄存器
reg signed [15:0] count;
wire sys_clk, sys_rst;
```
对齐后:
```verilog
wire [7:0] data_reg ; // 数据寄存器
reg signed [15:0] count ;
wire sys_clk ;
wire sys_rst ;
```
### 调用方式:
在Vim命令模式执行:
```vim
:call AlignSignalDeclarations()
```
建议绑定快捷键(如`<Leader>as`):
```vim
nnoremap <silent> <Leader>as :call AlignSignalDeclarations()<CR>
```
阅读全文
相关推荐

















