Как стать автором
Обновить
533.32
YADRO
Тут про железо и инженерную культуру

Из студентов в инженеры: как перестать бояться и полюбить системную верификацию

Уровень сложностиПростой
Время на прочтение15 мин
Количество просмотров687

Привет, Хабр! На связи Михаил Степанов, инженер в группе функциональной верификации YADRO. Еще в прошлом году мы с моим коллегой Романом Казаченко участвовали в хакатоне по разработке микропроцессоров как студенты, а сейчас — помогаем с задачами для SoC Design Challenge как сотрудники компании-организатора. В статье расскажем, что ждет участников трека «Системная верификация СнК» в этом году и как подготовиться к этому испытанию.

Если вы не планируете участвовать в хакатоне, но вам интересно, как инженеры тестируют системы на кристалле перед запуском в производство, эта статья тоже будет вам полезна. На примере заданий хакатона я кратко объясню, что такое системная верификация, из каких блоков состоят СнК и какие инструменты используются для их тестирования.

Инженерный хакатон SoC Design Challenge от НИУ МИЭТ и YADRO пройдет 18–20 апреля. Если вы студент очной формы обучения, то проверить свои знания и получить новые навыки можно в треках «Топологическое проектирование», «UVM-верификация», «Системная верификация СнК» и «RTL-проектирование». Хакатон — это отличная возможность пообщаться со специалистами «железной» индустрии, продемонстрировать им свои знания и получить классные подарки. Присоединяйтесь, заявки принимаются до 18 марта.

Как попасть на хакатон

В прошлом году мы с Ромой были студентами выпускного курса «Информатика и вычислительная техника» в ИТМО и решили испытать себя на хакатоне SoC Design Challenge. Тогда у нас было немного опыта в разработке системного ПО, поэтому мы выбрали трек «Системное программирование». В этом году его переименовали в «Системную верификацию СнК», что точнее отражает суть задания.

Путь истового инженера начинается с несложного отборочного теста. Обычно, чтобы его успешно пройти, достаточно знаний на уровне второго и третьего курсов. Самое сложное — успеть ответить на все вопросы за отведенные четверть часа. В интернете искать ответы можно, но мы не рекомендуем — можете впустую потратить драгоценные минуты.

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

Я на крыльце НИУ МИЭТ
Я на крыльце НИУ МИЭТ

Если вы пройдете отбор, то вас пригласят на основной этап в Зеленоград и поселят в классном отеле с трехразовым питанием. На решение кейса у вашей команды будет три дня. Первые два дня работать придется с утра до вечера, можете ориентироваться на восемь часов в день. В последний день времени будет меньше из-за подведения итогов. Рекомендуем быть готовыми трудиться над заданием по вечерам, но не в ущерб сну. Отдых — залог эффективной работы и вашей победы.

На SoC Design Challenge 2025 несколько треков с соответствующими заданиями: «Топологическое проектирование», «UVM-верификация», «Системная верификация СнК» и «RTL-проектирование». Описания заданий с прошлогоднего хакатона можно посмотреть в анонсе хакатона. В этой статье я делюсь опытом нашей команды в треке по системной верификации и даю подробные советы по работе с инструментами, которые пригодятся вам в этом году. Также, как участник процесса подготовки заданий, рассказываю об изменениях по сравнению с прошлым годом.

Про задание трека «Системная верификация СнК»

Нам нужно было провести верификацию аппаратного блока в косимуляционном окружении. Давайте разберемся, что это значит.

Системы на кристалле состоят из логических и функциональных блоков. Такое деление позволяет независимо разрабатывать разные части системы, а затем модульно отлаживать их в изолированном окружении. Функциональная верификация — это процесс тестирования таких блоков по спецификации. Она проводится перед отправкой чипа на завод и называется pre-silicon стадией. Без этой процедуры велик шанс получить нерабочий чип, причем в объемах всей произведенной партии.

Проводить тесты можно при помощи таких инструментов, как:

  • Функциональные симуляторы (например, QEMU) — работают очень быстро, но в отличие от других инструментов не предоставляют достоверной информации о работе устройства. Зато QEMU может почти молниеносно осуществить подачу требуемых данных и выдать результат в виде «прошел» или «не прошел», а также некоторые логи.

  • Потактовые симуляторы (например, VCS) — у них есть внутренняя система планирования времени, но скорость работы низкая. Они нужны для сбора трассировочных файлов, по которым можно восстановить состояние любого регистра и провода в любой момент.

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

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

