ctags, systemverilog and emacs
— Kaushal ModiUpdate (2017/02/23) — Now I use GNU Global with Universal
Ctags as back-end to generate the tag files. In emacs, I use the
ggtags
package – [config].
This post still has value if you are interesting in configuring
ctags
only.
This posts shows how to set up ctags
to parse SystemVerilog code and
how to access that tag database in emacs.
Exuberant ctags #
ctags
is an awesome application which crawls through all your code
and indexes everything you want, as you want as all of that can be
controlled using regular expressions. Here’s a scenario where ctags
comes helpful. Say you are in pqr.v file in which a function xyz is
called. Now that function may neither be defined in the same file nor
in some other file in the same directory. The function xyz could be
defined in some other folder in a file called abc.v. But with the help
of ctags, you can jump directly to the function xyz definition from
the place where it is called!
I code in Verilog and ctags helps be jump to the point where a variable/module/task/function/interface/define(or macro)/class/.. etc. is defined.
ctags customizations #
Custom configuration for ctags is usually stored in ~/.ctags
.
--exclude=.SOS
--exclude=.git
--extra=+q
# Hide the warning: ctags: Warning: xcmd recognizes
# /home/kmodi/usr_local2/libexec/ctags/drivers/coffeetags is not available
# https://github.com/fishman/ctags/issues/131#issuecomment-69467247
--quiet
--langmap=SystemVerilog:.sv.v.svh.vg.tv.vinc
--languages=SystemVerilog,C,C++,HTML,Lisp,Make,Matlab,Perl,Python,Sh,Tex
--regex-SystemVerilog=/^\s*`define\b\s*(\w+)/`\1/d,define/
Here are some notes about my customizations in .ctags
file:
- ctags doesn’t have a language defined systemverilog. But I can
define my own language called so:
--langdef=systemverilog
- I can define which files should be parsed for systemverilog code:
--langmap=systemverilog:.v.vg.sv.svh.tv.vinc
- Syntax to define your custom regex:
--regex-LANGUAGE=/REGEX/REGEX_GROUP/CTAGS_KIND_ABBREV, CTAGS_KIND_NAME/
- REGEX is the regular expression to define to find the line
containing the task, function, module, etc. You would normally
use atleast 1 regex grouping to filter out the portion of the
line that would contain the function / task / etc name. Example:
^\s*\bfunction\b.*(\b\w+\b)
- REGEX_GROUP section specifes what string you want to do the data
entry under. This is usually the task / function / etc name. For
example, for function xyz, the data entry would happen under the
string
xyz
. The REGEX_GROUP defined for the Verilogdefines
ormacros
is a special case because I wanted to also prefix the define/macro string with backtick`
. The reason is that in emacs when I have the cursor on a define like`XYZ
, etags-select-find-tag-at-point function uses that whole string including the backtick for searching in the TAGS file. - Examples of ctags kinds and their abbreviations:
t,task
f,function
m,module
. Specifying the kind is important because you can later specify which kinds of matches you want to log in the TAGS file. This is done using--systemverilog-kinds=+ctfmpied
.
- REGEX is the regular expression to define to find the line
containing the task, function, module, etc. You would normally
use atleast 1 regex grouping to filter out the portion of the
line that would contain the function / task / etc name. Example:
- You can specify which language files ctags should parse for tag generation:
--languages=systemverilog,C,C++,HTML,Lisp,Make,Matlab,Perl,Python,Sh,Tex
. NOTE: I learnt that specifying the language files you want to parse is better because if a particular extension is defined for more than 1 language, then they result in duplicate tag entries in the TAGS file. I am not sure if that duplication is done by ctags or emacs, but once I specified the languages I wanted to parse, I stopped getting duplicate entries when using the etags-select package in emacs. In my case, the .v extension was associated with Verilog language predefined in ctags, and it was also defined for systemverilog language in my.ctags
.
ctags execution #
Once you have your .ctags file ready, generate the TAGS file using
the command, ctags -Re -f /project/root/dir/TAGS /project/root/dir
.
Example: ctags -Re -f ~/.emacs.d/TAGS ~/.emacs.d
- The -R option makes ctags crawl through all directories recursively from the specified root directory.
- The -e option makes ctags generate the TAGS file in a format compatible with emacs.
I use Exuberant ctags 5.8 with emacs 24.3. My .ctags
is heavily
inspired from a verificationguild.com forum.
Ensure that you are using Exuberant ctags and not the ctags
that’s installed along with emacs by checking the output of
ctags --version
. Usually you would need to install ctags AFTER
installing emacs so that the ctags binary in /usr/local/bin
or
$HOME/local/bin
is the Exuberant version and not emacs.
emacs + ctags #
Here is my emacs configuration for ctags: [github].
My emacs ctags config starts by setting few variables to avoid any annoyances:
(setq tags-revert-without-query t)
This prevents emacs from asking you every time if you want to reread that updated TAGS file. Of course you would want to!(setq large-file-warning-threshold 30000000)
In most of the cases, TAGS files will be large (> 10MB). I didn’t want emacs warning me about that every time it accessed the TAGS files. So I increased the threshold to 30MB. So set the threshold as per your needs. You can also disable that warning completely by setting the value tonil
.(setq tags-case-fold-search nil)
I like the searches to be case-insensitive. It is useful when I manually search for a tag. But usually the way I use tags is: I put my cursor on the name of function/task/.. etc I want to jump to and hit my key-binding for tag search.
I rely on few packages to makes the emacs and TAGS files' interaction seemless: etags-table, ctags-update, etags-select. All are available through MELPA.
etags-table #
etags-table will help you load the correct TAGS file based on your
file path. But you have to load all the project path possibilities
into etags-table-alist
first! Let’s say one of the project roots
entered in that list is $PRJ. If your TAGS path is $PRJ/TAGS and you
search a tag in $PRJ/any/nested/path/file.c, etags-table will figure
out that you want to search in $PRJ/TAGS.
In my ctags setup file I check for a project-root
var and load that
into ’etags-table-list’ if available. I update the project-root var
using a shell env var. I haven’t committed that project-root var
assignment to github. But you can update that using projectile or any
other mechanism.
The beauty is that etags-table won’t load the TAGS files from ALL
the paths in ’etags-table-alist’. It will load only the relevant
one(s). Note that each entry in ’etags-table-alist’ is another
list. Each of those lists is of the nature '( PROJECT_PATH, TAGS_FILE_1, [OPTIONAL_TAGS_FILE_2, ..] )
.
(require 'etags-table)
(setq etags-table-alist
(list
`(,(concat user-emacs-directory "/.*") ,(concat user-emacs-directory "/TAGS"))
))
(setq etags-table-search-up-depth 15) ;; Max depth to search up for a tags file. nil means don't search.
;; Below function comes useful when you change the project-root symbol to a
;; different value (when switching projects)
(defun update-etags-table-then-find-tag ()
"Update etags-table based on the current value of project-root and then do
tag find"
(interactive)
(when (boundp 'project-root) ;; add-to-list if project-root symbol is defined
(add-to-list 'etags-table-alist
`(,(concat project-root "/.*") ,(concat project-root "/TAGS")) t))
(etags-select-find-tag-at-point)
)
ctags-update #
ctags-update will update the first TAGS file that is found while searching up the parent directories from the path of the file that gets modified. You can configure how frequent you want the update frequency to be.
(require 'ctags-update)
(setq ctags-update-delay-seconds (* 30 60)) ;; every 1/2 hour
(autoload 'turn-on-ctags-auto-update-mode "ctags-update" "turn on `ctags-auto-update-mode'." t)
(add-hook 'verilog-mode-hook 'turn-on-ctags-auto-update-mode)
(add-hook 'emacs-lisp-mode-hook 'turn-on-ctags-auto-update-mode)
etags-select #
You can use etags-select
to pick one of
multiple tag matches. It is useful when a same function/task/.. has
multiple definitions and you need to pick the definition to jump
to. If multiple matches don’t exist, finding a tag at point will make
you jump directly to the definition file.
I prefer etags-select as helm-etags+ doesn’t play well with tags
that have a `
prefix (which is very crucial for jumping to
define/macro definitions in Verilog).
(require 'etags-select)
(define-key etags-select-mode-map (kbd "C-g") 'etags-select-quit)
;; Also quit etags-select when cursor moves to another window
(define-key etags-select-mode-map (kbd "C-x o") 'etags-select-quit)
(define-key etags-select-mode-map (kbd "C-x O") 'etags-select-quit)
(define-key etags-select-mode-map (kbd "C-p") 'etags-select-previous-tag)
(define-key etags-select-mode-map (kbd "C-n") 'etags-select-next-tag)
Key Bindings #
Finally here is the key-binding I have set to my quick hyper-space jumps to definitions of any kind.
(global-set-key (kbd "M-.") 'update-etags-table-then-find-tag)