vim使用的verilog补全插件(即插即用)
本篇是利用verilator实现vim对Verilog代码实时纠错功能-CSDN博客的后文,主要用于使用简单的插件对verilog进行补全。
实现功能:
1.常见的verilog语法补全(支持模糊搜索,但是由于vim的complete的一些特性,有时候不会显示模糊搜索结果)

2.自定义模块的实例化,并且标注端口(在行首有模块名字的情况下,后续的输入会自动推荐端口补全)

模块实例化的结果,/*[a:b]*/代表端口的输入输出宽度(没有标注的话就是一个位宽),/*<-i | o->*/代表输入端口和输出端口的分界线(如果安装了verible的verible-verilog-format,整理后可以变成这样)

具体代码
接下来介绍插件安装(请严格按照文章标注的文件名字编写)
以下内容安装在~/.vim/plugin中
编写verilog.txt,
这个文件用于存储常用补全词语,如果你有其他需要补全的词,可以直接加入
加入其他词的话,作者不保证不会出bug(虽然目前这个也不能保证不出)
module
module_endmodule
endmodule
input
output
inout
wire
reg
assign
integer
real
parameter
localparam
always
always@___
initial
if
else
case
case()_endcase
for
while
repeat
begin
begin_end
end
posedge
negedge
display
finish
stop
time
random
defparam
generate
genvar
function
task
disable
top_module
编写autocomplete.vim
这段代码是插件的核心,拥有主要的补全函数
let g:external_word_list = []
let g:external_module_list = []
let g:external_module_IO = {}
function! s:LoadWordsFromFile(filepath)
let g:external_word_list = []
if !filereadable(a:filepath)
echo "文件未找到:" . a:filepath
return
endif
let l:content = readfile(a:filepath)
let l:words = []
for l:line in l:content
let l:words += filter(split(l:line, '[^a-zA-Z0-9$_]+'),'v:val != "" && v:val != " "')
endfor
let g:external_word_list = uniq(sort(l:words))
endfunction
function! LoadMoudlesFromFile()
silent !~/.vim/plugin/verilog_module %
redraw!
let g:external_module_list = []
let g:external_module_IO = {}
if !filereadable("./tmp")
return
endif
let l:content = readfile("./tmp")
let l:words = []
for l:line in l:content
if l:line !~ '[()]'
call add(l:words,l:line)
else
let line_tmp = ' ' . line
let l:function_name = matchstr(l:line_tmp,'\v^([^\(]+)',1)
let l:line = substitute(l:line,'\v^[a-zA-Z$_0-9]+','','')
let g:external_module_IO[l:function_name] = l:line
endif
endfor
let g:external_module_list = uniq(sort(l:words))
silent! execute '!rm tmp'
endfunction
function! s:AutoComplete(findstart, base)
let first_word = matchstr(getline('.'),'\w\+')
if a:findstart == 1
let l:start = col('.') - 1
while l:start > 0 && getline('.')[l:start - 1] =~ '\k'
let l:start -= 1
endwhile
return l:start
else
let l:matches = []
let l:base = substitute(a:base, '\s\+', '', 'g')
let letter_count = {}
let letter_count_in = LetterCount(l:base)
for l:word in g:external_module_list
let is_in = 1
let letter_count = LetterCount(l:word)
if first_word == l:word
if IsCursorInParens()
let l:matches = []
return l:matches
endif
call add(l:matches,l:base . g:external_module_IO[l:word])
return l:matches
endif
if CheckLetterCount(letter_count,letter_count_in) == 1
call add(l:matches,l:word)
endif
endfor
for l:word in g:external_word_list
let is_in = 1
let letter_count = LetterCount(l:word)
if CheckLetterCount(letter_count,letter_count_in) == 1
call add(l:matches,l:word)
endif
endfor
return l:matches
endif
endfunction
command! -nargs=1 LoadCompletionFile call s:LoadWordsFromFile(<f-args>)
function! TriggerAutoComplete(mode)
let is_complete = 0
let words = ""
let l:matches = []
if expand('%:e') != 'v'
return 0
elseif getline('.')[col('.') - 2] !~ '\w' && getline('.')[col('.') - 3] !~ ')'
return 0
endif
if col('.') > 1
let l:line = getline('.')
let l:col = col('.')
let l:word_start = l:col - 1
while strcharpart(getline('.'),l:word_start - 1,1) =~ '\k' && l:word_start >= 0
let l:word_start -= 1
endwhile
let words = matchstr(l:line, '\k\+', l:word_start)
let l:matches = s:AutoComplete(0,words )
let l:start = s:AutoComplete(1,words)
if len(l:matches) > 0
if a:mode == 1
call complete(l:start+1,l:matches)
endif
return 1
else
return 0
endif
else
return 0
endif
endfunction
function! MyAutoFunction()
let line = getline('.')
let col = col('.')
let char_before_cursor = strpart(line, col-2, 1)
if char_before_cursor =~ '\w' || char_before_cursor == "\b"
call TriggerAutoComplete(1)
endif
endfunction
function! LetterCount(word)
let letter_count = {}
for i in range(len(a:word))
let letter = a:word[i]
if has_key(letter_count, letter)
let letter_count[letter] += 1
else
let letter_count[letter] = 1
endif
endfor
return letter_count
endfunction
function! CheckLetterCount(arr1, arr2)
for [letter, letter_count] in items(a:arr2)
if !has_key(a:arr1, letter) || a:arr1[letter] < letter_count
return 0
endif
endfor
return 1
endfunction
augroup MyAutoGroup
autocmd!
autocmd TextChangedI *.v call MyAutoFunction()
augroup END
function! IsCursorInParens()
let line = getline('.')
let col = col('.')
let before_cursor = strpart(line, 0, col-1)
let after_cursor = strpart(line, col-1)
if match(before_cursor, '[({[]') != -1 && match(after_cursor, '[)}\]]') != -1
return 1 " 光标在括号内
else
return 0 " 光标不在括号内
endif
endfunction
编写complete_function.vim
这个函数主要实现部分语句补全之后自动换行的功能(例如输入begin-end的补全换行)
如果你需要添加其他补全后的操作,继续加elseif就行,l:complete_word是你补全名称里能检测到的的最后一个单词
autocmd CompleteDone * call InsertEndIfNeeded()
function! InsertEndIfNeeded()
let l:current_pos = getpos('.')
execute "normal! hh"
let l:completed_word = expand('<cword>')
let l:character = getline('.')[col('.') - 1]
call setpos('.', l:current_pos)
let type = 'NULL'
echo l:completed_word
if l:completed_word ==# 'begin_end'
silent! call feedkeys("\<ESC>?\\w\<CR>dwdwdwdwoend\<ESC>O", 'n')
let type = 'begin_end'
elseif l:completed_word ==# 'module_endmodule'
silent! call feedkeys("\<ESC>?\\w\<CR>dwdwdwdwdwdwdwdwdwdwoendmodule\<ESC>ka\<space>", 'n')
let type = 'module_endmodule'
elseif l:completed_word ==# '_endcase'
silent! call feedkeys("\<ESC>?\\w\<CR>dwdwdwdwdwdwdwdwoendcase\<ESC>ki", 'n')
let type = 'case_endcase'
elseif l:completed_word =~ '___'
silent! call feedkeys("\<ESC>?\\w\<CR>xxxxa\(\)\<left>", 'n')
let type = 'always@()'
elseif l:completed_word == '())'
silent! call feedkeys("\<ESC>^wwwwa", 'n')
let type = 'function'
elseif l:completed_word == '());'
silent! call feedkeys("\<ESC>^wwwwa", 'n')
let type = 'function'
elseif l:character == ':'
silent! call feedkeys("\<ESC>?\\w\<CR>la", 'n')
endif
echo l:completed_word . ' ' . l:character . ' ' . type
endfunction
编写verilog_module
需要赋予可执行权限,这个函数用于使用正则表达式将你编写的模块提取出来
这个函数是在fish shell中使用的,如果你不使用fish(还是建议你使用fish),请确定前三行代码是否能在你的终端中使用(比如bash估计就用不了)
#!/bin/fish
set input_file $argv[1]
cat $input_file > tmp
perl -0777 -ne '
my $first = 1;
while (/module\s+(\S[\s\S]*?\))/g) {
my $module = $1;
if ($first) {
$first = 0;
next;
}
$module =~ s/input/./g;
$module =~ s/output/\@output\@/g;
$module =~ s/^module\s+//;
$module =~ s/\s//g;
$module =~ s/\)/()\);/g;
$module =~ s/,/(),/g;
print "$module
";
}
' tmp | sed 's/\@output\@/%/' > tmp2;
perl -0777 -ne '
s/output/./g;
s/%/ \/\*<-i | o->\*\/ ./g;
s/\@//g;
print $_;
' tmp2 | sed -E "s/.(\[[0-9:]+\])([^\(]+)\(\)/.\2()\/\*\1\*\/ /g" > tmp
cat tmp2 | sed -E 's/^([A-Za-z_0-9$]+)\(.*/\1/g'>>tmp
rm tmp2
可选辅助模块
1,跳跃括号模块
你可以把<C-l>映射到这个功能上,当光标在括号内,会跳出括号,在括号外则跳到下一个括号内。
function! JumpToClosingParen()
let line = getline('.')
let col = col('.')
let after_cursor = strpart(line, col)
if match(after_cursor, '[()]') != -1
let match_pos = col('.') + match(after_cursor, '[()]') - 1
call cursor(line('.'), match_pos + 1)
else
echo "No parenthesis after cursor"
endif
endfunction
2.智能回车键
你可以把<C-j>映射到这个模块上,按下之后自动检测行尾是否需要输入‘';'‘,如果有需要就插入并跳入下一行,不需要的话会直接跳入下一行
function! Newline()
if getline('.')[col('.')-1] == ';' || getline('.')[col('.')-1] == '}' || getline('.') == ''
if getline(line('.')+1) =~ '^\s*$' || getline(line('.')+1) =~ '}'
call feedkeys("o",'n')
else
call feedkeys("a\<Down>",'n')
endif
else
if getline('.'+1) =~ '^\s*$'
call feedkeys("a;\<Down>",'n')
else
call feedkeys("a;\<CR>",'n')
endif
endif
endfunction
3.代码注释快捷键
类似vscode里面的<C-/>,功能,我采用了<C-X>映射(目前只支持c和verilog,其他语言可能会打出错误的批注插入符号)
function! Annotate()
let l = expand('%:e')
if l =='c' || l == 'h' || l == 'json' || l =='v'
if getline('.')[col('.')-1] == '/' || getline('.')[col('.')] == '/'
call feedkeys("xx",'n')
else
call feedkeys("i//\<ESC>",'n')
endif
else
if getline('.')[col('.')-1] == '"'
call feedkeys("x",'n')
else
call feedkeys("i\"\<ESC>",'n')
endif
endif
endfunction
4.代码整理快捷键
注意!!使用此功能要下载verible插件中的verible-verilog-format(去github找,然后下载安装包),解压后请置于系统目录下,确保可执行。
function! Verilogformat()
silent! execute '!touch tmp_format;
\verible-verilog-format %>tmp_format;
\cat tmp_format>%;
\rm tmp_format;'
redraw!
endfunction
它会一键整理代码到标准格式
可选快捷键的.vimrc配置,你可以根据需求自行修改
noremap <C-x> <ESC>^:call Annotate()<cr><ESC>
inoremap <C-x> <ESC>^:call Annotate()<cr><ESC>
inoremap <C-j> <ESC>^$:call Newline()<cr><ESC>
noremap <C-j> <ESC>^$:call Newline()<cr><ESC>
inoremap <C-l> <ESC>:call JumpToClosingParen()<CR>la
noremap <C-l> <ESC>:call JumpToClosingParen()<CR>l
autocmd FileType verilog map <C-b> :call Verilogformat()<CR>
autocmd FileType verilog inoremap <C-b> <ESC>:call Verilogformat()<CR>
其他辅助用的.vimrc配置
当你在输入模式下,按下Alt键,此时h,j,k,l,o键会恢复原有功能
execute "set <M-h>=\eh"
inoremap <M-h> <Left>
execute "set <M-j>=\ej"
inoremap <M-j> <Down>
execute "set <M-k>=\ek"
inoremap <M-k> <Up>
execute "set <M-l>=\el"
inoremap <M-l> <Right>
execute "set <M-o>=\eo"
inoremap <M-o> <ESC>o
execute "set <M-p>=\ep"
inoremap <M-p> <ESC>pa
imap <A-h> <Left>
imap <A-j> <Down>
imap <A-k> <Up>
imap <A-l> <Right>
实现补全的.vimrc配置
以下内容插入.vimrc中(请你同时阅读开头提到的文章,并进行修改,里面一部分功能不在本文中,如果不需要纠错功能,需要删除一些语句段)
"----------------------Verilog 相关的配套设置-----------------------------
autocmd FileType verilog setlocal shiftwidth=4 softtabstop=4
"在保存 .v 文件时运行 verilator 并加载错误到 Quickfix 窗口
autocmd BufWritePost *.v :silent! execute '!verilator -cc --exe
\-Wno-lint -Wall % 2>&1
\| sed -E "s/^(\%Warning-.*:|\%Error: [^:]*:|.*) (.+).v:/ \2.v:/g"
\| grep -E "^.+\.v(:[0-9]+)+:"
\| sort -n
\| tee /tmp/verilator_output.txt'
\| call LoadErrorMessage()
\| redraw!
\| call LoadMoudlesFromFile()
autocmd VimEnter *.v : LoadCompletionFile ~/.vim/plugin/verilog.txt
autocmd VimEnter *.v : call LoadMoudlesFromFile()
set autowrite
autocmd CursorHold *.v call ShowErrorPopup()
set wildmode=full
set wildmenu
set completeopt=menuone,noinsert,noselect,preview
set pumheight=10
" 设置补全菜单背景为灰色,前景为黑色
highlight Pmenu ctermbg=black ctermfg=grey
" 设置补全菜单中选中项的背景为红色,前景为白色
highlight PmenuSel ctermbg=red ctermfg=white
" 设置滚动条背景为浅灰色
highlight PmenuSbar ctermbg=lightgray
" 设置滚动条的滚动指示部分为深灰色
highlight PmenuThumb ctermbg=darkgray
"---------------------------------------------------------------------------
如果你使用了coc-nvim,以下内容请这样编写(补全功能开始时,tab键切换到下一个补全选项,shift+tab切换到上一个补全选项,回车键或者空格确认补全)
inoremap <silent><expr> <TAB>
\ TriggerAutoComplete(0) ? "\<C-n>" :
\ coc#pum#visible() ? coc#pum#next(1):
\ CheckBackspace() ? "\<Tab>" :
\ coc#refresh()
inoremap <expr> <S-TAB>
\ TriggerAutoComplete(0) ? "\<C-p>" :
\coc#pum#visible() ? coc#pum#prev(1) : "\<C-h>"
inoremap <silent><expr> <CR>
\ TriggerAutoComplete(0) ? "\<C-y>\<space>" :
\coc#pum#visible() ? coc#_select_confirm()
\: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"
如果没有使用,请这样编写
inoremap <silent><expr> <TAB>
\ TriggerAutoComplete(0) ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-TAB>
\ TriggerAutoComplete(0) ? "\<C-p>" : "\<C-h>"
inoremap <silent><expr> <CR>
\ TriggerAutoComplete(0) ? "\<C-y>\<space>" : "\<CR>"
同上一偏文章一样,这个补全模块也没有经过长期检验,如果发现问题欢迎在底下给作者留言
放个卫星,寒假的时候有空会做个autoinstall然后上传github

11.22新增,增加了变量名补全vim的verilog补全插件更新(支持变量名补全)-CSDN博客
更多推荐


所有评论(0)