Ein kleiner und plattformunabhängiger echter Zufallszahlengenerator für jedes FPGA (und sogar ASICs).
Der neoTRNG soll ein kleiner und plattformunabhängiger TRUE-Zufallszahlengenerator (TRNG) sein, der für jede Zieltechnologie (FPGAs und sogar ASICs) synthetisiert werden kann. Es basiert auf einfachen freilaufenden Ringoszillatoren, die durch eine spezielle Technik verbessert werden, um eine Synthese für jede Plattform zu ermöglichen. Als physikalische Entropiequelle wird das Phasenrauschen genutzt, das beim Abtasten freilaufender Ringoszillatoren auftritt.
Dieses Projekt ist ein „Spin-off“ des NEORV32 RISC-V-Prozessors, bei dem neoTRNG als Standard-SoC-Modul implementiert ist.
Hauptmerkmale
Vorsicht
Es ist möglich, dass es zumindest einige Kreuzkorrelationen zwischen internen/externen Signalen/Ereignissen und den generierten Zufallszahlen gibt. Daher gibt es überhaupt keine Garantie dafür, dass neoTRNG perfekte oder sogar kryptografisch sichere Zufallszahlen liefert! Sehen Sie sich die bereitgestellten Bewertungsergebnisse an oder (noch besser) testen Sie es selbst. Darüber hinaus gibt es noch keinen Manipulationserkennungsmechanismus oder eine Online-Gesundheitsüberwachung, um die Integrität/Qualität der generierten Zufallsdaten zu überprüfen.
Warnung
Wenn neoTRNG dauerhaft aktiviert bleibt, erhöht sich der dynamische Stromverbrauch und es kann auch zu einer lokalen Erwärmung des Chips kommen (bei Verwendung sehr großer Konfigurationen). Darüber hinaus können konstruktionsbedingt zusätzliche elektromagnetische Störungen (EMI) ausgehen.
Das gesamte Design wird als einzelne VHDL-Datei rtl/neoTRNG.vhd
implementiert, die keinerlei Abhängigkeiten aufweist (wie spezielle Bibliotheken, Pakete oder Submodule).
entity neoTRNG is
generic (
NUM_CELLS : natural range 1 to 99 := 3 ; -- number of ring-oscillator cells
NUM_INV_START : natural range 3 to 99 := 5 ; -- number of inverters in first cell, has to be odd
SIM_MODE : boolean := false -- enable simulation mode (no physical random if enabled!)
);
port (
clk_i : in std_ulogic ; -- module clock
rstn_i : in std_ulogic ; -- module reset, low-active, async, optional
enable_i : in std_ulogic ; -- module enable (high-active)
valid_o : out std_ulogic ; -- data_o is valid when set (high for one cycle)
data_o : out std_ulogic_vector ( 7 downto 0 ) -- random data byte output
);
end neoTRNG ;
Der neoTRNG verwendet eine einzelne Taktdomäne, die vom clk_i
-Signal gesteuert wird. Das Reset-Signal rstn_i
des Moduls ist optional (wird an '1'
gebunden, wenn es nicht verwendet wird). Zufallsdaten werden mithilfe einer einfachen Daten-/Gültigkeitsschnittstelle abgerufen: Immer wenn ein neues gültiges Zufallsbyte verfügbar ist, ist der Ausgang valid_o
genau einen Zyklus lang hoch, sodass der Ausgang data_o
von der Benutzerlogik abgetastet werden kann.
Das Signal enable_i
wird zum Initialisieren und Starten des TRNG verwendet. Bevor der TRNG verwendet werden kann, sollte dieses Signal für mindestens mehrere 100 Taktzyklen (je nach Konfiguration) niedrig gehalten werden, um sicherzustellen, dass alle Bits der internen Schieberegister wieder gelöscht werden. Wenn enable_i
gesetzt ist und valid_o
zum ersten Mal gesetzt wird, ist der TRNG betriebsbereit. Das Deaktivieren des TRNG erfordert außerdem, enable_i
für die gleiche Anzahl an Taktzyklen niedrig ist. Wenn enable_i
niedrig wird, werden alle Ringoszillatoren gestoppt, was die dynamische Schaltaktivität und den Stromverbrauch verringert.
Zur Konfiguration des neoTRNG werden drei Generics bereitgestellt. NUM_CELLS
definiert die Gesamtzahl der Entropiezellen. NUM_INV_START
definiert die Anzahl der Inverter (= die Länge des Ringoszillators) in der allerersten Zelle. Diese beiden Generika werden im Abschnitt „Architektur“ weiter unten beschrieben. Der letzte generische SIM_MODE
kann so eingestellt werden, dass er die Simulation des TRNG innerhalb einer einfachen RTL-Simulation ermöglicht.
Der neoTRNG basiert auf einer konfigurierbaren Anzahl ( NUM_CELLS
) von Entropiezellen. Jede Zelle stellt einen einfachen Ringoszillator („RO“) bereit, der aus einer ungeraden Anzahl von Wechselrichtern besteht. Die Schwingungsfrequenz des RO wird durch die Ausbreitungsverzögerung der Elemente innerhalb des Rings definiert. Diese Frequenz ist nicht statisch, da sie minimalen Schwankungen unterliegt, die durch thermisches Rauschen und elektronisches Schrotrauschen verursacht werden. Der Zustand des letzten Wechselrichters des RO wird mithilfe eines statischen Takts ( clk_i
) in einem Flip-Flop abgetastet. Da sich die Frequenz des RO im Laufe der Zeit chaotisch ändert, wird das inhärente Phasenrauschen der abgetasteten Daten als tatsächliche Entropiequelle verwendet.
Jede Entropiezelle erzeugt einen 1-Bit-Strom zufälliger Daten. Die Ausgaben aller Zellen werden mithilfe eines breiten XOR-Gatters gemischt, bevor der Stream durch einen einfachen Zufallsextraktor entzerrt wird. Mehrere entzerrte Bits werden von der Abtasteinheit abgetastet/deserialisiert, um eine byteweite Zufallszahl bereitzustellen. Die Sampling-Einheit wendet außerdem eine einfache Nachbearbeitung an, um die spektrale Verteilung der Zufallszahlen zu verbessern.
Jede Entropiezelle besteht aus einem Ringoszillator, der aus einer ungeraden Anzahl invertierender Latches aufgebaut ist. Die Länge des Rings in der allerersten Entropiezelle wird durch das Generikum NUM_INV_START
definiert. Jede zusätzliche Entropiezelle fügt dieser anfänglichen Kettenlänge weitere 2 Inverter hinzu. Daher schwingt jede weitere Entropiezelle mit einer niedrigeren Frequenz als die vorherige.
Asynchrone Elemente wie Ringoszillatoren lassen sich nur schwer plattformunabhängig implementieren, da sie normalerweise die Verwendung plattform-/technologiespezifischer Grundelemente, Attribute oder Syntheseeinstellungen erfordern. Um eine wirklich zielunabhängige Architektur bereitzustellen, die für jede Zieltechnologie synthetisiert werden kann, wird eine spezielle Technik angewendet: Auf jeden Wechselrichter im RO folgt ein Latch , der einen globalen Reset und auch eine individuelle Latch-Aktivierung zum Schalten ermöglicht den Riegel in den transparenten Modus.
Die einzelnen Latch-Aktivierungen werden durch ein langes Schieberegister gesteuert, das für jeden einzelnen Latch in der RO-Kette einen eigenen FF aufweist. Wenn TRNG aktiviert ist, beginnt sich dieses Schieberegister mit Einsen zu füllen. Somit werden die Latches einzeln einzeln aktiviert, was es dem Synthesetool unmöglich macht, Logik/Elemente aus der RO-Kette zu entfernen, da die Startzustände jedes Latches (theoretisch) durch externe Logik überwacht werden können. Die Aktivierungsschieberegister aller Entropiezellen sind in Reihe geschaltet, um diesen Startvorgang über das gesamte Entropie-Array fortzusetzen.
Das folgende Bild zeigt das vereinfachte Schema der allerersten Entropiezelle, bestehend aus 5 Inverter-Latch-Elementen für den Ringoszillator, 5 Flip-Flops für das Freigabeschieberegister und weiteren 2 Flip-Flops für den Synchronisierer.
Hier ist ein Bild zu sehen, das dem FPGA das Mapping-Ergebnis (generiert von Intel Quartus Prime) der allerersten Entropiezelle zeigt. Es zeigt, dass alle Latch+Inverter-Elemente der Ringoszillatorkette erfolgreich einzelnen LUT4s zugeordnet wurden.
Sobald das letzte Bit des verketteten Freigabeschieberegisters der Entropiezelle gesetzt ist, wird die De-Biasing-Einheit gestartet. Diese Einheit implementiert einen einfachen „John von Neumann Randomness Extractor“, um den erhaltenen Zufallsdatenstrom zu entzerren. Der Extraktor implementiert ein 2-Bit-Schieberegister, das das XOR-verknüpfte Zufallsbit aus dem Entropiezellenarray abtastet. In jedem zweiten Zyklus wertet der Extraktor die beiden abgetasteten Bits aus, um ein nicht überlappendes Bitpaar auf Kanten zu prüfen.
Sobald eine Kante erkannt wurde, wird ein „gültig“-Signal an die nachfolgende Abtasteinheit gesendet. Eine steigende Flanke ( 01
) gibt ein 1
Datenbit aus und eine fallende Flanke ( 10
) gibt ein 0
Datenbit aus. Daher benötigt die De-Biasing-Einheit mindestens zwei Taktzyklen, um ein einzelnes Zufallsbit zu erzeugen. Wenn keine Flanke erkannt wird ( 00
oder 11
), bleibt das gültige Signal niedrig und die Abtasteinheit stoppt.
Die Abtasteinheit implementiert ein 8-Bit-Schieberegister, um den seriellen entzerrten Bitstrom in byteweite Zufallszahlen umzuwandeln. Darüber hinaus bietet die Probeneinheit eine einfache Nachbearbeitung zur Verbesserung der spektralen Verteilung der erhaltenen Zufallsproben.
Um ein Byte Zufallsdaten zu erzeugen, setzt die Abtasteinheit ihr internes Schieberegister auf Null zurück und beginnt, 64 Bits des entzerrten Zufallsstroms zu verbrauchen. Das Schieberegister ist als linear rückgekoppeltes Schieberegister (LFSR) implementiert, das den Eingangsstrom mit dem letzten Bit des Registers XOR-verknüpft, um den zufälligen Bitstrom weiter zu verschlüsseln und zu mischen.
Der neoTRNG wird als Teil des NEORV32-Prozessors evaluiert, wobei der neoTRNG als Standard-SoC-Modul verfügbar ist. Der Prozessor wurde für einen Intel Cyclone IV EP4CE22F17C6N
FPGA mit 100 MHz synthetisiert. Für die Auswertung wurde die sehr kleine Standardkonfiguration verwendet: Es werden drei Entropiezellen implementiert, wobei die erste 5 Inverter implementiert, die zweite 9 Inverter implementiert und die dritte 11 Inverter implementiert. Komplexere Konfigurationen mit mehr/größeren Entropiezellen könnten eine „bessere“ Zufallsqualität liefern.
NUM_CELLS = 3
NUM_INV_START = 5
SIM_MODE = false
Notiz
Für die Auswertungen wurden insgesamt 4 MB Zufallsdaten erhoben. Dieser Datensatz ist als Binärdatei entropy.bin
in den Release-Assets verfügbar.
Für die einfache Histogrammanalyse wurden 4 MB zufällige Bytes aus dem neoTRNG abgetastet. Die erhaltenen Bytes wurden entsprechend ihrem Vorkommen akkumuliert und in Bins sortiert, wobei jedes Bin ein bestimmtes Bytemuster darstellt (1 Byte = 8 Bits = 256 verschiedene Muster). Das Ergebnis wurde dann hinsichtlich seiner statistischen Eigenschaften analysiert:
[NOTE] integer numbers only
Number of samples: 4194304
Arithmetic mean: 127 (optimum would be 127)
Histogram occurrence
Average: 16384 (optimum would be 4194304/256 = 16384)
Min: 16051 = average - 333 (deviation) at bin 183 (optimum deviation would be 0)
Max: 16706 = average + 322 (deviation) at bin 144 (optimum deviation would be 0)
Average dev.: +/- 96 (optimum would be 0)
$ ent entropy.bin
Entropy = 7.994306 bits per byte.
Optimum compression would reduce the size
of this 4194304 byte file by 0 percent.
Chi square distribution for 4194304 samples is 16726.32, and randomly
would exceed this value less than 0.01 percent of the times.
Arithmetic mean value of data bytes is 127.9417 (127.5 = random).
Monte Carlo value for Pi is 3.132416851 (error 0.29 percent).
Serial correlation coefficient is 0.000496 (totally uncorrelated = 0.0).
$ rngtest < entropy.bin
rngtest 5
Copyright (c) 2004 by Henrique de Moraes Holschuh
This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
rngtest: starting FIPS tests...
rngtest: entropy source drained
rngtest: bits received from input: 33554432
rngtest: FIPS 140-2 successes: 1676
rngtest: FIPS 140-2 failures: 1
rngtest: FIPS 140-2(2001-10-10) Monobit: 0
rngtest: FIPS 140-2(2001-10-10) Poker: 0
rngtest: FIPS 140-2(2001-10-10) Runs: 1
rngtest: FIPS 140-2(2001-10-10) Long run: 0
rngtest: FIPS 140-2(2001-10-10) Continuous run: 0
rngtest: input channel speed: (min=138.214; avg=1557.190; max=2119.276)Mibits/s
rngtest: FIPS tests speed: (min=32.660; avg=106.337; max=111.541)Mibits/s
rngtest: Program run time: 330110 microseconds
Die Dieharder Random Number Testsuite (Wikipedia, Homepage) von Robert G. Brown ist ein großartiges Toolset zum Stresstesten und Charakterisieren von Zufallszahlengeneratoren.
Wichtig
? in Arbeit?
Dieharder benötigt eine große Menge an Zufallsstichproben (ca. 4 GB). Andernfalls werden die Zufallsdaten zurückgespult, was offensichtlich die Gesamtentropie verringert. Im Moment verwende ich eine einfache UART-Verbindung, um Daten von einem FPGA zum PC zu übertragen. Aber selbst bei höheren Baudraten würde das Senden eines Datensatzes von 4 GB ewig dauern. Bis ich einen besseren Übertragungskanal habe (oder einfach nur viel Zeit), ist diese Evaluierung „work in progress“ .
Zuordnungsergebnisse für das im NEORV32 RISC-V-Prozessor implementierte neoTRNG unter Verwendung der Standardkonfiguration. Ergebnisse für einen Intel Cyclone EP4CE22F17C6N
FPGA mit 100 MHz und Intel Quartus Prime.
Module Hierarchy Logic Cells Logic Registers
------------------------------------------------------------------------------------
neoTRNG:neoTRNG_inst 56 (27) 46 (19)
neoTRNG_cell:entropy_source:0:neoTRNG_cell_inst 8 (8) 7 (7)
neoTRNG_cell:entropy_source:1:neoTRNG_cell_inst 10 (10) 9 (9)
neoTRNG_cell:entropy_source:2:neoTRNG_cell_inst 14 (14) 11 (11)
Notiz
Synthesewerkzeuge geben möglicherweise eine Warnung aus, dass Latches und kombinatorische Schleifen erkannt wurden. Dies ist jedoch kein Konstruktionsfehler, da es genau das ist, was wir wollen.
Die maximale Erzeugungsrate des neoTRNG wird durch zwei Faktoren definiert:
Daher benötigt der neoTRNG mindestens A * B = 2 * 64 = 128
Taktzyklen, um ein Zufallsbyte auszusenden. Die FPGA-Auswertung hat ergeben, dass die tatsächliche Abtastzeit etwa 300 Taktzyklen beträgt. Somit kann eine Implementierung mit 100 MHz ungefähr 330 kB Zufallsdaten pro Sekunde erzeugen. Höhere Generierungsraten können durch den parallelen Betrieb mehrerer neoTRNG-Instanzen erreicht werden.
Da die asynchronen Ringoszillatoren (aufgrund der kombinatorischen Schleifen) nicht rtl-simuliert werden können, bietet neoTRNG einen dedizierten Simulationsmodus, der durch das SIM_MODE
Generikum aktiviert wird. Bei Aktivierung wird den Invertern des Ringoszillators eine „Ausbreitungsverzögerung“ hinzugefügt, die als einfaches Flip-Flop implementiert ist.
Wichtig
Der Simulationsmodus ist nur für Simulation/Debugging gedacht! Designs mit aktiviertem SIM_MODE
können synthetisiert werden, liefern aber überhaupt keine echten/physikalischen Zufallszahlen !
Der sim
-Ordner bietet eine einfache Testbench für neoTRNG mit der Standardkonfiguration. Die Testbench gibt die erhaltenen Zufallsdatenbytes als Dezimalwerte an die Simulatorkonsole aus. Die Testbench kann mit GHDL mithilfe des bereitgestellten Skripts simuliert werden:
neoTRNG/sim$ sh ghdl.sh
../rtl/neoTRNG.vhd:105:3:@0ms:(assertion note): [neoTRNG] The neoTRNG (v3.2) - A Tiny and Platform-Independent True Random Number Generator, https://github.com/stnolting/neoTRNG
../rtl/neoTRNG.vhd:112:3:@0ms:(assertion warning): [neoTRNG] Simulation-mode enabled (NO TRUE/PHYSICAL RANDOM)!
18
210
147
5
79
94
70
100
185
246
203
220
ghdl:info: simulation stopped by --stop-time @100us
Die GHDL-Wellenformdaten werden in sim/neoTRNG_tb.ghw
gespeichert und können mit gtkwave
angezeigt werden:
neoTRNG/sim$ gtkwave neoTRNG_tb.ghw
Ein einfacher Simulationslauf wird durch den GitHub-Aktionsworkflow neoTRNG-sim
des Projekts ausgeführt.