Files
iceman1001andClaude Opus 5 cb48c928a5 hf felica: make reading actually work
FeliCa reading was broken on every card tested: 'hf felica reader' returned PM3_ETIMEOUT while the tag was answering correctly. The cause was in the FPGA demodulator, not the ARM.

fpga/hi_flite.v
---------------
Adaptive hysteresis thresholds. The envelope tracker clamped curmin to <= 70 and curmax to >= 180, so curminthres/curmaxthres were pinned near 91/160 no
matter where the signal actually sat. Measured on a RDV4 with the field on, the peak detector idles near 112 and a tag swings it by about +/-35, ie entirely
inside that window - so nothing ever crossed a threshold and every frame demodulated as a constant. The band is now derived from the tracked envelope,
3/16 of its span, floored at +/- 8 to stay clear of the 4..6 counts of carrier ripple.

Matched-filter bit detector. The slicer counted comparator trips (+1 above curmaxthres, -1 below curminthres, repeat the last crossing direction inside
the dead band), so every bit depended on where the band happened to sit. A mispositioned band railed the output to a constant and, since only the stable
branch can recompute thresholds or desync, it stayed that way for the rest of the session. It also discarded amplitude, gaining nothing from 32x
oversampling. Each half-bit is now integrated in the ADC domain and the larger half wins. Thresholds still drive bit phase and the desync, they no longer
decide bit values, so a clipped or mispositioned envelope can no longer rail the output.

Polarity lock guard. try_sync arms part way through a half-bit, so the first decision after arming is meaningless and could latch 'zero' inverted, decoding
the whole frame with the wrong polarity and losing the sync word. Skip the first two decisions; the preamble is 48 bits.

curbit re-timing. The bit decision was made in the bit-phase domain, which is aligned to the tag's edges, but sampled by the SSC in the carrier domain. The
ARM could latch a bit mid-transition at a phase that varied per frame. Both run at 64 carrier periods per bit, so re-timing curbit half an SSP bit away from the
sampling edge is a re-time, not a resample.

Envelope watchdog. FPGA registers persist across PM3 commands - only a bitstream reload clears them - so the tracker could enter a state it never left and the
first command after the client started would work while every one after it failed. Force a re-centre when the demodulator has not reached a known-good idle
for 19.3 ms, held off at the start of each frame so it cannot fire mid-reply.

state is marked (* fsm_extract = 'no' *). The project synthesises with -fsm_style bram; once XST recognised this register as a state machine it placed
the state ROM in a block RAM, and the xc2s30's six were already spoken for. MAP then failed to fit with nothing but a generic 'design is too large' error, no
BITGEN, and no new bitstream.

armsrc/felica.c
---------------
- felica_select_card() returning 4 (response too short for IDm+PMm) fell through to PM3_SUCCESS, so 'hf felica reader' reported an all-zero IDm as a good read.
- After a poll timeout the code still read FelicaFrame; with a stale POLLING_RES and len == 0, check_crc() was handed (len - 2) as a size_t, ie 65534.
- WaitForFelicaReply() could only time out from STATE_UNSYNCD/TRYING_SYNC and would spin forever if a frame never completed.
- felica_sniff() decremented and broke before LogTrace, so '-s 10' logged nine frames and '-s 0' logged none. CRC-failed noise no longer pollutes the trace.
- felica_sendraw() sent no reply at all for some flag combinations, leaving the client blocked until its own timeout.
- Polling used time slot 0 only, so several cards in the field collided forever. Retries now widen the TSN window.
- BuildFliteRdblk() warned about a bad block count and built the frame anyway.

Signal probe
------------
'hf felica raw -p' streams the per-window envelope min and max instead of demodulated bits, so reading distance and coupling can be measured rather than
guessed. This is what told 'tag out of range' apart from 'demodulator not locking', which are otherwise identical from the ARM's point of view.

Measured on a RDV4, both cards previously unreadable:
  FeliCa Standard RC-S830 (CJRC 0003): reader 4/4, info 4/4, 39 nodes discovered, dump complete in 2.0 s, 37/40 single polls.
  FeliCa Standard RC-S962 (Octopus 8008): reader 10/10, 23 nodes discovered, dump complete in 1.5 s, 40/60 single polls. This one drives the envelope onto the bottom ADC rail; the matched filter reads it anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 22:35:08 +02:00

260 lines
9.3 KiB
Makefile

#
# FPGA Makefile for all targets
#
# 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
XILINX_TOOLS_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
# 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 = __
# 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
# 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
# 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
help:
@echo "################################################################"
@echo "#"
@echo "# <target> - 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 "################################################################"