tangle readme
update readme md
explain keys.
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.
Frequently used digital electronics tools.
Compilers
Synthesis
LSP
Verification
Projects
Hardware
Development
tcl:
git:
make:
fd:
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
Other than default emacs or package keys, the following are defined here.
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
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 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.
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"
You may test this configuration with help of the example code provide
./src/alu.vhd
./sim/alu_tb.vhd
.hdl-prj.json
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")))
(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)))))
(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)))))
(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))
(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))))
(use-package eglot
:custom
;; activate Eglot in non-project files
(eglot-extend-to-xref t))
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)))
(use-package eldoc
:custom
(eldoc-echo-area-prefer-doc-buffer t)
(eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly))
(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)))
;; )
)
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))
(use-package which-key-posframe
:init (which-key-posframe-mode)
:after which-key
:custom
(which-key-posframe-border-width 2))
(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)))
(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)))
plugins, part of corfu
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)))
info
(use-package corfu-info
:after corfu)
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)))
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)))
plugins, not part of corfu
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)
(use-package corfu-terminal
:init (corfu-terminal-mode))
(use-package corfu-doc-terminal
:init (corfu-doc-terminal-mode)
:after corfu-terminal)
Add icons to completion candidates.
(use-package all-the-icons-completion
:init (all-the-icons-completion-mode))
(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)))
(use-package embark
:bind (("C-." . embark-act) ; pick some comfortable binding
("C-;" . embark-dwim))) ; good alternative: M-.
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))
(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))
https://github.com/minad/marginalia
(use-package marginalia
:init (marginalia-mode)
:bind
(:map minibuffer-local-map
("M-A" . marginalia-cycle)))
(use-package orderless
:custom
(completion-styles '(orderless partial-completion basic))
(completion-category-overrides '((file (styles partial-completion)))))
(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)
(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)))
(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")))))
(use-package which-function
:init (which-function-mode t))
(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)))
(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))
(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)))