На хакатоне участникам предложили поработать с небольшим DUT-устройством (Device Under Test): UART 16650. Нам нужно было найти спецификацию блока, внимательно ее изучить и составить тестовый план, который покрывает все основные главы спецификации. После составления тестового плана и написания тестов нужно прогнать их в двух разных конфигурациях интерфейса:

  • c обратной петлей — UART соединен сам с собой,

  • с подключением к термодатчику — интерфейс подключен к устройству, взаимодействие с которым также можно протестировать.

Успеть за три дня

Задание сначала может показаться сложным и не очень понятным. Но если грамотно разбить его на подзадачи, а затем постепенно двигаться к решению, то вам вполне хватит трех дней на успешную реализацию.

В первый день мы решили засесть за изучение материалов и спецификаций, а не бросаться судорожно писать тесты. Самой большой проблемой для нас тогда был страх перед непонятным и незнакомым заданием. Но он быстро прошел — заботливые инженеры YADRO снабдили нас исчерпывающей документацией. В ней описано, что и как устроено, а также в каком порядке выполнять работу. 

В общем, не старайтесь в первый день стать самыми быстрыми системными программистами на хакатоне. Как показала практика — медленный, но осознанный подход приводит к лучшим результатам в ограниченное время. Так проще выходить из тупиков, в которых вы наверняка окажитесь, а их может быть много.

Дмитрий Кишко, член жюри и руководитель группы системной верификации в YADRO, проводит вводный инструктаж для участников трека «Системное программирование»
Дмитрий Кишко, член жюри и руководитель группы системной верификации в YADRO, проводит вводный инструктаж для участников трека «Системное программирование»

Если на какую-то подзадачу вы безрезультатно потратили много времени, то попросите помощи у менторов. Штрафные баллы за это не начислят, а вы быстрее выйдите из сложной ситуации.

Отметим, что задание и исходный код могут быть неидеальны. Мы, например, нашли несоответствие спецификации RTL-коду. Но не стоит сразу же писать об этом во всех чатах хакатона — лучше несколько раз проверить найденную ошибку и записать ее в отчет по верификации.

Поспешайте медленно

Правильно презентовать результаты перед жюри так же важно, как и решить саму задачу. В некоторых заданиях нужно предоставить результат в конкретном формате, например:

  • корректно сформировать план тестирования,

  • прогнать все тесты через CI,

  • нарисовать все нужные схемы и таблицы,

  • добавить текстовое пояснение к каждому тесту.

Не рекомендуем гнаться за объемом написанного кода и количеством тестов — можете не успеть привести код в порядок и потерять заветные баллы. 

Распределите роли в команде, а работу разбейте на микроспринты. Задач и аспектов много, их надо учитывать на каждом этапе выполнения: поработали, обменялись кодом, провели кросс-ревью, позапускали на регрессе, записали проблемы и баги, задали вопросы ментору, повторили.

Я и Рома на хакатоне
Я и Рома на хакатоне

Залог вашего успеха — баланс скорости и осознанности. Если наброситься на выполнение заданий, то можно упустить важные моменты и прийти к финишу в аутсайдерах после спринтерского старта. Однако излишняя медлительность может заставить вас потратить время на задачи, которые для победы попросту не нужны.

На этом мы заканчиваем с вьетнамскими флешбэками нашим опытом на SoC Design Challenge 2024 и переходим к хакатону этого года, но уже с другой стороны — специалистов, которые помогают организаторам. Спойлеров не будет, а вот о критериях оценки ваших работ и инструментах с конкретными примерами кода с удовольствием расскажем.

Что будет на SoC Design Challenge 2025

Расскажем, что ждет участников трека «Системная верификация СнК» в апреле. Вы снова будете работать с DUT, для которого нужно провести верификацию. Но на этот раз не UART. А что именно — узнаете на самом хакатоне, всех секретов раскрывать не будем. Для DUT нужно будет разработать тестовый план и сами тесты.

В чем отличия от задания прошлого года:

  • Участникам больше не придется копаться в самом RTL-коде. Мы решили, что концепция «черного ящика» лучше подходит под формат проведения. К тому же не все участники в прошлом году были хорошо знакомы с SystemVerilog, что стало для них дополнительной сложностью.

  • Само задание не будет запускаться с целью проверки, что все работает. Мы планируем намеренно добавить некоторое количество неисправностей, чтобы участники выявили ситуации, в которых эта неисправность проявляется, и описали ее.

  • Зафиксированных тестовых данных не будет. Участники должны сами разобраться в том, как лучше представить входные данные и подготовить несколько шаблонов.

