Generátor náhodných čísel (Random Number Generator) je velmi potřebná věc, hlavně v kryptografii. Dobrý generátor náhody je možné udělat pouze elektronicky (nepočítačovým způsobem), protože počítače jsou deterministické stroje a neumí pořádně generovat náhodu. V tomto článku popíši konstrukci založenou na měření šumu tranzistoru (2N3904) při řízeném lavinovém průrazu v závěrné oblasti. Šum zesílíme a budeme měřit pomocí analogově digitálního převodníku (ADC) na Picu. Bude to spíše o elektronice, než o programování, to bude víc než jednoduché. Důležité budou testy RNG, protože se to nemusí povést a můžeme dostat generátor náhody, který až zas tak náhodný není.
Tento jednoduchý hardwarový projekt Generátor náhodných čísel (RNG) je navržen tak, aby vytvořil souvislý řetězec zcela náhodných čísel. Čísla jsou generována z náhodného šumu polovodičového přechodu s reverzním předpětím v tranzistoru. Základní jednotkou numerického výstupu z RNG je souvislý řetězec náhodných bitů (např. "1" nebo "0"). Jednotlivé bity lze kombinovat a vytvořit tak jakýkoli jiný typ výstupu… jako jsou čísla, textové řetězce, hesla nebo ASCII symboly.
Výstup dat z RNG je standardní sériový datový tok. Výstup může být shromažďován a zaznamenáván jakýmkoli počítačem se sériovým portem. Výstup z tohoto RNG je náhodný, ale nijak zvlášť rychlý. Tento RNG design je prezentován pro hobby účely. Neručím za vhodnost výstupu pro aplikace vyžadující dokonale náhodná čísla. Obrázek 2 ukazuje sestavený RNG a obrázek 1 ukazuje elektrické schéma.
Generování skutečně náhodných čísel je překvapivě obtížné. Jinak řečeno, generování čísel bez jakýchkoli stop opakujícího se nebo předvídatelného vzoru je velmi náročné. Počítače jsou notoricky špatné v generování náhodných čísel, což dává smysl, protože počítače předvídatelně provádějí instrukce, které dostanou, aby generovaly číselný výstup. Hardwarové generátory náhodných čísel používají jiný přístup ke generování náhodných čísel. RNG začíná fyzickým procesem, který je vnitřně náhodný. Fyzický výstup náhodného procesu je pak použit ke generování náhodných čísel.
V tomto RNG je náhodný fyzikální proces elektrický šum generovaný v polovodičovém přechodu tranzistoru 2N3904 (T1 na schématu). Reverzní předpětí 12V bylo pečlivě zvoleno tak, aby generovalo dostatečný šum, ale aby nedošlo k poškození tranzistoru. Proud tekoucí přechodem emitor — báze, který je polarizován v závěrném směru (dochází k řízenému lavinovému průrazu) je omezen rezistorem R1 na velmi nízkou hodnotu. Šum z tranzistoru T1 je zesílen druhým tranzistorem T2. Signál z druhého tranzistoru T2 je kapacitně připojen kondenzátorem C1 na odporový dělič napětí R4 a R5. Výsledkem je analogový šumový signál přibližně vycentrovaný na úrovni 1.2V, jak je znázorněno na křivce osciloskopu na videu.
Lavinový průraz tranzistoru T1 (kolektor není schválně zapojený) funguje až při napětí 12V, musíme si toto napětí vyrobit pomocí měniče. Použil jsem Step-up boost měnič s XL6009 modrý s drobnou úpravou. Vyhodil jsem několikaotáčkový potenciometr a nahradil ho dvěma rezistory 2k2 a 270R v sérii. Dosáhl jsem výstupního napětí 12.5 při vstupním napětí 5V. Zapojení tohoto měniče je celkem podivné (nějak neodpovídá žádnému příkladu z datasheetu) a měnič se chová při nižším vstupním napětí než 3.6V tak, že jeho výstupní napětí vyletí na 45.6V. Bude potřeba ještě udělat na výstupu nějakou ochranu pomocí zenerovy diody (měnič má ochranu před zkratem), anebo najít jiný lepší měnič. Tento měnič je tak trochu kanón na vrabce, protože má výstupní proud až 3A a samotný generátor má spotřebu pár miliampérů.

