├── .gitignore ├── LICENSE ├── README.md ├── img └── intel_technology_cell.png ├── rtl ├── fpga_puf.vhd └── neorv32_cfs.vhd └── sw ├── fpga_puf_neorv32_cfs.c ├── fpga_puf_neorv32_cfs.h ├── main.c ├── makefile └── neorv32_exe.bin /.gitignore: -------------------------------------------------------------------------------- 1 | ~* 2 | *.bin 3 | !*_exe.bin 4 | *.o 5 | *.elf 6 | *.asm 7 | *.out 8 | *.hex 9 | *_image.vhd 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Stephan Nolting 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :closed_lock_with_key: A Physical Unclonable Function for _any_ FPGA 2 | 3 | [![License](https://img.shields.io/github/license/stnolting/fpga_puf)](https://github.com/stnolting/fpga_puf/blob/main/LICENSE) 4 | [![DOI](https://zenodo.org/badge/428800193.svg)](https://zenodo.org/badge/latestdoi/428800193) 5 | 6 | * [Introduction](#Introduction) 7 | * [Theory of Operation](#Theory-of-Operation) 8 | * [Post-Processing](#Post-Processing) 9 | * [Top Entity](#Top-Entity) 10 | * [Evaluation](#Evaluation) 11 | * [Setup](#Setup) 12 | * [ID Results](#ID-Results) 13 | * [Reliability](#Reliability) 14 | * [Hardware Utilization](#Hardware-Utilization) 15 | * [Summary](#Summary) 16 | * [TODO](#TODO) 17 | 18 | 19 | ## Introduction 20 | 21 | A physical unclonable function (PUF) provides a digital _fingerprint_ that can be used as **unique identifier** for 22 | security-related applications like authentication. The `fpga_puf` hardware module provides an unclonable 96-bit unique identifier (ID) 23 | that is defined by the target chip's semiconductor characteristics. It is implemented in a technology-independent 24 | way that does not use any device-specific macros, primitives or attributes so it can be implemented on _any_ FPGA 25 | (verified using Intel Quartus Prime, Lattice Radiant and Xilinx Vivado). 26 | 27 | :key: The PUF ID is _unique_ for the combination of bitstream and _one_ specific FPGA. The same bitstream 28 | will lead to a different PUF ID on a different FPGA of same type. If the bitstream of a specific FPGA is changed 29 | (by changing design logic) the PUF ID of that specific FPGA will also change. 30 | 31 | **Key features** 32 | 33 | * [x] technology-, vendor- and platform-agnostic implementation 34 | * [x] easy to use 35 | * [x] tiny hardware footprint (less than 200 LUTs) 36 | * [ ] secure and reliable (:construction: work-in-progress) 37 | 38 | :loudspeaker: This is an ongoing research / proof-of-concept project. Feedback from the community is highly appreciated 39 | (see notes in section ["TODO"](#TODO))! 40 | 41 | 42 | ## Theory of Operation 43 | 44 | Each bit of the 96-bit PUF ID is generated by an individual **PUF cell**. Each cell consists of an asynchronous element that is 45 | based on a simple ring oscillator. The oscillator is constructed from a single inverter that negates it's own output signal. 46 | This feedback loop is interrupted by a latch. If the latch is open (=transparent) the oscillator starts oscillating. 47 | If the latch is closed, it stores the last state of the oscillator (high or low). The latch also provides a reset 48 | to bring each cell into a defined state. 49 | 50 | The frequency of each oscillator is defined by the mapping of the logic cell and the according routing. Both factors 51 | are constant for a specific bitstream. Furthermore, the frequency is also randomly (but constantly for a given setup) 52 | "tuned" by the chip's semiconductor characteristics. 53 | These are caused by tiny productions fluctuation (for example the capacitance / delay of a routing wire is affected 54 | by variation in oxide thickness). The frequency of each oscillator is considered to be fixed. Hence, sampling several periods 55 | within a fixed time window will always end in the same state (oscillator output is high or low). Temperature drift 56 | disturbs all oscillators in the (nearly) same way and has to be computationally eliminated/compensated by the 57 | [post-processing](#Post-Processing). 58 | 59 | Since `fpga_puf` does not use any device-specific attributes or primitives, there is a risk that the synthesis toolchain will 60 | remove the asynchronous PUF cells or collapse them into a single PUF cell ("optimize away"). Therefore, the design 61 | uses a shift register to control reset and the latch open/close phase of each cell individually and distributed over time. This concept is 62 | based on the [neoTRNG True Random Number Generator](https://github.com/stnolting/neoTRNG) and allows a **platform-independent implementation**. 63 | 64 | Whenever a new sampling of the PUF ID is started, a 96+1 bit wide shift register. A single '1' is applied to the 65 | least significant bit that travels throughout the whole shift register chain during operation. The PUF cell's control signals (reset and 66 | latch control) are connected to this shift register. Each shift register bit `i` controls the reset of PUF cell 67 | `i` and the latch control of PUF cell `i+1`. Thus, a cell's reset is active for one clock cycle. In the next clock 68 | cycle the cell's latch is opened for exactly one clock cycle allowing the oscillator to run. In the next clock cycle 69 | the latch is closed again and has captured the oscillator's last state. By this scheme only one oscillator is active 70 | at a time reducing potential distortion by oscillator cross-talk (i.e. the oscillation of one osc. affect the 71 | oscillations of another osc.). 72 | 73 | When the single one-bit reaches the most significant bit (bit 96) of the shift register the sampling process is 74 | completed and the latch states are sampled into a register that provides the obtained _raw_ PUF ID. 75 | 76 | The following figure is taken from the Intel Quartus Prime technology viewer showing a single PUF cell (cell #44). 77 | The oscillator as well as the latch are mapped to a single LUT3 element in the FPGA. The output of this asynchronous 78 | element is sampled by a simple register. Hence, a single PUF cell only requires one LUT and one FF. 79 | 80 | (`DATAA` is the latch reset (high-active), `DATAC` is the latch control (open when set), `DATAD` is the 81 | feedback of the inverter) 82 | 83 | ![puf_cell](https://raw.githubusercontent.com/stnolting/fpga_puf/main/img/intel_technology_cell.png) 84 | 85 | ### Post-Processing 86 | 87 | Some bits of the _raw_ PUF ID might be quite noisy, which complicates defining a _stable_ ID that does not 88 | change over time and over a broad range of operation conditions (e.g. temperature). A lot(!) of different 89 | approaches can be found in literatures. One promising approach is the use of error-correction codes to "stabilizes" 90 | an initially determined ID. 91 | 92 | However, these concepts are out of (my) scope so I used a simple "averaging" concept for this example. My approach samples 93 | the _raw_ PUF ID several times (for example 4096 times) and checks how often each bit of the ID is set across 94 | all sampled IDs. If an ID bit is set in more then half of the samples it is considered to be a static `1`, otherwise it is considered to 95 | be a static `0`. Since a few bits of the raw ID might be quite noisy, a hysteresis is used to eliminate those bits from the final PUF: 96 | if a bit is set or cleared more often than a certain _threshold_ it is considered "stable" in the final ID. These lower and 97 | upper hysteresis threshold define an _uncertain_ band between them. For the final ID all bits tha fall 98 | into this uncertainty band are masked to be always zero. 99 | 100 | My post-processing concept can be found in [`sw/main.c`](https://github.com/stnolting/fpga_puf/blob/main/sw/main.c). The 101 | source file provides more-detailed comments. 102 | 103 | 104 | ## Top Entity 105 | 106 | The top entity of the PUF IP module is `fpga_puf` ([`rtl/fpga_puf.vhd`](https://github.com/stnolting/fpga_puf/blob/main/rtl/fpga_puf.vhd)). 107 | It can be directly instantiated without the need for any special libraries. 108 | 109 | ```vhdl 110 | entity fpga_puf is 111 | port ( 112 | clk_i : in std_ulogic; -- global clock line 113 | rstn_i : in std_ulogic; -- SYNC reset, low-active 114 | trig_i : in std_ulogic; -- set high for one clock to trigger ID sampling 115 | busy_o : out std_ulogic; -- busy when set (sampling ID) 116 | id_o : out std_ulogic_vector(95 downto 0) -- PUF ID (valid after sampling is done) 117 | ); 118 | end fpga_puf; 119 | ``` 120 | 121 | :warning: Note that the PUF cannot be simulated due to it's combinatorial loops. 122 | 123 | ## Evaluation 124 | 125 | To evaluate this concept and the quality/reliability of the PUF IDs I am using the [NEORV32](https://github.com/stnolting/neorv32) 126 | as processor platform. The `fpga_puf` IP module is added to the processor's "Custom Functions Subsystem (CFS)", 127 | which is a _blank_ template for implementing custom application-specific co-processors. 128 | 129 | The setup is synthesized for different FPGAs using different toolchains (tested with Intel Quartus Prime, Lattice Radiant and 130 | Xilinx Vivado). A specific bitstream generated and programmed into _several_ FPGAs of the _same_ type to check for chip-specific 131 | ID variations. On one chip the ID is generated several times to check if it is "stable over time" and thus, reliable. 132 | 133 | ### Setup 134 | 135 | The application-specific PUF-wrapping CFS can be found in 136 | [`rtl/neorv32_cfs.vhd`](https://github.com/stnolting/fpga_puf/blob/main/rtl/neorv32_cfs.vhd). Make sure to use this 137 | CFS file instead of the default NEORV32 CFS source file when reproducing this setup. 138 | 139 | The CFS uses four memory-mapped registers to interface the PUF ID module: 140 | 141 | | CSF register address (_C_ access macro) | Access | Function | 142 | |:----------------------------------------|:------:|:------------------| 143 | | `NEORV32_CFS.REG[0]` | `r/w` | Control register | 144 | | `NEORV32_CFS.REG[1]` | `r/-` | PUF ID bits 31:0 | 145 | | `NEORV32_CFS.REG[2]` | `r/-` | PUF ID bits 63:32 | 146 | | `NEORV32_CFS.REG[3]` | `r/-` | PUF ID bits 95:64 | 147 | 148 | Bit _0_ of the control register (`PUF_CTRL_EN`) control the synchronous reset signal of the `fpga_puf` module 149 | (`rstn_i`). Setting this bit will activate the module, clearing it will put the module into reset state. 150 | 151 | Bit _1_ of the control register (`PUF_CTRL_SAMPLE`) is used to start the sampling of the ID. Writing one to it 152 | will set the trigger signal (`trig_i`) high for one cycle. Reading this control register bit will return 153 | the busy state of the `fpga_puf` module (`busy_o`). 154 | 155 | :floppy_disk: The low-level hardware-accessing functions are implemented in 156 | [`sw/fpga_puf_neorv32_cfs.c`](https://github.com/stnolting/fpga_puf/blob/main/sw/fpga_puf_neorv32_cfs.c). The header file 157 | [`sw/fpga_puf_neorv32_cfs.h`](https://github.com/stnolting/fpga_puf/blob/main/sw/fpga_puf_neorv32_cfs.h) 158 | provides the function prototypes, a PUF ID data type and also the NEORV32 CFS register mappings and bit definitions. 159 | 160 | The PUF test program ([`sw/main.c`](https://github.com/stnolting/fpga_puf/blob/main/sw/main.c)) is used to sample 161 | the chip's PUF ID and transmit it via UART to a terminal program: 162 | 163 | ``` 164 | Physical Unclonable Functions Test 165 | PUF implemented as NEORV32 Custom Functions Subsystem (CFS) 166 | 167 | Press any key to start PUF test (8 runs with 4096 samples each). 168 | Starting test... 169 | Run 0 ID: 0x37c0480063021011988c0095 170 | Run 1 ID: 0x37c0480063021011988c0095 171 | Run 2 ID: 0x37c0480063021011988c0095 172 | Run 3 ID: 0x37c0480063021011988c0095 173 | Run 4 ID: 0x37c0480063021011988c0095 174 | Run 5 ID: 0x37c0480063021011988c0095 175 | Run 6 ID: 0x37c0480063021011988c0095 176 | Run 7 ID: 0x37c0480063021011988c0095 177 | Test completed. 178 | ``` 179 | 180 | :floppy_disk: A pre-compiled NEORV32 executable of this test program is also available in this 181 | repository: [`sw/neorv32_exe.bin`](https://github.com/stnolting/fpga_puf/blob/main/sw/neorv32_exe.bin) 182 | (compiled for a minimal `rv32i` NEORV32 CPU) 183 | 184 | ### ID Results 185 | 186 | So far I have tested the PUF module on three Lattice iCE40 UltraPlus FPGAs and four Intel Cyclone IV FPGAs 187 | (shout-out to [@emb4fun](https://github.com/emb4fun) - thank you for your help!) and just one Xilinx Artix-7 188 | FPGA. The ID was generated 8 times on each chip to "check" if it is reproducible (drawback: just in a very 189 | short time window...). The specific FPGA types are shown in section [Hardware Utilization](#Hardware-Utilization). 190 | 191 | | FPGA | PUF ID ("Fingerprint Key") | 192 | |:--------------------------------|:-----------------------------| 193 | | Lattice iCE40 UltraPlus - **1** | `0x37c0480063021011988c0095` | 194 | | Lattice iCE40 UltraPlus - **2** | `0x592063e50118040c2000112b` | 195 | | Lattice iCE40 UltraPlus - **3** | `0x941c82505112323600b0c221` | 196 | | Intel Cyclone IV - **1** | `0x9fbe33dc9021be3156cae31b` | 197 | | Intel Cyclone IV - **2** | `0xa726db495a2a8346b30f2000` | 198 | | Intel Cyclone IV - **3** | `0x413fe25557311be7f9f5edfb` | 199 | | Intel Cyclone IV - **4** | `0xca8ee8dc7945fa60f770e1fa` | 200 | | Xilinx Artix-7 - **1** | `0x6faaf93af77cf77fef91fe79` | 201 | 202 | So far, the IDs are _unique_ for each tested FPGA! 203 | 204 | ### Reliability 205 | 206 | **:construction: work in progress :construction:** 207 | 208 | A long-time test is used to sample and check _raw_ IDs (not the pre-processed ones) for _stability over time_. 209 | Note that my setup is placed in pretty stable environment conditions. 210 | 211 | The test generates an _initital_ ID `I` right at the beginning of execution. In an endless loop two consecutive 212 | IDs `A` and `B` are sampled (a single "Run"). To evaluate the the "instability" the Hamming distance (number of bits that are 213 | not identical across two samples) is computed. This test computes the _relative_ Hamming distance of the two 214 | consecutive samples `A` and `B` (= `H(A,B)`) and also the relative Hamming distance between the initial sample 215 | and sample `A` (= `H(I,A)`) from the current run. Furthermore, the maximum ob both distances is computed over 216 | all runs (`H_max(A,B)` and `H_max(I,A)`). 217 | 218 | To identify _noisy bits_ an "accumulated bit-change-mask" `F` is computed. This mask is computed for every run 219 | by XOR-ing the obtained samples `A` and `B` with the initial ID `I`. The mask from a run is OR-ed with the mask from 220 | the previous run to accumulate the noisy bits over time. 221 | 222 | Cut-out of the test log: 223 | ``` 224 | ... 225 | Run 43373: I=0x9fae9dd83029bc7156cbe37b, A=0xbfbe3bfc9423beb156cae31b, B=0xbfbe3bfcb021beb156cae31b - F=0x6038be24ac1e83c080214860 (33) - H(A,B)=3, H_max(A,B)=9 - H(I,A)=19, H_max(I,A)=23 226 | Run 43374: I=0x9fae9dd83029bc7156cbe37b, A=0xbfbe3bfc9435beb156eae31b, B=0xbfbe3bfcb431beb156eae31b - F=0x6038be24ac1e83c080214860 (33) - H(A,B)=2, H_max(A,B)=9 - H(I,A)=21, H_max(I,A)=23 227 | Run 43375: I=0x9fae9dd83029bc7156cbe37b, A=0xbfbe3bfc9425beb156caeb1b, B=0xbfbe3bdcb421beb156cae31b - F=0x6038be24ac1e83c080214860 (33) - H(A,B)=4, H_max(A,B)=9 - H(I,A)=20, H_max(I,A)=23 228 | Run 43376: I=0x9fae9dd83029bc7156cbe37b, A=0xbfbe3bfc9425beb156eaeb1b, B=0xbfbe3bfcb021beb156eae31b - F=0x6038be24ac1e83c080214860 (33) - H(A,B)=4, H_max(A,B)=9 - H(I,A)=21, H_max(I,A)=23 229 | Run 43377: I=0x9fae9dd83029bc7156cbe37b, A=0xbfbe3bfc9021beb156caeb1b, B=0xbfbe3bdc9425beb156cae31b - F=0x6038be24ac1e83c080214860 (33) - H(A,B)=4, H_max(A,B)=9 - H(I,A)=18, H_max(I,A)=23 230 | ... 231 | ``` 232 | 233 | This very first test was run for approx. half an hours making ~20 runs per second. 234 | It shows a maximal Hamming distance of 23 bits while 33 bits (`F`) tend to be noisy (compared to the initial ID `I` 235 | sampled once right at the beginning of the test). The number of noisy bits increases slowly over time, probably 236 | because of increasing chip temperature. It tends to increase slower over time, so there might be a saturation at some point. 237 | The temperature of the PUF cells is most important because that impacts the oscillator frequencies. 238 | The PUF cells heat up due to the _continuous_ PUF operation. 239 | 240 | The PUF from this test setup provides 96-33=**63** bits that seem to be stable over the observed time. 241 | This also means that the usable key space is reduced (63-bit instead of 96-bit). 242 | 243 | ### Hardware Utilization 244 | 245 | Mapping results for the custom function subsystem (CFS), the `fpga_puf` module and a single PUF cell. 246 | 247 | | Lattice ice40 UltraPlus `iCE40UP5K-SG48I` @24MHz | Logic Cells | Logic Registers | 248 | |:-----------------------------------------------------------|------------:|----------------:| 249 | | neorv32_cfs_inst_true.neorv32_cfs_inst | 171 (71) | 232 (35) | 250 | | -fpga_puf_inst | 100 (4) | 197 (101) | 251 | | --fpga_puf_cell_inst[0].fpga_puf_cell_inst_i | 1 (1) | 1 (1) | 252 | 253 | | Intel Cyclone IV `EP4CE22F17C6N` @100MHz | Logic Cells | Logic Registers | 254 | |:-----------------------------------------------------------|------------:|----------------:| 255 | | neorv32_cfs:\neorv32_cfs_inst_true:neorv32_cfs_inst | 241 (43) | 232 (35) | 256 | | -fpga_puf:fpga_puf_inst | 198 (102) | 197 (101) | 257 | | --fpga_puf_cell:\fpga_puf_cell_inst:0:fpga_puf_cell_inst_i | 1 (1) | 1 (1) | 258 | 259 | | Xilinx Artix-7 `XC7A35TICSG324-1L` @100MHz | Logic Cells | Logic Registers | 260 | |:-------------------------------------------------------------|------------:|----------------:| 261 | | neorv32_cfs_inst_true.neorv32_cfs_inst (neorv32_cfs) | 133 | 328 | 262 | | -fpga_puf_inst (fpga_puf) | 133 | 293 | 263 | | --fpga_puf_cell_inst[0].fpga_puf_cell_inst_i (fpga_puf_cell) | 1 | 2 (Latch + FF) | 264 | 265 | :information_source: Xilinx Vivado can detect the PUF cell's latch and infers a FF primitive, which also provides 266 | a latch mode. This does not compromise the functionality of the PUF. 267 | 268 | ## Summary 269 | 270 | The PUF IDs are unique for each FPGA and can be successfully implemented on different FPGAs (Xilinx, Intel, Lattice). 271 | 272 | The **raw PUF ID** is only partly reproducible, because some bits tend to be quite noisy (see 273 | results above). A better post-processing algorithm using error-correction codes should be able to compensate for that. 274 | This is **work in progress**. 275 | 276 | The latest test showed that there is a certain number of bits that are quite noisy so they cannot be used to determine 277 | the PUF IF. Obviously, this reduces the maximum key space (for example only 63-bit of the 96-bit ID are usable). To circumvent this, 278 | the PUF can be made arbitrarily wide (for example providing a 256-bit raw ID). Of course this will also introduce additional 279 | unusable noisy bits, but we expect that the percentage of noisy bits within the PUF ID is a device-specific constant. 280 | Each additional PUF ID bit adds 2 FFs (one for the SREG and one for the PUFF cell) and 1 LUT. For each additional bit the 281 | ID sampling time is increased by one clock cycle. 282 | 283 | :loudspeaker: If you have any kind of ideas or feedback (for example how to improve reliability) feel free to open a new 284 | [issue](https://github.com/stnolting/fpga_puf/issues). I am also happy to get more data, so if you 285 | have ported the design on another FPGA you can open a [pull request](https://github.com/stnolting/fpga_puf/pulls) 286 | and add your results. 287 | 288 | ## TODO 289 | 290 | * test more FPGAs 291 | * check stability in a controlled environment (e.g. temperature chamber) 292 | * evaluate more sophisticated post-processing algorithms 293 | * sample more data from more long-time test 294 | * faulty bits over time (and temperature) 295 | * hamming distance over time (and temperature) 296 | * ... 297 | * to be continued... 298 | -------------------------------------------------------------------------------- /img/intel_technology_cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stnolting/fpga_puf/d1d50c0e10c30fcd7e2338910fdfa8686927b6e9/img/intel_technology_cell.png -------------------------------------------------------------------------------- /rtl/fpga_puf.vhd: -------------------------------------------------------------------------------- 1 | -- ################################################################################################# 2 | -- # << Technology-Agnostic Physical Unclonable Function (PUF) >> # 3 | -- # ********************************************************************************************* # 4 | -- # The PUF is based on single-bit ring oscillators, which are individually reset and enabled to # 5 | -- # ensure the synthesis tool does not remove ("optimize-away") the cells (this concept is taken # 6 | -- # from the NEORV32 TRNG -> https://github.com/stnolting/neoTRNG). # 7 | -- # # 8 | -- # The oscillators are enabled and for one clock cycle. After that, their current state is # 9 | -- # frozen and sampled to get another bit of the final PUF ID. The final state of an oscillator # 10 | -- # is defined by chip-specific manufacturing fluctuations (wire capacitance due to oxide # 11 | -- # thickness variations and stuff like that). For a large amount of samples (over time) this # 12 | -- # state seems to be reproducible (see exemplary results in fpga_puf documentation). # 13 | -- # # 14 | -- # NOTE: Some bits in the PUF ID might be very noisy. Hence, an appropriate post-processing is # 15 | -- # is required to determine an ID that is stable over time and operation conditions. # 16 | -- # ********************************************************************************************* # 17 | -- # BSD 3-Clause License # 18 | -- # # 19 | -- # Copyright (c) 2022, Stephan Nolting. All rights reserved. # 20 | -- # # 21 | -- # Redistribution and use in source and binary forms, with or without modification, are # 22 | -- # permitted provided that the following conditions are met: # 23 | -- # # 24 | -- # 1. Redistributions of source code must retain the above copyright notice, this list of # 25 | -- # conditions and the following disclaimer. # 26 | -- # # 27 | -- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 28 | -- # conditions and the following disclaimer in the documentation and/or other materials # 29 | -- # provided with the distribution. # 30 | -- # # 31 | -- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 32 | -- # endorse or promote products derived from this software without specific prior written # 33 | -- # permission. # 34 | -- # # 35 | -- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 36 | -- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 37 | -- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 38 | -- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 39 | -- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 40 | -- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 41 | -- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 42 | -- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 43 | -- # OF THE POSSIBILITY OF SUCH DAMAGE. # 44 | -- # ********************************************************************************************* # 45 | -- # fpga_puf - https://github.com/stnolting/fpga_puf (c) Stephan Nolting # 46 | -- ################################################################################################# 47 | 48 | library ieee; 49 | use ieee.std_logic_1164.all; 50 | 51 | entity fpga_puf is 52 | generic ( 53 | ID_SIZE : natural := 96 -- size of PUF ID in bits 54 | ); 55 | port ( 56 | clk_i : in std_ulogic; -- global clock line 57 | rstn_i : in std_ulogic; -- SYNC reset, low-active 58 | trig_i : in std_ulogic; -- set high for one clock to trigger ID sampling 59 | busy_o : out std_ulogic; -- busy when set (sampling ID) 60 | id_o : out std_ulogic_vector(ID_SIZE-1 downto 0) -- PUF ID (valid after sampling is done) 61 | ); 62 | end fpga_puf; 63 | 64 | architecture fpga_puf_rtl of fpga_puf is 65 | 66 | -- arbiter -- 67 | type state_t is (S_IDLE, S_RUN, S_SAMPLE); 68 | type arbiter_t is record 69 | state : state_t; 70 | sreg : std_ulogic_vector(ID_SIZE downto 0); 71 | sample : std_ulogic; 72 | end record; 73 | signal arbiter : arbiter_t; 74 | 75 | -- component: 1-bit PUF sell -- 76 | component fpga_puf_cell 77 | port ( 78 | clk_i : in std_ulogic; 79 | reset_i : in std_ulogic; 80 | latch_i : in std_ulogic; 81 | sample_i : in std_ulogic; 82 | data_o : out std_ulogic 83 | ); 84 | end component; 85 | 86 | begin 87 | 88 | -- Control Arbiter ------------------------------------------------------------------------ 89 | -- ------------------------------------------------------------------------------------------- 90 | sampling_arbiter: process(clk_i) 91 | begin 92 | if rising_edge(clk_i) then 93 | -- latch control SREG: control reset and transparent mode -- 94 | arbiter.sreg(arbiter.sreg'left downto 1) <= arbiter.sreg(arbiter.sreg'left-1 downto 0); 95 | arbiter.sreg(0) <= '0'; -- default 96 | 97 | -- sampling FSM -- 98 | if (rstn_i = '0') then -- reset 99 | arbiter.state <= S_IDLE; 100 | else 101 | case arbiter.state is 102 | 103 | when S_IDLE => -- wait for trigger 104 | if (trig_i = '1') then 105 | arbiter.sreg(0) <= '1'; 106 | arbiter.state <= S_RUN; 107 | end if; 108 | 109 | when S_RUN => -- reset & open latches for one cycle - one by one 110 | if (arbiter.sreg(arbiter.sreg'left) = '1') then 111 | arbiter.state <= S_SAMPLE; 112 | end if; 113 | 114 | when S_SAMPLE => -- sample latch states 115 | arbiter.state <= S_IDLE; 116 | 117 | when others => -- undefined 118 | arbiter.state <= S_IDLE; 119 | 120 | end case; 121 | end if; 122 | end if; 123 | end process sampling_arbiter; 124 | 125 | -- sample PUF data -- 126 | arbiter.sample <= '1' when (arbiter.state = S_SAMPLE) else '0'; 127 | 128 | -- busy flag -- 129 | busy_o <= '0' when (arbiter.state = S_IDLE) else '1'; 130 | 131 | 132 | -- PUF Cells ------------------------------------------------------------------------------ 133 | -- ------------------------------------------------------------------------------------------- 134 | fpga_puf_cell_inst: 135 | for i in 0 to ID_SIZE-1 generate 136 | fpga_puf_cell_inst_i: fpga_puf_cell 137 | port map ( 138 | clk_i => clk_i, 139 | reset_i => arbiter.sreg(i), -- reset once cycle before opening latch 140 | latch_i => arbiter.sreg(i+1), -- open latch for one cycle 141 | sample_i => arbiter.sample, 142 | data_o => id_o(i) 143 | ); 144 | end generate; 145 | 146 | 147 | end fpga_puf_rtl; 148 | 149 | 150 | -- ############################################################################################################################ 151 | -- ############################################################################################################################ 152 | 153 | 154 | -- ################################################################################################# 155 | -- # << Technology-Agnostic Physical Unclonable Function (PUF) - 1-bit PUF Cell >> # 156 | -- # ********************************************************************************************* # 157 | -- # Based on a simple 1-bit oscillator decoupled by a latch (also providing reset). # 158 | -- # ********************************************************************************************* # 159 | -- # BSD 3-Clause License # 160 | -- # # 161 | -- # Copyright (c) 2022, Stephan Nolting. All rights reserved. # 162 | -- # # 163 | -- # Redistribution and use in source and binary forms, with or without modification, are # 164 | -- # permitted provided that the following conditions are met: # 165 | -- # # 166 | -- # 1. Redistributions of source code must retain the above copyright notice, this list of # 167 | -- # conditions and the following disclaimer. # 168 | -- # # 169 | -- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 170 | -- # conditions and the following disclaimer in the documentation and/or other materials # 171 | -- # provided with the distribution. # 172 | -- # # 173 | -- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 174 | -- # endorse or promote products derived from this software without specific prior written # 175 | -- # permission. # 176 | -- # # 177 | -- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 178 | -- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 179 | -- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 180 | -- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 181 | -- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 182 | -- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 183 | -- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 184 | -- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 185 | -- # OF THE POSSIBILITY OF SUCH DAMAGE. # 186 | -- # ********************************************************************************************* # 187 | -- # fpga_puf - https://github.com/stnolting/fpga_puf (c) Stephan Nolting # 188 | -- ################################################################################################# 189 | 190 | library ieee; 191 | use ieee.std_logic_1164.all; 192 | 193 | entity fpga_puf_cell is 194 | port ( 195 | clk_i : in std_ulogic; 196 | reset_i : in std_ulogic; 197 | latch_i : in std_ulogic; 198 | sample_i : in std_ulogic; 199 | data_o : out std_ulogic 200 | ); 201 | end fpga_puf_cell; 202 | 203 | architecture fpga_puf_cell_rtl of fpga_puf_cell is 204 | 205 | signal osc : std_ulogic; 206 | 207 | begin 208 | 209 | -- Oscillator Cell ------------------------------------------------------------------------ 210 | -- ------------------------------------------------------------------------------------------- 211 | oscillator: process(osc, reset_i, latch_i) 212 | begin 213 | if (reset_i = '1') then -- reset oscillator to defined state 214 | osc <= '0'; 215 | elsif (latch_i = '1') then -- enable ring-oscillator; keep current state when disabled 216 | osc <= not osc; 217 | end if; 218 | end process oscillator; 219 | 220 | 221 | -- Output Capture Register ---------------------------------------------------------------- 222 | -- ------------------------------------------------------------------------------------------- 223 | cap_reg: process(clk_i) 224 | begin 225 | if rising_edge(clk_i) then 226 | if (sample_i = '1') then 227 | data_o <= osc; 228 | end if; 229 | end if; 230 | end process cap_reg; 231 | 232 | 233 | end fpga_puf_cell_rtl; 234 | -------------------------------------------------------------------------------- /rtl/neorv32_cfs.vhd: -------------------------------------------------------------------------------- 1 | -- ################################################################################################# 2 | -- # << NEORV32 - Custom Functions Subsystem (CFS) >> # 3 | -- # ********************************************************************************************* # 4 | -- # This CFS implements the "Physical Unclonable Function (PUF)" . Make sure to replace # 5 | -- # the default CFS file (neorv32_rtl/core/neorv32_cfs.vhd) by this one. # 6 | -- # # 7 | -- # Address map: # 8 | -- # * NEORV32_CFS.REG[0] (r/w) (@cfs_reg0_addr_c): Control register # 9 | -- # * bit 0: Enable (r/w), set high to enable unit, set low to reset unit # 10 | -- # * bit 1: Trigger (r/w), set one to trigger ID sampling, clears when sampling is done # 11 | -- # * NEORV32_CFS.REG[1] (r/-) (@cfs_reg1_addr_c): PUF ID bits 31..0 # 12 | -- # * NEORV32_CFS.REG[2] (r/-) (@cfs_reg2_addr_c): PUF ID bits 63..32 # 13 | -- # * NEORV32_CFS.REG[3] (r/-) (@cfs_reg3_addr_c): PUF ID bits 95..64 # 14 | -- # ********************************************************************************************* # 15 | -- # BSD 3-Clause License # 16 | -- # # 17 | -- # Copyright (c) 2021, Stephan Nolting. All rights reserved. # 18 | -- # # 19 | -- # Redistribution and use in source and binary forms, with or without modification, are # 20 | -- # permitted provided that the following conditions are met: # 21 | -- # # 22 | -- # 1. Redistributions of source code must retain the above copyright notice, this list of # 23 | -- # conditions and the following disclaimer. # 24 | -- # # 25 | -- # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 26 | -- # conditions and the following disclaimer in the documentation and/or other materials # 27 | -- # provided with the distribution. # 28 | -- # # 29 | -- # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 30 | -- # endorse or promote products derived from this software without specific prior written # 31 | -- # permission. # 32 | -- # # 33 | -- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 34 | -- # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 35 | -- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 36 | -- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 37 | -- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 38 | -- # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 39 | -- # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 40 | -- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 41 | -- # OF THE POSSIBILITY OF SUCH DAMAGE. # 42 | -- # ********************************************************************************************* # 43 | -- # The NEORV32 Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting # 44 | -- ################################################################################################# 45 | 46 | library ieee; 47 | use ieee.std_logic_1164.all; 48 | use ieee.numeric_std.all; 49 | 50 | library neorv32; 51 | use neorv32.neorv32_package.all; 52 | 53 | entity neorv32_cfs is 54 | generic ( 55 | CFS_CONFIG : std_ulogic_vector(31 downto 0); -- custom CFS configuration generic 56 | CFS_IN_SIZE : positive; -- size of CFS input conduit in bits 57 | CFS_OUT_SIZE : positive -- size of CFS output conduit in bits 58 | ); 59 | port ( 60 | -- host access -- 61 | clk_i : in std_ulogic; -- global clock line 62 | rstn_i : in std_ulogic; -- global reset line, low-active, use as async 63 | addr_i : in std_ulogic_vector(31 downto 0); -- address 64 | rden_i : in std_ulogic; -- read enable 65 | wren_i : in std_ulogic; -- word write enable 66 | data_i : in std_ulogic_vector(31 downto 0); -- data in 67 | data_o : out std_ulogic_vector(31 downto 0); -- data out 68 | ack_o : out std_ulogic; -- transfer acknowledge 69 | err_o : out std_ulogic; -- transfer error 70 | -- clock generator -- 71 | clkgen_en_o : out std_ulogic; -- enable clock generator 72 | clkgen_i : in std_ulogic_vector(07 downto 0); -- "clock" inputs 73 | -- interrupt -- 74 | irq_o : out std_ulogic; -- interrupt request 75 | -- custom io (conduits) -- 76 | cfs_in_i : in std_ulogic_vector(CFS_IN_SIZE-1 downto 0); -- custom inputs 77 | cfs_out_o : out std_ulogic_vector(CFS_OUT_SIZE-1 downto 0) -- custom outputs 78 | ); 79 | end neorv32_cfs; 80 | 81 | architecture neorv32_cfs_rtl of neorv32_cfs is 82 | 83 | -- IO space: module base address (DO NOT MODIFY!) -- 84 | constant hi_abb_c : natural := index_size_f(io_size_c)-1; -- high address boundary bit 85 | constant lo_abb_c : natural := index_size_f(cfs_size_c); -- low address boundary bit 86 | 87 | -- access control -- 88 | signal acc_en : std_ulogic; -- module access enable 89 | signal addr : std_ulogic_vector(31 downto 0); -- access address 90 | signal wren : std_ulogic; -- word write enable 91 | signal rden : std_ulogic; -- read enable 92 | 93 | -- control register bits -- 94 | constant ctrl_en_c : natural := 0; -- r/w: unit enable (reset when disabled) 95 | constant ctrl_sample_c : natural := 1; -- r/w: set to trigger ID sampling, clears when sampling is done 96 | 97 | -- accessible registers -- 98 | signal enable : std_ulogic; 99 | signal trigger : std_ulogic; 100 | signal busy : std_ulogic; 101 | signal puf_id : std_ulogic_vector(95 downto 0); 102 | 103 | -- component: FPGA PUF IP -- 104 | component fpga_puf 105 | port ( 106 | clk_i : in std_ulogic; -- global clock line 107 | rstn_i : in std_ulogic; -- SYNC reset, low-active 108 | trig_i : in std_ulogic; -- set high for one clock to trigger ID sampling 109 | busy_o : out std_ulogic; -- busy when set (sampling ID) 110 | id_o : out std_ulogic_vector(95 downto 0) -- PUF ID (valid after sampling is done) 111 | ); 112 | end component; 113 | 114 | begin 115 | 116 | -- Access Control ------------------------------------------------------------------------- 117 | -- ------------------------------------------------------------------------------------------- 118 | acc_en <= '1' when (addr_i(hi_abb_c downto lo_abb_c) = cfs_base_c(hi_abb_c downto lo_abb_c)) else '0'; 119 | addr <= cfs_base_c(31 downto lo_abb_c) & addr_i(lo_abb_c-1 downto 2) & "00"; 120 | wren <= acc_en and wren_i; 121 | rden <= acc_en and rden_i; 122 | 123 | -- unused -- 124 | err_o <= '0'; 125 | cfs_out_o <= (others => '0'); 126 | clkgen_en_o <= '0'; 127 | irq_o <= '0'; 128 | 129 | 130 | -- Read/Write Access ---------------------------------------------------------------------- 131 | -- ------------------------------------------------------------------------------------------- 132 | host_access: process(clk_i) 133 | begin 134 | if rising_edge(clk_i) then 135 | -- bus acknowledge -- 136 | ack_o <= rden or wren; 137 | 138 | -- write access -- 139 | trigger <= '0'; 140 | if (wren = '1') then 141 | if (addr = cfs_reg0_addr_c) then -- control register 142 | enable <= data_i(ctrl_en_c); 143 | trigger <= data_i(ctrl_sample_c); 144 | end if; 145 | end if; 146 | 147 | -- read access -- 148 | data_o <= (others => '0'); 149 | if (rden = '1') then 150 | case addr(3 downto 2) is 151 | when "00" => data_o(ctrl_en_c) <= enable; data_o(ctrl_sample_c) <= busy or trigger; -- busy flag 152 | when "01" => data_o <= puf_id(31 downto 00); 153 | when "10" => data_o <= puf_id(63 downto 32); 154 | when "11" => data_o <= puf_id(95 downto 64); 155 | when others => NULL; 156 | end case; 157 | end if; 158 | end if; 159 | end process host_access; 160 | 161 | 162 | -- PUF IP Block --------------------------------------------------------------------------- 163 | -- ------------------------------------------------------------------------------------------- 164 | fpga_puf_inst: fpga_puf 165 | port map ( 166 | clk_i => clk_i, 167 | rstn_i => enable, 168 | trig_i => trigger, 169 | busy_o => busy, 170 | id_o => puf_id 171 | ); 172 | 173 | 174 | end neorv32_cfs_rtl; 175 | -------------------------------------------------------------------------------- /sw/fpga_puf_neorv32_cfs.c: -------------------------------------------------------------------------------- 1 | // ################################################################################################# 2 | // # << fpga_puf_neorv32_cfs.c - Physical Unclonable Function (PUF) CFS HW Driver >> # 3 | // # ********************************************************************************************* # 4 | // # BSD 3-Clause License # 5 | // # # 6 | // # Copyright (c) 2021, Stephan Nolting. All rights reserved. # 7 | // # # 8 | // # Redistribution and use in source and binary forms, with or without modification, are # 9 | // # permitted provided that the following conditions are met: # 10 | // # # 11 | // # 1. Redistributions of source code must retain the above copyright notice, this list of # 12 | // # conditions and the following disclaimer. # 13 | // # # 14 | // # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 15 | // # conditions and the following disclaimer in the documentation and/or other materials # 16 | // # provided with the distribution. # 17 | // # # 18 | // # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 19 | // # endorse or promote products derived from this software without specific prior written # 20 | // # permission. # 21 | // # # 22 | // # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 23 | // # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 24 | // # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 25 | // # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 26 | // # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 27 | // # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 28 | // # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 29 | // # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 30 | // # OF THE POSSIBILITY OF SUCH DAMAGE. # 31 | // # ********************************************************************************************* # 32 | // # fpga_puf - https://github.com/stnolting/fpga_puf (c) Stephan Nolting # 33 | // ################################################################################################# 34 | 35 | #include 36 | #include "fpga_puf_neorv32_cfs.h" 37 | 38 | 39 | /**********************************************************************//** 40 | * Check if PUF CFS unit was synthesized. 41 | * 42 | * @return 0 if PUF CFS was not synthesized, 1 if PUF CFS is available. 43 | **************************************************************************/ 44 | int fpga_puf_available(void) { 45 | 46 | if (NEORV32_SYSINFO.SOC & (1 << SYSINFO_SOC_IO_CFS)) { 47 | return 1; 48 | } 49 | else { 50 | return 0; 51 | } 52 | } 53 | 54 | 55 | /**********************************************************************//** 56 | * Get 96-bit PUF ID raw (!) data. 57 | * 58 | * @warning This function returns the RAW PUF ID. Since some bits might be very noisy 59 | * an additional post-processing is required to ensure an ID that is reliable 60 | * (static over time and operating conditions). 61 | * 62 | * @param[in,out] puf_data 3x 32-bit array for the 96-bit raw ID (#puf_data_t); 63 | **************************************************************************/ 64 | void fpga_puf_get_raw(puf_data_t *puf_data) { 65 | 66 | FPGA_PUF_CTRL = 0; // reset 67 | 68 | // enable PUF and trigger sampling 69 | FPGA_PUF_CTRL = (1<id[0] = FPGA_PUF_ID0; 78 | puf_data->id[1] = FPGA_PUF_ID1; 79 | puf_data->id[2] = FPGA_PUF_ID2; 80 | } 81 | -------------------------------------------------------------------------------- /sw/fpga_puf_neorv32_cfs.h: -------------------------------------------------------------------------------- 1 | // ################################################################################################# 2 | // # << fpga_puf_neorv32_cfs.h - Physical Unclonable Function (PUF) CFS HW Driver >> # 3 | // # ********************************************************************************************* # 4 | // # BSD 3-Clause License # 5 | // # # 6 | // # Copyright (c) 2021, Stephan Nolting. All rights reserved. # 7 | // # # 8 | // # Redistribution and use in source and binary forms, with or without modification, are # 9 | // # permitted provided that the following conditions are met: # 10 | // # # 11 | // # 1. Redistributions of source code must retain the above copyright notice, this list of # 12 | // # conditions and the following disclaimer. # 13 | // # # 14 | // # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 15 | // # conditions and the following disclaimer in the documentation and/or other materials # 16 | // # provided with the distribution. # 17 | // # # 18 | // # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 19 | // # endorse or promote products derived from this software without specific prior written # 20 | // # permission. # 21 | // # # 22 | // # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 23 | // # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 24 | // # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 25 | // # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 26 | // # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 27 | // # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 28 | // # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 29 | // # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 30 | // # OF THE POSSIBILITY OF SUCH DAMAGE. # 31 | // # ********************************************************************************************* # 32 | // # fpga_puf - https://github.com/stnolting/fpga_puf (c) Stephan Nolting # 33 | // ################################################################################################# 34 | 35 | #ifndef fpga_puf_neorv32_cfs_h 36 | #define fpga_puf_neorv32_cfs_h 37 | 38 | // CFS registers 39 | #define FPGA_PUF_CTRL NEORV32_CFS.REG[0] // control register 40 | #define FPGA_PUF_ID0 NEORV32_CFS.REG[1] // puf id bits 31:0 41 | #define FPGA_PUF_ID1 NEORV32_CFS.REG[2] // puf id bits 63:32 42 | #define FPGA_PUF_ID2 NEORV32_CFS.REG[3] // puf id bits 95:64 43 | 44 | // control register bits 45 | #define PUF_CTRL_EN 0 // module enable/reset 46 | #define PUF_CTRL_SAMPLE 1 // trigger sampling / busy flag 47 | 48 | // PUF ID type 49 | typedef struct puf_data_t { uint32_t id[3]; } puf_data_t; 50 | 51 | // prototypes 52 | int fpga_puf_available(void); 53 | void fpga_puf_get_raw(puf_data_t *puf_data); 54 | 55 | #endif // fpga_puf_neorv32_cfs_h 56 | -------------------------------------------------------------------------------- /sw/main.c: -------------------------------------------------------------------------------- 1 | // ################################################################################################# 2 | // # << fpga_puf: Physical Unclonable Function (PUF) Test Program >> # 3 | // # The PUF is implemented as Custom Functions Unit (CFS) within the NEORV32 Processor. # 4 | // # ********************************************************************************************* # 5 | // # BSD 3-Clause License # 6 | // # # 7 | // # Copyright (c) 2021, Stephan Nolting. All rights reserved. # 8 | // # # 9 | // # Redistribution and use in source and binary forms, with or without modification, are # 10 | // # permitted provided that the following conditions are met: # 11 | // # # 12 | // # 1. Redistributions of source code must retain the above copyright notice, this list of # 13 | // # conditions and the following disclaimer. # 14 | // # # 15 | // # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 16 | // # conditions and the following disclaimer in the documentation and/or other materials # 17 | // # provided with the distribution. # 18 | // # # 19 | // # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 20 | // # endorse or promote products derived from this software without specific prior written # 21 | // # permission. # 22 | // # # 23 | // # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 24 | // # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 25 | // # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 26 | // # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 27 | // # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 28 | // # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 29 | // # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 30 | // # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 31 | // # OF THE POSSIBILITY OF SUCH DAMAGE. # 32 | // # ********************************************************************************************* # 33 | // # fpga_puf - https://github.com/stnolting/fpga_puf (c) Stephan Nolting # 34 | // ################################################################################################# 35 | 36 | #include 37 | #include "fpga_puf_neorv32_cfs.h" 38 | 39 | 40 | /**********************************************************************//** 41 | * User configuration 42 | **************************************************************************/ 43 | #define BAUD_RATE 19200 // UART baud rate 44 | #define NUM_RUNS 8 // number of PUF ID determination runs 45 | #define SAMPLES 4096 // number of PUF ID samples per run 46 | #define HYST_HI (SAMPLES-(SAMPLES/32)) // min number of times bit x in all samples has to be set to be identified as '1' 47 | #define HYST_LO (SAMPLES/32) // max number of times bit x in all samples has to be clear to be identified as '0' 48 | 49 | 50 | /**********************************************************************//** 51 | * Prototypes 52 | **************************************************************************/ 53 | void get_puf_id(puf_data_t *puf_data); 54 | uint32_t hamming_dist(puf_data_t id_a, puf_data_t id_b); 55 | 56 | void simple_test(void); 57 | void long_test_raw(void); 58 | 59 | 60 | /**********************************************************************//** 61 | * Main function 62 | **************************************************************************/ 63 | int main() { 64 | 65 | // initialize the neorv32 runtime environment 66 | neorv32_rte_setup(); 67 | 68 | // setup UART0 at default baud rate, no parity bits, ho hw flow control 69 | neorv32_uart0_setup(BAUD_RATE, PARITY_NONE, FLOW_CONTROL_RTSCTS); 70 | 71 | neorv32_uart0_printf("Physical Unclonable Functions Test\n"); 72 | neorv32_uart0_printf("PUF implemented as NEORV32 Custom Functions Subsystem (CFS)\n\n"); 73 | 74 | if (fpga_puf_available() == 0) { 75 | neorv32_uart0_printf("ERROR! CFS (PUF) not implemented!\n\n"); 76 | return 1; 77 | } 78 | 79 | while(1) { 80 | neorv32_uart0_printf("Select test mode:\n"); 81 | neorv32_uart0_printf(" a - run quick test (execute %u runs with %u ID samples each).\n", NUM_RUNS, SAMPLES); 82 | neorv32_uart0_printf(" b - compute hamming distance of raw IDs\n"); 83 | 84 | char c = neorv32_uart0_getc(); 85 | if ((c == 'a') || (c == 'b')) { 86 | neorv32_uart0_printf("starting test...\n"); 87 | } 88 | else { 89 | neorv32_uart0_printf("invalid test selected!\n"); 90 | continue; 91 | } 92 | 93 | if (c == 'a') { 94 | simple_test(); 95 | } 96 | else if (c == 'b') { 97 | long_test_raw(); 98 | } 99 | } 100 | 101 | neorv32_uart0_printf("Test completed.\n"); 102 | 103 | return 0; 104 | } 105 | 106 | 107 | /**********************************************************************//** 108 | * Get 96-bit PUF ID. 109 | * 110 | * @note This function also performs "post-processing" of the raw PUF data. 111 | * 112 | * @param[in,out] puf_data 3x 32-bit array for the 96-bit raw ID (#puf_data_t); 113 | **************************************************************************/ 114 | void get_puf_id(puf_data_t *puf_data) { 115 | 116 | uint32_t x, y; 117 | puf_data_t puf_raw; 118 | uint32_t cnt[96]; 119 | uint32_t tmp; 120 | uint32_t res[3]; 121 | uint32_t val[3]; 122 | 123 | for (x=0; x<96; x++) { 124 | cnt[x] = 0; 125 | } 126 | 127 | // count how often each bit of the 96-bit ID across all samples 128 | for (x=0; x<(uint32_t)SAMPLES; x++) { 129 | 130 | // get 96-bit raw ID sample 131 | fpga_puf_get_raw(&puf_raw); 132 | 133 | // test every single bit if set or cleared 134 | for (y=0; y<96; y++) { 135 | 136 | if (y>=64) { 137 | tmp = puf_raw.id[2]; 138 | } 139 | else if (y>=32) { 140 | tmp = puf_raw.id[1]; 141 | } 142 | else { 143 | tmp = puf_raw.id[0]; 144 | } 145 | 146 | tmp >>= (y % 32); 147 | 148 | // increment "set-bits" counter 149 | if (tmp & 1) { 150 | cnt[y]++; 151 | } 152 | } 153 | } 154 | 155 | // construct final ID 156 | res[0] = 0; res[1] = 0; res[2] = 0; 157 | val[0] = 0; val[1] = 0; val[2] = 0; 158 | 159 | // compute each bit of the final ID independently 160 | for (x=0; x<96; x++) { 161 | 162 | // bit value - majority decision 163 | if (cnt[x] >= (SAMPLES/2)) { 164 | tmp = 1; // bit is considered '1' if more than half of all samples had this bit set 165 | } 166 | else { 167 | tmp = 0; // bit is considered '0' if less than half of all samples had this bit cleared 168 | } 169 | tmp <<= (x % 32);; 170 | if (x>=64) { 171 | res[0] |= tmp; 172 | } 173 | else if (x>=32) { 174 | res[1] |= tmp; 175 | } 176 | else { 177 | res[2] |= tmp; 178 | } 179 | 180 | // bit valid - hysteresis decision 181 | // bit is only valid if it is "set most of the times" or "cleared most of the times" 182 | if ((cnt[x] >= (uint32_t)HYST_HI) || (cnt[x] <= (uint32_t)HYST_LO)) { 183 | tmp = 1; // bit valid 184 | } 185 | else { 186 | tmp = 0; // bit invalid 187 | } 188 | tmp <<= (x % 32);; 189 | if (x>=64) { 190 | val[0] |= tmp; 191 | } 192 | else if (x>=32) { 193 | val[1] |= tmp; 194 | } 195 | else { 196 | val[2] |= tmp; 197 | } 198 | } 199 | 200 | // final ID, unstable/noisy bits are masked out 201 | puf_data->id[0] = res[0] & val[0]; 202 | puf_data->id[1] = res[1] & val[1]; 203 | puf_data->id[2] = res[2] & val[2]; 204 | } 205 | 206 | 207 | /**********************************************************************//** 208 | * Compute Hamming distance of two IDs. 209 | * 210 | * @param[int] id_a 3x 32-bit array for first ID (#puf_data_t); 211 | * @param[int] id_b 3x 32-bit array for second ID (#puf_data_t); 212 | * @param return Hamming distance (0..96) 213 | **************************************************************************/ 214 | uint32_t hamming_dist(puf_data_t id_a, puf_data_t id_b) { 215 | 216 | uint32_t i, tmp; 217 | uint32_t cnt = 0; 218 | 219 | for (i=0; i<96; i++) { 220 | if (i>=64) { 221 | tmp = id_a.id[2] ^ id_b.id[2]; 222 | } 223 | else if (i>=32) { 224 | tmp = id_a.id[1] ^ id_b.id[1]; 225 | } 226 | else { 227 | tmp = id_a.id[0] ^ id_b.id[0]; 228 | } 229 | tmp >>= (i % 32); 230 | if (tmp & 1) { 231 | cnt++; 232 | } 233 | } 234 | 235 | return cnt; 236 | } 237 | 238 | 239 | /**********************************************************************//** 240 | * Simple test: Compute NUM_RUNS pre-processed IDs. 241 | **************************************************************************/ 242 | void simple_test(void) { 243 | 244 | int i; 245 | 246 | for(i=0; i h_abs_max) { 291 | h_abs_max = h_abs_tmp; 292 | } 293 | 294 | // compute Hamming distance of two consecutive pre-processed ID samples 295 | h_rel_tmp = hamming_dist(id_a, id_b); 296 | if (h_rel_tmp > h_rel_max) { 297 | h_rel_max = h_rel_tmp; 298 | } 299 | 300 | // accumulate noisy bits 301 | noisy_bits.id[0] |= (id_init.id[0] ^ id_a.id[0]) | (id_init.id[0] ^ id_b.id[0]); 302 | noisy_bits.id[1] |= (id_init.id[1] ^ id_a.id[1]) | (id_init.id[1] ^ id_b.id[1]); 303 | noisy_bits.id[2] |= (id_init.id[2] ^ id_a.id[2]) | (id_init.id[2] ^ id_b.id[2]); 304 | popcount = hamming_dist(noisy_bits, zero_id); 305 | 306 | neorv32_uart0_printf("Run %i: ", i); 307 | neorv32_uart0_printf("I=0x%x%x%x, ", id_init.id[2], id_init.id[1], id_init.id[0]); 308 | neorv32_uart0_printf("A=0x%x%x%x, ", id_a.id[2], id_a.id[1], id_a.id[0]); 309 | neorv32_uart0_printf("B=0x%x%x%x - ", id_b.id[2], id_b.id[1], id_b.id[0]); 310 | neorv32_uart0_printf("F=0x%x%x%x (%u) - ", noisy_bits.id[2], noisy_bits.id[1], noisy_bits.id[0], popcount); 311 | neorv32_uart0_printf("H(A,B)=%u, H_max(A,B)=%u - ", h_rel_tmp, h_rel_max); 312 | neorv32_uart0_printf("H(I,A)=%u, H_max(I,A)=%u\n", h_abs_tmp, h_abs_max); 313 | i++; 314 | } 315 | } 316 | 317 | -------------------------------------------------------------------------------- /sw/makefile: -------------------------------------------------------------------------------- 1 | ################################################################################################# 2 | # << NEORV32 - Application Makefile >> # 3 | # ********************************************************************************************* # 4 | # Make sure to add the RISC-V GCC compiler's bin folder to your PATH environment variable. # 5 | # ********************************************************************************************* # 6 | # BSD 3-Clause License # 7 | # # 8 | # Copyright (c) 2021, Stephan Nolting. All rights reserved. # 9 | # # 10 | # Redistribution and use in source and binary forms, with or without modification, are # 11 | # permitted provided that the following conditions are met: # 12 | # # 13 | # 1. Redistributions of source code must retain the above copyright notice, this list of # 14 | # conditions and the following disclaimer. # 15 | # # 16 | # 2. Redistributions in binary form must reproduce the above copyright notice, this list of # 17 | # conditions and the following disclaimer in the documentation and/or other materials # 18 | # provided with the distribution. # 19 | # # 20 | # 3. Neither the name of the copyright holder nor the names of its contributors may be used to # 21 | # endorse or promote products derived from this software without specific prior written # 22 | # permission. # 23 | # # 24 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS # 25 | # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # 26 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # 27 | # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # 28 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # 29 | # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED # 30 | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # 31 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # 32 | # OF THE POSSIBILITY OF SUCH DAMAGE. # 33 | # ********************************************************************************************* # 34 | # The NEORV32 Processor - https://github.com/stnolting/neorv32 (c) Stephan Nolting # 35 | ################################################################################################# 36 | 37 | # Modify this variable to fit your NEORV32 setup (neorv32 home folder) 38 | NEORV32_HOME ?= /mnt/n/Projects/neorv32 39 | 40 | include $(NEORV32_HOME)/sw/common/common.mk 41 | -------------------------------------------------------------------------------- /sw/neorv32_exe.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stnolting/fpga_puf/d1d50c0e10c30fcd7e2338910fdfa8686927b6e9/sw/neorv32_exe.bin --------------------------------------------------------------------------------