Надеемся, что изменения сделают хакатон еще более привлекательным для молодых специалистов.

Как подготовиться к хакатону

Далее поделимся критериями оценки работ участников, а также подробно разберем инструменты QEMU и Verilator, без которых на хакатоне не обойтись. Устраивайтесь поудобнее, будет много кода.

На что обращают внимание судьи

Как судьи выбирают победителей — наверное, самая важная информация. Поделимся основными критериями оценки:

  • Качество кода. Здесь будут приветствоваться проверка с помощью статических анализаторов, логичные и понятные названия переменных и функций, лаконичность самого кода и интересные входные данные.

  • Тестовое покрытие. Судьи соберут процент покрытых строчек кода с DUT и узнают, насколько хорошо вы справились с написанием тестов.

  • Количество найденных ошибок. Как уже говорилось, мы намеренно добавим несколько ошибок. Чем больше ошибок вы найдете, тем больше баллов получите.

  • Тестовый план. Он должен быть подробным и понятным.

Обзор инструментов с примерами кода: QEMU и Verilator

Мы также решили немного изменить нашу систему косимуляции. В распоряжении участников будет два инструмента: QEMU и Verilator. Займемся разработкой небольшой библиотеки rtl-bridge для Verilator — она будет отвечать за коммуникацию двух частей системы. Давайте разберемся, что это за инструменты.

QEMU

В косимуляции QEMU в основном нужен для управления тестом: передавать и считывать данные, а также проверять результаты на корректность.

Компания Xilinx уже задумывалась о создании подобных систем косимуляции, поэтому они разработали Remote Port — протокол передачи данных через сокеты. Чтобы этот протокол работал в реальных условиях, Xilinx создала свою версию QEMU и устройство с поддержкой взаимодействия по Remote-Port. Именно ее мы возьмем для небольшого примера.

Для начала надо скачать и собрать QEMU из официального репозитория. Процесс описан в файле README, поэтому подробно разбирать его не будем. Отметим, что не обязательно собирать все таргеты QEMU. Для это во время конфигурирования замените команду на такую:

configure --target-list=riscv32-softmmu,riscv64-softmmu

Рекомендуем использовать версию g++ 11.4.0, так как на более старых версиях могут возникнуть проблемы со сборкой.

Выбираем машину для запуска будущего теста с помощью команды:

qemu-system-riscv32 --machine ?

Получаем такой список:

Поскольку мы проводим косимуляцию, выбираем virt-cosim.

Напишем простой тест, чтобы отправить строчку «Hello world!» на сторону RTL-кода. Мы планируем писать bare metal-тест, который запускается на «голом железе», то есть без операционной системы. Поэтому нужно узнать регистры и области памяти, в которые будет записываться информация.

Сначала нам нужны регистры UART, чтобы тест мог вывести информацию в консоль QEMU. Открываем файл /hw/riscv/virt.c и находим таблицу virt_memmap. В ней указаны адреса, по которым находятся виртуальные устройства:

UART находится по адресу 0x10000000. Затем ищем вхождение строки «UART» в файле и находим следующий блок кода. Он показывает, что это устройство соответствует спецификации ns16650a:

Документацию к устройству можно прочитать самостоятельно, поэтому приводим уже готовый код:

#define UART0_BASE 0x10000000
#define REG(base, offset) ((*((volatile unsigned char *)(base + offset))))
#define UART0_DR    REG(UART0_BASE, 0x00)
#define UART0_FCR   REG(UART0_BASE, 0x02)
#define UART0_LSR   REG(UART0_BASE, 0x05)
																						
#define UARTFCR_FFENA 0x01                 // UART FIFO Control Register enable bit
#define UARTLSR_THRE 0x20                   // UART Line Status Register Transmit Hold Register Empty bit
#define UART0_FF_THR_EMPTY (UART0_LSR & UARTLSR_THRE)

void uart_putc(char c) {
  while (!UART0_FF_THR_EMPTY);            // Ждем, пока UART не освободится
  UART0_DR = c;                                          // Пишем символ в регистр UART
}

