diff --git a/README.md b/README.md index d860717..9a22d48 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# :lock: A Physical Unclonable Function for _any_ FPGA +# :closed_lock_with_key: A Physical Unclonable Function for _any_ FPGA [![license](https://img.shields.io/github/license/stnolting/fpga_puf?longCache=true&style=flat-square)](https://github.com/stnolting/fpga_puf/blob/main/LICENSE) @@ -20,7 +20,7 @@ security-related applications. The `fpga_puf` hardware module provides an unclon that is defined by the target chip's semiconductor characteristics. It is implemented in a technology-independent way that does not use any device-specific macros, primitives or attributes so it can be implemented on _any_ FPGA. -:lock: The PUF ID is _unique_ for the combination of bitstream and _one_ specific FPGA. The same bitstream +:key: The PUF ID is _unique_ for the combination of bitstream and _one_ specific FPGA. The same bitstream will lead to a different PUF ID on a different FPGA of same type. If the bitstream of a specific FPGA is changed (by changing design logic) the PUF ID of that specific FPGA will also change. @@ -37,24 +37,25 @@ will lead to a different PUF ID on a different FPGA of same type. If the bitstre ## Theory of Operation -Each bit of the PUF ID is generated by an individual **PUF cell**. Each cell consists of an asynchronous element that is -based on a simple 1-bit ring oscillator. The oscillator is constructed from an inverter that negates it's own output signal. +Each bit of the 96-bit PUF ID is generated by an individual **PUF cell**. Each cell consists of an asynchronous element that is +based on a simple ring oscillator. The oscillator is constructed from a single inverter that negates it's own output signal. This feedback loop is interrupted by a latch. If the latch is open (=transparent) the oscillator starts oscillating. If the latch is closed, it stores the last state of the oscillator (high or low). The latch also provides a reset to bring each cell into a defined state. The frequency of each oscillator is defined by the mapping of the logic cell and the according routing. Both factors -are constant for a single bitstream. Furthermore, the frequency is also defined by the chip's semiconductor characteristics. +are constant for a specific bitstream. Furthermore, the frequency is also randomly (but constantly for a given setup) +"tuned" by the chip's semiconductor characteristics. These are caused by tiny productions fluctuation (for example the capacitance / delay of a routing wire is affected -by variation in oxide thickness). The frequency of each oscillator is expected to be fixed. Hence, sampling several periods +by variation in oxide thickness). The frequency of each oscillator is considered to be fixed. Hence, sampling several periods within a fixed time window will always end in the same state (oscillator output is high or low). Temperature drift -affects all oscillators in the (nearly) same way and has to be computationally eliminated/compensated by the +disturbs all oscillators in the (nearly) same way and has to be computationally eliminated/compensated by the [post-processing](#Post-Processing). -Since `fpga_puf` does not use any kind of macros or primitives, there is a risk that the synthesis toolchain will -remove the asynchronous PUF cells or collapse the design to a single PUF cell ("optimize away"). Therefore, the design -used a shift register to control reset and the latch open/close phase of each cell individually. This concept is -based on the [NEORV32 TRNG](https://github.com/stnolting/neorv32) allowing a **platform-independent implementation**. +Since `fpga_puf` does not use any device-specific attributes or primitives, there is a risk that the synthesis toolchain will +remove the asynchronous PUF cells or collapse them into a single PUF cell ("optimize away"). Therefore, the design +uses a shift register to control reset and the latch open/close phase of each cell individually and distributed over time. This concept is +based on the [NEORV32 TRNG](https://github.com/stnolting/neorv32) and allows a **platform-independent implementation**. Whenever a new sampling of the PUF ID is started, a 96+1 bit wide shift register. A single '1' is applied to the least significant bit that travels throughout the whole shift register chain during operation. The PUF cell's control signals (reset and @@ -82,15 +83,16 @@ change over time and over a broad range of operation conditions (e.g. temperatur approaches can be found in literatures. One promising approach is the use of error-correction codes to "stabilizes" an initially determined ID. -However, these concepts are out of (my) scope so I used a simple "averaging" -concept for this example. My approach samples the _raw_ PUF ID several times and checks how often each bit is set across -all ID samples. If an ID bit is set in more then half of the sample it is considered to be `1`, otherwise it is considered to -be`0`. Since some bits of the raw ID are quite noisy, a hysteresis is used to eliminate those bits from the final PUF (these -bits are masked to be always zero). If a bit is set or cleared more often than a certain _threshold_ it is considered "stable" -in the final ID. +However, these concepts are out of (my) scope so I used a simple "averaging" concept for this example. My approach samples +the _raw_ PUF ID several times (for example 4096 times) and checks how often each bit of the ID is set across +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 +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: +if a bit is set or cleared more often than a certain _threshold_ it is considered "stable" in the final ID. These lower and +upper hysteresis threshold define an _uncertain_ band between them. For the final ID all bits tha fall +into this uncertainty band are masked to be always zero. -My post-processing concept can be found in [`sw/main.c`](https://github.com/stnolting/fpga_puf/blob/main/sw/main.c) -providing more-detailed comments. +My post-processing concept can be found in [`sw/main.c`](https://github.com/stnolting/fpga_puf/blob/main/sw/main.c). The +source file provids more-detailed comments. ## Top Entity @@ -111,6 +113,8 @@ end fpga_puf; ); ``` +:warning: Note that the PUF cannot be simulated due to it's combinatorial loops. + ## Evaluation To evaluate this concept and the quality/reliability of the PUF IDs I am using the [NEORV32](https://github.com/stnolting/neorv32) @@ -118,8 +122,8 @@ as processor platform. The `fpga_puf` IP module is added to the processor's "Cus which is a _blank_ template for implementing custom application-specific co-processors. The setup is synthesized for different FPGAs using different toolchains (like Intel and Lattice). A specific -bitstream generated and programmed into _several_ FPGAs of the _same type_ to check for chip-specific -ID variations. On one chip the ID is generated several times to check if it is "stable" and thus, reliable. +bitstream generated and programmed into _several_ FPGAs of the _same_ type to check for chip-specific +ID variations. On one chip the ID is generated several times to check if it is "stable over time" and thus, reliable. ### Setup