Output build artifacts to `build` subdirectory.
diff --git a/.gitignore b/.gitignore
index 56d3484..a299974 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,4 @@
 *.so
 *.swp
 *.log
-ql-qlf-plugin/pmgen/*
+/*/build
diff --git a/Makefile_plugin.common b/Makefile_plugin.common
index a244a66..33a769d 100644
--- a/Makefile_plugin.common
+++ b/Makefile_plugin.common
@@ -5,6 +5,7 @@
 # This shared object can be imported to Yosys with `plugin -i` command.
 #
 # Below is an example of a plugin Makefile that uses this template:
+# PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
 # NAME = plugin_name
 # SOURCES = source1.cc source2.cc
 # include ../Makefile_plugin.common
@@ -40,6 +41,11 @@
 
 SHELL := /usr/bin/env bash
 
+# Directory containing this Makefile
+TOP_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
+_MAKEFILES := $(abspath $(filter-out %.d,$(MAKEFILE_LIST)))
+
 # Either find yosys in system and use its path or use the given path
 YOSYS_PATH ?= $(realpath $(dir $(shell command -v yosys))/..)
 
@@ -58,32 +64,89 @@
 YOSYS_DATA_DIR = $(DESTDIR)$(shell $(YOSYS_CONFIG) --datdir)
 YOSYS_PLUGINS_DIR = $(YOSYS_DATA_DIR)/plugins
 
-OBJS := $(patsubst %.cc,%.o,$(SOURCES))
-DEPS ?=
+BUILD_DIR := $(PLUGIN_DIR)/build
 
-$(YOSYS_PLUGINS_DIR):
-	@mkdir -p $@
+# Filled below with all object file paths
+_ALL_OBJECTS :=
+# Filled below with all build directory paths
+_ALL_BUILD_SUBDIRS :=
 
+# Default rule
+
+.PHONY: all
 all: $(NAME).so
 
-$(OBJS): %.o: %.cc $(DEPS)
-	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(EXTRA_FLAGS) -c -o $@ $(filter %.cc, $^)
+# Object files
 
-$(NAME).so: $(OBJS)
-	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^ $(LDLIBS)
+define _process-single-source-file
+_source_abs := $(abspath $(addprefix $(PLUGIN_DIR)/,$(source)))
+_object_abs := $(abspath $(addprefix $(BUILD_DIR)/,$(source).o))
+_object_dir := $(abspath $(dir $(_object_abs)))
+_ALL_OBJECTS += $(_object_abs)
+_ALL_BUILD_SUBDIRS += $(_object_dir)
 
-../pmgen.py:
-	@$(MAKE) -C .. pmgen.py
+$(_object_abs): TARGET_SOURCES := $(_source_abs)
+$(_object_abs): $(_source_abs) | $(_object_dir)
+endef
+$(foreach source,$(SOURCES),$(eval $(value _process-single-source-file)))
 
-install_plugin: $(NAME).so | $(YOSYS_PLUGINS_DIR)
-	install -D $< $(YOSYS_PLUGINS_DIR)/$<
+$(_ALL_OBJECTS): $(_MAKEFILES)
+	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(EXTRA_FLAGS) -c -o $@ $(TARGET_SOURCES)
 
+# Objects list for the purpose of adding extra dependencies after inclusion.
+# Example use: `$(OBJECTS): $(BUILD_DIR)/some-file.h`
+OBJECTS := $(_ALL_OBJECTS)
+
+# Shared library
+
+_SO_LIB := $(BUILD_DIR)/$(NAME).so
+_ALL_BUILD_SUBDIRS += $(abspath $(dir $(_SO_LIB)))
+
+$(_SO_LIB): $(_ALL_OBJECTS) $(_MAKEFILES) | $(abspath $(dir $(_SO_LIB)))
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $(_ALL_OBJECTS) $(LDLIBS)
+
+.PHONY: $(NAME).so
+$(NAME).so: $(_SO_LIB)
+
+# Tests
+
+.PHONY: test test_clean
+ifneq ($(wildcard $(PLUGIN_DIR)/tests/Makefile),)
 test:
 	@$(MAKE) -C tests all
