diff --git a/.github/workflows/fpga-pm3.yml b/.github/workflows/fpga-pm3.yml new file mode 100644 index 000000000..afe9a969c --- /dev/null +++ b/.github/workflows/fpga-pm3.yml @@ -0,0 +1,72 @@ +name: FPGA PM3 Build + +on: + push: + paths: + - 'fpga/**' + - '.github/workflows/fpga-pm3.yml' + pull_request: + paths: + - 'fpga/**' + - '.github/workflows/fpga-pm3.yml' + workflow_dispatch: + +permissions: + contents: read + +env: + # The PM3, PM3-ULT and iCopy-X images are Spartan-II and Spartan-3E, and + # WebPack ISE 10.1 is the only tool chain that places and routes them. It is a + # licensed multi gigabyte install, so the build runs inside this image, the + # one doc/fpga_arm_notes.md points at. Pinned by digest rather than by the + # tag, which has been re-pushed since it was first published. + ISE_IMAGE: nhutton/prox-container@sha256:b42abab7f88df928e59ab97ce3a557b547bc5fc967c19e49f30d7cfb8d3a5a15 + +jobs: + build: + name: Build the PM3 bitstreams + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + # The image is 8 GB unpacked, so keep an eye on the runner's disk. + - name: Pull the ISE image + run: | + df -h / + docker pull "$ISE_IMAGE" + + - name: Build the PM3 bitstreams + run: | + set -euo pipefail + docker run --rm -v "$PWD:/src" -w /src "$ISE_IMAGE" bash -euc ' + # The image exports its PATH from ~/.bashrc, which returns early in + # a non interactive shell, so name the ISE bin directory here. + export PATH="$PATH:/opt/Xilinx/10.1/ISE/bin/lin" + # XST cannot create its project temp directory on the mounted + # volume, so build a copy inside the container and copy the + # bitstreams back out over the checked out ones. + rm -rf /build && mkdir /build && cp -r /src/fpga/. /build/ + cd /build + make all -j"$(nproc)" + cp *.bit /src/fpga/ + ' + + - name: Check the bitstreams + run: | + set -euo pipefail + # fpga_icopyx_felica, fpga_icopyx_hf_15 and fpga_icopyx_lf have no + # target of their own and stay empty; the rest must have content + for t in fpga_pm3_lf fpga_pm3_hf fpga_pm3_hf_15 fpga_pm3_felica \ + fpga_pm3_ult_lf fpga_pm3_ult_hf fpga_pm3_ult_hf_15 \ + fpga_pm3_ult_felica fpga_icopyx_hf; do + size=$(stat -c%s "fpga/$t.bit") + echo "fpga/$t.bit ${size} bytes" + test "$size" -gt 1000 + done + + - uses: actions/upload-artifact@v6 + with: + name: fpga-pm3-bitstreams + path: fpga/*.bit + if-no-files-found: error diff --git a/.github/workflows/fpga-pm5.yml b/.github/workflows/fpga-pm5.yml new file mode 100644 index 000000000..00076fff8 --- /dev/null +++ b/.github/workflows/fpga-pm5.yml @@ -0,0 +1,89 @@ +name: FPGA PM5 Build + +on: + push: + paths: + - 'fpga/**' + - '.github/workflows/fpga-pm5.yml' + pull_request: + paths: + - 'fpga/**' + - '.github/workflows/fpga-pm5.yml' + workflow_dispatch: + +env: + # oss-cad-suite is released daily; bump the date to move to a newer one. + OSS_CAD_SUITE_VERSION: '2026-10-11' + +permissions: + contents: read + +jobs: + pm5: + name: Build PM5 bitstream + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install oss-cad-suite + run: | + set -euo pipefail + url="https://github.com/YosysHQ/oss-cad-suite-build/releases/download/${OSS_CAD_SUITE_VERSION}/oss-cad-suite-linux-x64-${OSS_CAD_SUITE_VERSION//-/}.tgz" + echo "Fetching $url" + curl -sSL "$url" | tar -xz -C "$RUNNER_TEMP" + # the makefile puts the suite's bin/, lib/ and bundled python3 in + # front of PATH itself, so nothing else needs to be exported + echo "OSS_CAD_SUITE=$RUNNER_TEMP/oss-cad-suite" >> "$GITHUB_ENV" + + # Runs the real tools through the same environment the build uses, so a + # suite that cannot run fails here instead of half way through the build. + - name: Check the toolchain + run: make -C fpga -f proxmark5_fpga.mk smoke + + # Place and route fails the job on a timing violation, so this does not + # only prove the design still elaborates. + - name: Build the PM5 bitstream + run: make -C fpga pm5 + + - name: Check the bitstream + run: | + set -euo pipefail + bin_size=$(stat -c%s fpga/fpga_pm5.bin) + fs_size=$(stat -c%s fpga/fpga_pm5.fs) + echo "fpga_pm5.bin ${bin_size} bytes" + echo "fpga_pm5.fs ${fs_size} bytes" + # the .bin is the bitstream the ARM JTAG loader shifts out, the .fs is + # its ASCII form: 8 characters plus a newline per byte + test "$bin_size" -gt 100000 + test "$fs_size" -gt $((bin_size * 6)) + test "$fs_size" -lt $((bin_size * 10)) + + # The device only accepts a bitstream naming its own IDCODE and + # otherwise stays unconfigured, with nothing driving the antennas. + # apycula's GW1N-4 database writes the plain GW1N-4, so the build + # stamps the part's in (fpga/common/gowin_devid.py) - keep it that way. + # The IDCODE is the four bytes at 0x1C of the header. + idcode=$(od -An -tx1 -j 28 -N 4 fpga/fpga_pm5.bin | tr -d ' \n') + echo "device idcode in the bitstream: 0x${idcode}" + test "$idcode" = "1100381b" + + # A rebuild from scratch has to produce the same bytes; the tools carry no + # timestamp, so a difference means something started leaking one in, and + # a released bitstream could no longer be checked against the sources. + - name: Check the build is reproducible + run: | + set -euo pipefail + md5sum fpga/fpga_pm5.fs fpga/fpga_pm5.bin > "$RUNNER_TEMP/bitstream.md5" + make -C fpga -f proxmark5_fpga.mk clean + make -C fpga pm5 + md5sum -c "$RUNNER_TEMP/bitstream.md5" + + - name: Upload the bitstream + uses: actions/upload-artifact@v6 + with: + name: fpga-pm5-bitstream + path: | + fpga/fpga_pm5.fs + fpga/fpga_pm5.bin + if-no-files-found: error diff --git a/.gitignore b/.gitignore index e667db8a6..22a5ed0a2 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,8 @@ tools/mfulc_des_brute/mfulc_des_brute !tools/lena.bmp fpga/__build* +# the ASCII form of the PM5 bitstream, regenerated by the build like its .bin +fpga/*.fs # offcial dumps folder dumps/* diff --git a/CHANGELOG.md b/CHANGELOG.md index bd984ee51..6c7808541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Fixed `hf 14a info` now correctly handles NAK when receiving RATS (@iceman1001) - Fixed `hf mfu restore` to support Ultralight AES (@iceman1001) - Fixed `hf mfu info` to print UL-AES config pages again (@iceman1001) +- Added FPGA opensource (@xianglin1998) ## [Frosty Lemon.4.23346][2026-09-18] - Fixed `hf mfdes sim` - the ATS now advertises 106 kbit/s only, PPS to a higher rate is refused, a selected card ignores REQA/WUPA, and ISO-DEP block recovery now follows the card sequence (@mistial-dev) diff --git a/fpga/Makefile b/fpga/Makefile index e358cea65..f34fe80f2 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -1,259 +1,68 @@ # -# FPGA Makefile for all targets +# FPGA build dispatcher. # -# The top part of this Makefile is used to define custom options for a number of compilation targets -# To define an additional target simply look at the other defined targets and add a new TARGET entry with a unique number and the custom options required +# Each requested target is routed to the implementation of the platform that +# owns it: +# proxmark3_fpga.mk Xilinx ISE - PM3, PM3-ULT, iCopy-X +# proxmark5_fpga.mk yosys/nextpnr - PM5 +# +# make - every PM3 image +# make pm3 - same +# make pm5 - the PM5 bitstream +# make - routed by name, e.g. fpga_pm3_hf, fpga_pm3_ult_lf, fpga_pm5 +# make clean - both platforms +# make help - both target lists +# +# Both platform files are also usable directly, which is how the recursive +# builds call them: +# make -C fpga -f proxmark5_fpga.mk all -XILINX_TOOLS_PREFIX= +include common_src.mk -# Copy update (only when destination is older or missing) -CP = cp -u +PM3_MK = proxmark3_fpga.mk +PM5_MK = proxmark5_fpga.mk -# Make directory, no error if already existing -MKDIR = mkdir -p +PLATFORMS = pm3 pm5 -# Remove recursive, force -RMDIR = rm -rf +# Everything that is not a dispatcher builtin is forwarded to a platform file. +GOALS = $(filter-out $(PLATFORMS) all clean help,$(MAKECMDGOALS)) -# Path to make -MAKE = make +# PM5 owns the fpga_pm5* targets, everything else is PM3. +mk_for = $(if $(filter fpga_pm5%,$1),$(PM5_MK),$(PM3_MK)) -# Custom prefix for build directories, each target is built into its own separate directory name formed by combining the PREFIX and TARGET names. -# This way the source is not polluted with build files and the build directories are left behind after compilation so logs and reports can be -# examined or can be easily deleted with "make clean" -PREFIX = __ +.PHONY: all pm3 pm5 clean help $(GOALS) -# Options to be passed to XST -XST_OPTS_BASE = run -XST_OPTS_BASE += -ifn xst.prj -XST_OPTS_BASE += -ifmt mixed -XST_OPTS_BASE += -ofmt NGC -XST_OPTS_BASE += -lso xst.lso -XST_OPTS_BASE += -top fpga_top -XST_OPTS_BASE += -resource_sharing yes +ifneq ($(GOALS),) -# Optimizations for speed (default) -XST_OPTS_SPEED = -opt_mode Speed -XST_OPTS_SPEED += -opt_level 1 -XST_OPTS_SPEED += -fsm_style lut -XST_OPTS_SPEED += -fsm_encoding auto +$(GOALS): + $(Q)$(MAKE) --no-print-directory -f $(call mk_for,$@) $@ -# Optimization for reduced space -XST_OPTS_AREA = -opt_mode area -XST_OPTS_AREA += -opt_level 2 -XST_OPTS_AREA += -fsm_style bram -XST_OPTS_AREA += -fsm_encoding compact +all pm3 pm5 clean help: ; -# par specific option (set determistic seed) -PAR_OPTIONS = -t 2 +else -# Types of selective module compilation: -# WITH_LF Enables selection of LF modules (and disables all HF) +all: pm3 -# To enable these modules WITH_LF _MUST_ be defined -# WITH_LF0 enable LF reader (generic) -# WITH_LF1 enable LF edge detect (generic) -# WITH_LF2 enable LF passthrough -# WITH_LF3 enable LF ADC (read/write) +pm3: + $(Q)$(MAKE) --no-print-directory -f $(PM3_MK) all -# To enable these modules WITH_LF _MUST_NOT_ be defined -# WITH_HF0 enable HF reader (see also WITH_HF_15 below) -# WITH_HF_15 select "iso15 2sc mode" extensions instead of original -# WITH_HF1 enable HF simulated tag -# WITH_HF2 enable HF ISO14443-A -# WITH_HF3 enable sniff -# WITH_HF4 enable HF ISO18092 FeliCa -# WITH_HF5 enable HF get trace +pm5: + $(Q)$(MAKE) --no-print-directory -f $(PM5_MK) all -# RDV40/Generic - Enable LF and all the LF modules -TARGET1_OPTIONS = -define \{WITH_LF WITH_LF0 WITH_LF1 WITH_LF2 WITH_LF3\} -# RDV40/Generic - Enable all HF modules except Felica -TARGET2_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF2 WITH_HF3 WITH_HF5\} -# RDV40/Generic - Enable all HF modules except Felica and ISO14443, select HF_15 instead of HF -TARGET3_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF5 WITH_HF_15 WITH_HF_15_LOWSIGNAL\} -# RDV40/Generic - Enable all HF modules except ISO14443 -TARGET4_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF4 WITH_HF5 WITH_FELICA_PROBE\} -# ICOPYX -TARGET5_OPTIONS = $(TARGET1_OPTIONS) -TARGET6_OPTIONS = $(TARGET1_OPTIONS) -TARGET7_OPTIONS = $(TARGET1_OPTIONS) -TARGET8_OPTIONS = $(TARGET1_OPTIONS) -TARGET9_OPTIONS = $(TARGET1_OPTIONS) - -# Here we list the target names -TARGET1_NAME = fpga_pm3_lf -TARGET2_NAME = fpga_pm3_hf -TARGET3_NAME = fpga_pm3_hf_15 -TARGET4_NAME = fpga_pm3_felica -TARGET5_NAME = fpga_icopyx_hf -TARGET6_NAME = fpga_pm3_ult_lf -TARGET7_NAME = fpga_pm3_ult_hf -TARGET8_NAME = fpga_pm3_ult_hf_15 -TARGET9_NAME = fpga_pm3_ult_felica - -# Targets can be compiled for different FPGA flavours -TARGET1_FPGA = xc2s30-5-vq100 -TARGET2_FPGA = $(TARGET1_FPGA) -TARGET3_FPGA = $(TARGET1_FPGA) -TARGET4_FPGA = $(TARGET1_FPGA) -TARGET5_FPGA = xc3s100e-4-vq100 -TARGET6_FPGA = xc2s50-5-tq144 -TARGET7_FPGA = $(TARGET6_FPGA) -TARGET8_FPGA = $(TARGET6_FPGA) -TARGET9_FPGA = $(TARGET6_FPGA) - -# Assemble the final XST options for each target -TARGET1_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET1_FPGA) -ofn $(TARGET1_NAME) $(TARGET1_OPTIONS) -TARGET2_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET2_FPGA) -ofn $(TARGET2_NAME) $(TARGET2_OPTIONS) -TARGET3_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET3_FPGA) -ofn $(TARGET3_NAME) $(TARGET3_OPTIONS) -TARGET4_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET4_FPGA) -ofn $(TARGET4_NAME) $(TARGET4_OPTIONS) -TARGET5_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET5_FPGA) -ofn $(TARGET5_NAME) $(TARGET5_OPTIONS) -TARGET6_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET6_FPGA) -ofn $(TARGET6_NAME) $(TARGET6_OPTIONS) -TARGET7_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET7_FPGA) -ofn $(TARGET7_NAME) $(TARGET7_OPTIONS) -TARGET8_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET8_FPGA) -ofn $(TARGET8_NAME) $(TARGET8_OPTIONS) -TARGET9_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET9_FPGA) -ofn $(TARGET9_NAME) $(TARGET9_OPTIONS) - -# these files are common for all targets -TARGET_COMMON_FILES = define.v -TARGET_COMMON_FILES += mux8.v -TARGET_COMMON_FILES += clk_divider.v -TARGET_COMMON_FILES += lp20khz_1MSa_iir_filter.v -TARGET_COMMON_FILES += min_max_tracker.v -TARGET_COMMON_FILES += hi_flite.v -TARGET_COMMON_FILES += hi_get_trace.v -TARGET_COMMON_FILES += hi_iso14443a.v -TARGET_COMMON_FILES += hi_reader.v -TARGET_COMMON_FILES += hi_simulate.v -TARGET_COMMON_FILES += hi_sniffer.v -TARGET_COMMON_FILES += lf_edge_detect.v -TARGET_COMMON_FILES += lo_adc.v -TARGET_COMMON_FILES += lo_edge_detect.v -TARGET_COMMON_FILES += lo_passthru.v -TARGET_COMMON_FILES += lo_read.v - -# Add the files that are unique per target and all the common files -TARGET1_FILES = $(TARGET_COMMON_FILES) fpga_pm3_top.v -TARGET2_FILES = $(TARGET1_FILES) -TARGET3_FILES = $(TARGET1_FILES) -TARGET4_FILES = $(TARGET1_FILES) -TARGET5_FILES = $(TARGET_COMMON_FILES) mux2_onein.v mux2_oneout.v fpga_icopyx_hf.v fpga_icopyx_lf.v fpga_icopyx_top.v -TARGET6_FILES = $(TARGET1_FILES) -TARGET7_FILES = $(TARGET1_FILES) -TARGET8_FILES = $(TARGET1_FILES) -TARGET9_FILES = $(TARGET1_FILES) - -# List of all valid target FPGA images to build -TARGETS = $(TARGET1_NAME) $(TARGET2_NAME) $(TARGET3_NAME) $(TARGET4_NAME) $(TARGET5_NAME) $(TARGET6_NAME) $(TARGET7_NAME) $(TARGET8_NAME) $(TARGET9_NAME) - -# Verbosity type for ISE tools ise|xflow|silent -VERBOSITY = -intstyle silent -# Echo (Q=) or not echo (Q=@) build commands to the terminal -Q= - -# Pass the custom variables to the lower make rules -$(TARGET1_NAME).bit: TARGET_FPGA = $(TARGET1_FPGA) -$(TARGET1_NAME).bit: TARGET_FILES = $(TARGET1_FILES) -$(TARGET1_NAME).bit: TARGET_XST_OPTS = $(TARGET1_XST_OPTS) - -$(TARGET2_NAME).bit: TARGET_FPGA = $(TARGET2_FPGA) -$(TARGET2_NAME).bit: TARGET_FILES = $(TARGET2_FILES) -$(TARGET2_NAME).bit: TARGET_XST_OPTS = $(TARGET2_XST_OPTS) - -$(TARGET3_NAME).bit: TARGET_FPGA = $(TARGET3_FPGA) -$(TARGET3_NAME).bit: TARGET_FILES = $(TARGET3_FILES) -$(TARGET3_NAME).bit: TARGET_XST_OPTS = $(TARGET3_XST_OPTS) - -$(TARGET4_NAME).bit: TARGET_FPGA = $(TARGET4_FPGA) -$(TARGET4_NAME).bit: TARGET_FILES = $(TARGET4_FILES) -$(TARGET4_NAME).bit: TARGET_XST_OPTS = $(TARGET4_XST_OPTS) - -$(TARGET5_NAME).bit: TARGET_FPGA = $(TARGET5_FPGA) -$(TARGET5_NAME).bit: TARGET_FILES = $(TARGET5_FILES) -$(TARGET5_NAME).bit: TARGET_XST_OPTS = $(TARGET5_XST_OPTS) - -$(TARGET6_NAME).bit: TARGET_FPGA = $(TARGET6_FPGA) -$(TARGET6_NAME).bit: TARGET_FILES = $(TARGET6_FILES) -$(TARGET6_NAME).bit: TARGET_XST_OPTS = $(TARGET6_XST_OPTS) - -$(TARGET7_NAME).bit: TARGET_FPGA = $(TARGET7_FPGA) -$(TARGET7_NAME).bit: TARGET_FILES = $(TARGET7_FILES) -$(TARGET7_NAME).bit: TARGET_XST_OPTS = $(TARGET7_XST_OPTS) - -$(TARGET8_NAME).bit: TARGET_FPGA = $(TARGET8_FPGA) -$(TARGET8_NAME).bit: TARGET_FILES = $(TARGET8_FILES) -$(TARGET8_NAME).bit: TARGET_XST_OPTS = $(TARGET8_XST_OPTS) - -$(TARGET9_NAME).bit: TARGET_FPGA = $(TARGET9_FPGA) -$(TARGET9_NAME).bit: TARGET_FILES = $(TARGET9_FILES) -$(TARGET9_NAME).bit: TARGET_XST_OPTS = $(TARGET9_XST_OPTS) - -$(TARGETS): - $(Q)$(MKDIR) $(PREFIX)build_$@ - $(Q)$(MAKE) -C $(PREFIX)build_$@ -f ../Makefile $(notdir $@).bit - -work: - $(Q)$(RM) xst.prj - $(Q)for item in $(TARGET_FILES); do echo verilog work ../$$item>>xst.prj; done - $(Q)echo work> xst.lso - -%.xst: work - $(Q)$(RM) $@ - $(Q)echo $(TARGET_XST_OPTS)> $@ - -%.ngc: %.xst - $(Q)$(RM) $@ - $(info [-] XST $@) - $(Q)$(XILINX_TOOLS_PREFIX)xst $(VERBOSITY) -ifn $< - -%.ngd: %.ngc - $(Q)$(RM) $@ - $(info [-] NGD $@) - $(Q)$(XILINX_TOOLS_PREFIX)ngdbuild $(VERBOSITY) -quiet -p $(TARGET_FPGA) -nt timestamp -uc ../$(TARGET_FPGA).ucf $< $@ - -%_map.ncd: %.ngd - $(Q)$(RM) $@ - $(info [-] MAP $@) - $(Q)$(XILINX_TOOLS_PREFIX)map $(VERBOSITY) -p $(TARGET_FPGA) -o $*_map $* - -%.ncd: %_map.ncd - $(Q)$(RM) $@ - $(info [-] PAR $@) - $(Q)$(XILINX_TOOLS_PREFIX)par $(PAR_OPTIONS) $(VERBOSITY) -w $< $@ - -%.bit: %.ncd - $(Q)$(RM) $@ $*.drc $*.rbt - $(info [=] BITGEN $@) - $(Q)$(XILINX_TOOLS_PREFIX)bitgen $(VERBOSITY) -w $* $@ - $(Q)python3 ../strip_date_time_from_binary.py $@ || true - $(Q)$(CP) $@ .. - -# Build all targets -all: $(TARGETS) - -# ALWAYS have some hardcoded text after $(PREFIX) to avoid rm -rf * or rm -rf /* situations if PREFIX is incorrectly set to empty "" or just "/" clean: - $(Q)$(RMDIR) $(PREFIX)build_* - $(info [-] Build files deleted) - -.DEFAULT: - @if [ "$@" != "all" ] && [ ! "$(filter $@,$(TARGETS))" ]; then \ - make help; \ - else \ - make all; \ - fi - -.PHONY: all help clean + $(Q)$(MAKE) --no-print-directory -f $(PM3_MK) clean + $(Q)$(MAKE) --no-print-directory -f $(PM5_MK) clean help: @echo "################################################################" + @echo "# FPGA build dispatcher" @echo "#" - @echo "# - Builds only one of the above listed targets" - @echo "# all - Builds the FPGA bitstreams for all targets" - @echo "# clean - Keeps .bit files but cleans intermediate build files for all targets" - @echo "#" - @echo "#" - @echo "# Valid targets are:" - @echo "# $(TARGETS)" - @echo "#" + @echo "# make - every PM3 image (Xilinx ISE)" + @echo "# make pm5 - the PM5 bitstream (yosys/nextpnr-himbaechel)" + @echo "# make - routed by name, e.g. fpga_pm3_hf, fpga_pm5" + @echo "# make clean - both platforms" @echo "################################################################" + $(Q)$(MAKE) --no-print-directory -f $(PM3_MK) help + $(Q)$(MAKE) --no-print-directory -f $(PM5_MK) help +endif diff --git a/fpga/clk_divider.v b/fpga/common/clk_divider.v similarity index 96% rename from fpga/clk_divider.v rename to fpga/common/clk_divider.v index 7cb9925f3..6a55d1d3a 100644 --- a/fpga/clk_divider.v +++ b/fpga/common/clk_divider.v @@ -32,7 +32,7 @@ module clk_divider( div_cnt_ <= 8'd0; div_clk_ = !div_clk_; end else - div_cnt_ <= div_cnt_ + 1; + div_cnt_ <= div_cnt_ + 1'b1; end endmodule diff --git a/fpga/define.v b/fpga/common/define.v similarity index 98% rename from fpga/define.v rename to fpga/common/define.v index b7267a545..bf610f204 100644 --- a/fpga/define.v +++ b/fpga/common/define.v @@ -99,6 +99,10 @@ `define FPGA_CMD_SET_DIVISOR 2 `define FPGA_CMD_SET_EDGE_DETECT_THRESHOLD 3 `define FPGA_CMD_TRACE_ENABLE 2 +// Definitions for the FPGA commands that are only available on PM5 +`ifdef PM5 +`define FPGA_CMD_SET_PWR_PWM_LOW_COUNT 4 +`endif // Major modes `define FPGA_MAJOR_MODE_LF_READER 0 diff --git a/fpga/common/fs2bin.py b/fpga/common/fs2bin.py new file mode 100644 index 000000000..409ddf3cb --- /dev/null +++ b/fpga/common/fs2bin.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Pack an ASCII Gowin bitstream (.fs from apycula's gowin_pack) into the binary +# form the ARM JTAG loader shifts out for `hw fpga config`. +# Every byte in the .fs is 8 ASCII '0'/'1' characters, most significant bit +# first; the inverse of apycula's bslib.bytearr(), which is reused here so the +# two cannot drift apart. +# +# usage: fs2bin.py + +import sys + +from apycula.bslib import bytearr + + +def main(infile, outfile): + data = bytearray() + with open(infile) as f: + for line in f: + line = line.strip() + if not line or line.startswith("//"): + continue + if len(line) % 8: + sys.exit("{}: line of {} characters is not a multiple of 8".format(infile, len(line))) + data += bytearr(line) + + with open(outfile, "wb") as f: + f.write(data) + + print("[=] PACK {} ({} bytes)".format(outfile, len(data))) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.exit("usage: fs2bin.py ") + main(sys.argv[1], sys.argv[2]) diff --git a/fpga/common/gowin_devid.py b/fpga/common/gowin_devid.py new file mode 100644 index 000000000..b8ee2aee0 --- /dev/null +++ b/fpga/common/gowin_devid.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +# Stamp the real device IDCODE into a Gowin bitstream (.fs from apycula's +# gowin_pack), in place, and repair the CRC that depends on it. +# +# apycula's GW1N-4 database writes 0x0100381B, the plain GW1N-4, but the +# GW1N-4B/4D silicon on a PM5 reports 0x1100381B. The device compares that +# field with its own IDCODE and refuses an image that disagrees: the FPGA stays +# unconfigured (Gowin status register done_final = 0), so nothing drives the +# antenna and `hw tune` reads a couple of volts instead of tens. `hw fpga +# config` prints the value to use here as "gowin_jtag OK: idcode = ...". +# +# Only the IDCODE changes. The running CRC that apycula's write_bitstream() +# carries into the first row covers the header entries, so that one CRC is +# recomputed. Every other row is verified rather than rewritten, so a change to +# the apycula bitstream layout is reported instead of silently producing an +# image that will not configure. +# +# usage: gowin_devid.py + +import sys + +PREAMBLE = 3 # write_bitstream() starts folding header entries into the CRC after these +SKIP = 0xd2 # ... and leaves the SPI address entry out + + +def crc16_arc(data): + """CRC-16/ARC: poly 0x8005 reflected, init 0, xorout 0, as apycula computes it.""" + crc = 0 + for byte in data: + crc ^= byte + for _ in range(8): + crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1 + return crc + + +def to_bytes(line): + """8 ASCII '0'/'1' characters per byte, most significant bit first""" + return bytearray(int(line[i:i + 8], 2) for i in range(0, len(line), 8)) + + +def to_line(data): + return "".join("{:08b}".format(b) for b in data) + + +def split(blocks): + """header, rows, footer: the rows are the repeated long lines in the middle""" + lengths = [len(b) for b in blocks] + row_len = max(set(lengths), key=lengths.count) + first = next(n for n, b in enumerate(blocks) if len(b) == row_len) + last = len(blocks) - 1 - next(n for n, b in enumerate(reversed(blocks)) if len(b) == row_len) + head, rows, foot = blocks[:first], blocks[first:last + 1], blocks[last + 1:] + if len(head) <= PREAMBLE or not rows or not foot: + sys.exit("unexpected .fs layout: {} header, {} row(s), {} footer entries".format( + len(head), len(rows), len(foot))) + for row in rows: + if row[-6:] != b"\xff" * 6: + sys.exit("unexpected .fs row: not terminated by {} CRC + 6 padding bytes".format(2)) + return head, rows, foot + + +def main(path, idcode): + blocks = [to_bytes(l) for l in open(path).read().splitlines() if l] + head, rows, foot = split(blocks) + + # the first entry write_bitstream() folds into the CRC, right after the preamble + chip = head[PREAMBLE] + old = int.from_bytes(chip[-4:], "big") + if old == idcode: + print("[=] DEVID {} already 0x{:08X}".format(path, idcode)) + return + chip[-4:] = idcode.to_bytes(4, "big") + + # what write_bitstream() has accumulated once it reaches the first row + seed = b"".join(bytes(entry) for entry in head[PREAMBLE:] if entry[0] != SKIP) + + for n, row in enumerate(rows): + data = bytes(row[:-8]) + want = crc16_arc(seed + data if n == 0 else b"\xff" * 6 + data) + if n == 0: + row[-8:-6] = want.to_bytes(2, "little") + elif row[-8:-6] != want.to_bytes(2, "little"): + sys.exit("row {} CRC mismatch: apycula changed the bitstream layout".format(n)) + + with open(path, "w") as f: + for block in head + rows + foot: + f.write(to_line(block) + "\n") + + print("[=] DEVID {} 0x{:08X} -> 0x{:08X}".format(path, old, idcode)) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.exit("usage: gowin_devid.py ") + main(sys.argv[1], int(sys.argv[2], 16)) diff --git a/fpga/common/gowin_sdc_clean.py b/fpga/common/gowin_sdc_clean.py new file mode 100644 index 000000000..43b70b072 --- /dev/null +++ b/fpga/common/gowin_sdc_clean.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# Rewrite a Gowin IDE timing constraint file into the subset of SDC that +# nextpnr-himbaechel understands: create_clock and set_false_path, both taking +# [get_ports {...}]. The // comments, the create_clock -waveform arguments and +# the closing report_timing are dropped, and each set_clock_groups +# -asynchronous is expanded into pairwise false paths between the ports the +# clocks were declared on, since nextpnr has no notion of clock names. +# +# usage: gowin_sdc_clean.py + +import re +import sys + +WAVEFORM = re.compile(r"-waveform\s*\{[^}]*\}") +CLOCK = re.compile(r"create_clock\s+(?:.*?-name\s+(\S+))?.*?\[get_ports\s*\{([^}]*)\}\]") +CLOCK_GROUP = re.compile(r"\[get_clocks\s*\{([^}]*)\}\]") + + +def read_clocks(lines): + """"clock name -> port it is declared on" for the create_clock entries""" + ports = {} + for line in lines: + m = CLOCK.match(line.strip()) + if m and m.group(1): + ports[m.group(1)] = m.group(2) + return ports + + +def clean(line, clocks): + if line.startswith("//") or not line.strip(): + return [] + if re.match(r"\s*report_timing\b", line): + return [] + if re.match(r"\s*set_clock_groups\b", line): + if "-asynchronous" not in line: + return [line] + # every pair of groups is asynchronous, not just the neighbours + ports = [clocks.get(name, name) for name in CLOCK_GROUP.findall(line)] + return ["set_false_path -from [get_ports {%s}] -to [get_ports {%s}]" % (a, b) + for i, a in enumerate(ports) for b in ports[i + 1:]] + return [WAVEFORM.sub("", line).rstrip()] + + +def main(infile, outfile): + with open(infile) as f: + lines = f.read().splitlines() + clocks = read_clocks(lines) + rules = [out for line in lines for out in clean(line, clocks)] + with open(outfile, "w") as f: + f.write("\n".join(rules) + "\n") + + print("[=] SDC {} ({} rules, {} clocks)".format(outfile, len(rules), len(clocks))) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.exit("usage: gowin_sdc_clean.py ") + main(sys.argv[1], sys.argv[2]) diff --git a/fpga/hi_flite.v b/fpga/common/hi_flite.v similarity index 93% rename from fpga/hi_flite.v rename to fpga/common/hi_flite.v index e1a8f9ca9..f132ce1a4 100644 --- a/fpga/hi_flite.v +++ b/fpga/common/hi_flite.v @@ -200,7 +200,7 @@ reg zero = 1'b0; // Manchester first halfbit low second high corresponds to this reg [8:0] ssp_cnt = 9'd0; always @(posedge adc_clk) - ssp_cnt <= (ssp_cnt + 1); + ssp_cnt <= (ssp_cnt + 1'b1); always @(negedge adc_clk) if (ssp_cnt[5:0] == 6'd32) @@ -242,9 +242,26 @@ end //maybe change it so that ARM sends preamble as well. //then: ready bits sent to ARM, 8 bits sent from ARM (all ones), then preamble (all zeros, presumably) - which starts modulation +// SSP(FRAME_H) ~speed -> 9'd31 speed -> 8'd15 +// SSP(FRAME_L) ~speed -> 9'b1011111(d95 OF 512) speed -> 8'b101111(d47 OF 256) +// TIMODE(FRAME_H) ~speed -> 9'd(512-64) speed -> 8'd(256-32) +// TIMODE(FRAME_L) ~speed -> 9'b0 speed -> 8'b0 +`ifdef WITH_TIMODE + `define HI_FLITE_FRAME_SET_L 9'd448 + `define HI_FLITE_FRAME_SET_H 8'd224 + `define HI_FLITE_FRAME_CLR_L 9'd0 + `define HI_FLITE_FRAME_CLR_H 8'd0 +`else + `define HI_FLITE_FRAME_SET_L 9'd31 + `define HI_FLITE_FRAME_SET_H 8'd15 + `define HI_FLITE_FRAME_CLR_L 9'b1011111 // 95 = 96 - 1 + `define HI_FLITE_FRAME_CLR_H 8'b101111 // 47 = 48 - 1 +`endif + always @(negedge adc_clk) begin //count fc/64 - transfer bits to ARM at the rate they are received + //At speed 0, clk rises at 0 or 64; at speed 1, clk rises at 0 or 32. if( ((~speed) && (ssp_cnt[5:0] == 6'b000000) ) || (speed && (ssp_cnt[4:0] == 5'b00000)) ) begin ssp_clk <= 1'b1; @@ -270,15 +287,16 @@ begin ssp_din <= curbit; end end + // At speed 0, clk falls at 32; at speed 1, clk falls at 16. if( ( (~speed) && (ssp_cnt[5:0] == 6'b100000)) ||(speed && ssp_cnt[4:0] == 5'b10000)) ssp_clk <= 1'b0; //create frame pulses. TBH, I still don't know what they do exactly, but they are crucial for ARM->FPGA transfer. If the frame is in the beginning of the byte, transfer slows to a crawl for some reason // took me a day to figure THAT out. - if(( (~speed) && (ssp_cnt[8:0] == 9'd31)) || (speed && ssp_cnt[7:0] == 8'd15)) + if(( (~speed) && (ssp_cnt[8:0] == `HI_FLITE_FRAME_SET_L)) || (speed && ssp_cnt[7:0] == `HI_FLITE_FRAME_SET_H)) begin ssp_frame <= 1'b1; end - if(( (~speed) && (ssp_cnt[8:0] == 9'b1011111)) || (speed &&ssp_cnt[7:0] == 8'b101111) ) + if(( (~speed) && (ssp_cnt[8:0] == `HI_FLITE_FRAME_CLR_L)) || (speed && ssp_cnt[7:0] == `HI_FLITE_FRAME_CLR_H)) begin ssp_frame <= 1'b0; end @@ -315,7 +333,7 @@ begin // Watchdog clock, see `stalebit. It runs first so that the three places // that clear it below - a clean desync, a successful Manchester lock, and // the watchdog firing itself - all override it. - stale <= stale + 1; + stale <= stale + 1'b1; //envelope follow code... //////////// @@ -338,7 +356,7 @@ begin if (power) bit_counts <= 0; else - bit_counts <= bit_counts + 1; + bit_counts <= bit_counts + 1'b1; end else begin @@ -348,7 +366,7 @@ begin end else begin - fccount <= fccount + 1; + fccount <= fccount + 1'b1; end end @@ -453,7 +471,7 @@ begin acc <= 0; end else - tsinceedge <= (tsinceedge + 1); + tsinceedge <= (tsinceedge + 1'b1); end end @@ -541,7 +559,11 @@ always @(ck_1356meg or ssp_dout or power or disabl or mod) begin if (power) begin +`ifdef PM5 // pwr_hi is used to control the 1356 clk enable on PM5, not clk output. + pwr_hi <= 1'b1; // enable 1356 clk output +`else pwr_hi <= ck_1356meg; +`endif pwr_lo <= 1'b0; pwr_oe1 <= 1'b0;//mod; pwr_oe2 <= 1'b0;//mod; diff --git a/fpga/hi_get_trace.v b/fpga/common/hi_get_trace.v similarity index 88% rename from fpga/hi_get_trace.v rename to fpga/common/hi_get_trace.v index b577ff6be..52dcf4638 100644 --- a/fpga/hi_get_trace.v +++ b/fpga/common/hi_get_trace.v @@ -29,7 +29,7 @@ module hi_get_trace( reg [6:0] clock_cnt; always @(negedge ck_1356megb) begin - clock_cnt <= clock_cnt + 1; + clock_cnt <= clock_cnt + 1'b1; end // sample at 13,56MHz / 8. The highest signal frequency (subcarrier) is 848,5kHz, i.e. in this case we oversample by a factor of 2 @@ -39,7 +39,7 @@ begin if (sample_clock == 3'd7) sample_clock <= 3'd0; else - sample_clock <= sample_clock + 1; + sample_clock <= sample_clock + 1'b1; end @@ -62,7 +62,7 @@ begin if (addr == 12'd3071) addr <= 12'd0; else - addr <= addr + 1; + addr <= addr + 1'b1; end end else if (major_mode != `FPGA_MAJOR_MODE_OFF) @@ -89,7 +89,7 @@ begin end else begin - addr <= addr + 1; + addr <= addr + 1'b1; end end end @@ -156,10 +156,21 @@ begin ssp_clk <= ~clock_cnt[3]; // ssp_clk frequency = 13,56MHz / 16 = 847,5 kHz +`ifdef WITH_TIMODE + // Toggle once every 8 ssp_clk cycles + // TI mode 7D112: starts with the TI frame start signal; the line is driven high at the (128-16)th ssp_clk cycle. + if(clock_cnt[6:0] == 7'd112) + ssp_frame <= 1'b1; + + // Clear the frame signal at the new frame start (0th ssp_clk cycle) + if(clock_cnt[6:0] == 7'd0) + ssp_frame <= 1'b0; +`else if (clock_cnt[6:4] == 3'b000) // set ssp_frame for 0...31 ssp_frame <= 1'b1; else ssp_frame <= 1'b0; +`endif end diff --git a/fpga/hi_iso14443a.v b/fpga/common/hi_iso14443a.v similarity index 87% rename from fpga/hi_iso14443a.v rename to fpga/common/hi_iso14443a.v index bbc20bee7..1dafdb1bd 100644 --- a/fpga/hi_iso14443a.v +++ b/fpga/common/hi_iso14443a.v @@ -36,7 +36,12 @@ module hi_iso14443a( output debug ); +// The PM5's ADC is inverting (for HF). +`ifdef PM5 +assign adc_clk = ~ck_1356meg; +`else assign adc_clk = ck_1356meg; +`endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Reader -> PM3: @@ -47,8 +52,16 @@ reg [11:0] has_been_low_for; always @(negedge adc_clk) begin - if(adc_d >= 16) after_hysteresis <= 1'b1; // U >= 1,14V -> after_hysteresis = 1 - else if(adc_d < 8) after_hysteresis <= 1'b0; // U < 1,04V -> after_hysteresis = 0 + + // The PM5's ADC is inverting (for HF). Therefore, the trigger values are inverted as well. +`ifdef PM5 + if(adc_d <= 240) after_hysteresis <= 1'b1; // U >= 1,14V -> after_hysteresis = 1 + else if(adc_d > 248) after_hysteresis <= 1'b0; // U < 1,04V -> after_hysteresis = 0 +`else + if(adc_d >= 16) after_hysteresis <= 1'b1; // U >= 1,14V -> after_hysteresis = 1 + else if(adc_d < 8) after_hysteresis <= 1'b0; // U < 1,04V -> after_hysteresis = 0 +`endif + // Note: was >= 3,53V and <= 1,19V. The new trigger values allow more reliable detection of the first bit // (it might not reach 3,53V due to the high time constant of the high pass filter in the analogue RF part). // In addition, the new values are more in line with ISO14443-2: "The PICC shall detect the ”End of Pause” after the field exceeds @@ -57,7 +70,11 @@ begin // detecting a loss of reader's field (adc_d < 192 for 4096 clock cycles). If this is the case, // set the detected reader signal (after_hysteresis) to '1' (unmodulated) +`ifdef PM5 + if(adc_d <= 32) +`else if(adc_d >= 192) +`endif begin has_been_low_for <= 12'd0; end @@ -70,7 +87,7 @@ begin end else begin - has_been_low_for <= has_been_low_for + 1; + has_been_low_for <= has_been_low_for + 1'b1; end end @@ -86,7 +103,11 @@ reg [8:0] saw_deep_modulation; always @(negedge adc_clk) begin +`ifdef PM5 + if(& adc_d[7:0]) // if adc_d == 255 (ADC Value of PM5 is inverted) +`else if(~(| adc_d[7:0])) // if adc_d == 0 (U <= 0,94V) +`endif begin if(deep_counter == 3'd7) // adc_d == 0 for 8 adc_clk ticks -> deep_modulation (by reader) begin @@ -94,7 +115,7 @@ begin saw_deep_modulation <= 8'd0; end else - deep_counter <= deep_counter + 1; + deep_counter <= deep_counter + 1'b1; end else begin @@ -102,7 +123,7 @@ begin if(saw_deep_modulation == 8'd255) // adc_d != 0 for 256 adc_clk ticks -> deep_modulation is over, probably waiting for tag's response deep_modulation <= 1'b0; else - saw_deep_modulation <= saw_deep_modulation + 1; + saw_deep_modulation <= saw_deep_modulation + 1'b1; end end @@ -153,7 +174,7 @@ begin begin if (reader_falling_edge_time == 4'd1) // reader signal changes right after sampling. Better sample earlier next time. begin - negedge_cnt <= negedge_cnt + 2; // time warp + negedge_cnt <= negedge_cnt + 2'd2; // time warp end else if (reader_falling_edge_time == 4'd0) // reader signal changes right before sampling. Better sample later next time. begin @@ -161,7 +182,7 @@ begin end else begin - negedge_cnt <= negedge_cnt + 1; // Continue as usual + negedge_cnt <= negedge_cnt + 1'b1; // Continue as usual end reader_falling_edge_time[3:0] <= 4'd8; // adjust only once per detected edge end @@ -171,7 +192,7 @@ begin end else begin - negedge_cnt <= negedge_cnt + 1; + negedge_cnt <= negedge_cnt + 1'b1; end end @@ -221,7 +242,7 @@ begin if (mod_type == `FPGA_HF_ISO14443A_SNIFFER) begin // detect modulation signal: if modulating, there must have been a falling AND a rising edge - if ((rx_mod_falling_edge_max > edge_detect_threshold_high) && (rx_mod_rising_edge_max < -edge_detect_threshold_high)) + if ((rx_mod_falling_edge_max > $signed(edge_detect_threshold_high)) && (rx_mod_rising_edge_max < -$signed(edge_detect_threshold_high))) curbit <= 1'b1; // modulation else curbit <= 1'b0; // no modulation @@ -229,7 +250,7 @@ begin else begin // detect modulation signal: if modulating, there must have been a falling AND a rising edge - if ((rx_mod_falling_edge_max > edge_detect_threshold) && (rx_mod_rising_edge_max < -edge_detect_threshold)) + if ((rx_mod_falling_edge_max > $signed(edge_detect_threshold)) && (rx_mod_rising_edge_max < -$signed(edge_detect_threshold))) curbit <= 1'b1; // modulation else curbit <= 1'b0; // no modulation @@ -341,12 +362,12 @@ begin end else begin - sub_carrier_cnt <= sub_carrier_cnt + 1; + sub_carrier_cnt <= sub_carrier_cnt + 1'b1; end end else begin - fdt_counter <= fdt_counter + 1; + fdt_counter <= fdt_counter + 1'b1; end end @@ -407,7 +428,7 @@ begin if (mod_sig_ptr == 5'd31) mod_sig_ptr <= 5'd0; // buffer overflow - data loss. else - mod_sig_ptr <= mod_sig_ptr + 1; // increase buffer (= increase delay by 16 adc_clk ticks). mod_sig_ptr always points ahead of first 1. + mod_sig_ptr <= mod_sig_ptr + 1'b1; // increase buffer (= increase delay by 16 adc_clk ticks). mod_sig_ptr always points ahead of first 1. else if(fdt_elapsed && ~temp_buffer_reset) begin // wait for the next 1 after fdt_elapsed before fixing the delay and starting modulation. This ensures that the response can only happen @@ -417,7 +438,7 @@ begin if(mod_sig_ptr == 5'd1) mod_sig_ptr <= 5'd8; // still nothing received, need to go for the next interval else - mod_sig_ptr <= mod_sig_ptr - 1; // decrease buffer. + mod_sig_ptr <= mod_sig_ptr - 1'b1; // decrease buffer. end end end @@ -472,32 +493,47 @@ end // FPGA <-> ARM communication: // generate a ssp clock and ssp frame signal for the synchronous transfer from/to the ARM +`ifdef WITH_TIMODE +`define HI_ISO14443A_FRAME_SET_8B 6'd56 +`define HI_ISO14443A_FRAME_CLR_8B 6'd0 +`define HI_ISO14443A_FRAME_SET_16B 7'd112 +`define HI_ISO14443A_FRAME_CLR_16B 7'd0 +`else +`define HI_ISO14443A_FRAME_SET_8B 6'd0 +`define HI_ISO14443A_FRAME_CLR_8B 6'd8 +`define HI_ISO14443A_FRAME_SET_16B 7'd7 +`define HI_ISO14443A_FRAME_CLR_16B 7'd23 +`endif + always @(negedge adc_clk) begin if(mod_type == `FPGA_HF_ISO14443A_SNIFFER) // FPGA_HF_ISO14443A_SNIFFER mode (ssp_clk = adc_clk / 8, ssp_frame clock = adc_clk / 64)): begin - if(negedge_cnt[2:0] == 3'd0) + if(negedge_cnt[2:0] == 3'd0) // High level for 4 cycles ssp_clk <= 1'b1; - if(negedge_cnt[2:0] == 3'd4) + if(negedge_cnt[2:0] == 3'd4)// Low level for 4 cycles ssp_clk <= 1'b0; - if(negedge_cnt[5:0] == 6'd0) // ssp_frame rising edge indicates start of frame + //ssp模式6D0,TI模式6D56,TI帧开始信号开始:最后一个ssp_clk拉高(64-8)的时候就拉高 + // ssp_frame rising edge indicates start of frame + if(negedge_cnt[5:0] == `HI_ISO14443A_FRAME_SET_8B) ssp_frame <= 1'b1; - if(negedge_cnt[5:0] == 6'd8) + //ssp模式6D8,TI模式6D0,TI帧开始信号结束:在第一次ssp_clk拉高的时候就拉低(清除上一个周期拉高) + if(negedge_cnt[5:0] == `HI_ISO14443A_FRAME_CLR_8B) ssp_frame <= 1'b0; end else // all other modes (ssp_clk = adc_clk / 16, ssp_frame clock = adc_clk / 128): begin - if(negedge_cnt[3:0] == 4'd0) + if(negedge_cnt[3:0] == 4'd0) // High level for 8 cycles (rising edge at cycle 0 or 16) ssp_clk <= 1'b1; - if(negedge_cnt[3:0] == 4'd8) + if(negedge_cnt[3:0] == 4'd8) // Low level for 8 cycles (falling edge at cycle 8) ssp_clk <= 1'b0; - if(negedge_cnt[6:0] == 7'd7) // ssp_frame rising edge indicates start of frame, sampled on falling edge of ssp_clk + if(negedge_cnt[6:0] == `HI_ISO14443A_FRAME_SET_16B) // ssp_frame rising edge indicates start of frame, sampled on falling edge of ssp_clk ssp_frame <= 1'b1; - if(negedge_cnt[6:0] == 7'd23) + if(negedge_cnt[6:0] == `HI_ISO14443A_FRAME_CLR_16B) ssp_frame <= 1'b0; end end @@ -544,7 +580,12 @@ assign sub_carrier = ~sub_carrier_cnt[3]; // in FPGA_HF_ISO14443A_READER_MOD: drop carrier for mod_sig_coil == 1 (pause); // in FPGA_HF_ISO14443A_READER_LISTEN: carrier always on; in other modes: carrier always off + +`ifdef PM5 +assign pwr_hi = (((mod_type == `FPGA_HF_ISO14443A_READER_MOD) & ~mod_sig_coil) || (mod_type == `FPGA_HF_ISO14443A_READER_LISTEN)); +`else assign pwr_hi = (ck_1356meg & (((mod_type == `FPGA_HF_ISO14443A_READER_MOD) & ~mod_sig_coil) || (mod_type == `FPGA_HF_ISO14443A_READER_LISTEN))); +`endif // Enable HF antenna drivers: assign pwr_oe1 = 1'b0; diff --git a/fpga/hi_reader.v b/fpga/common/hi_reader.v similarity index 84% rename from fpga/hi_reader.v rename to fpga/common/hi_reader.v index 07afdc2de..8fabc2e35 100644 --- a/fpga/hi_reader.v +++ b/fpga/common/hi_reader.v @@ -42,15 +42,31 @@ assign adc_clk = ck_1356meg; // sample frequency is 13,56 MHz // using AM. Do this the same way that we do it for the simulated tag. reg after_hysteresis, after_hysteresis_prev, after_hysteresis_prev_prev; reg [11:0] has_been_low_for; + +// Reader-carrier detection thresholds, selected at runtime by the configured mode. +// The lower thresholds (240 / 63 instead of 255 / 0) allow the reader signal to still be recovered +// when the received level is weak. They are meant for ISO15693: the 2-subcarrier modes, plus the +// amplitude sniffer that "hf 15 sniff" uses -- including the plain SNIFF_AMPLITUDE that its jam +// sequence switches to and keeps for the rest of the command. +// This module is shared with other protocols (ISO14443B sniff reads the very same +// after_hysteresis bits) and must keep the rail-to-rail thresholds there, so the selection is done +// at runtime instead of with a compile-time switch. +wire low_signal_mode = (subcarrier_frequency == `FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ) + || (minor_mode == `FPGA_HF_READER_MODE_SNIFF_AMPLITUDE); + always @(negedge adc_clk) begin -`ifdef WITH_HF_15_LOWSIGNAL - if (& adc_d[7:4]) after_hysteresis <= 1'b1; - else if (~(| adc_d[7:6])) after_hysteresis <= 1'b0; -`else - if (& adc_d[7:0]) after_hysteresis <= 1'b1; - else if (~(| adc_d[7:0])) after_hysteresis <= 1'b0; -`endif + // TODO DXL - 阈值是绝对值,这是否会在PM5上导致问题?需要考虑判定的adc值是否和场的信号同步,待测 + if (low_signal_mode) + begin + if (& adc_d[7:4]) after_hysteresis <= 1'b1; + else if (~(| adc_d[7:6])) after_hysteresis <= 1'b0; + end + else + begin + if (& adc_d[7:0]) after_hysteresis <= 1'b1; + else if (~(| adc_d[7:0])) after_hysteresis <= 1'b0; + end if (after_hysteresis) begin @@ -64,7 +80,7 @@ begin after_hysteresis <= 1'b1; end else - has_been_low_for <= has_been_low_for + 1; + has_been_low_for <= has_been_low_for + 1'b1; end end @@ -75,7 +91,7 @@ end // We need a 6-bit counter for the timing. reg [5:0] corr_i_cnt; always @(negedge adc_clk) - corr_i_cnt <= corr_i_cnt + 1; + corr_i_cnt <= corr_i_cnt + 1'b1; `ifdef WITH_HF_15 reg [1:0] fskout = 2'd0; @@ -147,8 +163,8 @@ begin begin if (match28 < match32) begin - diff28 = match32 - match28; - diff16 = match16 - match28; + diff28 = (match32 - match28) & 8'hFF; + diff16 = (match16 - match28) & 8'hFF; if (diff28*2 > diff16) fskout = 2'b01; else if (!last0) @@ -158,8 +174,8 @@ begin end else //if (match32 <= match28) begin - diff32 = match28 - match32; - diff16 = match16 - match32; + diff32 = (match28 - match32) & 8'hFF; + diff16 = (match16 - match32) & 8'hFF; if (diff32*2 > diff16) fskout = 2'b10; else if (!last0) @@ -368,7 +384,15 @@ begin end -// ssp clock and frame signal for communication to and from ARM +`ifdef WITH_TIMODE // In TI mode, the frame signal goes high during the last `ssp_clk` pulse (at the 60th clock cycle, i.e., 64-4). + `define HI_READER_FRAME_SET 6'd60 + `define HI_READER_FRAME_CLR 6'd0 +`else + `define HI_READER_FRAME_SET 6'd1 + `define HI_READER_FRAME_CLR 6'd3 +`endif + +// ssp clock and frame signal for communication to and from ARM, not SPI TI-MODE // _____ _____ _____ _ // ssp_clk | |_____| |_____| |_____| // _____ @@ -380,18 +404,18 @@ end // always @(negedge adc_clk) begin - if (corr_i_cnt[1:0] == 2'b00) + if (corr_i_cnt[1:0] == 2'b00) // High level for 2 cycles (rising edge at cycle 0 or 4) ssp_clk <= 1'b1; - if (corr_i_cnt[1:0] == 2'b10) + if (corr_i_cnt[1:0] == 2'b10) // Low level for 2 cycles (falling edge at the second cycle) ssp_clk <= 1'b0; // set ssp_frame signal for corr_i_cnt = 1..3 // (send one frame with 16 Bits) - if (corr_i_cnt == 6'd1) + if (corr_i_cnt == `HI_READER_FRAME_SET) ssp_frame <= 1'b1; - if (corr_i_cnt == 6'd3) + if (corr_i_cnt == `HI_READER_FRAME_CLR) ssp_frame <= 1'b0; end @@ -405,11 +429,15 @@ always @(negedge adc_clk) begin if (corr_i_cnt == 6'd0) begin - jam_counter <= jam_counter + 1; + jam_counter <= jam_counter + 1'b1; jam_signal <= jam_counter[1] ^ jam_counter[3]; end end +// --- +// pwr_hi is used to control the 1356 clk enable on PM5, not clk output. +// --- + always @(*) begin pwr_oe1 = 1'b0; @@ -417,22 +445,38 @@ begin if (minor_mode == `FPGA_HF_READER_MODE_SEND_SHALLOW_MOD) begin +`ifdef PM5 + pwr_hi = 1'b1; +`else pwr_hi = ck_1356meg; +`endif pwr_oe4 = ssp_dout; end else if (minor_mode == `FPGA_HF_READER_MODE_SEND_SHALLOW_MOD_RDV4) begin +`ifdef PM5 + pwr_hi = 1'b1; +`else pwr_hi = ck_1356meg; +`endif pwr_oe1 = ssp_dout; end else if (minor_mode == `FPGA_HF_READER_MODE_SEND_FULL_MOD) begin +`ifdef PM5 + pwr_hi = ~ssp_dout; +`else pwr_hi = ck_1356meg & ~ssp_dout; +`endif pwr_oe4 = 1'b0; end else if (minor_mode == `FPGA_HF_READER_MODE_SEND_JAM) begin +`ifdef PM5 + pwr_hi = jam_signal; +`else pwr_hi = ck_1356meg & jam_signal; +`endif pwr_oe4 = 1'b0; end else if (minor_mode == `FPGA_HF_READER_MODE_SNIFF_IQ @@ -444,7 +488,11 @@ begin end else // receiving from tag begin +`ifdef PM5 + pwr_hi = 1'b1; +`else pwr_hi = ck_1356meg; +`endif pwr_oe4 = 1'b0; end end diff --git a/fpga/hi_simulate.v b/fpga/common/hi_simulate.v similarity index 83% rename from fpga/hi_simulate.v rename to fpga/common/hi_simulate.v index 77b9a44ed..720074866 100644 --- a/fpga/hi_simulate.v +++ b/fpga/common/hi_simulate.v @@ -83,7 +83,7 @@ begin end else begin - has_been_low_for <= has_been_low_for + 1; + has_been_low_for <= has_been_low_for + 1'b1; end end end @@ -93,7 +93,7 @@ end reg [8:0] ssp_clk_divider; always @(negedge adc_clk) - ssp_clk_divider <= (ssp_clk_divider + 1); + ssp_clk_divider <= (ssp_clk_divider + 1'b1); always @(negedge adc_clk) begin @@ -108,22 +108,33 @@ begin ssp_clk <= ~ssp_clk_divider[4]; end +// Since the 212k clock is generated by ssp_clk_divider[5], a full cycle here involves 16 bits—effectively 8 clock periods. +// This means an 8-bit transfer; in SSP mode, the frame signal is driven high at bit 1 (clock rising edge) and low at bit 5 (clock falling edge). +// To switch to TI mode, this must be changed to the rising edge of the final cycle: driving high at bit 14 (rising edge) and low at bit 0 (falling edge). +`ifdef WITH_TIMODE +`define HI_SIMULATE_FRAME_SET_212K 4'd14 +`define HI_SIMULATE_FRAME_CLR_212K 4'd0 +`else +`define HI_SIMULATE_FRAME_SET_212K 4'd1 +`define HI_SIMULATE_FRAME_CLR_212K 4'd5 +`endif + // Produce the byte framing signal; the phase of this signal // is arbitrary, because it's just a bit stream in this module. always @(negedge adc_clk) begin if (mod_type == `FPGA_HF_SIMULATOR_MODULATE_212K) begin - if (ssp_clk_divider[8:5] == 4'd1) + if (ssp_clk_divider[8:5] == `HI_SIMULATE_FRAME_SET_212K) ssp_frame <= 1'b1; - if (ssp_clk_divider[8:5] == 4'd5) + if (ssp_clk_divider[8:5] == `HI_SIMULATE_FRAME_CLR_212K) ssp_frame <= 1'b0; end else begin - if (ssp_clk_divider[7:4] == 4'd1) + if (ssp_clk_divider[7:4] == `HI_SIMULATE_FRAME_SET_212K) ssp_frame <= 1'b1; - if (ssp_clk_divider[7:4] == 4'd5) + if (ssp_clk_divider[7:4] == `HI_SIMULATE_FRAME_CLR_212K) ssp_frame <= 1'b0; end end diff --git a/fpga/hi_sniffer.v b/fpga/common/hi_sniffer.v similarity index 75% rename from fpga/hi_sniffer.v rename to fpga/common/hi_sniffer.v index 8cfb0fe9a..3ca0a8691 100644 --- a/fpga/hi_sniffer.v +++ b/fpga/common/hi_sniffer.v @@ -48,18 +48,36 @@ assign ssp_din = adc_d_out[0]; always @(posedge ssp_clk) begin - ssp_cnt <= ssp_cnt + 1; + ssp_cnt <= ssp_cnt + 1'b1; + +`ifdef WITH_TIMODE + if(ssp_cnt[2:0] == 3'b111) // set frame pin to high for TI mode on the last bit of the frame + begin + ssp_frame <= 1'b1; + end +`endif if(ssp_cnt[2:0] == 3'b000) // set frame length begin adc_d_out[7:0] <= adc_d; - ssp_frame <= 1'b1; + +`ifdef WITH_TIMODE + ssp_frame <= 1'b0; // In TI mode, pull the frame signal low here—at the rising edge of the first bit (marking the end of the last bit). +`else + ssp_frame <= 1'b1; // In SSP mode, pull the frame signal high here. +`endif + end + else begin // shift value right one bit adc_d_out[7:0] <= {1'b0, adc_d_out[7:1]}; - ssp_frame <= 1'b0; + +`ifndef WITH_TIMODE + ssp_frame <= 1'b0; // In SSP mode, pull the frame signal low here. +`endif + end end diff --git a/fpga/lf_edge_detect.v b/fpga/common/lf_edge_detect.v similarity index 100% rename from fpga/lf_edge_detect.v rename to fpga/common/lf_edge_detect.v diff --git a/fpga/lo_adc.v b/fpga/common/lo_adc.v similarity index 54% rename from fpga/lo_adc.v rename to fpga/common/lo_adc.v index f20442d4b..cf2865ba5 100644 --- a/fpga/lo_adc.v +++ b/fpga/common/lo_adc.v @@ -30,7 +30,7 @@ module lo_adc( input ssp_dout, output ssp_din, - output ssp_frame, + output reg ssp_frame, output ssp_clk, output adc_clk, output pwr_lo, @@ -66,7 +66,14 @@ assign pwr_oe4 = tag_modulation & lf_weak_load; // 10k Ohms assign debug = adc_clk; // ADC clock out of phase with antenna driver +`ifdef PM5 +// Since the PM5's ADC sampling occurs on the rising edge, +// whereas the falling edge of the field signal causes a rapid voltage rise—making it unsuitable for sampling +// the phase was adjusted to align them. +assign adc_clk = clk_state; +`else assign adc_clk = ~clk_state; +`endif // serialized SSP data is gated by clk_state to suppress unwanted signal assign ssp_din = to_arm_shiftreg[7] && !clk_state; @@ -74,20 +81,40 @@ assign ssp_din = to_arm_shiftreg[7] && !clk_state; // SSP clock always runs at 24MHz assign ssp_clk = pck0; -// SSP frame is gated by clk_state and goes high when pck_divider=8..15 -assign ssp_frame = (pck_divider[7:3] == 5'd1) && !clk_state; +// ssp_frame window: TI = 1-cycle pulse (at divider 7), SSP = spans the entire data window (dividers 8–15). +// Note: The 'always' block below reads the value of pck_divider *before* it toggles for the current cycle, +// so SET is triggered at the window start minus 1, and CLR is triggered at the window end. +`ifdef WITH_TIMODE + `define LO_ADC_FRAME_SET 8'd6 + `define LO_ADC_FRAME_CLR 8'd7 +`else + `define LO_ADC_FRAME_SET 8'd7 + `define LO_ADC_FRAME_CLR 8'd15 +`endif + +// Frame signal generation. +always @(posedge pck0) +begin + if ((pck_divider == `LO_ADC_FRAME_SET) && !clk_state) + ssp_frame <= 1'b1; + if ((pck_divider == `LO_ADC_FRAME_CLR) && !clk_state) + ssp_frame <= 1'b0; +end // divide 24mhz down to 3mhz -always @(posedge pck0) +always @(posedge pck0) // SSPCLK rising edge begin if (pck_divider == divisor[7:0]) begin + // Resets to zero upon reaching the frequency division factor; effectively, the value 95 corresponds to 125 kHz, meaning it executes 96 times (counting from 0 to 95) and resets on the 96th iteration. + // After resetting, the cycle count becomes 0 and does not increment. pck_divider <= 8'd0; + // Toggle clk upon reaching the frequency division factor; the value 95 (corresponding to 125 kHz) actually implies operation at a frequency of 250 kHz. clk_state = !clk_state; end else begin - pck_divider <= pck_divider + 1; + pck_divider <= pck_divider + 1'd1; // Increments by 1 on each rising edge of sspclk. end end @@ -95,6 +122,15 @@ end // the ADC output which is then clocked into the ARM SSP. always @(posedge pck0) begin + + // For PM5: Since adc_clk = ~clk_state, the ADC receives a rising edge when clk_state is 0. + // Note: Because the PM5 ADC samples on the rising edge, this causes hardware sampling to occur the moment the field is turned off (when voltage rises rapidly); therefore, a modification was made here. + // The adc_clk is now set to be in-phase with clk_state (since pwrlo is based on clk_state, this aligns the phases). + // Consequently, when clk_state is 0, the ADC receives a falling edge; sampling the output of the 9280 on the falling edge is safe, so this approach was adopted. + // ADC sampling completes when pck_divider == 8'd7; one cycle later, the 'else' condition takes effect. + // Shift transmission begins at 8'd8, but since the data payload is only 8 bits, zeros are transmitted continuously after the actual data is sent—a point easily misunderstood. + // In reality, the active sequence spans pck_divider values from 0 to 7 (sampling), to 8 (transmission start), and finally to 16 (transmission end). + if ((pck_divider == 8'd7) && !clk_state) to_arm_shiftreg <= adc_d; else diff --git a/fpga/lo_edge_detect.v b/fpga/common/lo_edge_detect.v similarity index 88% rename from fpga/lo_edge_detect.v rename to fpga/common/lo_edge_detect.v index e06cbda02..828bffb7e 100644 --- a/fpga/lo_edge_detect.v +++ b/fpga/common/lo_edge_detect.v @@ -131,6 +131,13 @@ wire tracker_freeze = lf_ed_hold_tracker & (hold_cnt != 0); // filter the ADC values wire data_rdy; wire [7:0] adc_filtered; + +// Regarding PM5, here is why the ADC sampling points remain in-phase (unlike other cases where sampling occurs at the same frequency as the signal): +// 1. The ADC operates at 125 kHz (derived from a 24 MHz oversampling clock); sampling points are distributed throughout the entire cycle. High dV/dt events near the signal edges will inevitably be captured regardless of phase inversion, +// and are subsequently suppressed by the 20 kHz low-pass filter, making phase alignment irrelevant; +// 2. Phase inversion (adc_clk = ~pck0) causes the ADC to output data on the pck0 falling edge while downstream components (adc_filter/lf_edge_detect) read it on the pck0 rising edge. +// This leaves a margin of only half a clock cycle (20.8 ns); given the ADC's maximum tOD of approximately 25 ns, the timing margin for reading is insufficient, creating a risk of capturing unstable or metastable data. + assign adc_clk = pck0; lp20khz_1MSa_iir_filter adc_filter( diff --git a/fpga/lo_passthru.v b/fpga/common/lo_passthru.v similarity index 100% rename from fpga/lo_passthru.v rename to fpga/common/lo_passthru.v diff --git a/fpga/lo_read.v b/fpga/common/lo_read.v similarity index 76% rename from fpga/lo_read.v rename to fpga/common/lo_read.v index 87279e8ec..96dbb8a27 100644 --- a/fpga/lo_read.v +++ b/fpga/common/lo_read.v @@ -29,7 +29,7 @@ module lo_read( input lf_field, output ssp_din, - output ssp_frame, + output reg ssp_frame, output ssp_clk, output adc_clk, output pwr_lo, @@ -74,8 +74,27 @@ end assign ssp_din = to_arm_shiftreg[7] && !pck_divclk; // SSP clock always runs at 24MHz assign ssp_clk = pck0; -// SSP frame is gated by pck_divclk and goes high when pck_cnt=8..15 -assign ssp_frame = (pck_cnt[7:3] == 5'd1) && !pck_divclk; + +// ssp_frame window: TI = 1-cycle pulse (at counter 7), SSP = spans the entire data window (counts 8-15). +// Note: The 'always' block below reads the value of pck_cnt *before* it toggles for the current cycle, +// so SET is triggered at the window start minus 1, and CLR is triggered at the window end. +`ifdef WITH_TIMODE + `define LO_READ_FRAME_SET 8'd6 + `define LO_READ_FRAME_CLR 8'd7 +`else + `define LO_READ_FRAME_SET 8'd7 + `define LO_READ_FRAME_CLR 8'd15 +`endif + +// Frame signal generation. +always @(posedge pck0) +begin + if ((pck_cnt == `LO_READ_FRAME_SET) && !pck_divclk) + ssp_frame <= 1'b1; + if ((pck_cnt == `LO_READ_FRAME_CLR) && !pck_divclk) + ssp_frame <= 1'b0; +end + // unused signals tied low assign pwr_hi = 1'b0; // always on outputs, unused @@ -85,8 +104,17 @@ assign pwr_oe3 = 1'b0; assign pwr_oe4 = 1'b0; // this is the antenna driver signal assign pwr_lo = lf_field & pck_divclk; + // ADC clock out of phase with antenna driver +`ifdef PM5 +// Since the PM5's ADC sampling occurs on the rising edge, +// whereas the falling edge of the field signal causes a rapid voltage rise, making it unsuitable for sampling, +// the phase was adjusted to align them. +assign adc_clk = pck_divclk; +`else assign adc_clk = ~pck_divclk; +`endif + // ADC clock also routed to debug pin assign debug = adc_clk; endmodule diff --git a/fpga/lo_simulate.v b/fpga/common/lo_simulate.v similarity index 100% rename from fpga/lo_simulate.v rename to fpga/common/lo_simulate.v diff --git a/fpga/lp20khz_1MSa_iir_filter.v b/fpga/common/lp20khz_1MSa_iir_filter.v similarity index 98% rename from fpga/lp20khz_1MSa_iir_filter.v rename to fpga/common/lp20khz_1MSa_iir_filter.v index d4a16cde1..ba5a02d33 100644 --- a/fpga/lp20khz_1MSa_iir_filter.v +++ b/fpga/common/lp20khz_1MSa_iir_filter.v @@ -72,7 +72,7 @@ module lp20khz_1MSa_iir_filter( // We could use 9bit x registers for that, but that would be // a waste, let's just add the constant during the computation // (x0+109) + 2*(x1+109) + (x2+109) = x0 + 2*x1 + x2 + 436 - x0 + {x1, 1'b0} + adc_d + 436 + x0 + {x1, 1'b0} + adc_d + 14'd436 // we want "- y0 * 0xd6 / 0x100" using only shift and add // 0xd6 == 0b11010110 // so *0xd6/0x100 is equivalent to diff --git a/fpga/min_max_tracker.v b/fpga/common/min_max_tracker.v similarity index 100% rename from fpga/min_max_tracker.v rename to fpga/common/min_max_tracker.v diff --git a/fpga/mux16.v b/fpga/common/mux16.v similarity index 100% rename from fpga/mux16.v rename to fpga/common/mux16.v diff --git a/fpga/mux2_onein.v b/fpga/common/mux2_onein.v similarity index 100% rename from fpga/mux2_onein.v rename to fpga/common/mux2_onein.v diff --git a/fpga/mux2_oneout.v b/fpga/common/mux2_oneout.v similarity index 100% rename from fpga/mux2_oneout.v rename to fpga/common/mux2_oneout.v diff --git a/fpga/mux8.v b/fpga/common/mux8.v similarity index 100% rename from fpga/mux8.v rename to fpga/common/mux8.v diff --git a/fpga/strip_date_time_from_binary.py b/fpga/common/strip_date_time_from_binary.py similarity index 100% rename from fpga/strip_date_time_from_binary.py rename to fpga/common/strip_date_time_from_binary.py diff --git a/fpga/common_src.mk b/fpga/common_src.mk new file mode 100644 index 000000000..a6e36c5fb --- /dev/null +++ b/fpga/common_src.mk @@ -0,0 +1,64 @@ +# +# Shared definitions for the FPGA build files. +# +# Source layout: +# common/ HDL shared by every platform +# pm3/ Xilinx PM3 / PM3-ULT top level and pin constraints +# icopyx/ Xilinx iCopy-X top level and pin constraints +# pm5/ Gowin PM5 top level, PLL, yosys script and pin constraints +# +# Included by proxmark3_fpga.mk and proxmark5_fpga.mk. + +COMMON_DIR = common +PM3_DIR = pm3 +ICOPYX_DIR = icopyx +PM5_DIR = pm5 + +# Prefix of the per target build directories, gitignored as fpga/__build* +PREFIX = __ + +# Copy update (only when destination is older or missing) +CP = cp -u + +# Make directory, no error if already existing +MKDIR = mkdir -p + +# Remove recursive, force +RMDIR = rm -rf + +# Path to make +MAKE = make + +# Echo (Q=) or not echo (Q=@) build commands to the terminal +Q = + +# HDL shared by every target, in synthesis order +COMMON_HDL = $(COMMON_DIR)/define.v +COMMON_HDL += $(COMMON_DIR)/mux8.v +COMMON_HDL += $(COMMON_DIR)/clk_divider.v +COMMON_HDL += $(COMMON_DIR)/lp20khz_1MSa_iir_filter.v +COMMON_HDL += $(COMMON_DIR)/min_max_tracker.v +COMMON_HDL += $(COMMON_DIR)/hi_flite.v +COMMON_HDL += $(COMMON_DIR)/hi_get_trace.v +COMMON_HDL += $(COMMON_DIR)/hi_iso14443a.v +COMMON_HDL += $(COMMON_DIR)/hi_reader.v +COMMON_HDL += $(COMMON_DIR)/hi_simulate.v +COMMON_HDL += $(COMMON_DIR)/hi_sniffer.v +COMMON_HDL += $(COMMON_DIR)/lf_edge_detect.v +COMMON_HDL += $(COMMON_DIR)/lo_adc.v +COMMON_HDL += $(COMMON_DIR)/lo_edge_detect.v +COMMON_HDL += $(COMMON_DIR)/lo_passthru.v +COMMON_HDL += $(COMMON_DIR)/lo_read.v + +# HDL of the PM5 (Gowin) bitstream, in synthesis order. One image covers both +# halves; the board selects LF or HF with the module_sel pin. +PM5_HDL = $(COMMON_HDL) +PM5_HDL += $(PM5_DIR)/gowin_rpll.v +PM5_HDL += $(PM5_DIR)/fpga_pm5_hf.v +PM5_HDL += $(PM5_DIR)/fpga_pm5_lf.v +PM5_HDL += $(PM5_DIR)/fpga_pm5_top.v + +# Strips the build date/time out of a generated Xilinx bitstream, so that +# rebuilding without a source change produces a byte identical file. +# Xilinx specific: not applicable to the Gowin bitstream. +STRIP_DATE_TIME = $(COMMON_DIR)/strip_date_time_from_binary.py diff --git a/fpga/fpga_icopyx_hf.v b/fpga/icopyx/fpga_icopyx_hf.v similarity index 100% rename from fpga/fpga_icopyx_hf.v rename to fpga/icopyx/fpga_icopyx_hf.v diff --git a/fpga/fpga_icopyx_lf.v b/fpga/icopyx/fpga_icopyx_lf.v similarity index 100% rename from fpga/fpga_icopyx_lf.v rename to fpga/icopyx/fpga_icopyx_lf.v diff --git a/fpga/fpga_icopyx_top.v b/fpga/icopyx/fpga_icopyx_top.v similarity index 100% rename from fpga/fpga_icopyx_top.v rename to fpga/icopyx/fpga_icopyx_top.v diff --git a/fpga/xc3s100e-4-vq100.ucf b/fpga/icopyx/xc3s100e-4-vq100.ucf similarity index 100% rename from fpga/xc3s100e-4-vq100.ucf rename to fpga/icopyx/xc3s100e-4-vq100.ucf diff --git a/fpga/fpga_pm3_top.v b/fpga/pm3/fpga_pm3_top.v similarity index 100% rename from fpga/fpga_pm3_top.v rename to fpga/pm3/fpga_pm3_top.v diff --git a/fpga/xc2s30-5-vq100.ucf b/fpga/pm3/xc2s30-5-vq100.ucf similarity index 100% rename from fpga/xc2s30-5-vq100.ucf rename to fpga/pm3/xc2s30-5-vq100.ucf diff --git a/fpga/xc2s50-5-tq144.ucf b/fpga/pm3/xc2s50-5-tq144.ucf similarity index 97% rename from fpga/xc2s50-5-tq144.ucf rename to fpga/pm3/xc2s50-5-tq144.ucf index 0964e1acf..f7e847c0e 100644 --- a/fpga/xc2s50-5-tq144.ucf +++ b/fpga/pm3/xc2s50-5-tq144.ucf @@ -1,45 +1,45 @@ -# See the schematic for the pin assignment. - -NET "adc_d<0>" LOC = "P54" ; -NET "adc_d<1>" LOC = "P57" ; -NET "adc_d<2>" LOC = "P59" ; -NET "adc_d<3>" LOC = "P60" ; -NET "adc_d<4>" LOC = "P62" ; -NET "adc_d<5>" LOC = "P63" ; -NET "adc_d<6>" LOC = "P65" ; -NET "adc_d<7>" LOC = "P67" ; -#NET "cross_hi" LOC = "P88" ; -#NET "miso" LOC = "P40" ; -NET "adc_clk" LOC = "P75" ; -NET "adc_noe" LOC = "P74" ; -NET "ck_1356meg" LOC = "P15" ; -NET "ck_1356megb" LOC = "P12" ; -NET "cross_lo" LOC = "P19" ; -NET "dbg" LOC = "P112" ; -NET "mosi" LOC = "P80" ; -NET "ncs" LOC = "P79" ; -NET "pck0" LOC = "P91" ; -NET "pwr_hi" LOC = "P31" ; -NET "pwr_lo" LOC = "P30" ; -NET "pwr_oe1" LOC = "P28" ; -NET "pwr_oe2" LOC = "P27" ; -NET "pwr_oe3" LOC = "P26" ; -NET "pwr_oe4" LOC = "P21" ; -NET "spck" LOC = "P88" ; -NET "ssp_clk" LOC = "P43" ; -NET "ssp_din" LOC = "P99" ; -NET "ssp_dout" LOC = "P94" ; -NET "ssp_frame" LOC = "P100" ; - -# definition of Clock nets: -NET "ck_1356meg" TNM_NET = "clk_net_1356" ; -NET "ck_1356megb" TNM_NET = "clk_net_1356b"; -NET "pck0" TNM_NET = "clk_net_pck0" ; -NET "spck" TNM_NET = "clk_net_spck" ; - -# Timing specs of clock nets: -TIMEGRP "clk_net_1356_all" = "clk_net_1356" "clk_net_1356b" ; -TIMESPEC "TS_1356MHz" = PERIOD "clk_net_1356_all" 74 ns HIGH 37 ns ; -TIMESPEC "TS_24MHz" = PERIOD "clk_net_pck0" 42 ns HIGH 21 ns ; -TIMESPEC "TS_4MHz" = PERIOD "clk_net_spck" 250 ns HIGH 125 ns ; - +# See the schematic for the pin assignment. + +NET "adc_d<0>" LOC = "P54" ; +NET "adc_d<1>" LOC = "P57" ; +NET "adc_d<2>" LOC = "P59" ; +NET "adc_d<3>" LOC = "P60" ; +NET "adc_d<4>" LOC = "P62" ; +NET "adc_d<5>" LOC = "P63" ; +NET "adc_d<6>" LOC = "P65" ; +NET "adc_d<7>" LOC = "P67" ; +#NET "cross_hi" LOC = "P88" ; +#NET "miso" LOC = "P40" ; +NET "adc_clk" LOC = "P75" ; +NET "adc_noe" LOC = "P74" ; +NET "ck_1356meg" LOC = "P15" ; +NET "ck_1356megb" LOC = "P12" ; +NET "cross_lo" LOC = "P19" ; +NET "dbg" LOC = "P112" ; +NET "mosi" LOC = "P80" ; +NET "ncs" LOC = "P79" ; +NET "pck0" LOC = "P91" ; +NET "pwr_hi" LOC = "P31" ; +NET "pwr_lo" LOC = "P30" ; +NET "pwr_oe1" LOC = "P28" ; +NET "pwr_oe2" LOC = "P27" ; +NET "pwr_oe3" LOC = "P26" ; +NET "pwr_oe4" LOC = "P21" ; +NET "spck" LOC = "P88" ; +NET "ssp_clk" LOC = "P43" ; +NET "ssp_din" LOC = "P99" ; +NET "ssp_dout" LOC = "P94" ; +NET "ssp_frame" LOC = "P100" ; + +# definition of Clock nets: +NET "ck_1356meg" TNM_NET = "clk_net_1356" ; +NET "ck_1356megb" TNM_NET = "clk_net_1356b"; +NET "pck0" TNM_NET = "clk_net_pck0" ; +NET "spck" TNM_NET = "clk_net_spck" ; + +# Timing specs of clock nets: +TIMEGRP "clk_net_1356_all" = "clk_net_1356" "clk_net_1356b" ; +TIMESPEC "TS_1356MHz" = PERIOD "clk_net_1356_all" 74 ns HIGH 37 ns ; +TIMESPEC "TS_24MHz" = PERIOD "clk_net_pck0" 42 ns HIGH 21 ns ; +TIMESPEC "TS_4MHz" = PERIOD "clk_net_spck" 250 ns HIGH 125 ns ; + diff --git a/fpga/pm5/fpga_pm5_hf.v b/fpga/pm5/fpga_pm5_hf.v new file mode 100644 index 000000000..b6b37670a --- /dev/null +++ b/fpga/pm5/fpga_pm5_hf.v @@ -0,0 +1,382 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// +// The FPGA is responsible for interfacing between the A/D, the coil drivers, +// and the ARM. In the low-frequency modes it passes the data straight +// through, so that the ARM gets raw A/D samples over the SSP. In the high- +// frequency modes, the FPGA might perform some demodulation first, to +// reduce the amount of data that we must send to the ARM. +//----------------------------------------------------------------------------- + + +module fpga_hf( + input ck_1356meg, + input spck, + //input pck0, + input ncs, + input [7:0] adc_d, + //input cross_hi, + //input cross_lo, + input mosi, + input ssp_dout, + input ce, // 复位输入,常态高 + + output ssp_din, + output ssp_frame, + output ssp_clk, + output adc_clk, + //output adc_noe, + output miso, + output pwr_lo, + output pwr_hi, + output pwr_oe1, + output pwr_oe2, + //output pwr_oe3, + output pwr_oe4, + output dbg, + output reg [11:0] pwm_duty = 12'h800 +); + + +// In all modes, let the ADC's outputs be enabled. +//assign adc_noe = 1'b0; +assign miso = 1'bZ; +wire cross_hi = 1'b0; + +wire [2:0] major_mode; +assign major_mode = ce ? conf_word[8:6] : 3'b111; + +//----------------------------------------------------------------------------- +// The SPI receiver. This sets up the configuration word, which the rest of +// the logic looks at to determine how to connect the A/D and the coil +// drivers (i.e., which section gets it). Also assign some symbolic names +// to the configuration bits, for use below. +//----------------------------------------------------------------------------- + +// Receive 16bits of data from ARM here. +reg [15:0] shift_reg; + +//always @(posedge spck) if (~ncs) shift_reg <= {shift_reg[14:0], mosi}; +always @(posedge spck) begin + if(~ncs & ce) + shift_reg <= {shift_reg[14:0], mosi}; +end + +reg trace_enable; + +reg [5:0] hf_edge_detect_threshold = 6'd7; +reg [5:0] hf_edge_detect_threshold_high = 6'd20; + +reg [8:0] conf_word; + +initial +begin + hf_edge_detect_threshold <= 7; + hf_edge_detect_threshold_high <= 20; +end + +// We switch modes between transmitting to the 13.56 MHz tag and receiving +// from it, which means that we must make sure that we can do so without +// glitching, or else we will glitch the transmitted carrier. +always @(posedge ncs) +begin + if(ce) begin + // 4 bit command + case (shift_reg[15:12]) + `FPGA_CMD_SET_CONFREG: conf_word <= shift_reg[8:0]; + `FPGA_CMD_TRACE_ENABLE: trace_enable <= shift_reg[0]; + `FPGA_CMD_SET_EDGE_DETECT_THRESHOLD: + begin + hf_edge_detect_threshold <= shift_reg[5:0]; + hf_edge_detect_threshold_high <= shift_reg[11:6]; + end + `FPGA_CMD_SET_PWR_PWM_LOW_COUNT:pwm_duty <= shift_reg[11:0]; + endcase + end +end +//----------------------------------------------------------------------------- +// And then we instantiate the modules corresponding to each of the FPGA's +// major modes, and use muxes to connect the outputs of the active mode to +// the output pins. +//----------------------------------------------------------------------------- + +// HF reader +hi_reader hr( + .ck_1356meg (ck_1356meg), + .adc_d (adc_d), + .subcarrier_frequency (conf_word[5:4]), + .minor_mode (conf_word[3:0]), + .ssp_dout (ssp_dout), + + .ssp_din (mux0_ssp_din), + .ssp_frame (mux0_ssp_frame), + .ssp_clk (mux0_ssp_clk), + .adc_clk (mux0_adc_clk), + .pwr_lo (mux0_pwr_lo), + .pwr_hi (mux0_pwr_hi), + .pwr_oe1 (mux0_pwr_oe1), + .pwr_oe2 (mux0_pwr_oe2), + .pwr_oe3 (mux0_pwr_oe3), + .pwr_oe4 (mux0_pwr_oe4), + .debug (mux0_debug) +); + +// HF simulated tag +hi_simulate hs( + .ck_1356meg (ck_1356meg), + .adc_d (adc_d), + .mod_type (conf_word[3:0]), + .ssp_dout (ssp_dout), + + .ssp_din (mux1_ssp_din), + .ssp_frame (mux1_ssp_frame), + .ssp_clk (mux1_ssp_clk), + .adc_clk (mux1_adc_clk), + .pwr_lo (mux1_pwr_lo), + .pwr_hi (mux1_pwr_hi), + .pwr_oe1 (mux1_pwr_oe1), + .pwr_oe2 (mux1_pwr_oe2), + .pwr_oe3 (mux1_pwr_oe3), + .pwr_oe4 (mux1_pwr_oe4), + .debug (mux1_debug) +); + +// HF ISO14443-A +hi_iso14443a hisn( + .ck_1356meg (ck_1356meg), + .adc_d (adc_d), + .mod_type (conf_word[3:0]), + .ssp_dout (ssp_dout), + + .ssp_din (mux2_ssp_din), + .ssp_frame (mux2_ssp_frame), + .ssp_clk (mux2_ssp_clk), + .adc_clk (mux2_adc_clk), + .pwr_lo (mux2_pwr_lo), + .pwr_hi (mux2_pwr_hi), + .pwr_oe1 (mux2_pwr_oe1), + .pwr_oe2 (mux2_pwr_oe2), + .pwr_oe3 (mux2_pwr_oe3), + .pwr_oe4 (mux2_pwr_oe4), + .debug (mux2_debug), + .edge_detect_threshold ({5'b00000, hf_edge_detect_threshold}), + .edge_detect_threshold_high ({5'b00000, hf_edge_detect_threshold_high}) +); + +// HF sniff +hi_sniffer he( + .ck_1356meg (ck_1356meg), + .adc_d (adc_d), + + .ssp_din (mux3_ssp_din), + .ssp_frame (mux3_ssp_frame), + .ssp_clk (mux3_ssp_clk), + .adc_clk (mux3_adc_clk), + .pwr_lo (mux3_pwr_lo), + .pwr_hi (mux3_pwr_hi), + .pwr_oe1 (mux3_pwr_oe1), + .pwr_oe2 (mux3_pwr_oe2), + .pwr_oe3 (mux3_pwr_oe3), + .pwr_oe4 (mux3_pwr_oe4) +); + +// HF ISO18092 FeliCa +hi_flite hfl( + .ck_1356meg (ck_1356meg), + .adc_d (adc_d), + .mod_type (conf_word[3:0]), + .ssp_dout (ssp_dout), + + .ssp_din (mux4_ssp_din), + .ssp_frame (mux4_ssp_frame), + .ssp_clk (mux4_ssp_clk), + .adc_clk (mux4_adc_clk), + .pwr_lo (mux4_pwr_lo), + .pwr_hi (mux4_pwr_hi), + .pwr_oe1 (mux4_pwr_oe1), + .pwr_oe2 (mux4_pwr_oe2), + .pwr_oe3 (mux4_pwr_oe3), + .pwr_oe4 (mux4_pwr_oe4), + .debug (mux4_debug) +); + +// HF get trace + +hi_get_trace gt( + .ck_1356megb (ck_1356meg), + .adc_d (adc_d), + .trace_enable (trace_enable), + .major_mode (major_mode), + .ssp_din (mux5_ssp_din), + .ssp_frame (mux5_ssp_frame), + .ssp_clk (mux5_ssp_clk) +); + +// These assignments must agree with the defines in fpgaloader.h + +// Major modes High Frequency +// mux0 = HF reader +// mux1 = HF simulated tag +// mux2 = HF ISO14443-A +// mux3 = HF sniff +// mux4 = HF ISO18092 FeliCa +// mux5 = HF get trace +// mux6 = unused +// mux7 = FPGA_MAJOR_MODE_OFF + +mux8 mux_ssp_clk ( + .sel (major_mode), + .y (ssp_clk ), + .x0 (mux0_ssp_clk ), + .x1 (mux1_ssp_clk ), + .x2 (mux2_ssp_clk ), + .x3 (mux3_ssp_clk ), + .x4 (mux4_ssp_clk ), + .x5 (mux5_ssp_clk ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_ssp_din ( + .sel (major_mode), + .y (ssp_din ), + .x0 (mux0_ssp_din ), + .x1 (mux1_ssp_din ), + .x2 (mux2_ssp_din ), + .x3 (mux3_ssp_din ), + .x4 (mux4_ssp_din ), + .x5 (mux5_ssp_din ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_ssp_frame ( + .sel (major_mode), + .y (ssp_frame ), + .x0 (mux0_ssp_frame ), + .x1 (mux1_ssp_frame ), + .x2 (mux2_ssp_frame ), + .x3 (mux3_ssp_frame ), + .x4 (mux4_ssp_frame ), + .x5 (mux5_ssp_frame ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe1 ( + .sel (major_mode), + .y (pwr_oe1 ), + .x0 (mux0_pwr_oe1 ), + .x1 (mux1_pwr_oe1 ), + .x2 (mux2_pwr_oe1 ), + .x3 (mux3_pwr_oe1 ), + .x4 (mux4_pwr_oe1 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe2 ( + .sel (major_mode), + .y (pwr_oe2 ), + .x0 (mux0_pwr_oe2 ), + .x1 (mux1_pwr_oe2 ), + .x2 (mux2_pwr_oe2 ), + .x3 (mux3_pwr_oe2 ), + .x4 (mux4_pwr_oe2 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe3 ( + .sel (major_mode), + .y (pwr_oe3 ), + .x0 (mux0_pwr_oe3 ), + .x1 (mux1_pwr_oe3 ), + .x2 (mux2_pwr_oe3 ), + .x3 (mux3_pwr_oe3 ), + .x4 (mux4_pwr_oe3 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe4 ( + .sel (major_mode), + .y (pwr_oe4 ), + .x0 (mux0_pwr_oe4 ), + .x1 (mux1_pwr_oe4 ), + .x2 (mux2_pwr_oe4 ), + .x3 (mux3_pwr_oe4 ), + .x4 (mux4_pwr_oe4 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_lo ( + .sel (major_mode), + .y (pwr_lo ), + .x0 (mux0_pwr_lo ), + .x1 (mux1_pwr_lo ), + .x2 (mux2_pwr_lo ), + .x3 (mux3_pwr_lo ), + .x4 (mux4_pwr_lo ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_hi ( + .sel (major_mode), + .y (pwr_hi ), + .x0 (mux0_pwr_hi ), + .x1 (mux1_pwr_hi ), + .x2 (mux2_pwr_hi ), + .x3 (mux3_pwr_hi ), + .x4 (mux4_pwr_hi ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_adc_clk ( + .sel (major_mode), + .y (adc_clk ), + .x0 (mux0_adc_clk ), + .x1 (mux1_adc_clk ), + .x2 (mux2_adc_clk ), + .x3 (mux3_adc_clk ), + .x4 (mux4_adc_clk ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_dbg ( + .sel (major_mode), + .y (dbg ), + .x0 (mux0_debug ), + .x1 (mux1_debug ), + .x2 (mux2_debug ), + .x3 (1'b0 ), + .x4 (mux4_debug ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); +endmodule diff --git a/fpga/pm5/fpga_pm5_lf.v b/fpga/pm5/fpga_pm5_lf.v new file mode 100644 index 000000000..564723f9d --- /dev/null +++ b/fpga/pm5/fpga_pm5_lf.v @@ -0,0 +1,340 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// +// The FPGA is responsible for interfacing between the A/D, the coil drivers, +// and the ARM. In the low-frequency modes it passes the data straight +// through, so that the ARM gets raw A/D samples over the SSP. In the high- +// frequency modes, the FPGA might perform some demodulation first, to +// reduce the amount of data that we must send to the ARM. +//----------------------------------------------------------------------------- + + +module fpga_lf( + //input ck_1356meg, + input spck, + input pck0, + input ncs, + input [7:0] adc_d, + input cross_lo, + input mosi, + input ssp_dout, + input ce, // 复位输入,常态高 + + output ssp_din, + output ssp_frame, + output ssp_clk, + output adc_clk, + output miso, + output pwr_lo, + output pwr_oe1, + output pwr_oe2, + output pwr_oe4, + output dbg, + output reg [11:0] pwm_duty = 12'h500 +); + +// In all modes, let the ADC's outputs be enabled. +//assign adc_noe = 1'b0; +assign miso = 1'bZ; + +wire [2:0] major_mode; +assign major_mode = ce ? conf_word[8:6] : 3'b111; + +//----------------------------------------------------------------------------- +// The SPI receiver. This sets up the configuration word, which the rest of +// the logic looks at to determine how to connect the A/D and the coil +// drivers (i.e., which section gets it). Also assign some symbolic names +// to the configuration bits, for use below. +//----------------------------------------------------------------------------- + +// Receive 16bits of data from ARM here. +reg [15:0] shift_reg; + +always @(posedge spck) begin + if(~ncs & ce) + shift_reg <= {shift_reg[14:0], mosi}; +end + +reg [7:0] lf_ed_threshold; + +// adjustable frequency clock +wire [7:0] pck_cnt; +wire pck_divclk; +reg [7:0] divisor; +clk_divider div_clk(pck0, divisor, pck_cnt, pck_divclk); + +reg [11:0] conf_word; + +// We switch modes between transmitting to the 13.56 MHz tag and receiving +// from it, which means that we must make sure that we can do so without +// glitching, or else we will glitch the transmitted carrier. +always @(posedge ncs) +begin + if(ce) begin + // 4 bit command + case (shift_reg[15:12]) + `FPGA_CMD_SET_CONFREG: + begin + // 12 bit data + conf_word <= shift_reg[11:0]; + if (shift_reg[8:6] == `FPGA_MAJOR_MODE_LF_EDGE_DETECT) lf_ed_threshold <= 127; // default threshold + end + + `FPGA_CMD_SET_DIVISOR: + divisor <= shift_reg[7:0]; // 8bits + + `FPGA_CMD_SET_EDGE_DETECT_THRESHOLD: + lf_ed_threshold <= shift_reg[7:0]; // 8 bits + `FPGA_CMD_SET_PWR_PWM_LOW_COUNT:pwm_duty <= shift_reg[11:0]; + endcase + end +end + +//----------------------------------------------------------------------------- +// And then we instantiate the modules corresponding to each of the FPGA's +// major modes, and use muxes to connect the outputs of the active mode to +// the output pins. +//----------------------------------------------------------------------------- + +// ############################################################################ + +// LF reader (generic) +lo_read lr( + .pck0 (pck0), + .pck_divclk (pck_divclk), + .pck_cnt (pck_cnt), + .adc_d (adc_d), + .lf_field (conf_word[0]), + + .ssp_din (mux0_ssp_din), + .ssp_frame (mux0_ssp_frame), + .ssp_clk (mux0_ssp_clk), + .adc_clk (mux0_adc_clk), + .pwr_lo (mux0_pwr_lo), + .pwr_oe1 (mux0_pwr_oe1), + .pwr_oe2 (mux0_pwr_oe2), + .pwr_oe3 (mux0_pwr_oe3), + .pwr_oe4 (mux0_pwr_oe4), + .debug (mux0_debug) +); + +// LF edge detect (generic) +lo_edge_detect le( + .pck0 (pck0), + .pck_divclk (pck_divclk), + .adc_d (adc_d), + .cross_lo (cross_lo), + .lf_field (conf_word[0]), + .lf_ed_toggle_mode (conf_word[1]), + .lf_weak_load (conf_word[2]), + .lf_ed_hold_tracker (conf_word[3]), + .lf_ed_sensitive (conf_word[4]), + .lf_ed_slope (conf_word[5]), + .lf_ed_threshold (lf_ed_threshold), + .ssp_dout (ssp_dout), + + .ssp_frame (mux1_ssp_frame), + .ssp_clk (mux1_ssp_clk), + .adc_clk (mux1_adc_clk), + .pwr_lo (mux1_pwr_lo), + .pwr_oe1 (mux1_pwr_oe1), + .pwr_oe2 (mux1_pwr_oe2), + .pwr_oe3 (mux1_pwr_oe3), + .pwr_oe4 (mux1_pwr_oe4), + .debug (mux1_debug) +); + +// LF passthrough +lo_passthru lp( + .pck_divclk (pck_divclk), + .cross_lo (cross_lo), + .ssp_dout (ssp_dout), + + .ssp_din (mux2_ssp_din), + .adc_clk (mux2_adc_clk), + .pwr_lo (mux2_pwr_lo), + .pwr_oe1 (mux2_pwr_oe1), + .pwr_oe2 (mux2_pwr_oe2), + .pwr_oe3 (mux2_pwr_oe3), + .pwr_oe4 (mux2_pwr_oe4), + .debug (mux2_debug) +); + +// LF ADC (read/write) +lo_adc la( + .pck0 (pck0), + .adc_d (adc_d), + .divisor (divisor), + .lf_field (conf_word[0]), + .lf_weak_load (conf_word[2]), + .ssp_dout (ssp_dout), + + .ssp_din (mux3_ssp_din), + .ssp_frame (mux3_ssp_frame), + .ssp_clk (mux3_ssp_clk), + .adc_clk (mux3_adc_clk), + .pwr_lo (mux3_pwr_lo ), + .pwr_oe1 (mux3_pwr_oe1), + .pwr_oe2 (mux3_pwr_oe2), + .pwr_oe3 (mux3_pwr_oe3), + .pwr_oe4 (mux3_pwr_oe4), + .debug (mux3_debug) +); + +assign mux6_pwr_lo = 1'b1; +// 7 -- SPARE + +// These assignments must agree with the defines in fpgaloader.h +// Major modes Low Frequency +// mux0 = LF reader (generic) +// mux1 = LF edge detect (generic) +// mux2 = LF passthrough +// mux3 = LF ADC (read/write) +// mux4 = SPARE +// mux5 = SPARE +// mux6 = SPARE +// mux7 = FPGA_MAJOR_MODE_OFF + +mux8 mux_ssp_clk ( + .sel (major_mode), + .y (ssp_clk ), + .x0 (mux0_ssp_clk ), + .x1 (mux1_ssp_clk ), + .x2 (1'b0 ), + .x3 (mux3_ssp_clk ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_ssp_din ( + .sel (major_mode), + .y (ssp_din ), + .x0 (mux0_ssp_din ), + .x1 (1'b0 ), + .x2 (mux2_ssp_din ), + .x3 (mux3_ssp_din ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_ssp_frame ( + .sel (major_mode), + .y (ssp_frame ), + .x0 (mux0_ssp_frame ), + .x1 (mux1_ssp_frame ), + .x2 (1'b0 ), + .x3 (mux3_ssp_frame ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe1 ( + .sel (major_mode), + .y (pwr_oe1 ), + .x0 (mux0_pwr_oe1 ), + .x1 (mux1_pwr_oe1 ), + .x2 (mux2_pwr_oe1 ), + .x3 (mux3_pwr_oe1 ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe2 ( + .sel (major_mode), + .y (pwr_oe2 ), + .x0 (mux0_pwr_oe2 ), + .x1 (mux1_pwr_oe2 ), + .x2 (mux2_pwr_oe2 ), + .x3 (mux3_pwr_oe2 ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe3 ( + .sel (major_mode), + .y (pwr_oe3 ), + .x0 (mux0_pwr_oe3 ), + .x1 (mux1_pwr_oe3 ), + .x2 (mux2_pwr_oe3 ), + .x3 (mux3_pwr_oe3 ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_oe4 ( + .sel (major_mode), + .y (pwr_oe4 ), + .x0 (mux0_pwr_oe4 ), + .x1 (mux1_pwr_oe4 ), + .x2 (mux2_pwr_oe4 ), + .x3 (mux3_pwr_oe4 ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_pwr_lo ( + .sel (major_mode), + .y (pwr_lo ), + .x0 (mux0_pwr_lo ), + .x1 (mux1_pwr_lo ), + .x2 (mux2_pwr_lo ), + .x3 (mux3_pwr_lo ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (mux6_pwr_lo ), + .x7 (1'b0 ) +); + +mux8 mux_adc_clk ( + .sel (major_mode), + .y (adc_clk ), + .x0 (mux0_adc_clk ), + .x1 (mux1_adc_clk ), + .x2 (mux2_adc_clk ), + .x3 (mux3_adc_clk ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); + +mux8 mux_dbg ( + .sel (major_mode), + .y (dbg ), + .x0 (mux0_debug ), + .x1 (mux1_debug ), + .x2 (mux2_debug ), + .x3 (mux3_debug ), + .x4 (1'b0 ), + .x5 (1'b0 ), + .x6 (1'b0 ), + .x7 (1'b0 ) +); +endmodule diff --git a/fpga/pm5/fpga_pm5_top.v b/fpga/pm5/fpga_pm5_top.v new file mode 100644 index 000000000..a3b14e9fb --- /dev/null +++ b/fpga/pm5/fpga_pm5_top.v @@ -0,0 +1,132 @@ +module fpga_top ( + input ck_1356meg, + input ck_1356meg_b, // B is for close range wiring + input spi_sck, + input pck0, + input spi_ncs, + input [9:0] adc_d, + input cross_lo, + input spi_mosi, + input ssp_dout, + input module_sel, // 选择模块,1=HF,0=LF + + output ssp_din, + output ssp_frame, + output ssp_clk, + output adc_clk, + output spi_miso, + output pwr_lo, + output pwr_hi, + output pwr_oe1, + output pwr_oe2, + output pwr_oe4, + output dbg1, + output dbg2, + output dbgled, + + output clk_gate, //门控输出,控制切断外部13.56m + output pwr_pwm, //升压器输出,pwm控制升压电压 + output pan_en, //模拟供电使能 + output pdp_en //升压器使能 +); + + wire ce_lf = ~module_sel; //高启用,低重置 + wire ce_hf = module_sel; //高启用,低重置 + //-----------------------------------------GATE------------------------- + //门控使能信号同步到输入时钟域,避免亚稳态 + reg gate_en_sync1, gate_en_sync2; + always @(posedge ck_1356meg) begin + gate_en_sync1 <= gate_en; + gate_en_sync2 <= gate_en_sync1; + end + + assign clk_gate = ~gate_en_sync2;//这里取反是因为外部门控是低有效的 + //-----------------------------------------PWM-------------------------- + + Gowin_rPLL pwr_pwm_pll( + .clkout(pwr_pwm_pll_clko), //output clkout + .clkin(ck_1356meg) //input clkin + ); + + wire [11:0] pwm_duty_lf; // LF 模块的 PWM 占空比输出 + wire [11:0] pwm_duty_hf; // HF 模块的 PWM 占空比输出 + + wire [11:0] pwm_duty; // PWM占空比,实际上是高电平长度,和4096做比例 + reg [11:0] pwm_cnt= 12'h0; + + always @(posedge pwr_pwm_pll_clko) pwm_cnt <= pwm_cnt + 1'b1; //计数器 + assign pwr_pwm = (pwm_cnt > pwm_duty); //pwm输出 + + assign pan_en = 1'b1; + assign pdp_en = 1'b1; + + assign pwr_hi = 1'bZ; //使用外部门控时禁用PWRHI,由于是并联的,需要高阻 + assign dbgled = 1'b1; + assign dbg2 = 1'bZ; // 未使用 + + fpga_lf uut_lf ( + .pck0(pck0), + + .spck(spi_sck), + .ncs(spi_ncs), + + .adc_d(adc_d[9:2]), + .cross_lo(cross_lo), + .mosi(spi_mosi), + .ssp_dout(ssp_dout), + .ce(ce_lf), + + .ssp_din(ssp_din_lf), + .ssp_frame(ssp_frame_lf), + .ssp_clk(ssp_clk_lf), + .adc_clk(adc_clk_lf), + .miso(miso_lf), + .pwr_lo(pwr_lo_lf), + .pwr_oe1(pwr_oe1_lf), + .pwr_oe2(pwr_oe2_lf), + .pwr_oe4(pwr_oe4_lf), + .dbg(dbg_lf), + + .pwm_duty(pwm_duty_lf) + ); + + fpga_hf uut_hf ( + .ck_1356meg(ck_1356meg), + + .spck(spi_sck), + .ncs(spi_ncs), + + .adc_d(adc_d[9:2]), + .mosi(spi_mosi), + .ssp_dout(ssp_dout), + .ce(ce_hf), + + .ssp_din(ssp_din_hf), + .ssp_frame(ssp_frame_hf), + .ssp_clk(ssp_clk_hf), + .adc_clk(adc_clk_hf), + .miso(miso_hf), + .pwr_lo(pwr_lo_hf), + .pwr_hi(gate_en), + .pwr_oe1(pwr_oe1_hf), + .pwr_oe2(pwr_oe2_hf), + .pwr_oe4(pwr_oe4_hf), + .dbg(dbg_hf), + + .pwm_duty(pwm_duty_hf) + ); + + assign ssp_din = ~module_sel ? ssp_din_lf : ssp_din_hf; + assign ssp_frame = ~module_sel ? ssp_frame_lf : ssp_frame_hf; + assign ssp_clk = ~module_sel ? ssp_clk_lf : ssp_clk_hf; + assign adc_clk = ~module_sel ? adc_clk_lf : adc_clk_hf; + assign spi_miso = ~module_sel ? miso_lf : miso_hf; + assign pwr_lo = ~module_sel ? pwr_lo_lf : pwr_lo_hf; + //assign pwr_hi = ~module_sel ? pwr_hi_lf : pwr_hi_hf; + assign pwr_oe1 = ~module_sel ? pwr_oe1_lf : pwr_oe1_hf; + assign pwr_oe2 = ~module_sel ? pwr_oe2_lf : pwr_oe2_hf; + assign pwr_oe4 = ~module_sel ? pwr_oe4_lf : pwr_oe4_hf; + assign dbg1 = ~module_sel ? dbg_lf : dbg_hf; + assign pwm_duty = ~module_sel ? pwm_duty_lf : pwm_duty_hf; + +endmodule \ No newline at end of file diff --git a/fpga/pm5/gowin.cst b/fpga/pm5/gowin.cst new file mode 100644 index 000000000..32e1ac5b1 --- /dev/null +++ b/fpga/pm5/gowin.cst @@ -0,0 +1,81 @@ +//Copyright (C)2014-2023 Gowin Semiconductor Corporation. +//All rights reserved. +//File Title: Physical Constraints file +//Tool Version: V1.9.9 (64-bit) +//Part Number: GW1N-UV4QN48C6/I5 +//Device: GW1N-4 +//Device Version: D +//Created Time: Thu 06 04 12:54:44 2026 + +IO_LOC "pdp_en" 42; +IO_PORT "pdp_en" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pan_en" 24; +IO_PORT "pan_en" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pwr_pwm" 38; +IO_PORT "pwr_pwm" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "clk_gate" 29; +IO_PORT "clk_gate" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "dbgled" 41; +IO_PORT "dbgled" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "dbg2" 39; +IO_PORT "dbg2" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "dbg1" 40; +IO_PORT "dbg1" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pwr_oe4" 31; +IO_PORT "pwr_oe4" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pwr_oe2" 34; +IO_PORT "pwr_oe2" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pwr_oe1" 32; +IO_PORT "pwr_oe1" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pwr_lo" 33; +IO_PORT "pwr_lo" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "adc_clk" 13; +IO_PORT "adc_clk" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "ssp_clk" 47; +IO_PORT "ssp_clk" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "ssp_frame" 44; +IO_PORT "ssp_frame" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "ssp_din" 45; +IO_PORT "ssp_din" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "spi_miso" 7; +IO_PORT "spi_miso" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "pwr_hi" 27; +IO_PORT "pwr_hi" IO_TYPE=LVTTL33 PULL_MODE=NONE DRIVE=8 BANK_VCCIO=3.3; +IO_LOC "module_sel" 11; +IO_PORT "module_sel" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "ssp_dout" 46; +IO_PORT "ssp_dout" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "spi_mosi" 6; +IO_PORT "spi_mosi" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "cross_lo" 35; +IO_PORT "cross_lo" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[9]" 14; +IO_PORT "adc_d[9]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[8]" 15; +IO_PORT "adc_d[8]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[7]" 16; +IO_PORT "adc_d[7]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[6]" 17; +IO_PORT "adc_d[6]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[5]" 18; +IO_PORT "adc_d[5]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[4]" 19; +IO_PORT "adc_d[4]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[3]" 20; +IO_PORT "adc_d[3]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[2]" 21; +IO_PORT "adc_d[2]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "spi_ncs" 4; +IO_PORT "spi_ncs" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "pck0" 30; +IO_PORT "pck0" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "spi_sck" 5; +IO_PORT "spi_sck" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "ck_1356meg" 10; +IO_PORT "ck_1356meg" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[1]" 22; +IO_PORT "adc_d[1]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "adc_d[0]" 23; +IO_PORT "adc_d[0]" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; +IO_LOC "ck_1356meg_b" 28; +IO_PORT "ck_1356meg_b" IO_TYPE=LVTTL33 PULL_MODE=NONE BANK_VCCIO=3.3; diff --git a/fpga/pm5/gowin.sdc b/fpga/pm5/gowin.sdc new file mode 100644 index 000000000..0d5281899 --- /dev/null +++ b/fpga/pm5/gowin.sdc @@ -0,0 +1,11 @@ +//Copyright (C)2014-2026 GOWIN Semiconductor Corporation. +//All rights reserved. +//File Title: Timing Constraints file +//Tool Version: V1.9.9 (64-bit) +//Created Time: 2026-04-20 17:50:38 +create_clock -name CLK_SPI -period 250 -waveform {0 125} [get_ports {spi_sck}] +create_clock -name CLK_NCS -period 1000 -waveform {0 500} [get_ports {spi_ncs}] +create_clock -name clk24 -period 41.667 -waveform {0 20.834} [get_ports {pck0}] +create_clock -name clk1356 -period 73.746 -waveform {0 36.873} [get_ports {ck_1356meg}] +set_clock_groups -asynchronous -group [get_clocks {clk1356}] -group [get_clocks {clk24}] -group [get_clocks {CLK_SPI}] -group [get_clocks {CLK_NCS}] +report_timing -setup -from_clock [get_clocks {clk1356}] -to [get_ports {pwr_hi}] diff --git a/fpga/pm5/gowin_rpll.v b/fpga/pm5/gowin_rpll.v new file mode 100644 index 000000000..00895111a --- /dev/null +++ b/fpga/pm5/gowin_rpll.v @@ -0,0 +1,64 @@ +//Copyright (C)2014-2023 Gowin Semiconductor Corporation. +//All rights reserved. +//File Title: IP file +//Tool Version: V1.9.9 (64-bit) +//Part Number: GW1N-UV4QN48C6/I5 +//Device: GW1N-4 +//Device Version: D +//Created Time: Mon Jul 6 18:07:08 2026 + +module Gowin_rPLL (clkout, clkin); + +output clkout; +input clkin; + +wire lock_o; +wire clkoutp_o; +wire clkoutd_o; +wire clkoutd3_o; +wire gw_gnd; + +assign gw_gnd = 1'b0; + +rPLL rpll_inst ( + .CLKOUT(clkout), + .LOCK(lock_o), + .CLKOUTP(clkoutp_o), + .CLKOUTD(clkoutd_o), + .CLKOUTD3(clkoutd3_o), + .RESET(gw_gnd), + .RESET_P(gw_gnd), + .CLKIN(clkin), + .CLKFB(gw_gnd), + .FBDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), + .IDSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), + .ODSEL({gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd,gw_gnd}), + .PSDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}), + .DUTYDA({gw_gnd,gw_gnd,gw_gnd,gw_gnd}), + .FDLY({gw_gnd,gw_gnd,gw_gnd,gw_gnd}) +); + +defparam rpll_inst.FCLKIN = "13.56"; +defparam rpll_inst.DYN_IDIV_SEL = "false"; +defparam rpll_inst.IDIV_SEL = 1; +defparam rpll_inst.DYN_FBDIV_SEL = "false"; +defparam rpll_inst.FBDIV_SEL = 58; +defparam rpll_inst.DYN_ODIV_SEL = "false"; +defparam rpll_inst.ODIV_SEL = 2; +defparam rpll_inst.PSDA_SEL = "0000"; +defparam rpll_inst.DYN_DA_EN = "false"; +defparam rpll_inst.DUTYDA_SEL = "1000"; +defparam rpll_inst.CLKOUT_FT_DIR = 1'b1; +defparam rpll_inst.CLKOUTP_FT_DIR = 1'b1; +defparam rpll_inst.CLKOUT_DLY_STEP = 0; +defparam rpll_inst.CLKOUTP_DLY_STEP = 0; +defparam rpll_inst.CLKFB_SEL = "internal"; +defparam rpll_inst.CLKOUT_BYPASS = "false"; +defparam rpll_inst.CLKOUTP_BYPASS = "false"; +defparam rpll_inst.CLKOUTD_BYPASS = "false"; +defparam rpll_inst.DYN_SDIV_SEL = 2; +defparam rpll_inst.CLKOUTD_SRC = "CLKOUT"; +defparam rpll_inst.CLKOUTD3_SRC = "CLKOUT"; +defparam rpll_inst.DEVICE = "GW1N-4D"; + +endmodule //Gowin_rPLL diff --git a/fpga/pm5/run.ys b/fpga/pm5/run.ys new file mode 100644 index 000000000..fbf60070d --- /dev/null +++ b/fpga/pm5/run.ys @@ -0,0 +1,11 @@ +# 手动综合脚本,等价于 proxmark5_fpga.mk 自动生成的步骤。 +# 在 fpga 目录下运行:yosys -s pm5/run.ys (构建时请用 make pm5) + +# 1. 读取源代码和宏定义 +read_verilog -D PM5 -D WITH_TIMODE -D WITH_HF_15 -D WITH_FELICA_PROBE common/define.v common/mux8.v common/clk_divider.v common/lp20khz_1MSa_iir_filter.v common/min_max_tracker.v common/hi_flite.v common/hi_get_trace.v common/hi_iso14443a.v common/hi_reader.v common/hi_simulate.v common/hi_sniffer.v common/lf_edge_detect.v common/lo_adc.v common/lo_edge_detect.v common/lo_passthru.v common/lo_read.v pm5/gowin_rpll.v pm5/fpga_pm5_hf.v pm5/fpga_pm5_lf.v pm5/fpga_pm5_top.v + +# 2. 检查并强制指定顶层 +hierarchy -top fpga_top + +# 3. 进行综合 +synth_gowin -nolutram -json output.json diff --git a/fpga/proxmark3_fpga.mk b/fpga/proxmark3_fpga.mk new file mode 100644 index 000000000..3c4d03334 --- /dev/null +++ b/fpga/proxmark3_fpga.mk @@ -0,0 +1,255 @@ +# +# Proxmark3 / Proxmark3-ULT / iCopy-X FPGA build (Xilinx ISE toolchain) +# +# Invoked by the fpga/Makefile dispatcher, or directly: +# make -C fpga -f proxmark3_fpga.mk [|all|clean] +# +# The top part of this file is used to define custom options for a number of compilation targets +# To define an additional target simply look at the other defined targets and add a new TARGET entry with a unique number and the custom options required + +include $(dir $(lastword $(MAKEFILE_LIST)))common_src.mk + +XILINX_TOOLS_PREFIX= + +# Each target is built into its own separate directory name formed by combining the PREFIX and TARGET names. +# This way the source is not polluted with build files and the build directories are left behind after compilation so logs and reports can be +# examined or can be easily deleted with "make clean" + +# Options to be passed to XST +XST_OPTS_BASE = run +XST_OPTS_BASE += -ifn xst.prj +XST_OPTS_BASE += -ifmt mixed +XST_OPTS_BASE += -ofmt NGC +XST_OPTS_BASE += -lso xst.lso +XST_OPTS_BASE += -top fpga_top +XST_OPTS_BASE += -resource_sharing yes + +# Optimizations for speed (default) +XST_OPTS_SPEED = -opt_mode Speed +XST_OPTS_SPEED += -opt_level 1 +XST_OPTS_SPEED += -fsm_style lut +XST_OPTS_SPEED += -fsm_encoding auto + +# Optimization for reduced space +XST_OPTS_AREA = -opt_mode area +XST_OPTS_AREA += -opt_level 2 +XST_OPTS_AREA += -fsm_style bram +XST_OPTS_AREA += -fsm_encoding compact + +# par specific option (set determistic seed) +PAR_OPTIONS = -t 2 + +# Types of selective module compilation: +# WITH_LF Enables selection of LF modules (and disables all HF) + +# To enable these modules WITH_LF _MUST_ be defined +# WITH_LF0 enable LF reader (generic) +# WITH_LF1 enable LF edge detect (generic) +# WITH_LF2 enable LF passthrough +# WITH_LF3 enable LF ADC (read/write) + +# To enable these modules WITH_LF _MUST_NOT_ be defined +# WITH_HF0 enable HF reader (see also WITH_HF_15 below) +# WITH_HF_15 select "iso15 2sc mode" extensions instead of original +# (the iso15 low-signal reader detection thresholds are selected at runtime +# in hi_reader.v, based on subcarrier/minor mode -- no separate define) +# WITH_HF1 enable HF simulated tag +# WITH_HF2 enable HF ISO14443-A +# WITH_HF3 enable sniff +# WITH_HF4 enable HF ISO18092 FeliCa +# WITH_HF5 enable HF get trace + +# WITH_TIMODE enable TI mode (for SPI_SSC. PM5 only currently) + +# RDV40/Generic - Enable LF and all the LF modules +TARGET1_OPTIONS = -define \{WITH_LF WITH_LF0 WITH_LF1 WITH_LF2 WITH_LF3\} +# RDV40/Generic - Enable all HF modules except Felica +TARGET2_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF2 WITH_HF3 WITH_HF5\} +# RDV40/Generic - Enable all HF modules except Felica and ISO14443, select HF_15 instead of HF +TARGET3_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF5 WITH_HF_15\} +# RDV40/Generic - Enable all HF modules except ISO14443 +TARGET4_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF4 WITH_HF5 WITH_FELICA_PROBE\} +# ICOPYX +TARGET5_OPTIONS = $(TARGET1_OPTIONS) +# PM3-ULT (xc2s50): same four images as the RDV4 targets +TARGET6_OPTIONS = $(TARGET1_OPTIONS) +TARGET7_OPTIONS = $(TARGET1_OPTIONS) +TARGET8_OPTIONS = $(TARGET1_OPTIONS) +TARGET9_OPTIONS = $(TARGET1_OPTIONS) + +# Here we list the target names +TARGET1_NAME = fpga_pm3_lf +TARGET2_NAME = fpga_pm3_hf +TARGET3_NAME = fpga_pm3_hf_15 +TARGET4_NAME = fpga_pm3_felica +TARGET5_NAME = fpga_icopyx_hf +TARGET6_NAME = fpga_pm3_ult_lf +TARGET7_NAME = fpga_pm3_ult_hf +TARGET8_NAME = fpga_pm3_ult_hf_15 +TARGET9_NAME = fpga_pm3_ult_felica + +# Targets can be compiled for different FPGA flavours +TARGET1_FPGA = xc2s30-5-vq100 +TARGET2_FPGA = $(TARGET1_FPGA) +TARGET3_FPGA = $(TARGET1_FPGA) +TARGET4_FPGA = $(TARGET1_FPGA) +TARGET5_FPGA = xc3s100e-4-vq100 +TARGET6_FPGA = xc2s50-5-tq144 +TARGET7_FPGA = $(TARGET6_FPGA) +TARGET8_FPGA = $(TARGET6_FPGA) +TARGET9_FPGA = $(TARGET6_FPGA) + +# Pin constraints, one file per FPGA flavour +TARGET1_UCF = $(PM3_DIR)/xc2s30-5-vq100.ucf +TARGET2_UCF = $(TARGET1_UCF) +TARGET3_UCF = $(TARGET1_UCF) +TARGET4_UCF = $(TARGET1_UCF) +TARGET5_UCF = $(ICOPYX_DIR)/xc3s100e-4-vq100.ucf +TARGET6_UCF = $(PM3_DIR)/xc2s50-5-tq144.ucf +TARGET7_UCF = $(TARGET6_UCF) +TARGET8_UCF = $(TARGET6_UCF) +TARGET9_UCF = $(TARGET6_UCF) + +# Assemble the final XST options for each target +TARGET1_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET1_FPGA) -ofn $(TARGET1_NAME) $(TARGET1_OPTIONS) +TARGET2_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET2_FPGA) -ofn $(TARGET2_NAME) $(TARGET2_OPTIONS) +TARGET3_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET3_FPGA) -ofn $(TARGET3_NAME) $(TARGET3_OPTIONS) +TARGET4_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_AREA) -p $(TARGET4_FPGA) -ofn $(TARGET4_NAME) $(TARGET4_OPTIONS) +TARGET5_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET5_FPGA) -ofn $(TARGET5_NAME) $(TARGET5_OPTIONS) +TARGET6_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET6_FPGA) -ofn $(TARGET6_NAME) $(TARGET6_OPTIONS) +TARGET7_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET7_FPGA) -ofn $(TARGET7_NAME) $(TARGET7_OPTIONS) +TARGET8_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET8_FPGA) -ofn $(TARGET8_NAME) $(TARGET8_OPTIONS) +TARGET9_XST_OPTS = $(XST_OPTS_BASE) $(XST_OPTS_SPEED) -p $(TARGET9_FPGA) -ofn $(TARGET9_NAME) $(TARGET9_OPTIONS) + +# Add the files that are unique per target and all the common files +TARGET1_FILES = $(COMMON_HDL) $(PM3_DIR)/fpga_pm3_top.v +TARGET2_FILES = $(TARGET1_FILES) +TARGET3_FILES = $(TARGET1_FILES) +TARGET4_FILES = $(TARGET1_FILES) +TARGET5_FILES = $(COMMON_HDL) $(COMMON_DIR)/mux2_onein.v $(COMMON_DIR)/mux2_oneout.v $(ICOPYX_DIR)/fpga_icopyx_hf.v $(ICOPYX_DIR)/fpga_icopyx_lf.v $(ICOPYX_DIR)/fpga_icopyx_top.v +TARGET6_FILES = $(TARGET1_FILES) +TARGET7_FILES = $(TARGET1_FILES) +TARGET8_FILES = $(TARGET1_FILES) +TARGET9_FILES = $(TARGET1_FILES) + +# List of all valid target FPGA images to build +TARGETS = $(TARGET1_NAME) $(TARGET2_NAME) $(TARGET3_NAME) $(TARGET4_NAME) $(TARGET5_NAME) $(TARGET6_NAME) $(TARGET7_NAME) $(TARGET8_NAME) $(TARGET9_NAME) + +# Verbosity type for ISE tools ise|xflow|silent +VERBOSITY = -intstyle silent + +# Pass the custom variables to the lower make rules +$(TARGET1_NAME).bit: TARGET_FPGA = $(TARGET1_FPGA) +$(TARGET1_NAME).bit: TARGET_UCF = $(TARGET1_UCF) +$(TARGET1_NAME).bit: TARGET_FILES = $(TARGET1_FILES) +$(TARGET1_NAME).bit: TARGET_XST_OPTS = $(TARGET1_XST_OPTS) + +$(TARGET2_NAME).bit: TARGET_FPGA = $(TARGET2_FPGA) +$(TARGET2_NAME).bit: TARGET_UCF = $(TARGET2_UCF) +$(TARGET2_NAME).bit: TARGET_FILES = $(TARGET2_FILES) +$(TARGET2_NAME).bit: TARGET_XST_OPTS = $(TARGET2_XST_OPTS) + +$(TARGET3_NAME).bit: TARGET_FPGA = $(TARGET3_FPGA) +$(TARGET3_NAME).bit: TARGET_UCF = $(TARGET3_UCF) +$(TARGET3_NAME).bit: TARGET_FILES = $(TARGET3_FILES) +$(TARGET3_NAME).bit: TARGET_XST_OPTS = $(TARGET3_XST_OPTS) + +$(TARGET4_NAME).bit: TARGET_FPGA = $(TARGET4_FPGA) +$(TARGET4_NAME).bit: TARGET_UCF = $(TARGET4_UCF) +$(TARGET4_NAME).bit: TARGET_FILES = $(TARGET4_FILES) +$(TARGET4_NAME).bit: TARGET_XST_OPTS = $(TARGET4_XST_OPTS) + +$(TARGET5_NAME).bit: TARGET_FPGA = $(TARGET5_FPGA) +$(TARGET5_NAME).bit: TARGET_UCF = $(TARGET5_UCF) +$(TARGET5_NAME).bit: TARGET_FILES = $(TARGET5_FILES) +$(TARGET5_NAME).bit: TARGET_XST_OPTS = $(TARGET5_XST_OPTS) + +$(TARGET6_NAME).bit: TARGET_FPGA = $(TARGET6_FPGA) +$(TARGET6_NAME).bit: TARGET_UCF = $(TARGET6_UCF) +$(TARGET6_NAME).bit: TARGET_FILES = $(TARGET6_FILES) +$(TARGET6_NAME).bit: TARGET_XST_OPTS = $(TARGET6_XST_OPTS) + +$(TARGET7_NAME).bit: TARGET_FPGA = $(TARGET7_FPGA) +$(TARGET7_NAME).bit: TARGET_UCF = $(TARGET7_UCF) +$(TARGET7_NAME).bit: TARGET_FILES = $(TARGET7_FILES) +$(TARGET7_NAME).bit: TARGET_XST_OPTS = $(TARGET7_XST_OPTS) + +$(TARGET8_NAME).bit: TARGET_FPGA = $(TARGET8_FPGA) +$(TARGET8_NAME).bit: TARGET_UCF = $(TARGET8_UCF) +$(TARGET8_NAME).bit: TARGET_FILES = $(TARGET8_FILES) +$(TARGET8_NAME).bit: TARGET_XST_OPTS = $(TARGET8_XST_OPTS) + +$(TARGET9_NAME).bit: TARGET_FPGA = $(TARGET9_FPGA) +$(TARGET9_NAME).bit: TARGET_UCF = $(TARGET9_UCF) +$(TARGET9_NAME).bit: TARGET_FILES = $(TARGET9_FILES) +$(TARGET9_NAME).bit: TARGET_XST_OPTS = $(TARGET9_XST_OPTS) + +$(TARGETS): + $(Q)$(MKDIR) $(PREFIX)build_$@ + $(Q)$(MAKE) -C $(PREFIX)build_$@ -f ../proxmark3_fpga.mk $(notdir $@).bit + +work: + $(Q)$(RM) xst.prj + $(Q)for item in $(TARGET_FILES); do echo verilog work ../$$item>>xst.prj; done + $(Q)echo work> xst.lso + +%.xst: work + $(Q)$(RM) $@ + $(Q)echo $(TARGET_XST_OPTS)> $@ + +%.ngc: %.xst + $(Q)$(RM) $@ + $(info [-] XST $@) + $(Q)$(XILINX_TOOLS_PREFIX)xst $(VERBOSITY) -ifn $< + +%.ngd: %.ngc + $(Q)$(RM) $@ + $(info [-] NGD $@) + $(Q)$(XILINX_TOOLS_PREFIX)ngdbuild $(VERBOSITY) -quiet -p $(TARGET_FPGA) -nt timestamp -uc ../$(TARGET_UCF) $< $@ + +%_map.ncd: %.ngd + $(Q)$(RM) $@ + $(info [-] MAP $@) + $(Q)$(XILINX_TOOLS_PREFIX)map $(VERBOSITY) -p $(TARGET_FPGA) -o $*_map $* + +%.ncd: %_map.ncd + $(Q)$(RM) $@ + $(info [-] PAR $@) + $(Q)$(XILINX_TOOLS_PREFIX)par $(PAR_OPTIONS) $(VERBOSITY) -w $< $@ + +%.bit: %.ncd + $(Q)$(RM) $@ $*.drc $*.rbt + $(info [=] BITGEN $@) + $(Q)$(XILINX_TOOLS_PREFIX)bitgen $(VERBOSITY) -w $* $@ + $(Q)python3 ../$(STRIP_DATE_TIME) $@ || true + $(Q)$(CP) $@ .. + +# Build all targets +all: $(TARGETS) + +# ALWAYS have some hardcoded text after $(PREFIX) to avoid rm -rf * or rm -rf /* situations if PREFIX is incorrectly set to empty "" or just "/" +clean: + $(Q)$(RMDIR) $(PREFIX)build_* + $(info [-] Build files deleted) + +.DEFAULT: + @if [ "$@" != "all" ] && [ ! "$(filter $@,$(TARGETS))" ]; then \ + make help; \ + else \ + make all; \ + fi + +.PHONY: all help clean + +help: + @echo "################################################################" + @echo "#" + @echo "# - Builds only one of the above listed targets" + @echo "# all - Builds the FPGA bitstreams for all targets" + @echo "# clean - Keeps .bit files but cleans intermediate build files for all targets" + @echo "#" + @echo "#" + @echo "# Valid targets are:" + @echo "# $(TARGETS)" + @echo "#" + @echo "################################################################" diff --git a/fpga/proxmark5_fpga.mk b/fpga/proxmark5_fpga.mk new file mode 100644 index 000000000..853a3b265 --- /dev/null +++ b/fpga/proxmark5_fpga.mk @@ -0,0 +1,171 @@ +# +# Proxmark5 FPGA build (open source toolchain: yosys + nextpnr-himbaechel + apycula) +# +# Invoked by the fpga/Makefile dispatcher, or directly: +# make -C fpga -f proxmark5_fpga.mk [all|clean] +# +# Overridable variables: +# OSS_CAD_SUITE oss-cad-suite root, holds yosys/nextpnr-himbaechel/gowin_pack +# PM5_FAMILY Gowin family, selects the chip database in nextpnr +# PM5_PART exact part, must match the board +# PM5_IDCODE IDCODE the bitstream declares, must match the silicon +# PM5_CST pin constraints, generated by the Gowin IDE +# PM5_SDC timing constraints, generated by the Gowin IDE +# PM5_FREQ fallback clock constraint, used when PM5_SDC is empty +# PM5_DEFINES HDL defines, one image covers both the LF and HF halves + +include $(dir $(lastword $(MAKEFILE_LIST)))common_src.mk + +# oss-cad-suite is looked up next to the checkout and under the usual Desktop +# locations. Leave OSS_CAD_SUITE empty to use tools that are already on PATH +# (i.e. when make runs inside the suite's own shell). +OSS_CAD_SUITE ?= $(firstword $(wildcard \ + $(abspath $(CURDIR)/../../oss-cad-suite) \ + $(HOME)/Desktop/oss-cad-suite \ + $(USERPROFILE)/Desktop/oss-cad-suite)) + + +PM5_FAMILY ?= GW1N-4 +PM5_PART ?= GW1N-UV4QN48C6/I5 +PM5_IDCODE ?= 0x1100381B +PM5_CST ?= $(PM5_DIR)/gowin.cst +PM5_SDC ?= $(PM5_DIR)/gowin.sdc +PM5_FREQ ?= 24 +PM5_DEFINES ?= PM5 WITH_TIMODE WITH_HF_15 WITH_FELICA_PROBE + +# The Gowin IDE generated SDC covers every clock, but nextpnr only takes a +# subset of SDC; it is rewritten into the build directory, leaving the original +# file untouched for the next regeneration from the IDE. See the script for +# what it drops. PM5_FREQ is the fallback for the unconstrained case. +PM5_TIMING = $(if $(PM5_SDC),--sdc $(PM5_NAME).sdc,--freq $(PM5_FREQ)) +PM5_SDC_CLEAN = $(OC_ENV) $(OC_PYTHON) $(COMMON_DIR)/gowin_sdc_clean.py + +PM5_NAME = fpga_pm5 +PM5_OUT = $(PREFIX)build_$(PM5_NAME) + +# The oss-cad-suite launchers need their root, and both bin/ and lib/ on PATH +# (lib/ carries the shared libraries), otherwise they exit without a message. +# The suite is laid out per platform, and each of its own environment scripts +# says what to put where. On Windows the shared libraries sit in lib/ and are +# found through PATH, as environment.bat does. Everywhere else they are found by +# the tools themselves, and adding lib/ to PATH or LD_LIBRARY_PATH makes yosys +# load the suite's libstdc++ instead of the system one and segfault at startup. +# With OSS_CAD_SUITE unset the tools come from PATH, which is what sourcing the +# suite's environment gives you. +ifneq ($(OSS_CAD_SUITE),) +ifneq ($(wildcard $(OSS_CAD_SUITE)/lib/python3.exe),) +OC_PATH = $(OSS_CAD_SUITE)/bin:$(OSS_CAD_SUITE)/lib +OC_PYTHON = $(OSS_CAD_SUITE)/lib/python3.exe +else +OC_PATH = $(OSS_CAD_SUITE)/bin:$(OSS_CAD_SUITE)/py3bin +OC_PYTHON = $(firstword $(wildcard $(OSS_CAD_SUITE)/py3bin/python3 $(OSS_CAD_SUITE)/bin/python3) python3) +endif +OC_ENV = YOSYSHQ_ROOT="$(OSS_CAD_SUITE)/" PATH="$(OC_PATH):$$PATH" PYTHON_EXECUTABLE="$(OC_PYTHON)" +else +OC_ENV = +OC_PYTHON = python3 +endif + +PM5_BUILD_HDL = $(addprefix ../,$(PM5_HDL)) +PM5_DEFS = $(foreach d,$(PM5_DEFINES),-D $(d)) + +.PHONY: all clean help check-tools smoke $(PM5_NAME) + +all: $(PM5_NAME) + +$(PM5_NAME): $(PM5_NAME).fs $(PM5_NAME).bin + +# Only the bitstream is copied next to the Xilinx ones, everything else stays +# in the build directory. Unlike the PM3 targets these recipes run in the fpga +# directory itself, so the destination is this directory, not its parent. +$(PM5_NAME).fs: $(PM5_OUT)/$(PM5_NAME).fs + $(Q)$(CP) $< . + +$(PM5_NAME).bin: $(PM5_OUT)/$(PM5_NAME).bin + $(Q)$(CP) $< . + +# The pin constraints are board specific and are not part of the repository. +$(PM5_CST): + $(error [!] missing $@ -- fill in the PM5 pin constraints, then rerun) + +# The makefiles are prerequisites of the generated files so that editing the +# defines or the source lists regenerates them. +$(PM5_OUT)/$(PM5_NAME).json: $(PM5_HDL) $(MAKEFILE_LIST) | check-tools $(PM5_OUT) + $(info [-] YOSYS $@) + $(Q)$(RM) $@ + $(Q)printf '%s\n' \ + "read_verilog $(PM5_DEFS) $(PM5_BUILD_HDL)" \ + "hierarchy -top fpga_top" \ + "synth_gowin -nolutram -json $(PM5_NAME).json" > $(PM5_OUT)/synth.ys + $(Q)cd $(PM5_OUT) && $(OC_ENV) yosys -q -s synth.ys + +$(PM5_OUT)/$(PM5_NAME).sdc: $(PM5_SDC) $(COMMON_DIR)/gowin_sdc_clean.py $(MAKEFILE_LIST) | $(PM5_OUT) + $(Q)$(PM5_SDC_CLEAN) $< $@ + +$(PM5_OUT)/$(PM5_NAME)_pnr.json: $(PM5_OUT)/$(PM5_NAME).json $(PM5_CST) $(if $(PM5_SDC),$(PM5_OUT)/$(PM5_NAME).sdc) + $(info [=] NEXTPNR $@) + $(Q)cd $(PM5_OUT) && $(OC_ENV) nextpnr-himbaechel \ + --device "$(PM5_PART)" \ + --vopt family=$(PM5_FAMILY) \ + --vopt cst=../$(PM5_CST) \ + $(PM5_TIMING) \ + --json $(PM5_NAME).json \ + --write $(PM5_NAME)_pnr.json + +# TODO: workaround, drop once apycula can target GW1N-4D. The GW1N-4 database +# apycula ships stamps the plain GW1N-4 IDCODE (0x0100381B), which the silicon +# on a PM5 (GW1N-4B/4D, 0x1100381B) rejects: done_final stays 0 and nothing +# drives the antennas. gowin_devid.py rewrites that field to PM5_IDCODE here so +# the image is accepted at all. Remove this step, PM5_IDCODE and the workflow's +# IDCODE check once apycula gains a GW1N-4B/4D target or stops discarding the +# C6/I5 device version from the part number. Until then, suspect the fuse map +# first when a bitstream misbehaves: it comes from the non-D database entry. +$(PM5_OUT)/$(PM5_NAME).fs: $(PM5_OUT)/$(PM5_NAME)_pnr.json $(COMMON_DIR)/gowin_devid.py + $(info [=] GOWIN_PACK $@) + $(Q)cd $(PM5_OUT) && $(OC_ENV) gowin_pack -d $(PM5_FAMILY) -o $(PM5_NAME).fs $(PM5_NAME)_pnr.json + $(Q)$(OC_ENV) $(OC_PYTHON) $(COMMON_DIR)/gowin_devid.py $(PM5_OUT)/$(PM5_NAME).fs $(PM5_IDCODE) + +$(PM5_OUT)/$(PM5_NAME).bin: $(PM5_OUT)/$(PM5_NAME).fs + $(info [=] FS2BIN $@) + $(Q)cd $(PM5_OUT) && $(OC_ENV) $(OC_PYTHON) ../$(COMMON_DIR)/fs2bin.py $(PM5_NAME).fs $(PM5_NAME).bin + +$(PM5_OUT): + $(Q)$(MKDIR) $@ + +check-tools: + $(Q)missing=""; \ + for t in yosys nextpnr-himbaechel gowin_pack; do \ + $(OC_ENV) command -v $$t >/dev/null 2>&1 || missing="$$missing $$t"; \ + done; \ + if [ -n "$$missing" ]; then \ + echo "[!] not found:$$missing"; \ + echo "[!] point OSS_CAD_SUITE at oss-cad-suite, or add its bin/ to PATH"; \ + exit 1; \ + fi + +# Compile a two line design through the same toolchain and environment the +# build uses, so a suite that cannot run fails here with a clear message +# instead of half way through the build. +smoke: | check-tools $(PM5_OUT) + $(Q)$(OC_ENV) yosys -V + $(Q)printf '%s\n' "module smoke(input a, output b);" " assign b = ~a;" "endmodule" > $(PM5_OUT)/smoke.v + $(Q)cd $(PM5_OUT) && $(OC_ENV) yosys -q -p "read_verilog smoke.v; synth_gowin -json smoke.json" + $(Q)test -s $(PM5_OUT)/smoke.json && echo "[=] SMOKE ok" + +# ALWAYS have some hardcoded text after $(PREFIX) to avoid rm -rf * or rm -rf /* situations if PREFIX is incorrectly set to empty "" or just "/" +clean: + $(Q)$(RMDIR) $(PREFIX)build_$(PM5_NAME) + $(info [-] Build files deleted) + +help: + @echo "################################################################" + @echo "#" + @echo "# $(PM5_NAME) - Builds the PM5 bitstream into $(PM5_NAME).fs/.bin" + @echo "# all - same as $(PM5_NAME)" + @echo "# clean - Keeps the bitstream but cleans intermediate build files" + @echo "#" + @echo "# Device: $(PM5_PART) (family $(PM5_FAMILY))" + @echo "# Pins : $(PM5_CST)" + @echo "# Toolchain: $(OSS_CAD_SUITE)" + @echo "#" + @echo "################################################################" diff --git a/fpga/tests/Makefile b/fpga/tests/Makefile index f2ae6d729..1dc96731a 100644 --- a/fpga/tests/Makefile +++ b/fpga/tests/Makefile @@ -8,6 +8,10 @@ MKDIR = mkdir -p TEST_OUTDIR = tb_tmp +# iverilog ships with oss-cad-suite; point at it when it is not on PATH, e.g. +# make IVERILOG=$(HOME)/Desktop/oss-cad-suite/bin/iverilog +IVERILOG ?= iverilog + TB_SOURCES = \ tb_lp20khz_1MSa_iir_filter.v \ tb_min_max_tracker.v \ @@ -22,7 +26,7 @@ TB_DATA = \ all: $(TBS) tests %.vvp: %.v - iverilog -I .. -o $@ $< + $(IVERILOG) -I ../common -o $@ $< clean: rm -rf *.vvp $(TEST_OUTDIR)