void uart_puts(const char *str) {
  while (*str) {                                                 // Цикл для всех символов строки
    uart_putc(*str++);                                      // Пишем очередной символ
  }
}

Функция uart_putc нужна для вывода в консоль одного символа, а uart_puts — для вывода строки.

Теперь пишем тело теста. Для этого выясним, по каким адресам расположено косимуляционное устройство. В таблице virt_memmap указано, что устройство находится по адресу 0x28000000. Тело нашего теста выглядит так:

void write_mem_32(int addr, int value) {
	volatile int *const __ptr = (volatile int *)(addr);
	*__ptr = value;
};

int read_mem_32(const int addr) {
	const volatile int *const __ptr = (const volatile int *)(addr);
	const int __value = *__ptr;
	return __value;
};


void main(){
    UART0_FCR = UARTFCR_FFENA;
    uart_puts("main start\n");
    const char *str = "Hello World!";
    while(*str) {
		uart_puts("Send char: "); uart_putc(*str); uart_putc('\n');
		write_mem_32(0x28000000, *str++);
    }
    write_mem_32(0x28000004, 1);
    while (1);
}

Отметим три важных момента:

  • Мы добавили шаблонные функции для записи в память. Формально это не обязательно, но позволяет отделить использование обычной памяти от обращения к устройствам.

  • В конце присутствует бесконечный цикл, чтобы машина QEMU не завершала свое выполнение на некотором адресе — это может привести в непредвиденной ошибке. При желании вы можете организовать штатное завершение выполнения.

  • Перед бесконечным циклом мы пишем в адрес, отличный от адреса для записи строки. Эта условность нужна, чтобы мы могли завершить симуляцию.

Для сборки нам нужные специальные компиляторы, которые умеют собирать код под платформу RISC-V. Рекомендую использовать инструменты Syntacore.

Перед сборкой лучше написать код на ассемблере, чтобы установить корректный указатель на стек и линковочный файл для правильного расположения секций:

.global _start
.section .text._start

_start:
    la sp, __stack_top    
    add s0, sp, zero      
    jal zero, main        
loop:	j loop    
													
.section .data
.space 1024*8            
.align 16               
__stack_top:
DRAM_BASE = 0x80000000;
DRAM_SIZE = 0x20000000;

MEMORY {
	DRAM 	(rwx): ORIGIN = DRAM_BASE, LENGTH = DRAM_SIZE
}

SECTIONS
{
	. = DRAM_BASE;
  	.text : {
    	KEEP(*(.text._start));
		*(.text*);
  	}
	. = ALIGN (CONSTANT (COMMONPAGESIZE));
	.data : { 
		*(.data*)
	}
}

Пришло время заняться сборкой с помощью инструментов от Syntacore. Makefile будет состоять из четырех этапов:

  1. Сборка кода на C.

  2. Сборка ассемблера.

  3. Компоновка всего этого добра в elf-файл.

  4. Запуск самого QEMU.

.PHONY: all QEMU dir
all: dir QEMU

dir:
	mkdir build/

build/test.o: src/test.c
	riscv64-unknown-elf-gcc -c -g -O0 -Iinclude/ -ffreestanding -march=rv32i -mabi=ilp32 -o build/test.o src/test.c

build/start.o: asm/start.s
	riscv64-unknown-elf-as -g -march=rv32i -mabi=ilp32 -o build/start.o asm/start.s

build/hello.elf: build/test.o build/start.o
	riscv64-unknown-elf-ld -T ld/baremetal.ld -m elf32lriscv -o build/hello.elf build/test.o build/start.o

QEMU: build/hello.elf
	${XILINX_QEMU}/QEMU-system-riscv32 -M virt-cosim -chardev socket,id=cosim,path=/tmp/cosim.sock,server=on -nographic -machine-path /tmp/machine-riscv32 -bios build/hello.elf

Разберемся, как запустить QEMU:

  1. Укажем cosim-машину, чтобы создалось устройство cosim.

  2. В качестве используемого сокета укажем UNIX-сокет по пути /tmp/cosim.sock.

  3. Укажем директорию для создания записей о работе машины.

  4. Выключим графику.

  5. Передадим elf-файл под видом BIOS. 

Поясним последний пункт: в линковочном файле указан 0x80000000 — это стандартный адрес расположения BIOS. Поэтому передаем elf-файл под видом BIOS. Если мы захотим запустить программу на другом адресе, сначала запустится OpenSBI, который проверит подключенные устройства и передаст управление прикладной функции. Тогда нужно указать другой адрес и запускать QEMU с параметром loader.

