~csantosb/emacs.vhdl-ide

GNU/Emacs as a full VHDL IDE

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~csantosb/emacs.vhdl-ide
read/write
git@git.sr.ht:~csantosb/emacs.vhdl-ide

You can also use your local clone with git send-email.

GNU/Emacs 29.4 full IDE for VHDL development - with tools.

Using Guix as a package manager. Requires guix-electronics channel.

Tested under GNU/Emacs 29.4.

#Tools

Frequently used digital electronics tools.

Compilers

  • nvc: compiler and simulator
  • ghdl: analyzer, compiler, simulator and (experimental) synthesizer

Synthesis

LSP

Verification

  • cocotb: coroutine based cosimulation testbench environment using python
  • cocotb-bus: cocotb reusable tools
  • vunit: unit testing framework
  • osvvm: verification libraries and scripts
  • gtkwave: waveform viewer

Projects

  • hdlmake: multi-purpose makefiles for projects

Hardware

Development

  • tcl:

  • git:

  • make:

  • fd:

#Use

The init.el configuration file in this repository must be used as Emacs init file

git clone --depth=1 https://git.sr.ht/~csantosb/emacs.vhdl-ide $TMPDIR/vhdl-ide

Then, just simply

cd $TMPDIR/vhdl-ide
./install.sh # create guix profile and install emacs ide here
./launch-emacs.sh

#keys

Other than default emacs or package keys, the following are defined here.

  • C-M-SPC: launch a terminal
  • M-.: find definition
  • M-,: back from definition

#Scripts

#install.sh

Install script.

First, create a dedicated guix profile

. ./_emacs-ide.sh

Download and install packages in the dedicated profile from manifest file.

printf 'GUIX profile:\t%s\n' "$GUIX_PROFILE"
guix package -p "$GUIX_PROFILE" -m manifest.scm

#manifest.scm

List of required packages to install.

(specifications->manifest
 (list

  ;; compiler
  "ghdl-lsp"
  "nvc"

  ;; scripting
  "tcl"
  "tcllib"

  ;; development
  "make"
  "fd"
  "git"

  ;; emacs packages
  "emacs"
  "tree-sitter"
  "emacs-elisp-demos"
  "emacs-vhdl-ext"
  "emacs-vhdl-ts-mode"
  "emacs-fpga"
  "emacs-which-key-posframe"
  "emacs-all-the-icons"
  "emacs-all-the-icons-completion"
  "emacs-consult"
  "emacs-consult-eglot"
  "emacs-consult-dir"
  "emacs-embark"
  "emacs-vertico"
  "emacs-vertico-posframe"
  "emacs-marginalia"
  "emacs-vterm"
  "emacs-orderless"
  "emacs-corfu"
  "emacs-corfu-terminal"
  "emacs-corfu-doc"
  "emacs-corfu-doc-terminal"
  "emacs-nano-theme"
  "emacs-nano-modeline"
  "emacs-kind-icon"
  "emacs-crux"
  "emacs-helpful"
  "emacs-eldoc-box"
  "emacs-visual-fill-column"
  "emacs-doom-modeline"

  ;; fonts
  "font-google-roboto"
  "font-fira-code"
  "font-fira-mono"
  "font-fira-sans"

  ;; project management
  "python-hdlmake"

  ;; synthesis
  "yosys"
  "ghdl-yosys-plugin"

  ;; wave viewer
  "gtkwave"

  ;; programming
  "openfpgaloader"

  ;; verification
  "python-cocotb"
  "python-cocotb-bus"
  "python-vunit"

  ;; osvvm
  "osvvm"
  "osvvm:uart"
  "osvvm:scripts"
  "osvvm:common"))

#launch-emacs.sh

Launch emacs vhdl ide with this script

First, activate the dedicated guix profile

. ./_emacs-ide.sh

Then, launch emacs with:

printf 'GUIX profile:\t%s\n' "$GUIX_PROFILE"
emacs --init-directory=. #"$TMPDIR/vhdl-ide"

It assumes /$TMPDIR/vhdl-vhdlide/ as user-emacs-directory to avoid overwritting the default user directory.