+test_clean:
+	$(MAKE) -C tests clean
+else
+test:
+test_clean:
+endif
+
+# Installation
+
+$(YOSYS_PLUGINS_DIR)/$(NAME).so: $(_SO_LIB) | $(YOSYS_PLUGINS_DIR)
+	install -D $(_SO_LIB) $@
+
+.PHONY: install_plugin
+install_plugin: $(YOSYS_PLUGINS_DIR)/$(NAME).so
 
 .PHONY: install
 install: install_plugin
 
-clean:
-	rm -f *.d *.o *.so
-	$(MAKE) -C tests clean
+# Cleanup
+
+clean: test_clean
+	rm -rf $(BUILD_DIR)
+
+# Other
+
+$(sort $(_ALL_BUILD_SUBDIRS)):
+	mkdir -p $@
+
+$(YOSYS_PLUGINS_DIR):
+	@mkdir -p $@
+
+PMGEN_PY := $(TOP_DIR)/pmgen.py
+
+$(PMGEN_PY):
+	@$(MAKE) -C $(TOP_DIR) pmgen.py
+
diff --git a/design_introspection-plugin/Makefile b/design_introspection-plugin/Makefile
index 604254e..178d5f3 100644
--- a/design_introspection-plugin/Makefile
+++ b/design_introspection-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = design_introspection
 SOURCES = design_introspection.cc \
 	  get_cmd.cc \
diff --git a/dsp-ff-plugin/Makefile b/dsp-ff-plugin/Makefile
index 5eeb910..0466d85 100644
--- a/dsp-ff-plugin/Makefile
+++ b/dsp-ff-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = dsp-ff
 SOURCES = dsp_ff.cc 
 
diff --git a/fasm-plugin/Makefile b/fasm-plugin/Makefile
index b149198..3ce41d5 100644
--- a/fasm-plugin/Makefile
+++ b/fasm-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = fasm
 SOURCES = fasm.cc
 include ../Makefile_plugin.common
diff --git a/integrateinv-plugin/Makefile b/integrateinv-plugin/Makefile
index f93225a..21b75ca 100644
--- a/integrateinv-plugin/Makefile
+++ b/integrateinv-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = integrateinv
 SOURCES = integrateinv.cc
 include ../Makefile_plugin.common
diff --git a/params-plugin/Makefile b/params-plugin/Makefile
index 9740c7b..b4e2d29 100644
--- a/params-plugin/Makefile
+++ b/params-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = params
 SOURCES = params.cc
 include ../Makefile_plugin.common
diff --git a/ql-iob-plugin/Makefile b/ql-iob-plugin/Makefile
index dc89757..4091a6a 100644
--- a/ql-iob-plugin/Makefile
+++ b/ql-iob-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = ql-iob
 SOURCES = ql-iob.cc pcf_parser.cc pinmap_parser.cc
 include ../Makefile_plugin.common
diff --git a/ql-qlf-plugin/Makefile b/ql-qlf-plugin/Makefile
index fd13bd5..d754156 100644
--- a/ql-qlf-plugin/Makefile
+++ b/ql-qlf-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = ql-qlf
 SOURCES = synth_quicklogic.cc \
           ql-dsp.cc \
@@ -26,13 +28,11 @@
           ql-dsp-io-regs.cc \
           ql-bram-asymmetric.cc
 
-DEPS = pmgen/ql-dsp-pm.h \
-       pmgen/ql-dsp-macc.h \
-       pmgen/ql-bram-asymmetric-wider-write.h \
-       pmgen/ql-bram-asymmetric-wider-read.h
-
 include ../Makefile_plugin.common
 
+# For pmgen/*.h
+CXXFLAGS += -I$(BUILD_DIR)
+
 COMMON          = common
 QLF_K4N8_DIR    = qlf_k4n8
 QLF_K6N10_DIR   = qlf_k6n10
@@ -78,28 +78,35 @@
                   $(PP3_DIR)/bram_init_32.vh   \
                   $(PP3_DIR)/qlal4s3b_sim.v    \
                   $(PP3_DIR)/mult_sim.v        \
-                  $(PP3_DIR)/qlal3_sim.v       \
+                  $(PP3_DIR)/qlal3_sim.v
 
-pmgen:
-	mkdir -p pmgen
+PMGEN_OUT_DIR := $(BUILD_DIR)/pmgen
 