Poznámka: Nultá verze generátoru byla provedena na nepájivém poli, součástky se však viklaly a proto jsem je připájel na universální prototypové desce. Tuto si však nekupujte, je laciná a hodně mizerná.

/* rng.c
* Random Number Generator v 0.1
* (c) Jirka Chráska 2024, <jirka@lixis.cz> All rights reserved.
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
int main( void )
{
char buf[128];
uint16_t a;
int i = 1;
stdio_init_all();
sleep_ms(20000); // čas na nastavení výstupu
adc_init();
adc_select_input(0);
while(1) {
a = adc_read();
printf("%08d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",i,a & 32768 ?1:0,//bit 15
a & 16384 ?1:0,//bit 14
a & 8192 ?1:0,//bit 13
a & 4096 ?1:0,//bit 12
a & 2048 ?1:0,//bit 11
a & 1024 ?1:0,//bit 10
a & 512 ?1:0,//bit 9
a & 256 ?1:0,//bit 8
a & 128 ?1:0,//bit 7
a & 64 ?1:0,//bit 6
a & 32 ?1:0,//bit 5
a & 16 ?1:0,//bit 4
a & 8 ?1:0,//bit 3
a & 4 ?1:0,//bit 2
a & 2 ?1:0,//bit 1
a & 1 ?1:0);//bit 0
sleep_ms(10);
i++;
}
}
Program generuje na sériovou konzoli tento výstup:
# pořadí bity 15,14,...,1,0
00165642 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0
00165643 0 0 0 0 1 0 0 1 1 1 0 0 0 1 1 1
00165644 0 0 0 0 1 0 1 0 1 1 1 1 1 1 0 0
00165645 0 0 0 0 1 0 0 1 0 0 0 1 0 1 1 0
00165646 0 0 0 0 0 0 1 1 1 0 0 1 1 0 1 1
00165647 0 0 0 0 0 1 0 0 1 1 0 1 1 1 0 0
00165648 0 0 0 0 1 0 1 0 1 1 0 0 0 1 0 1
00166982 0 0 0 0 0 1 0 1 1 1 1 1 0 1 0 1
00166983 0 0 0 0 0 0 1 0 0 1 1 1 0 0 0 0
00166984 0 0 0 0 0 1 1 0 1 1 0 0 1 1 0 0
00166985 0 0 0 0 1 0 0 1 1 1 1 1 0 0 1 1
00166986 0 0 0 0 1 0 0 0 1 1 1 1 0 1 1 1
00166987 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1 0
00166988 0 0 0 0 1 0 1 0 1 0 1 0 0 1 0 1
00166989 0 0 0 0 1 0 0 0 0 1 1 1 0 1 1 1
00166990 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
00166991 0 0 0 0 1 0 1 0 0 1 0 1 1 0 0 0
00166992 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 1
00166993 0 0 0 0 0 1 1 1 0 1 0 0 0 1 1 0
00166994 0 0 0 0 0 0 1 0 0 1 0 1 0 0 1 0
00166995 0 0 0 0 1 0 0 0 0 1 0 0 1 1 0 0
00166996 0 0 0 0 1 0 0 1 0 1 0 0 0 0 0 0
00166997 0 0 0 0 1 0 0 1 1 0 1 1 1 1 0 1
00166998 0 0 0 0 1 0 1 0 1 1 0 1 0 1 0 1
00166999 0 0 0 0 1 0 0 1 0 1 1 1 0 1 0 1
00167000 0 0 0 0 1 0 1 0 1 1 1 0 0 0 1 0
00167001 0 0 0 0 0 1 0 1 0 0 0 1 1 1 1 1
00167002 0 0 0 0 0 1 1 0 1 0 1 0 0 1 0 0
00167003 0 0 0 0 0 0 1 1 0 1 1 1 1 0 1 1
00167004 0 0 0 0 1 0 1 0 1 0 1 1 0 1 0 1
00167005 0 0 0 0 0 1 0 1 1 0 1 0 0 1 1 0
00167006 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 0
00167007 0 0 0 0 1 0 1 0 0 1 0 0 0 1 1 0
00167008 0 0 0 0 0 1 1 0 1 1 0 1 1 0 0 0
00167009 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0
00167010 0 0 0 0 0 1 0 1 0 0 1 1 0 0 1 0
00167011 0 0 0 0 1 0 1 0 1 1 1 0 0 0 0 0
00167012 0 0 0 0 1 0 0 1 0 1 1 0 1 0 1 1
00167013 0 0 0 0 0 0 0 1 1 1 0 1 1 1 0 0
00167014 0 0 0 0 0 1 0 1 1 0 0 1 0 1 0 1
00167015 0 0 0 0 1 0 0 0 1 0 0 1 0 1 0 1
00167016 0 0 0 0 0 1 0 0 1 0 1 0 1 0 1 0
00167017 0 0 0 0 0 1 1 0 1 1 1 0 1 0 0 0
00167018 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0
00167019 0 0 0 0 1 0 1 0 1 0 0 0 1 1 1 0
00167020 0 0 0 0 1 0 0 1 0 1 0 0 1 1 1 1
00167021 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0
00167022 0 0 0 0 0 1 0 1 0 1 0 1 1 0 1 0
00167023 0 0 0 0 0 1 1 1 1 1 0 0 1 1 0 1
00167024 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 1
00167025 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0
00167026 0 0 0 0 1 0 0 1 1 0 0 1 1 1 0 0
00167027 0 0 0 0 0 1 0 1 0 0 1 1 1 0 0 0
00167028 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
00167029 0 0 0 0 1 0 0 1 0 0 0 1 1 1 1 1
00167030 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0
00167031 0 0 0 0 0 0 1 1 1 1 1 0 1 1 0 0
00167032 0 0 0 0 1 0 1 0 1 0 1 1 0 0 0 0
00167033 0 0 0 0 1 0 0 1 0 1 0 1 0 0 0 0
00167034 0 0 0 0 0 1 1 1 1 1 0 0 1 0 1 1
00167035 0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0
Je vidět, že 4 nejvyšší bity (12 až 15) z 16bitového čísla vůbec nejsou náhodné (jsou tam samé 0). Je to dáno tím, že ADC převodník u Pica je 12 bitový a ne 16 bitový. Nižšší bajt (bity 0 až 7) je docela pěkně náhodný, to ale budeme ještě muset prověřit nějakými testy. Bity 8 až 11 sice vypadají náhodně, ale moc bych na ně nesázel, protože šum nedosahuje špičkové hodnoty 3.3V, ale jenom přibližně 2.2V a proto je nebudu používat.
$ minicom -b 115200 -o -D /dev/ttyACM0 -C rng_sequence.dat
Testování a prověření RNG bude zpracováno později, protože je dost časově náročné.
Testování
Testování je velmi důležitá věc, pomůže nám odhalit chyby jak v návrhu tak i v programování.
Program jsem trochu upravil, aby bral jenom dolních 8 bitů z ADC převodníku a vypisoval na výstup ASCII 0 pro nulový bit a ASCII 1 pro jedničkový bit. Bity budeme skládat do bufferu a vyplivneme je na sériovou linku po zaplnění bufferu a ne po každém měření. Měření se bude opakovat každou 1 milisekundu.
/* rng.c
* Random Number Generator v 0.2
* (c) Jirka Chráska 2024, <jirka@lixis.cz> All rights reserved.
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#define SEQ 128
int main( void )
{
char buf[SEQ*8+1];
uint16_t a;
stdio_init_all();
sleep_ms(60000);
adc_init();
adc_select_input(0);
while(1) {
buf[SEQ*8]='\0';
for(uint8_t j=0; j<SEQ; j++) {
a = adc_read();
sprintf(buf+(j*8),"%d%d%d%d%d%d%d%d", a&0x80?1:0, a&0x40?1:0, a&0x20?1:0, a&0x0f?1:0, a&0x08?1:0, a&0x04?1:0, a&0x02?1:0, a&0x01?1:0);
sleep_ms(1);
}
printf("%s",buf);
}
}
RNG generátor běžel skoro 2 dny a vyrobil 632232K dat (jenom nuly a jedničky).
Testování pomocí obrázku
Z těchto dat udělám obrázek o rozměrech 4000x4000 bodů (0 znamená černá, 1 znamená bílá).
cat rng_sequence1.dat | ./make_bitmap 4000 4000 (1)
| 1 | Tohle vytvoří obrázek ve formátu pgm, převedu ho do formátu png, který je úspornější v ukládání dat. |

Vypadá to, že obrázek neobsahuje žádné vzorce, což je dobrý příznak kvalitní náhody. Udělám ještě podobné obrázky z dalších částí dat.
Program na vytvoření bitmapy jsem si vypůjčil od pana Giorgia Vazzana.
/*
* Copyright 2012 Giorgio Vazzana
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Compile: gcc -Wall -O make_bitmap.c -o make_bitmap */
#include <stdio.h>
#include <stdlib.h>
struct pgm {
unsigned int width;
unsigned int height;
unsigned int max;
unsigned char *data;
};
int write_pgm(const char *filename, const struct pgm *frame)
{
FILE *fp;
fp = fopen(filename, "w");
if (!fp) {
printf("Error while opening %s\n", filename);
return 1;
}
fprintf(fp, "P5\n%d %d\n%d\n", frame->width, frame->height, frame->max);
fwrite(frame->data, (size_t) frame->width, (size_t) frame->height, fp);
fclose(fp);
return 0;
}
int main(int argc, char *argv[])
{
int i, c;
unsigned char byte;
struct pgm frame;
if (argc < 3) {
printf("Usage: cat sequence | make_bitmap width height\n");
return 1;
}
frame.width = atoi(argv[1]);
frame.height = atoi(argv[2]);
if (frame.width > 4096 || frame.height > 4096) {
printf("Error: one dimension is > 4096\n");
return 1;
}
frame.max = 255;
frame.data = malloc(frame.width * frame.height);
if (!frame.data) {
printf("Error: malloc() failed\n");
return 1;
}
for (i = 0; i < frame.width * frame.height; i++) {
c = fgetc(stdin);
if (c == EOF) {
printf("Error: unexpected EOF\n");
return 1;
}
byte = (c == '0') ? 0 : 255;
frame.data[i] = byte;
}
write_pgm("bitmap.pgm", &frame);
return 0;
}
Rozdělím si data ze souboru rng_sequence1.dat na bloky po 100 MB.
$ dd if=rng_sequence1.dat of=rng_sequence1.0.dat bs=100000000 count=1 skip=0
1+0 záznamů přečteno
1+0 záznamů zapsáno
100000000 bajtů (100 MB, 95 MiB) zkopírováno, 0,131591 s, 760 MB/s
$ dd if=rng_sequence1.dat of=rng_sequence1.1.dat bs=100000000 count=1 skip=1
1+0 záznamů přečteno
1+0 záznamů zapsáno
100000000 bajtů (100 MB, 95 MiB) zkopírováno, 0,158612 s, 630 MB/s
$ dd if=rng_sequence1.dat of=rng_sequence1.2.dat bs=100000000 count=1 skip=2
1+0 záznamů přečteno
1+0 záznamů zapsáno
100000000 bajtů (100 MB, 95 MiB) zkopírováno, 0,162107 s, 617 MB/s
$ dd if=rng_sequence1.dat of=rng_sequence1.3.dat bs=100000000 count=1 skip=3
1+0 záznamů přečteno
1+0 záznamů zapsáno
100000000 bajtů (100 MB, 95 MiB) zkopírováno, 0,161285 s, 620 MB/s
$ dd if=rng_sequence1.dat of=rng_sequence1.4.dat bs=100000000 count=1 skip=4
1+0 záznamů přečteno
1+0 záznamů zapsáno
100000000 bajtů (100 MB, 95 MiB) zkopírováno, 0,168433 s, 594 MB/s
$ dd if=rng_sequence1.dat of=rng_sequence1.5.dat bs=100000000 count=1 skip=5
1+0 záznamů přečteno
1+0 záznamů zapsáno
100000000 bajtů (100 MB, 95 MiB) zkopírováno, 0,159576 s, 627 MB/s
$ dd if=rng_sequence1.dat of=rng_sequence1.6.dat bs=100000000 count=1 skip=6
0+1 záznamů přečteno
0+1 záznamů zapsáno
47405763 bajtů (47 MB, 45 MiB) zkopírováno, 0,0680521 s, 697 MB/s
Dostávám šest 100 MB bloků (sedmý blok není úplný, nebudu ho používat) a vytvořím další obrázky.
$ cat rng_sequence1.0.dat | ./make_bitmap 4000 4000
$ cat rng_sequence1.1.dat | ./make_bitmap 4000 4000
$ cat rng_sequence1.2.dat | ./make_bitmap 4000 4000
$ cat rng_sequence1.3.dat | ./make_bitmap 4000 4000
$ cat rng_sequence1.4.dat | ./make_bitmap 4000 4000
$ cat rng_sequence1.5.dat | ./make_bitmap 4000 4000





