Иногда лучший способ чему-то научиться, будь то ПЛИС, модель ИИ или простейшая логическая задача — это позволить технологии научить саму себя.

Однажды мне захотелось запустить нейронную сеть на ПЛИС, и это стало моим первым опытом работы с тем и другим. К счастью, у меня был доступ к Anthropic API, что позволило воспользоваться помощью LLM-сервиса в создании нейронной сети XOR для ПЛИС.

На картинке выше приведена макетка с Arduino MKR Vidor 4000 и мигающими светодиодами. Два левых светодиода отображают входные значения, а правый крайний светодиод показывает результат операции XOR.

Первые шаги

До этого у меня не было абсолютно никакого опыта работы с ПЛИС. Запуск базового примера «Hello World» с мигающим светодиодом стал хорошим началом и придал мне уверенности для решения более сложной задачи: использования языковой модели при написании кода Verilog для нейронной сети XOR.

Почему XOR?

Реализация вентиля XOR с помощью нейронной сети может показаться излишеством. Для этой базовой функции цифровой логики достаточно нескольких логических элементов. Тем не менее, в изучении ИИ эта задача может послужить идеальной отправной точкой: небольшая, контролируемая и с легко предсказуемыми результатами.

Моделируем на Verilator

Компиляция кода для ПЛИС представляет из себя нечто вроде работы с чёрным ящиком. Здесь может помочь инструментарий Verilator. Он преобразует код Verilog в C++, что позволяет выполнять его симуляцию на компьютере перед развёртыванием на ПЛИС. Этот шаг оказался особенно кстати при работе с сигмоидальной функцией. Вместо того чтобы вычислять результат операции XOR напрямую, LLM сгенерировала его методом кусочно-линейной аппроксимации с использованием LUT (таблиц поиска).

XOR Neural Network Test Results
================================
Input: (0.000, 0.000) -> Output: 1.000 (Expected: 0.000) - FAIL [13 cycles]
Input: (0.000, 1.000) -> Output: 1.000 (Expected: 1.000) - PASS [13 cycles]
Input: (1.000, 0.000) -> Output: 1.000 (Expected: 1.000) - PASS [13 cycles]
Input: (1.000, 1.000) -> Output: 1.000 (Expected: 0.000) - FAIL [13 cycles]

Обучение модели

С помощью языка Python и пакета NumPy я обучил простую модель, чтобы получить значения вес—смещение для нейронной сети XOR. Затем они были помещены в код Verilog, чтобы нейросеть могла правильно подбирать значения XOR.

Epoch 0, Error: 0.4808
Epoch 1000, Error: 0.0653
...
Epoch 9000, Error: 0.0158
Trained weights in fixed-point format:
// Hidden weights:
w_hidden[0] = 16'h00DE;  // 0.870
w_hidden[1] = 16'hFFAB;  // -0.335
...
w_hidden[15] = 16'hFE8D;  // -1.451
// Hidden biases:
b_hidden[0] = 16'hFFFC;  // -0.018
b_hidden[1] = 16'h0058;  // 0.344
...
b_hidden[7] = 16'h009D;  // 0.615
// Output weights:
w_output[0] = 16'hFF97;  // -0.412
w_output[1] = 16'h015B;  // 1.358
...
w_output[7] = 16'hFDCF;  // -2.194
// Output bias:
b_output = 16'h01EE;  // 1.933
Testing:
Input: [0 0] -> Output: 0.011 (Expected: 0)
Input: [0 1] -> Output: 0.984 (Expected: 1)
Input: [1 0] -> Output: 0.985 (Expected: 1)
Input: [1 1] -> Output: 0.017 (Expected: 0)

Quartus Prime

Quartus Prime — это ПО разработки и программирования ПЛИС. В нем имеются инструменты для логического синтеза, компоновки, маршрутизации и моделирования. Бесплатная версия Lite хорошо подходит для этого небольшого проекта.

На скриншоте показана сигмоидальная функция. При работе с ПЛИС числа часто отображаются в формате вида 16’h0400, который представляет собой 16-разрядное значение, где «h» означает «шестнадцатеричное».

Конечный автомат

Приведенный ниже конечный автомат координирует операции в ПЛИС, определяя последовательность вычислений для получения результата XOR. Он переходит из одного состояния в другое, например из состояния «ожидание» в состояние «вычисление», «применение сигмоиды» и «вывод результата», обеспечивая выполнение вычислений в правильном порядке.

// State machine
always @(posedge clk or posedge rst) begin
	if (rst) begin
		state <= IDLE;
		done <= 0;
		y_out <= 0;
		acc_idx <= 0;
		mult_result <= 0;  // Initialize this too
	end else begin
		case (state)
			IDLE: begin
				done <= 0;
				acc_idx <= 0;
				if (start) begin
					state <= COMPUTE_HIDDEN;
				end
			end
			COMPUTE_HIDDEN: begin
				// ... computation logic ...
				if (acc_idx == HIDDEN_LAYER_SIZE) begin
				   state <= APPLY_SIGMOID;
				end
			end
			APPLY_SIGMOID: begin
				// ... apply sigmoid function ...
				state <= OUTPUT_RESULT;
			end
			OUTPUT_RESULT: begin
				// ... output result ...
				state <= IDLE;
			end
		endcase
	end
end

Совместная работа MCU и FPGA

На последнем этапе микроконтроллер SAMD21 платы Arduino должен был взаимодействовать с ПЛИС. Обратите внимание, что сигнал OUT_Y_PIN по отношению к микроконтроллеру действует как входное значение, которое задается соответствующим выходом ПЛИС:

// ... setup() function ...
pinMode(INPUT_X1_PIN, OUTPUT);
pinMode(INPUT_X2_PIN, OUTPUT);
pinMode(START_PIN, OUTPUT);
pinMode(OUT_Y_PIN, INPUT);
// Start the computation on the FPGA
digitalWrite(START_PIN, HIGH);
delay(100);
digitalWrite(START_PIN, LOW);
 
// ... loop() function ...
digitalWrite(INPUT_X1_PIN, inputPattern & 0x01);
digitalWrite(INPUT_X2_PIN, (inputPattern & 0x02) >> 1);
...
int xorResult = digitalRead(OUT_Y_PIN);```
 

Резюме

В этом пусть небольшом проекте мне удалось реализовать свое первое решение на ПЛИС. Сервис LLM Anthropic помог мне создать весь код для нейронной сети XOR, моделирования, обучения и интеграции с Arduino. Поначалу код оказался неидеален, но послужил хорошей отправной точкой. Благодаря большему количеству взаимодействий «мы» (ИИ Anthropic и я) нашли решение.
Моделирование проекта с помощью Verilator перед развертыванием помогает выявить и устранить потенциальные проблемы на ранних этапах. Хотя вентиль XOR в виде нейросети может показаться излишеством, это послужит отличным введением в ИИ и программирование ПЛИС и может открыть дверь для более сложных приложений.

Полный код, включая реализацию на Verilog и интеграцию с Arduino, доступен в моем репозитории GitHub: FPGA_XOR_NN.

Комментарии (1)


  1. longtolik
    06.07.2025 15:14

    Спасибо за статью. Попробую, когда будет время.

    Когда-то мигал светодиодом на Vidor. Там ещё надо было бит-файл преобразовывать перед загрузкой.

    Хорошая плата, но вот что-то она у них не пошла...