commit a1107b911aaeb1cca0febfba0fd6d64077222a03 Author: Nikolay Puzanov Date: Thu Feb 5 14:53:00 2026 +0300 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..c24e3a1 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# verilog-align-ports + +Align SystemVerilog ANSI-style port declarations in a contiguous block around point. + +## Features + +- Aligns direction, type, range, name, and trailing `//` comments. +- Works on the contiguous block of port declarations above and below point. +- Stops at the first non-port line (including blank lines). + +## Installation + +```elisp +(add-to-list 'load-path "/path/to/verilog-align-ports") +(require 'verilog-align-ports) +``` + +## Usage + +1. Place point on a line that declares a port (input/output/inout). +2. Run `M-x verilog-align-ports`. + +Example input: + +```systemverilog + input wire rst_i, // rst, + input wire clk_i, // wr_clk, + input wire [DataWidth-1:0] d_i, // din, + output logic full_o, // full, + output [DataWidth-1:0] d_o, // dout, + output empty_o,// empty, + output logic valid_o // data_valid +``` + +Example output: + +```systemverilog + input wire rst_i, // rst, + input wire clk_i, // wr_clk, + input wire [DataWidth-1:0] d_i, // din, + output logic full_o, // full, + output [DataWidth-1:0] d_o, // dout, + output empty_o, // empty, + output logic valid_o // data_valid +``` + +## Tests + +Run: + +```bash +./run-tests.sh +``` diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..b0a7604 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +emacs -batch \ + -l ert \ + -l verilog-align-ports-test.el \ + -f ert-run-tests-batch-and-exit diff --git a/verilog-align-ports-test.el b/verilog-align-ports-test.el new file mode 100644 index 0000000..4c03dc0 --- /dev/null +++ b/verilog-align-ports-test.el @@ -0,0 +1,71 @@ +;;; verilog-align-ports-test.el --- Tests for verilog-align-ports -*- lexical-binding: t; -*- + +;;; Commentary: +;; ERT tests for verilog-align-ports. + +;;; Code: + +(require 'ert) + +(defconst verilog-align-ports-test--dir + (file-name-directory (or load-file-name buffer-file-name))) + +(load-file (expand-file-name "verilog-align-ports.el" + verilog-align-ports-test--dir)) + +(ert-deftest verilog-align-ports-aligns-block () + (let* ((input (concat + (mapconcat + #'identity + '("module fifo_sync #(" + " parameter integer FifoWriteDepth = 2048," + " parameter integer DataWidth = 32," + " parameter integer WrDataCountWidth = $clog2(FifoWriteDepth)+1," + " parameter integer RdDataCountWidth = 4" + ") (" + " input wire rst_i, // rst," + " input wire clk_i, // wr_clk," + " input wire wr_en_i, // wr_en," + " input wire [DataWidth-1:0] d_i, // din," + " output logic full_o, // full," + " output logic [WrDataCountWidth-1:0] wr_data_count_o, // wr_data_count," + " input wire rd_en_i, // rd_en," + " output [DataWidth-1:0] d_o, // dout," + " output empty_o,// empty," + " output logic valid_o // data_valid" + ");") + "\n") + "\n")) + (expected (concat + (mapconcat + #'identity + '("module fifo_sync #(" + " parameter integer FifoWriteDepth = 2048," + " parameter integer DataWidth = 32," + " parameter integer WrDataCountWidth = $clog2(FifoWriteDepth)+1," + " parameter integer RdDataCountWidth = 4" + ") (" + " input wire rst_i, // rst," + " input wire clk_i, // wr_clk," + " input wire wr_en_i, // wr_en," + " input wire [DataWidth-1:0] d_i, // din," + " output logic full_o, // full," + " output logic [WrDataCountWidth-1:0] wr_data_count_o, // wr_data_count," + " input wire rd_en_i, // rd_en," + " output [DataWidth-1:0] d_o, // dout," + " output empty_o, // empty," + " output logic valid_o // data_valid" + ");") + "\n") + "\n"))) + (with-temp-buffer + (insert input) + (goto-char (point-min)) + (search-forward "input wire clk_i") + (beginning-of-line) + (verilog-align-ports) + (should (string= (buffer-string) expected))))) + +(provide 'verilog-align-ports-test) + +;;; verilog-align-ports-test.el ends here diff --git a/verilog-align-ports.el b/verilog-align-ports.el new file mode 100644 index 0000000..4681b8c --- /dev/null +++ b/verilog-align-ports.el @@ -0,0 +1,183 @@ +;;; verilog-align-ports.el --- Align SystemVerilog port declarations -*- lexical-binding: t; -*- + +;;; Commentary: +;; Align SystemVerilog ANSI-style port declarations in a contiguous block. + +;;; Code: + +(require 'cl-lib) +(require 'subr-x) + +(defun verilog-align-ports--line-port-p (pos) + (save-excursion + (goto-char pos) + (let ((line (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)))) + (string-match-p "^\\s-*\\(input\\|output\\|inout\\)\\b" line)))) + +(defun verilog-align-ports--split-comment (line) + (let ((pos (string-match "//" line))) + (if pos + (cons (substring line 0 pos) (substring line pos)) + (cons line nil)))) + +(defun verilog-align-ports--parse-line (line) + (let* ((split (verilog-align-ports--split-comment line)) + (code (string-trim-right (car split))) + (comment (cdr split))) + (when (string-match + "^\\(\\s-*\\)\\(input\\|output\\|inout\\)\\b\\s-*\\(.*\\)$" + code) + (let* ((indent (match-string 1 code)) + (dir (match-string 2 code)) + (rest (string-trim (match-string 3 code))) + (name nil) + (comma nil) + (type "") + (range "") + (before "")) + (if (string-match + "\\(\\\\[^[:space:]]+\\|[A-Za-z_][A-Za-z0-9_$]*\\)\\(\\s-*,\\)?\\s-*$" + rest) + (progn + (setq name (match-string 1 rest)) + (setq comma (when (match-string 2 rest) ",")) + (setq before + (string-trim-right (substring rest 0 (match-beginning 1))))) + (setq name rest) + (setq comma nil) + (setq before "")) + (setq before (string-trim-right before)) + (if (and (not (string-empty-p before)) + (string-match "\\(\\[[^][]+\\]\\)\\s-*$" before)) + (progn + (setq range (match-string 1 before)) + (setq type + (string-trim-right (substring before 0 (match-beginning 1))))) + (setq type (string-trim before)) + (setq range "")) + (list :indent indent + :dir dir + :type type + :range range + :name name + :comma comma + :comment comment))))) + +(defun verilog-align-ports--bounds () + (save-excursion + (unless (verilog-align-ports--line-port-p (line-beginning-position)) + (user-error "Point is not on a port declaration line")) + (let ((start (line-beginning-position)) + (end nil)) + (while (and (not (bobp)) + (save-excursion + (forward-line -1) + (verilog-align-ports--line-port-p + (line-beginning-position)))) + (forward-line -1) + (setq start (line-beginning-position))) + (goto-char start) + (while (and (not (eobp)) + (verilog-align-ports--line-port-p + (line-beginning-position))) + (forward-line 1)) + (setq end (line-beginning-position)) + (cons start end)))) + +(defun verilog-align-ports--collect (start end) + (let (entries) + (save-excursion + (goto-char start) + (while (< (point) end) + (let* ((line (buffer-substring-no-properties + (line-beginning-position) + (line-end-position))) + (entry (verilog-align-ports--parse-line line))) + (when entry + (push entry entries))) + (forward-line 1))) + (nreverse entries))) + +(defun verilog-align-ports--max-lengths (entries) + (let ((max-dir 0) + (max-type 0) + (max-range 0) + (max-name 0) + (has-comment nil)) + (dolist (entry entries) + (setq max-dir (max max-dir (length (plist-get entry :dir)))) + (setq max-type (max max-type (length (plist-get entry :type)))) + (setq max-range (max max-range (length (plist-get entry :range)))) + (let ((name (concat (plist-get entry :name) + (or (plist-get entry :comma) "")))) + (setq max-name (max max-name (length name)))) + (when (plist-get entry :comment) + (setq has-comment t))) + (list max-dir max-type max-range max-name has-comment))) + +(defun verilog-align-ports--pad (max-len len) + (make-string (+ 1 (- max-len len)) ?\s)) + +(defun verilog-align-ports--format-lines (entries) + (let* ((base-indent (plist-get (car entries) :indent)) + (maxes (verilog-align-ports--max-lengths entries)) + (max-dir (nth 0 maxes)) + (max-type (nth 1 maxes)) + (max-range (nth 2 maxes)) + (max-name (nth 3 maxes)) + (has-comment (nth 4 maxes)) + (has-type (> max-type 0)) + (has-range (> max-range 0))) + (mapcar + (lambda (entry) + (let* ((dir (plist-get entry :dir)) + (type (plist-get entry :type)) + (range (plist-get entry :range)) + (name (concat (plist-get entry :name) + (or (plist-get entry :comma) ""))) + (comment (plist-get entry :comment)) + (dir-pad (verilog-align-ports--pad max-dir (length dir))) + (type-pad (when has-type + (verilog-align-ports--pad max-type (length type)))) + (range-pad (when has-range + (verilog-align-ports--pad max-range (length range)))) + (name-pad (when (and has-comment comment) + (verilog-align-ports--pad max-name (length name))))) + (concat base-indent + dir + dir-pad + (when has-type type) + (when has-type type-pad) + (when has-range range) + (when has-range range-pad) + name + (when (and has-comment comment) name-pad) + (or comment "")))) + entries))) + +(defun verilog-align-ports--apply (start lines) + (save-excursion + (goto-char start) + (dolist (line lines) + (delete-region (line-beginning-position) (line-end-position)) + (insert line) + (forward-line 1)))) + +;;;###autoload +(defun verilog-align-ports () + "Align SystemVerilog port declarations around point." + (interactive) + (let* ((bounds (verilog-align-ports--bounds)) + (start (car bounds)) + (end (cdr bounds)) + (entries (verilog-align-ports--collect start end))) + (when (null entries) + (user-error "No port declarations found")) + (let ((lines (verilog-align-ports--format-lines entries))) + (verilog-align-ports--apply start lines)))) + +(provide 'verilog-align-ports) + +;;; verilog-align-ports.el ends here