Verilator

Приступаем к созданию второй части системы. Для этого устанавливаем Verilator версии 5 и выше. Это потактовый симулятор для языка Verilog, которым можно управлять при помощи программы на C++.

Чтобы разобраться, как работает система Verilator, напишем такую программу:

#include "Vtestbench.h"
#include "verilated.h"

int main(int argc, char** argv) {
  VerilatedContext* contextp = new VerilatedContext;
  contextp->commandArgs(argc, argv);
  Vtestbench* top = new Vtestbench{contextp};
  while (!contextp->gotFinish()) {
    top->eval();
    contextp->time(top->nextTimeSlot());
  }
  delete top;
  delete contextp;
  return 0;
}

В программе создаем экземпляр класса, который отвечает за логику нашего Verilog-кода. Пока симуляция не закончится, в цикле производим подсчет новых значений регистров и проводов, а также двигаем время симуляции. Можно считать, что top->eval() передает управление коду на verilog, а contextp->time(top->nextTimeSlot()) изменяет значение clk.

Для написания testbench нужно понять концепцию DPI-функций. Фактически это функции, которые позволяют передавать данные и поток управления между кодом на C++ и кодом на Verilog. Такой подход позволит выполнять логику RTL-кода и при этом предоставлять интерфейс для Remote-Port.

Для косимуляции нам понадобится несколько функций, которые передают управление из C++ кода и обратно:

  • sv_write — функция из C++ сообщает Verilog, что пришла транзакция на запись,

  • c_write_resp — Verilog сообщает C++, что транзакцию обработали,

  • accept_transaction — Verilog опрашивает код на C++ на предмет пришедших транзакций,

  • sv_finish — функция из C++ завершает симуляцию.

Схема вызовов Verilog из кода C++ и обратно:

Теперь напишем Testbench (пока только базовую часть) и объявим все необходимые функции. Про особенности объявления DPI-функций можете почитать на официальном сайте Verilator.