#setup profile

Create and activate a custom, dedicated profile to include all necessary packages.

export _PROFILE=emacs-vhdl-ide
[ ! -d "$HOME/.guix-profiles/$_PROFILE" ] && mkdir -p "$HOME/.guix-profiles/$_PROFILE"
export GUIX_PROFILE=$HOME/.guix-profiles/$_PROFILE/guix-profile
[ -f "$GUIX_PROFILE/etc/profile" ] && . "$GUIX_PROFILE/etc/profile"
printf 'GUIX profile:\t%s\n' "$GUIX_PROFILE"

#Testing

You may test this configuration with help of the example code provide

./src/alu.vhd
./sim/alu_tb.vhd
.hdl-prj.json

#Init.el

#variables

Setup necessary variables.

(setq use-package-always-defer t
      use-package-always-ensure nil)

(setenv "GHDL"
        (format "%s/bin/ghdl" (getenv "GUIX_PROFILE")))

(add-to-list 'treesit-extra-load-path
             (format "%s/lib/tree-sitter" (getenv "GUIX_PROFILE")))

#vhdl mode

(use-package vhdl-mode
  :hook
  (vhdl-mode . (lambda()
                 (vhdl-stutter-mode t)
                 (vhdl-electric-mode t)
                 (when (and (executable-find "ghdl-ls")
                            (locate-dominating-file
                             default-directory "hdl-prj.json"))
                   (eglot-ensure)))))

#vhdl-ts mode

