本篇是利用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博客

更多推荐