A Scripter's Notes

Emacs, scripting and anything text oriented.

ctags, systemverilog and emacs

Update (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 Verilog defines or macros 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.
  • 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 to nil.
  • (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)

Comments

comments powered by Disqus