Obrázky nevykazují žádné vzory. Dobře.
Testování systematického zkreslení
Další věc, kterou jsem na těchto sekvencích udělal, bylo testování jejich systematického zkreslení (bias). Ideálně by měl RNG generovat ve velkém statistickém vzorku stejný počet jedniček a nul.
Abych to mohl udělal, rozdělil jsem sekvenci do bloků po N=100000 bitů a pro každý blok byla vypočtena chyba pomocí následujícího vzorce:
\(B = \frac{P - \frac{N}{2}}{\frac{N}{2}} \cdot 100\) , kde P je počet jedniček v daném bloku 100000 bitů.
/* bias_test.c
* (c) Jirka Chráska 2024, <jirka@lixis.cz>
*
*/
#include <stdio.h>
#include <string.h>
#define N 100000
int main(int argc, char *argv[])
{
if( argc != 3) {
printf("Usage: %s <rng_sequence> <bias_file>\n",argv[0]);
return 1;
}
char buf[N];
FILE *f = fopen(argv[1],"r");
FILE *g = fopen(argv[2],"a");
int len;
long seq = 0;
long sum = 0l;
double bias = 0.0;
while( fread(buf,N,1,f)>0 ) {
sum = 0l;
for(long i=0l; i<N; i++ ) {
if(buf[i]=='1') sum++;
}
bias = (sum - N*0.5)/(N*0.5) * 100;
fprintf(g,"%8ld %6.2f\n",seq,bias);
seq++;
}
fclose(f);
fclose(g);
}
$ gcc -o bias_test bias_test.c
$ ./bias_test rng_sequence1.dat bias_test.txt
Pokud je systematické zkreslení nízké, očekáváme, že tyto chybové hodnoty budou kolem 0. Tato analýza pro naměřenou sekvenci je znázorněna na následujícím obrázku:

A vidíme nepěknost. Systematické zkreslení nemáme okolo 0, ale pod nulou o 1.5%, což úplně pěkné není. Znamená to, že můj generátor dělá statisticky více nul než jedniček. Není to zase hrozné číslo, ale přesto tam systematická chyba je.
Zkusím změřit ještě jednou pořádně osciloskopem rozpětí šumu a lépe ho vycentrovat, aby nešel do záporných hodnot, které ADC neměří a použiju všech 12 bitů převodníku ADC a uvidíme, zda se něco zlepší.
Datový soubor z programu bias_test vypadá takto (je to jenom část):
741 -1.54
742 -1.55
743 -1.27
744 -2.08
745 -1.20
746 -1.95
747 -0.97
748 -1.56
749 -1.80
750 -1.46
751 -1.02
752 -0.71
753 -1.69
754 -1.43
755 -1.42
756 -1.61
757 -1.21
758 -0.71
759 -1.01
760 -1.62
Obrázek jsem nakreslil pomocí gnuplot takto:
jirka@jirka-Precision-T3610:~/pico/mojepokusy/rng/build$ gnuplot
G N U P L O T
Version 5.2 patchlevel 8 last modified 2019-12-01
Copyright (C) 1986-1993, 1998, 2004, 2007-2019
Thomas Williams, Colin Kelley and many others
gnuplot home: http://www.gnuplot.info
faq, bugs, etc: type "help FAQ"
immediate help: type "help" (plot window: hit 'h')
Terminal type is now 'qt'
gnuplot> plot "bias_test.txt"
qt5ct: using qt5ct plugin
gnuplot> plot "bias_test.txt" title "Mereni biasu" linetype 7 linecolor 9 with linespoints
gnuplot> set xlabel "Cislo bloku"
gnuplot> set ylabel "Chyba (%)"
gnuplot> replot
gnuplot> set xlabel "Cislo bloku (100 000 bitu)"
gnuplot> replot
gnuplot> set terminal png size 1200,600
Terminal type is now 'png'
Options are 'nocrop enhanced size 1200,600 font "arial,12.0" '
gnuplot> set output "bias_test.png"
gnuplot> replot
Poznámka: Háčky a čárky gnuplot neumí, prosím promiňte tuto drobnost.
Testování metodou Monte Carlo
Dalším jednoduchým způsobem testování náhodnosti je vyhodnocení Monte Carlo pro π, jak je vysvětleno v [4]. Tato metoda je založena na této myšlence: vezmeme čtverec a vepíšeme do něj kruh, který se dotýká každého okraje čtverce. Víme, že pokud je poloměr kruhu r, pak obsah kruhu je πr² a obsah čtverce je 4r². Vypočteme-li poměr q plochy kruhu ke čtvercové ploše, dostaneme q = π/4; můžeme tedy vypočítat π s π = 4q. Stejný výsledek stále platí, pokud pracujeme v prvním kvadrantu. Poměr q lze najít pomocí dvojic náhodných bodů (x, y), které vyjmeme z naší posloupnosti. Zejména páry (x, y) jsou generovány pomocí bloků po sobě jdoucích 48 bitů, přičemž každá souřadnice je 24 bitové číslo. Pokud spočítáme počet bodů, které spadají do kruhu a vydělíme toto číslo celkovým počtem bodů, dostaneme odhad q. Pro nezávislé náhodné body, které jsou rovnoměrně rozmístěny ve čtverci, by nám tato metoda měla dát sekvenci, která pomalu konverguje k π. Toto jsou výsledky pro prvních 10 000 bodů z první sekvence sekvence:
$ cat rng_sequence1.0.dat | ./montecarlo_pi 10000 2>points.0 | tail -n1
n=10000 pi=2.953600000 e=-5.984% (1)
$ ./montecarlo_image.m (2)
| 1 | Číslo 2.59 nevypadá jako π = 3.1415926 |
| 2 | Takto se dělá obrázek pomocí programvu v jazyce Octave. |


Je evidentní, že body nejsou rozloženy rovnoměrně. Že by za to mohlo oříznutí vrchních 4 bitů z ADC převodníku? Generátor bude potřeba předělat.
$ cat rng_sequence1.0.dat | ./montecarlo_pi 10000 2>points.0 | tail -n1
n=10000 pi=2.953600000 e=-5.984%
$ ./montecarlo_image.m
Zdroje
Monte Carlo [4]
Poznámky
Schéma zapojení jsem kreslil programem KiCad EDA, skvělý tutoriál v češtině je na kanále Youtube: Názorná elektrotechnika. Instalace ma Linux Mintu je triviální:
sudo add-apt-repository ppa:kicad/kicad-8.0-releases
sudo apt update
sudo apt install kicad
Tutoriál k používání gnuplotu gnuplot Tutorial 1: basic Plotting tips & tricks, errorbars, png output