-pmgen/ql-dsp-pm.h: ../pmgen.py ql_dsp.pmg | pmgen
-	python3 ../pmgen.py -o $@ -p ql_dsp ql_dsp.pmg
+$(PMGEN_OUT_DIR):
+	mkdir -p $@
 
-pmgen/ql-dsp-macc.h: ../pmgen.py ql-dsp-macc.pmg | pmgen
-	python3 ../pmgen.py -o $@ -p ql_dsp_macc ql-dsp-macc.pmg
+DEPS := $(PMGEN_OUT_DIR)/ql-dsp-pm.h \
+        $(PMGEN_OUT_DIR)/ql-dsp-macc.h \
+        $(PMGEN_OUT_DIR)/ql-bram-asymmetric-wider-write.h \
+        $(PMGEN_OUT_DIR)/ql-bram-asymmetric-wider-read.h
 
-pmgen/ql-bram-asymmetric-wider-write.h: ../pmgen.py ql-bram-asymmetric-wider-write.pmg | pmgen
-	python3 ../pmgen.py -o $@ -p ql_bram_asymmetric_wider_write ql-bram-asymmetric-wider-write.pmg
+$(DEPS): $(PMGEN_PY) | $(PMGEN_OUT_DIR)
 
-pmgen/ql-bram-asymmetric-wider-read.h: ../pmgen.py ql-bram-asymmetric-wider-read.pmg | pmgen
-	python3 ../pmgen.py -o $@ -p ql_bram_asymmetric_wider_read ql-bram-asymmetric-wider-read.pmg
+$(OBJECTS): $(DEPS)
+
+$(PMGEN_OUT_DIR)/ql-dsp-pm.h: ql_dsp.pmg
+	python3 $(PMGEN_PY) -o $@ -p ql_dsp ql_dsp.pmg
+
+$(PMGEN_OUT_DIR)/ql-dsp-macc.h: ql-dsp-macc.pmg
+	python3 $(PMGEN_PY) -o $@ -p ql_dsp_macc ql-dsp-macc.pmg
+
+$(PMGEN_OUT_DIR)/ql-bram-asymmetric-wider-write.h: ql-bram-asymmetric-wider-write.pmg
+	python3 $(PMGEN_PY) -o $@ -p ql_bram_asymmetric_wider_write ql-bram-asymmetric-wider-write.pmg
+
+$(PMGEN_OUT_DIR)/ql-bram-asymmetric-wider-read.h: ql-bram-asymmetric-wider-read.pmg
+	python3 $(PMGEN_PY) -o $@ -p ql_bram_asymmetric_wider_read ql-bram-asymmetric-wider-read.pmg
 
 install_modules: $(VERILOG_MODULES)
 	$(foreach f,$^,install -D $(f) $(YOSYS_DATA_DIR)/quicklogic/$(f);)
 
 install: install_modules
-
-clean:
-	$(MAKE) -f ../Makefile_plugin.common $@
-	rm -rf pmgen
diff --git a/sdc-plugin/Makefile b/sdc-plugin/Makefile
index 3ae63e0..871c9bc 100644
--- a/sdc-plugin/Makefile
+++ b/sdc-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = sdc
 SOURCES = buffers.cc \
           clocks.cc \
diff --git a/systemverilog-plugin/Makefile b/systemverilog-plugin/Makefile
index fdfcaed..7d912cf 100644
--- a/systemverilog-plugin/Makefile
+++ b/systemverilog-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = systemverilog
 SOURCES = UhdmAst.cc \
           uhdmastfrontend.cc \
diff --git a/uhdm-plugin/Makefile b/uhdm-plugin/Makefile
index 3eea3d4..4f67830 100644
--- a/uhdm-plugin/Makefile
+++ b/uhdm-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = uhdm
 SOURCES = uhdm.cc
 include ../Makefile_plugin.common
diff --git a/xdc-plugin/Makefile b/xdc-plugin/Makefile
index a81220b..b2ebd88 100644
--- a/xdc-plugin/Makefile
+++ b/xdc-plugin/Makefile
@@ -14,6 +14,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+PLUGIN_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+
 NAME = xdc
 SOURCES = xdc.cc
 include ../Makefile_plugin.common