`timescale 1ns / 1ps

import "DPI-C" context task c_write_resp;
import "DPI-C" context task accept_transaction;

module testbench;
	export "DPI-C" task sv_write;
	export "DPI-C" task sv_finish;
	reg rst_n;
	reg clk;

	always begin
		#5 clk= ~clk;
	end

Допишем часть опроса на наличие транзакций и сами DPI-функции:

initial begin
	rst_n = 0;
	clk = 0;
	#10;
	rst_n = 1;
end

task sv_write;
	input int addr;
	input int data;
	$display("Get on addr %d char %c", addr, data); // Получаем адрес и данные и выводим их в консоль
	c_write_resp();
endtask
	
always @(posedge clk) begin
	if (rst_n) begin
		accept_transaction();
	end
end

task sv_finish;
	$finish();
endtask

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

static struct {
    int value; // Значение
    bool enable; // Транзакция действительно пришла
    long addr; // Адрес
    volatile bool ping; // Для опроса, действительно ли транзакция была обработана
} transaction;

void sk_close() {
  shutdown(fd, SHUT_RDWR);
  close(fd);
}

void sk_connect() {
  int fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
  struct sockaddr_un addr_;
  addr_.sun_family = AF_UNIX;
  strcpy(addr_.sun_path, "/tmp/cosim.sock");
  connect(fd_, (sockaddr*)&addr_, sizeof(addr_));
  fd = fd_;
  std::thread(&readSerializer).detach();
}

В нашем случае readSerializer — это поток, который все время читает сокет и сохраняет полученные пакеты Remote-Port. Код этой функции выглядит так:

static void readSerializer() {
  while (1) {
    std::vector<uint32_t> packet;
    packet.resize(30, 0);
    if (readPacket((uint8_t*)packet.data())) {
      handleRequest(packet.data());
    }
  }
}

static int readSocket(uint8_t* data, size_t size) {
  int ret = read(fd, data, size);
  if (ret <= 0)
    exit(1);
  return be32toh(((uint32_t*)data)[1]);
}

static int readPacket(uint8_t* packet) {
  int ret = readSocket(packet, 20);
  readSocket(packet + 20, ret);
  uint32_t* p = (uint32_t*)packet;
  return ret;
}

Для обработки транзакций напишем функцию handleRequest. Рекомендуем почитать документацию к протоколу Remote-Port, чтобы разобраться с кодом. 

Обратите внимание, что все данные приходят в обратной последовательности байт, поэтому необходимо использовать функцию be32toh. Она возвращает байтам правильную последовательность. Также научимся обрабатывать sync-пакеты для корректной работы с QEMU.

Пример кода, который обрабатывает sync-, write- и read-пакеты:

void handleRequest(uint32_t* data) {
  switch (be32toh(data[0])) { // Проверяем поле command в пакете
    case 4:  // Если команда write
      onWriteOperation((be32toh(data[9]) << 32) + be32toh(data[10]),
                       ((uint32_t*)(((uint8_t*)data) + 2))[14]); // Сохраняем адрес и данные из пакета, ждем ответ от verilog
      data[1] = be32toh(be32toh(data[1]) - 4); // уменьшаем поле длины
      data[3] = be32toh(2); // Выставляем бит response
      writePacket(data); // Отправляем ответ
      break;
    case 6:  // Если команда sync
      data[3] = be32toh(2); // Выставляем бит response
      writePacket(data); // Отправляем ответ
      break;
    default:
      return;
  }
}
void onWriteOperation(long addr, int value) {
  transaction.value = value;
  transaction.addr = addr;
  transaction.ping = false;
  transaction.enable = true;
  while (!transaction.ping) {} // Ждем, пока не придет ответ от Verilog
}

void writeSocket(uint8_t* data, size_t size) { write(fd, data, size); }

void writePacket(uint32_t* data) {
  writeSocket((uint8_t*)data, 20 + be32toh(data[1]));

Осталось написать функцию опроса из Verilog, она совсем простая:

int accept_transaction() {
  if (transaction.enable) {
    transaction.enable = false;
      sv_write(transaction.addr, transaction.value);
  }
  return 0;
}

Доработаем main, чтобы он создавал сокет:

#include "Vtestbench.h"
#include "svdpi.h"
#include "verilated.h"

void sk_connect();
void sk_сlose();

int main(int argc, char** argv) {
  VerilatedContext* contextp = new VerilatedContext;
  contextp->commandArgs(argc, argv);
  Vtestbench* top = new Vtestbench{contextp};
  sk_connect();
  while (!contextp->gotFinish()) {
    top->eval();
    contextp->time(top->nextTimeSlot());
  }
  void sk_close();и
  delete top;
  delete contextp;
  return 0;
}

Теперь можно запускать тест в косимуляции. Приведу пример своего Makefile:

.PHONY: all clean sim

all: sim

TARGET = main
TOP_MODULE = testbench

BUILD_DIR = build
SIM_DIR = $(BUILD_DIR)/simulation
SRC_DIR = rtl-bridge

SRCS := $(shell find ./$(SRC_DIR) -name '*.cpp')

VERILATOR_FLAGS = --trace --exe --cc --build --timing -Wno-PINMISSING  -Wno-IMPLICIT -Wno-WIDTHEXPAND -Wno-INITIALDLY  -Wno-CASEINCOMPLETE -Wno-WIDTHTRUNC --Mdir $(SIM_DIR) 

TARGET = main

$(TARGET): $(SRCS) $(SRC_DIR)/$(TOP_MODULE).sv
	mkdir -p $(SIM_DIR)
	verilator $(VERILATOR_FLAGS) -o V$(TOP_MODULE) $(SRC_DIR)/$(TOP_MODULE).sv $(SRCS)
	make -C $(SIM_DIR) -f V$(TOP_MODULE).mk
	ln -sf $(SIM_DIR)/V$(TOP_MODULE) $(TARGET)

sim: $(TARGET) Makefile
	./$(TARGET)

clean:
	rm -rf $(BUILD_DIR) $(TARGET)

В окне с QEMU:

В окне с Verilator:

Общая схема полученной системы:

Ждем на хакатоне

Еще есть время разобраться с инструментами и поработать с исходниками, которые мы упомянули в статье. Всех студентов, которым интересно проверить свои силы и пообщаться с профессионалами рынка системного программирования, ждем на инженерном хакатоне SoC Design Challenge 2025. Главное — верить в свои силы и не пасовать перед вызовами. Желаем удачи!

Теги:
Хабы:
+16
Комментарии0

Публикации

Информация

Сайт
yadro.com
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
Россия
Представитель
Ульяна Соловьева