Не знаю кому как, а меня прошедший 2017 год шокировал стремительным взлетом биткоина. Сейчас, конечно, ажиотаж уже ушел, а в 17-м году про криптовалюты говорили и писали все кому не лень.
Я видел, что люди пытаются зарабатывать на криптовалютах. Кто как умеет. Кто-то на все сбережения скупал видеокарты и начинал самостоятельно майнить в гараже. Кто-то вкладывался в облачный майнинг. Кто-то пытается организовать свой пул. Кто-то запустил в производство шоколадные биткоины, а кто-то выпускает минеральную воду:
Я тоже стал изучать, что же такое эти самые биткоины. Когда-то я даже начал свое собственное иследование алгоритма SHA256 и написал статью здесь на хабре "Можно ли вычислять биткоины быстрее, проще или легче?". Мои исследования алгоритмов хеширования до сих пор продолжаются и еще и близко не завершены… Может быть когда нибудь напишу про это отдельную статью. А сейчас пока вот это..
Я попробовал запустить bitcoin майнер в FPGA. Я понимал, что время уже ушло, но хотелось все же прикоснуться к технологии. Уже в конце прошлого года я вдруг почему-то вспомнил, что у меня совершенно без дела лежит плата Terasic DE10-Standard с ПЛИС Intel Cyclone V 5CSXFC6D6F31C6 — это тот чип, который со встроенным процессором ARM. Я подумал, что было бы интересно запустить какой нибудь альткоин майнер в этой плате. А что? Инвестировать в оборудование мне уже не надо, оно и так есть. Главное, чтобы плата зарабатывала больше, чем потребляет энергии.
Поиск подходящего альткоина был весьма прост. Я искал готовые проекты для FPGA, которые я смогу адаптировать под свою плату. Таковых оказалось не очень много. На самом деле как я понимаю во всем мире есть всего несколько человек, которые делали FPGA проекты и главное публиковали их в открытом доступе, например, на github.
Таким образом, я взял проект github.com/kramble/FPGA-Blakecoin-Miner и адаптировал его под имеющуюся у меня плату Марсоход3, а так же адаптировал этот проект для DE10-Standard.
Собственно о том, как я адаптировал проект для платы Марсоход3 написано здесь. Для Cyclone V в принципе все то же самое — только ревизия проекта квартуса blake_cv, мои исходники вот.
К моему сожалению в имеющийся у меня Cyclone V помещается только три хэш функции blake.
Чуть-чуть не хватает емкости ПЛИС до четырех хэшеров. Я запускаю проект на частоте 120МГц и за один такт рабочей частоты вычисляется один хэш blake. Значит производительность моего проекта 120*3=360MH/sec. Не очень много честно говоря, однако, как я уже сказал, плата у меня уже была, и возвращаеть ее стоимость мне не нужно… Тут еще Quartus говорит, что Fmax=150MHz. Можно попытаться поднять частоту, но боюсь придется ставить кулер, будет гудеть — ну не на столько мне нужны эти крипты, чтоб еще гул в комнате слушать.
Общая задумка проекта такая: плата имеет микросхему у которой есть и ПЛИС и Dual-ARM:
Когда плата стартует, то из U-BOOT первым делом загружается ПЛИС, затем стартует Linux и в нем программа майнинга cgminer. Я сперва думал, что я смогу устроить виртуальный канал связи между ARM и FPGA, и это на самом деле возможно, но так не получилось. Дело в том, что программа майнера cgminer работает с аппаратными майнерами через USB и использует библиотеку libusb. То есть мне проще подключить ПЛИС к Linux системе через преобразователь USB-COM на FTDI, чем городить городушку соединяя ПЛИС на шину ARMа. Я таким уже как-то занимался и это было не очень просто.
Сейчас мой «майнер» выглядит вот так (на Cyclone V поставил радиатор на термопасте, а то сильно греется):
Сказать по правде основные проблемы у меня как раз возникли не с FPGA проектом, а с cgminer.
Проблемы следующие:
1) Какой cgminer брать за основу своей разработки? И связанный с этим вопрос «Куда подключаться, чтобы начать майнить?». А какая связь между этими вопросами? Казалось бы, где тут проблема — бери самый свежий cgminer, какой найдешь. Но позвольте: на github есть 98 форков программы cgminer. Все они чем-то отличаются, какой есть хороший, а какой плохой, какой есть вообще хотя бы рабочий? Вот вам и опенсоурс. Каждый автор чего-то там себе добавлял и исправлял, или ломал… или делал свою монету. Разобраться не просто. Нашел для себя сайт, где на одной странице есть ссылка и на github проект и на github проект для FPGA. То есть эти два проекта видимо как-то могут и должны пересекаться.
2) Поскольку я взял за основу FPGA проект от автора kramble, то на самом деле, конечно, логично было бы взять его патчи, которые он приложил к своему проекту. Но и тут не без проблем. У него есть патчи к программе cgminer-3.1.1 и cgminer-3.4.3. Я решил, что лучше брать ту, что новее 3.4.3, но только потерял с ней время. Похоже автор начал адаптировать для этой версии, но что-то там не довел до конца и эта версия совсем сырая. Пришлось брать 3.1.1 а это кажется вообще старючая версия.
3) Авторы изменяющие программу cgminer в своих форках для своих альткоинов не следят за правильностью комментариев и именованием функций в коде. Зачастую в коде тут и там встречается слово bitcoin, а сам этот форк cgminer-а уже кажется не может считать для биткоина, а может только в альткоин.
4) Тесты. ГДЕ ТЕСТЫ? Я чего-то не понимаю, как можно делать сложный продукт без тестов? Я их не нашел.
Сказать по правде даже начинать что-то делать было не просто. Представьте себе, что нужно запустить некоторый проект в FPGA, но не очень понятно, что он должен делать, как получать данные, какие данные и в каком виде нужно выдавать результат. К этому FPGA проекту должна прилагаться некоторая программа, которую не известно точно где взять, но она должна обнаружить плату майнера, что-то туда посылать (неизвестно что) и что-то из нее получать. В каком формате, какими блоками, как часто — ничего не известно.
На самом деле, изучая патчи cgminer от kramble я примерно представляю себе как оно должно работать.
В файле usbutils.c прописаны устройства, которые могут рассматриваться как аппаратные внешние майнеры на шине USB:
static struct usb_find_devices find_dev[] = {
#ifdef USE_BFLSC
{
.drv = DRV_BFLSC,
.name = "BAS",
.ident = IDENT_BAS,
.idVendor = IDVENDOR_FTDI,
.idProduct = 0x6014,
//.iManufacturer = "Butterfly Labs",
.iProduct = "BitFORCE SHA256 SC",
.kernel = 0,
.config = 1,
.interface = 0,
.timeout = BFLSC_TIMEOUT_MS,
.latency = LATENCY_STD,
.epcount = ARRAY_SIZE(bas_eps),
.eps = bas_eps },
#endif
...
{
.drv = DRV_ICARUS,
.name = "BLT",
.ident = IDENT_BLT,
.idVendor = IDVENDOR_FTDI,
.idProduct = 0x6010,
//.iProduct = "Dual RS232-HS",
.iProduct = "USB <-> Serial Cable",
.kernel = 0,
.config = 1,
.interface = 1,
.timeout = ICARUS_TIMEOUT_MS,
.latency = LATENCY_STD,
.epcount = ARRAY_SIZE(ftdi2232h_eps),
.eps = ftdi2232h_eps },
Я в эту структуру добавил описатель своего USB-to-COM преобразователя FTDI-2232H. Теперь, если cgminer обнаружит устройство с VendorId/DeviceId = 0x0403:0x6010, то он попробует работать с этим устройством, как с платой Icarus, хоть она таковой и не является.
Дальше смотрим файл driver-icarus.c и тут есть функция icarus_detect_one:
static bool icarus_detect_one(struct libusb_device *dev, struct usb_find_devices *found)
{
int this_option_offset = ++option_offset;
struct ICARUS_INFO *info;
struct timeval tv_start, tv_finish;
/* Blakecoin detection hash
N.B. golden_ob MUST take less time to calculate than the timeout set in icarus_open()
0000007002685447273026edebf62cf5e17454f35cc7b1f2da57caeb008cf4fb00000000dad683f2975c7e00a8088275099c69a3c589916aaa9c7c2501d136c1bf78422d5256fbaa1c01d9d1b48b4600000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
{ midstate, data } = { 256'h553bf521cf6f816d21b2e3c660f29469f8b6ae935291176ef5dda6fe442ca6e4, 96'hd1d9011caafb56522d4278bf };
*/
const char golden_ob[] =
// "553bf521cf6f816d21b2e3c660f29469"
// "f8b6ae935291176ef5dda6fe442ca6e4"
// "00000000000000000000000000000000"
// "00000000d1d9011caafb56522d4278bf";
//-----------
"a8c369073d7dc0a63168f5fcf0246e4f"
"eb916bda12787ad1607d2303186ed8f1"
"00000000000000000000000000000000"
"0142b9a0e7b4001cf8b35852a3accab0";
const char golden_nonce[] = "0142b9b1"; //"000187a2";
const uint32_t golden_nonce_val = 0x0142b9b1; //0x000187a2;
unsigned char ob_bin[64];
unsigned char nonce_bin[ICARUS_READ_SIZE];
char *nonce_hex;
int baud, uninitialised_var(work_division), uninitialised_var(fpga_count);
struct cgpu_info *icarus;
int ret, err, amount, tries;
bool ok;
char tmpbuf[256]; //lancelot52
unsigned char* wr_buf = ob_bin;
int bufLen = sizeof(ob_bin);
icarus = usb_alloc_cgpu(&icarus_drv, 1);
if (!usb_init(icarus, dev, found))
goto shin;
usb_buffer_enable(icarus);
get_options(this_option_offset, icarus, &baud, &work_division, &fpga_count);
hex2bin(ob_bin, golden_ob, sizeof(ob_bin));
tries = 2;
ok = false;
while (!ok && tries-- > 0) {
icarus_initialise(icarus, baud);
err = usb_write_ica(icarus, (char *)wr_buf, bufLen, &amount, C_SENDTESTWORK);
if (err != LIBUSB_SUCCESS || amount != bufLen)
continue;
memset(nonce_bin, 0, sizeof(nonce_bin));
ret = icarus_get_nonce(icarus, nonce_bin, &tv_start, &tv_finish, NULL, 500);
Смысл такой. Программа передает плате заведомо известное задание на поиск хэша, причем в задании сказано с какого нонсе начинать вычисление и это нонсе немного меньше настоящего GOLDEN nonce. Таким образом, плата начнет считать с указанного места и буквально сразу в считанные доли секунды наткнется на GOLDEN nonce и вернет его. Программа тут же получит этот результат, сравнит его с правильным ответом и сразу становится понятно — это действительно тот HW майнер с которым можно работать или нет.
И вот тут была ужасная проблема — в проекте есть патчи на языке C, есть тестовая программа на питоне и тестбенч для FPGA.
В патчах на C тестовые данные выглядят вот так:
1) патч для cgminer-3.1.1
const char golden_ob[] =
"553bf521cf6f816d21b2e3c660f29469"
"f8b6ae935291176ef5dda6fe442ca6e4"
"00000000000000000000000000000000"
"00000000d1d9011caafb56522d4278bf";
const char golden_nonce[] = "00468bb4";
const uint32_t golden_nonce_val = 0x00468bb4;
1) патч для cgminer-3.4.3
const char golden_ob[] =
"553bf521cf6f816d21b2e3c660f29469"
"f8b6ae935291176ef5dda6fe442ca6e4"
"00000000000000000000000000000000"
"00000000d1d9011caafb56522d4278bf";
const char golden_nonce[] = "000187a2";
const uint32_t golden_nonce_val = 0x000187a2;
И что тут правильно, а что нет? Исходные данные одинаковые, а golden nonce объявлен разным!!! Парадокс… (заранее скажу, что в патче для cgminer-3.4.3 ошибка — нонсе 0x000187a2 не верный, а сколько времени я на это потратил..)
В проекте есть тестовая программа на питоне, которая читает текстовый файл, извлекает из него данные и передает в плату через последовательный порт… Там тестовые данные вот такие:
0000007057711b0d70d8682bd9eace78d4d1b42f82da7d934fac0db4001124d600000000cfb48fb35e8c6798b32e0f08f1dc3b6819faf768e1b23cc4226b944113334cc45255cc1f1c085340967d6c0e000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
0000007057711b0d70d8682bd9eace78d4d1b42f82da7d934fac0db4001124d6000000008fa40da64f312f0fa4ad43e2075558faf4e6d910020709bb1f79d0fe94e0416f5255cc521c085340df6b6e01000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
0000007095696e4529ae6568e4b2a0057a18e82ccf8d370bf87e358900f8ab5000000000253c6078c7245036a36c8e25fb2c1f99c938aeb8fac0be157c3b2fe34da2fa0952587a471c00fa391d2e5b02000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
000000704445e0446fcf2a84c47ce7305722c76507ba74796eaf39fe0007d44d00000000cac961f63513134a82713b172f45c9b5e5eea25d63e27851fac443081f453de1525886fe1c01741184a5c70e000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070a3ac7627ca52f2b9d9a5607ac8212674e50eb8c6fb1219c80061ccd500000000ed5222b4f77e0d1b434e1e1c70608bc5d8cd9d363a59cbeb890f6cd433a6bd8d5258a0141c00b4e770777200000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
000000706c90b789e84044d5be8b2fac01fafe3933ca3735269671e90043f8d900000000d74578c643ab8e267ab58bf117d61bb71a04960a10af9a649c0060cdb0caaca35258b3f81c00b4e7b1b94201000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070171d2644781cccf873ce3b6e54967afda244c47fc963bb240141b4ad00000000d56c4fbdc326e8f672834c8dbca53a087147fe0996d0c3a908a860e3db0589665258da3d1c016a2a14603a0a000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070d03c78cb0bb0b41a5a2c6ce75402e5be8a705a823928a5640011110400000000028fb80785a6310685f66a4e81e8f38800ea389df7f16cf2ffad16bb98e0c4855258dda01c016a2ae026d404000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
0000007091a7eef446c4cb686aff8908ab5539d03a9ab2e975b9fe5700ed4ca9000000000f83bb385440decc66c10c0657fcd05f94c0bc844ebc744bba25b5bc2a7a557b5258e27c1c016a2a6ce1900a000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
00000070856bd0a3fda5dac9ede45137e0c5648d82e64fbe72477f5300e96aec0000000026ca273dbbd919bdd13ba1fcac2106e1f63b70f1f5f5f068dd1da94491ed0aa45258e51b1c017a7644697709000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000
Ну то есть совершенно другие! Потом я уже понял, что это не те даннае, что посылаются в плату, из этих только извлекаются данные, специальным образом конвертируются в задание и отсылаются в плату.
Но все равно, среди этих тестовых данных для программы на питоне НЕТ задания похожего на то, которое описано в программе на C!!!
Ну хорошо, тогда смотрю тестовую программу-тестбенч на verilog:
blakeminer #(.comm_clk_frequency(comm_clk_frequency)) uut
(clk, RxD, TxD, led, extminer_rxd, extminer_txd, dip, TMP_SCL, TMP_SDA, TMP_ALERT);
// TEST DATA (diff=1) NB target, nonce, data, midstate (shifted from the msb/left end) - GENESIS BLOCK
reg [415:0] data = 416'h000007ffffbd9207ffff001e11f35052d554469e3171e6831d493f45254964259bc31bade1b5bb1ae3c327bc54073d19f0ea633b;
// ALSO test starting at -1 and -2 nonce to check for timing issues
// reg [415:0] data = 416'h000007ffffbd9206ffff001e11f35052d554469e3171e6831d493f45254964259bc31bade1b5bb1ae3c327bc54073d19f0ea633b;
// reg [415:0] data = 416'h000007ffffbd9205ffff001e11f35052d554469e3171e6831d493f45254964259bc31bade1b5bb1ae3c327bc54073d19f0ea633b;
reg serial_send = 0;
wire serial_busy;
reg [31:0] data_32 = 0;
reg [31:0] start_cycle = 0;
serial_transmit #(.comm_clk_frequency(comm_clk_frequency), .baud_rate(baud_rate)) sertx (.clk(clk), .TxD(RxD), .send(serial_send), .busy(serial_busy), .word(data_32));
Здесь есть предполагаемый пакет данных, который плата должна принять. Но опять этот предполагаемый пакет данных никак не похож на пакет данных в программе на C или на данные для тестовой программы на питоне.
Вот это отсутствие общих тестовых данных для программы на питоне, C и Verilog очень сильно портит картину. Получается, что между компонентами как бы нет общих точек соприкосновения, общих тестов и это печально.
Вообще, в верилог проекте blakecoin майнера было скрыто еще одно форменное издевательство над моим организмом.
Если проводить симуляцию проекта с verilog тестбенчем, то в симуляторе с вот этими тестовыми данными 416'h000007ffffbd9207ffff001e11f35052d5544… замечательно находится и возвращается результат GOLDEN nonce.
Потом проект компилирую для реальной FPGA платы, эти же самые данные подаю из программы на питоне и… плата не находит GOLDEN nonce…
Оказывается, что тестовые данные в verilog тестбенче «немного плохие». Они для низкой сложности, когда в результирующем хэше всего 24 ведущих нуля, а не 32, как требуется.
В файле experimental/LX150-FourPiped/BLAKE_CORE_FOURPIPED.v есть вот такой код
reg gn_match_d = 1'b0;
always @(posedge clk)
`ifndef SIM
gn_match_d <= (IV7 ^ b76 ^ d74) == 0;
`else
gn_match_d <= (IV7[23:0] ^ b76[23:0] ^ d74[23:0]) == 0;
`endif
В Verilog симуляторе проверяется не так, как будет будет работать в железе! То есть для реальной FPGA платы будем проверять на 32 бита ведущих нулей, а в симуляции будем проверять только 24 бита. Это просто прелестно. Хочется побить автора.
Я конечно, все это победил. По крайней мере, тестовая программа на питоне выдает бодрые сообщения:
Ладно, что в результате? Сколько намайнил? К сожалению нисколько.
Как только я был уже готов начать майнить, буквально в конце января сложность блейка сильно возросла:
Теперь я мог оставить на сутки плату и она хоть и находила решения, но их не принимал пул — все еще мало ведущих нулей.
Я пробовал переключиться на другую валюту — VCASH. С этой валютой пул хотя бы иногда выдавал мне бодрящие сообщения вроде вот этого:
Но все равно и VCASH пул ничего не начисляет. Печаль-беда.
Пользуясь случаем хотел бы спросить у знающих людей. Вот у меня есть видеокарта Nvidia 1060. Она выдает 1,25GHash/sec на блейкоине и за час два-три раза выдает nonce, который принимает пул (и начисляет копеечку). Я думал, что если моя FPGA плата считает 360MHash/sec, ну то есть примерно в 3 раза хуже, чем видеокарта, то я за два часа получу хотя бы один нонсе принятый пулом. Однако, этого не происходит. Даже за сутки нет ни одной копеечки… Где тут подвох для меня так и осталось загадка…
Сейчас я на досуге пытаюсь понять можно ли как-то оптимизировать имеющийся FPGA проект, скажем задействовать встроенную память или еще что-то. Может быть, если повезет, что-то и придумаю.
Автор: nckma