(require 'treesit)
(use-package vhdl-ts-mode
  :mode (("\\.\\(vhd\\(?:l?\\)?\\)" . vhdl-ts-mode))
  :when (and (treesit-available-p)
             (treesit-language-available-p 'vhdl) ; vhdl-ts-install-grammar
             (treesit-ready-p 'vhdl t))
  :bind
  (:map vhdl-ts-mode-map
        ("C-c C-b" . nil) ; vhdl-ts-beautify-buffer
        ("C-M-u" . #'vhdl-ts-find-entity-instance-bwd))
  :custom
  (vhdl-ts-indent-level 2)
  (vhdl-ts-imenu-style 'tree-group)
  (vhdl-ts-beautify-align-ports-and-params t)
  :hook
  (vhdl-ts-mode . (lambda ()
                    (vhdl-ext-mode)
                    (require 'fpga)
                    (superword-mode -1)
                    (subword-mode t)
                    (when outline-minor-mode
                      (setq-local
                       outline-regexp
                       "^\\s-*--\\s-\\([*]\\{1,8\\}\\)\\s-\\(.*\\)$"))
                    ;; otherwise, I’m unable to make imenu work properly
                    (setq-local eglot-stay-out-of (list 'imenu)))))

#vhdl-ext mode

(use-package vhdl-ext
  :after vhdl-ts-mode
  :init
  (setq vhdl-ext-eglot-default-server 've-ghdl-ls
        vhdl-ext-feature-list '(font-lock
                                xref
                                hierarchy
                                eglot ; sets vhdl-ext-eglot-default-server
                                beautify
                                navigation
                                imenu
                                which-func))
  :bind
  (:map vhdl-ext-mode-map
        ("C-M-i" . nil) ; vhdl-ext-beautify-block-at-point
        ("C-c C-c" . vhdlide/vhdl-ext-beautify-region-or-buffer)
        ("C-c C-b" . vhdlide/vhdl-ext-beautify-region-or-buffer))
  :config

  (defun vhdlide/vhdl-ext-beautify-region-or-buffer (&optional arg)
    "Beautify with vhdl-ts, using ‘beautify-block-at-point’.
With a prefix ARG, call ‘vhdl-ts-beautify-buffer’ instead.
With a double prefix ARG, beautify current dir instead."
    (interactive "P")
    (cond
     ((equal arg '(16))
      (call-interactively 'vhdl-ext-beautify-dir-files))
     ((equal arg '(4))
      (vhdl-ts-beautify-buffer))
     (t
      (vhdl-ext-beautify-block-at-point)))
    (save-buffer))
  (vhdl-ext-mode-setup))

#fpga mode

(use-package fpga

;;; Mode

  :mode (("\\.vds?\\'" . fpga-xilinx-vivado-compilation-mode)
         ("\\runme.log?\\'" . fpga-xilinx-vivado-compilation-mode))

;;; Init

  :init

  (setq fpga-feature-list '(xilinx yosys))

;;; Config

  :config

  (require 'tcl)

  (defvar csb/fpga-xilinx-vivado-shell-outline-regexp
    (regexp-opt
     (append
      (mapcar (lambda (arg) (format "%s:" arg)) fpga-xilinx-vivado-shell-commands)
      (list "*** Running"
            "ERROR: "
            "WARNING:"
            "Command: "
            "wait_on_runs: "
            "___ ")))
    "docstring")

  (defvar csb/fpga-xilinx-vivado-compilation-outline-regexp
    (regexp-opt
     (append
      (mapcar (lambda (arg) (format "%s:" arg)) fpga-xilinx-vivado-shell-commands)
      (list "*** Running"
            "Command: "
            "wait_on_runs: "
            "___ ")))
    "docstring")

  (defvar csb/fpga-xilinx-vivado-make-targets
    (list "project"
          "synthesize"
          "par"
          "bitstream"
          "all")
    "toto")

;;; Custom

  :custom

  ;; yosys
  (fpga-yosys-cmd-opts (list "-m $HOME/.guix-profile/lib/yosys/ghdl.so"))
  (fpga-yosys-buf nil)
  (fpga-yosys-bin "~/.guix-profile/bin/yosys")
  ;; check fpga-yosys--base-cmd

  ;; xilinx-vivado
  (fpga-xilinx-vivado-buf nil)
  ;; (fpga-xilinx-vivado-cmd-opts '("-mode" "tcl" "-nojournal" "-nolog"))
  (fpga-xilinx-vivado-cmd-opts '("-mode" "tcl"))
  (fpga-xilinx-vivado-bin
   "export TERM=xterm; /opt/Xilinx/Vivado/2024.1/bin/vivado")
  (fpga-xilinx-vivado--base-cmd
   (concat fpga-xilinx-vivado-bin
           " " (mapconcat #'identity fpga-xilinx-vivado-cmd-opts " ")))

;;; Bind

  :bind

  (:map tcl-mode-map
        ("C-c C-c" . fpga-xilinx-vivado-shell-send-line-or-region-and-step)
        ("C-c C-j" . fpga-xilinx-vivado-shell-send-line-or-region-and-step))

;;; Hook

  :hook

  ;; (setq compilation-start-hook nil)
  (compilation-start .(lambda (arg)
                        ;; auto set mode in compilation buffer
                        (when compilation-arguments
                          (let ((_tmp (car (last
                                            (split-string
                                             (car compilation-arguments))))))
                            (when (member _tmp
                                          csb/fpga-xilinx-vivado-make-targets)
                              (fpga-xilinx-vivado-compilation-mode))))))

  ;; (setq tcl-mode-hook nil)
  (tcl-mode . (lambda ()
                (setq-local csb/toggle-inferior-shell
                            #'fpga-xilinx-vivado-shell)))

  ;; (setq fpga-xilinx-vivado-compilation-mode-hook nil)
  (fpga-xilinx-vivado-compilation-mode .
                                       (lambda ()
                                         (setq-local
                                          outline-regexp
                                          csb/fpga-xilinx-vivado-compilation-outline-regexp)
                                         (outline-minor-mode)
                                         (setq fill-column 150)
                                         (visual-fill-column-mode)
                                         (when (buffer-file-name)
                                           (turn-on-auto-revert-tail-mode))))

  ;; (setq fpga-xilinx-vivado-compilation-mode-hook nil)
  (fpga-xilinx-vivado-shell-mode .(lambda ()
                                    (setq-local
                                     outline-regexp
                                     csb/fpga-xilinx-vivado-shell-outline-regexp)
                                    (setq fill-column 150)
                                    (visual-fill-column-mode))))

#eglot

(use-package eglot
  :custom
  ;; activate Eglot in non-project files
  (eglot-extend-to-xref t))

#flymake

Flymake is a modern on-the-fly syntax checking extension for GNU Emacs.

(use-package flymake
  :hook
  (prog-mode . flymake-mode)
  :config
  (vhdlvhdlide/which-key-add "M-SPC")
  :bind
  (:map flymake-diagnostics-buffer-mode-map
        ("C-j" . flymake-goto-diagnostic)
        ("RET" . flymake-goto-diagnostic)
        ("SPC" . flymake-show-diagnostic)
        :map flymake-mode-map
        ("M-SPC n" . flymake-goto-next-error)
        ("M-SPC p" . flymake-goto-prev-error)
        ;; Diagnostics
        ("M-SPC d" . flymake-show-buffer-diagnostics)
        ("M-SPC b" . flymake-show-buffer-diagnostics)
        ("M-SPC P" . flymake-show-project-diagnostics)
        ;;
        ("M-SPC l" . flymake-switch-to-log-buffer)
        ("M-SPC r" . flymake-reporting-backends)
        ("M-SPC R" . flymake-running-backends)))

#eldoc

(use-package eldoc
  :custom
  (eldoc-echo-area-prefer-doc-buffer t)
  (eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly))

#eldoc-box

(use-package eldoc-box
  :after eldoc
  :bind
  (("C-M-?" . eldoc-box-help-at-point))
  :config
  ;; check with (describe-face 'eldoc-box-body)
  ;; (set-face-attribute
  ;;  'eldoc-box-body nil
  ;;  ;; :family "Symbola"
  ;;  :height (* 10 (+ 1 csb/font-size)))
  ;; (set-face-attribute
  ;;  'eldoc-box-border nil
  ;;  :background "#000000"
  ;;  )
  ;; :custom
  ;; (eldoc-box-offset '(30 30 30))
  ;; (eldoc-box-max-pixel-width 800)
  ;; (eldoc-box-max-pixel-height ) ; 700 def
  ;; (eldoc-box-clear-with-C-g t)
  ;; (eldoc-box-cleanup-interval 5)

  :hook

  (prog-mode . eldoc-box-hover-at-point-mode)
  ;; (eglot-managed-mode . eldoc-box-hover-mode)
  ;; eldoc-box-buffer-hook
  ;; (
  ;;  ;; (org-mode . (lambda ()
  ;;  ;;               (eldoc-box-hover-mode)))
  ;;  ;; (emacs-lisp-mode . (lambda ()
  ;;  ;;                      (eldoc-box-hover-mode -1)
  ;;  ;;                      (eldoc-box-hover-at-point-mode t)))
  ;;  )
  )

#which-key

Emacs package that displays available keybindings in popup.

(use-package which-key
  :init
  (which-key-mode 1)
  (defun vhdlvhdlide/which-key-add (key)
    (when (require 'which-key nil t)
      (add-to-list 'which-key-allow-regexps key)))
  :config
  (which-key-setup-minibuffer)
  :custom
  (which-key-idle-delay 0.2))

#which-key posframe

(use-package which-key-posframe
  :init (which-key-posframe-mode)
  :after which-key
  :custom
  (which-key-posframe-border-width 2))

#project

(use-package project
  :init
  (keymap-unset global-map "C-x p") ; remove default
  (keymap-set global-map "C-c p" project-prefix-map) ; andd replace it by
  (vhdlvhdlide/which-key-add "C-c p") ; a little help
  :custom
  (project-switch-commands 'project-dired)
  :bind
  (("M-l" . consult-project-buffer)
   :map project-prefix-map
   ("SPC" . consult-dir)
   ("p" . project-switch-project)
   ("k" . project-kill-buffers)
   ("q" . nil)))

#corfu

#gui corfu

(use-package corfu
  :init (global-corfu-mode)
  :custom
  ;; Quit ?
  (corfu-quit-at-boundary 'separator)
  (corfu-quit-no-match t)
  (corfu-cycle t)
  (corfu-auto t)
  (corfu-auto-prefix 2)
  (corfu-auto-delay 0.5)
  (corfu-preview-current 'insert)
  :bind
  (:map corfu-map
        ("C-j" . corfu-complete)
        ("SPC" . corfu-insert-separator)))
  1. plugins, part of corfu

    1. history

      Sort candidates by their history position.

      (use-package corfu-history
        :after corfu
        :init (corfu-history-mode)
        :config
        (when (require 'savehist nil t)
          (add-to-list 'savehist-additional-variables 'corfu-history)))
      
    2. info

      (use-package corfu-info
        :after corfu)
      
    3. popupinfo

      (use-package corfu-popupinfo
        :after corfu
        :init (corfu-popupinfo-mode)
        :bind
        (:map corfu-popupinfo-map
              ("M-t" . corfu-popupinfo-toggle)
              ("M-p" . corfu-popupinfo-scroll-down)
              ("M-n" . corfu-popupinfo-scroll-up))
        :custom
        (corfu-popupinfo-max-height 30)
        (corfu-popupinfo-delay '(1.0 . 0.5)))
      
    4. quick

      (use-package corfu-quick
        :after corfu
        :bind
        (:map corfu-map
              ("M-j" . corfu-quick-jump) ; Jump to candidate using quick keys
              ("M-c" . corfu-quick-complete) ; Complete candidate using quick keys
              ("M-i" . corfu-quick-insert)))
      
  2. plugins, not part of corfu

    1. kind-icon

      (use-package kind-icon
        :after corfu
        :custom
        ;; ;; TODO: remove when problem fixed
        ;; (kind-icon-use-icons nil)
        (kind-icon-default-face 'corfu-default) ; compute blended backgrounds
        :config
        (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
      (require 'kind-icon)
      

#terminal corfu

(use-package corfu-terminal
  :init (corfu-terminal-mode))

(use-package corfu-doc-terminal
  :init (corfu-doc-terminal-mode)
  :after corfu-terminal)

#all the icons completions

Add icons to completion candidates.

(use-package all-the-icons-completion
  :init (all-the-icons-completion-mode))

#consult

(use-package consult
  :init
  (define-prefix-command 'vhdlide/consult-map)
  (define-key ctl-x-map "c" 'vhdlide/consult-map)
  (vhdlide/which-key-add "C-x c")
  :bind
  (("M-s M-g" . consult-grep) ; A recursive grep
   ("C-x C-b" . consult-buffer) ; Switch to another buffer, or bookmarked file, or recently
   ("M-SPC" . consult-buffer) ; Switch to another buffer, or bookmarked file, or recently
   ("M-s M-l" . consult-line)
   :map vhdlide/consult-map
   ("L" . consult-locate)
   ("i" . consult-imenu)
   ("o" . consult-outline)
   ("I" . consult-imenu-multi)
   ("M-y" . consult-yank-from-kill-ring)
   ("M-s" . consult-ripgrep)))

#embark

(use-package embark
  :bind (("C-." . embark-act)         ; pick some comfortable binding
         ("C-;" . embark-dwim)))      ; good alternative: M-.

#vertico

VERTical Interactive COmpletion provides a performant and minimalistic vertical completion UI based on the default completion system.

(use-package vertico
  :custom
  (vertico-cycle t)
  (vertico-resize t)
  (vertico-count 30)
  :bind
  (:map vertico-map
        ("TAB" . embark-act)
        ("C-c C-c" . embark-collect)
        ("C-c C-e" . embark-export))
  :init
  (vertico-mode t))

#vertico posframe

(use-package vertico-posframe
  :init (vertico-posframe-mode)
  :after vertico
  :custom
  (vertico-posframe-min-width 20)
  (vertico-posframe-min-height 1)
  (vertico-posframe-border-width 2)
  (vertico-posframe-poshandler #'posframe-poshandler-frame-center))

#marginalia

https://github.com/minad/marginalia

(use-package marginalia
  :init (marginalia-mode)
  :bind
  (:map minibuffer-local-map
        ("M-A" . marginalia-cycle)))

#orderless

(use-package orderless
  :custom
  (completion-styles '(orderless partial-completion basic))
  (completion-category-overrides '((file (styles partial-completion)))))

#nano

(require 'nano-theme)
(setq nano-font-size 12
      nano-fonts-use t)
(load-theme 'nano-light t)

(require 'nano-modeline)
(nano-modeline-org-mode)
(add-hook 'prog-mode-hook #'nano-modeline-prog-mode)

#crux

(use-package crux
  :bind
  (([remap kill-whole-line] . crux-kill-whole-line)
   ([remap move-beginning-of-line] . crux-move-beginning-of-line)
   ("C-a"  . crux-move-beginning-of-line)
   :map ctl-x-map
   ("C-n"  . crux-create-scratch-buffer)))

#vterm

(use-package vterm
  :commands vterm
  :init
  :custom
  (vterm-kill-buffer-on-exit t)
  (vterm-clear-scrollback-when-clearing nil)
  :config
  :hook
  ((vterm-mode . (lambda ()
                   (nano-modeline-vterm-mode)
                   (hl-line-mode -1)))
   (vterm-copy-mode . (lambda ()
                        (hl-line-mode t))))
  :bind
  (("C-M-SPC" . (lambda (&optional arg)
                  "Launch vterm below current window"
                  (interactive)
                  (split-window-below)
                  (vterm-other-window)))
   :map vterm-mode-map
   ("C-c C-t" . nil) ; disable default copy mode
   ("C-c C-j" . vterm-copy-mode) ; I prefer this one
   ("C-q" . vterm-send-next-key)
   ("C-y" . vterm-yank)
   ("C-c C-n" . vterm-next-prompt)
   ("C-c C-p" . vterm-previous-prompt)
   :map vterm-copy-mode-map
   ("C-c C-k" . (lambda ()
                  (interactive)
                  (vterm-copy-mode -1)
                  (message "Disabling vterm copy mode")))))

#which function

(use-package which-function
  :init (which-function-mode t))

#helpful

(use-package helpful
  :config
  (advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update)
  :bind
  (("C-h c" . helpful-command)
   ("C-h @" . helpful-macro)
   ("C-h k" . helpful-key)
   ("C-h f" . helpful-function)
   ("C-h F" . helpful-callable)
   ("C-h v" . helpful-variable)
   ("C-h ." . helpful-at-point)
   :map helpful-mode-map
   ("C-j" . push-button)))

#doom mode line

(use-package doom-modeline
  :init (doom-modeline-mode t)
  :custom
  (doom-modeline-support-imenu t)
  (doom-modeline-time t)
  (doom-modeline-battery t)
  (doom-modeline-env-version t)
  (doom-modeline-major-mode-color-icon t)
  (doom-modeline-height 20))

#isearch

(use-package isearch

  :custom

  (isearch-lazy-count t) ; Show match numbers in the search prompt.
  (lazy-count-prefix-format nil)
  (lazy-count-suffix-format "   (%s/%s)")
  (isearch-regexp t)

  :config

  (defun vhdlvhdlide/isearch-forward (&optional arg)
    "Defaults to ‘isearch-forward’.
With prefix ‘ARG’ use ‘isearch-forward-symbol-at-point’ instead."
    (interactive "P")
    (cond ((equal arg '(4))
           (isearch-forward-symbol-at-point))
          (t
           ;; prefer regexp
           (isearch-forward '(4)))))

  (defun vhdlvhdlide/isearch-backward (&optional arg)
    "Defaults to ‘isearch-backward’.
With prefix ‘ARG’ use ‘isearch-forward-symbol-at-point’ instead."
    (interactive "P")
    (cond ((equal arg '(4))
           (progn
             (isearch-forward-symbol-at-point)
             (let ((superword-mode t))
               (backward-word 1))))
          (t
           ;; prefer regexp
           (isearch-backward '(4)))))

  :bind

  (("C-s" . vhdlvhdlide/isearch-forward)
   ("C-r" . vhdlvhdlide/isearch-backward)

   :map isearch-mode-map

   ("C-j" . isearch-exit)))
Do not follow this link