ESPNMMA pound-for-pound rankings: Natalia Silva steps up, stands out from crowdESPN DeportesMessi y Cristiano Ronaldo, diferentes hasta para irseThe Jerusalem PostYemen displacement surges past 200,000 following fresh Houthi offensive, UN migration body warnsColliderAlfred Hitchcock’s ‘Psycho’ Officially Returns to Alamo Drafthouse Theaters With Eric Nam [Exclusive]South China Morning PostSingapore’s air pollution worst in world as haze shuts Malaysia schoolsZDF heuteEntdecken Sie das ZDF-NachrichtenstudioAnime News NetworkManga Up! Global Adds Magical Girl Recruiter Puicho!, Bitter Knight in My Sweet Café, 3 More MangaThe RegisterWhen one datacenter is no longer enoughDeadlineToby Emmerich Joins FilmNation Entertainment’s Infrared As Consultant
The Daily Newsstand · Free, Always
Wednesday, October 7, 2026

Испытание абстракций: как чужая процессорная система RISC-V переехала на десятки отладочных плат

Translate

10 октября стартует новый сезон Школы синтеза цифровых схем. Её проведение рассредоточено по множеству независимых кластеров, в каждом из которых есть свой преподаватель, свои отладочные платы и САПР, и необходимо обеспечить выполнение всех примеров школы на этом зоопарке различных устройств таким образом, чтобы преподавателю не нужно было думать о проблеме вроде: “Этот код точно собирался у меня в BRAM, почему здесь он синтезируется в миллион регистров, сжирающих все ресурсы платы”.

Чтобы избежать подобных проблем был создан репозиторий basics-graphics-music (BGM), добавляющий примерам несколько слоёв абстракции, которые позволяют избежать подобных проблем.

Упор этого года будет сделан на архитектуру RISC-V. Для этого в репозиторий был добавлен пример многопоточной программы, а также несколько RISC-V ядер, включая ядро НИУ МИЭТ, проектируемое на курсе “Архитектуры процессорных систем”. Это стало отдельным испытанием существующих абстракций на прочность: данное ядро проектировалось независимо от репозитория BGM и его абстракций, поэтому его интеграция стала интересным опытом. Подробнее об этом под катом.

Часть I. Инфраструктура basics-graphics-music

Проблема, которую решает репозиторий: это сделать возможным запустить множество независимых проектов (примеров) на множестве независимых отладочных плат. У каждого примера свой набор входов и выходов, и у каждой платы есть свой набор периферии. Эти наборы могут входить друг в друга, либо пересекаться. Может конечно быть ситуация, когда эти множества не пересекаются, но это скорее вырожденный случай либо совсем голого кристалла, либо примера с узким набором специализированной периферии (вроде ps/2 и vga) которой может не быть на конкретной отладочной плате. Переписывать каждый из проектов под конкретную плату из набора — сизифов труд, ведь любую правку в исходном проекте придется позже разливать на все остальные подпроекты.

Репозиторий BGM решает эту проблему путем выделения трёх уровней иерархии, скреплённых одним контрактом.

Один пример, один контракт, десятки плат

Один пример, один контракт, десятки плат

Интеграция HDL-кода

На нижнем уровне абстракции находится файл board_specific_top.sv, располагающийся в директории boards/<board_name>. Это модуль верхнего уровня, написанный под конкретную плату. Он знает про эту плату всё: как называются пины, инвертированы ли светодиоды, какой частоты кварц, какова разрядность VGA — четыре бита на канал или пять, сколько доступно семисегментных индикаторов и есть ли они вообще.

Данный файл не зависит от примеров, которые будут на нём запускаться и всё что нужно сделать для добавления поддержки новой платы — это описать новый board_specific_top (в контексте HDL-кода).

Независимость данного файла от запускаемых примеров обеспечивается файлом центрального уровня абстракции: lab_top.sv. Этот файл и является тем контрактом, который приводит порты конкретного проекта и порты конкретной платы к единому интерфейсу. На ПЛИС может не быть конкретных пинов микрофонного входа, но могут быть GPIO, в этом случае в board_specific_top будет инстанциирован экземпляр i2s-модуля, который будет принимать данные с заданных GPIO-пинов и передавать их в виде микрофонного сигнала на внутренний провод mic, который подсоединён к одноимённому порту экземпляра модуля lab_top.

Прототип модуля lab_top:

module lab_top
# (
    parameter clk_mhz = 50, w_key = 4, w_sw = 8, w_led = 8, w_digit = 8,
              w_gpio = 100, screen_width = 640, screen_height = 480,
              w_red = 4, w_green = 4, w_blue = 4,
              w_x = $clog2 (screen_width), w_y = $clog2 (screen_height)
)
(
    input                        clk, slow_clk, rst,
    input        [w_key   - 1:0] key,
    input        [w_sw    - 1:0] sw,
    output logic [w_led   - 1:0] led,
    output logic [          7:0] abcdefgh,     // сегменты
    output logic [w_digit - 1:0] digit,        // разряды индикатора
    input        [w_x     - 1:0] x,            // координаты пикселя
    input        [w_y     - 1:0] y,
    output logic [w_red   - 1:0] red,
    output logic [w_green - 1:0] green,
    output logic [w_blue  - 1:0] blue,
    input        [         23:0] mic,          // микрофон
    output       [         15:0] sound,        // звук
    input                        uart_rx,
    output                       uart_tx,
    inout        [w_gpio  - 1:0] gpio
);

Все значения в списке параметров — это значения по умолчанию. При создании экземпляра lab_top они переписываются модулем board_specific_top на те, что соответствуют конкретной отладочной плате.

board_specific_top создаёт экземпляр данного модуля и подключает к нему всю возможную периферию, которую может обеспечить выбранная отладочная плата. Модуль lab_top является параметризованным, поэтому если на плате есть только 8 светодиодов, в lab_top будет именно 8-разрядный выход led.

Более того, светодиодов на плате может не быть вовсе — зато к ней может быть подключён интерфейсный модуль TM1638 с последовательным интерфейсом на трёх проводах. В этом случае внутрь board_specific_top ставится экземпляр контроллера TM1638, в который входят 8 проводов со значениями светодиодов, а в параметры lab_top точно так же прописывается 8. Для самого примера разницы нет: он по-прежнему видит 8-разрядный выход led, не зная, что за ним стоит не восемь ножек ПЛИС, а сдвиговый протокол на трёх.

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

Адаптеры справляются, когда ресурса просто больше или меньше. Но бывает, что его на плате физически нет. К примеру, лабораторная работа с ядром YRV использует пять управляющих сигналов: замедление тактовой частоты, переключение отображения адрес/данные, внешнее прерывание и два локальных. На Nexys A7 с её кнопками и переключателями это не проблема, в отличие от платы Omdazz с четырьмя кнопками. Решается это каскадом условий, который раскладывает сигналы по тому, что есть и зануляя то, чего нет:

generate
    if (w_key >= 7) begin : keys_at_least_7
        assign slow_clk_mode          = key [w_key - 1];
        assign external_interrupt_raw = key [w_key - 3];
        // ...
    end
    else if (w_sw >= 7) begin : switches_at_least_7
        // DE10-Lite: две кнопки, зато десять переключателей
        assign slow_clk_mode          = sw [w_sw - 1];
        // ...
    end
    else if (w_key >= 4) begin : keys_at_least_4
        // Omdazz: четыре кнопки, часть функций отключается
        assign slow_clk_mode          = key [w_key - 1];
        assign local_interrupt_0_raw  = 1'b0;
        // ...
    end
    else begin : few_keys_and_sw_available
        // ...
    end
endgenerate

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

На верхнем уровне абстракции находится сам проект. Он не знает ничего ни про lab_top, ни про board_specific_top, все адаптеры находятся на более нижних уровнях абстракции.

Один BASH-скрипт, чтоб править всеми

Конкретный пример мало привести к формату пинов выбранной платы — проект нужно ещё и собрать. И здесь снова проявляется себя проблема множества плат: они могут использовать САПР разных вендоров и даже требовать разные версии САПР одного и того же вендора. Сборка может запускаться на разных системах: Linux/Mac/Windows.

Чтобы справиться с этой проблемой, в репозитории BGM представлена коллекция скриптов, задающих единый интерфейс сборки, абстрагирующийся от конкретного САПР или ОС. Сама логика сборки находится в папке scripts/steps в виде файлов *.source_bash, а в директории каждого примера лежит одинаковый пронумерованный набор скриптов-редиректов: 01_clean.bash, 02_simulate_rtl.bash, 03_synthesize_for_fpga.bash, 06_choose_another_fpga_board.bash и так далее.

Эти скрипты-редиректы не содержат никакой логики — все они являются побайтово одинаковыми копиями одного шаблона scripts/steps/local_redirect.template_bash. Отличает их друг от друга только собственное имя: из него подстановкой получается имя подключаемого файла, после чего скрипт поднимается вверх по дереву каталогов в поисках папки scripts/steps и подключает оттуда нужный .source_bash:

script=$(basename "$0")
source_script=${script/\.bash/.source_bash}
dir_source_script=../scripts/steps/$source_script

for i in {1..5}
do
    [ -f $dir_source_script ] && break
    dir_source_script=../$dir_source_script
done

# ... проверка на то, что файл найден ...

dir_source_script=$(readlink -f $dir_source_script)
. "$dir_source_script"

Польза от такой схемы двойная. Со стороны пользователя в каждом примере под рукой лежит один и тот же набор действий, не зависящий ни от платы, ни от САПР: почистить, промоделировать, синтезировать, прошить, сменить плату. Со стороны разработчика репозитория вся реальная логика живёт в одном месте, и её правка автоматически распространяется на все примеры сразу.

Часть II. Интеграция в репозиторий нового ядра

Испытанием выбранной архитектуры стала интеграция в репозиторий сторонней процессорной системы архитектуры RISC-V, которая даётся в Московском институте электронной техники на дисциплине “Архитектуры процессорных систем”. Данная процессорная система была спроектирована независимо от репозитория BGM, и нашей целью стала интеграция этой системы без каких-либо правок исходного кода процессорной системы, либо (если такой подход невозможен) с минимальными её правками.

Прежде чем рассказать о том, как велась интеграция, давайте коротко рассмотрим систему, о которой идет речь.

Саму процессорную систему можно представить в виде межсоединения:

  • процессорного ядра;

  • памяти инструкций;

  • памяти данных;

  • контроллеров периферии;

  • программатора.

Структура процессорной системы

Структура процессорной системы

При этом, поскольку архитектура RISC-V использует совмещённое адресное пространство, контроллеры периферии процессор считает точно такой же памятью данных, просто с другими адресами.

Ядро представляет собой однотактный процессор rv32i_zicsr. Поддерживает инструкции стандартного набора rv32i, а так же инструкции взаимодействия с регистрами контроля и статуса (CSR) и привилегированную инструкцию mret для возврата из обработчиков прерываний и исключений.

Структура процессорного ядра

Структура процессорного ядра

Из периферии процессорная система имеет:

  • контроллер 16 переключателей;

  • контроллер 16 светодиодов;

  • контроллер клавиатуры PS/2;

  • контроллер 8 семисегментных индикаторов;

  • контроллер приёмника UART;

  • контроллер передатчика UART;

  • символьный VGA-контроллер разрешением 80x30 символов;

  • контроллер системного таймера.

Адресное пространство периферии выглядит следующим образом:

Адресное пространство периферии

Адресное пространство периферии

Система проектировалась под работу на конкретной отладочной плате Nexys A7100T, и, как следствие, САПР Vivado. Система использует чисто гарвардскую архитектуру: память инструкций полностью отделена от памяти данных, и процессор работает с ними по раздельным интерфейсам.

Поскольку процессор однотактный, используется память инструкций с асинхронным чтением, чтобы чтение и исполнение инструкций выполнялось за один такт. Из-за этого, большая часть критического пути лежит в памяти инструкций на мультиплексировании всех имеющихся там регистров — и чем больше размер этой памяти, тем больше критический путь. Поскольку запуск некоторых программ (например coremark) требует памяти инструкций объёмом в 32 КБ, получающийся критический путь не позволял свести схему к 100 МГц тактовому сигналу платы — в связи с этим было принято решение жёстко ограничить тактирование процессорной системы до 10 МГц — в этом случае даже самый неоптимальный дизайн студента и самый большой размер памяти инструкций смогут уложиться в заданную частоту.

Прототип модуля процессорной системы:

module processor_system(

  input  logic        clk10mhz_i,    // Сигнал синхронизации системы
  input  logic        clk25175khz_i, // Сигнал пиксельной развертки VGA
  input  logic        rst_i,

  // Входы и выходы периферии
  input  logic [15:0] sw_i,       // Переключатели

  output logic [15:0] led_o,      // Светодиоды

  input  logic        kclk_i,     // Тактирующий сигнал клавиатуры
  input  logic        kdata_i,    // Сигнал данных клавиатуры

  output logic [ 6:0] hex_led_o,  // Вывод семисегментных индикаторов
  output logic [ 7:0] hex_sel_o,  // Селектор семисегментных индикаторов

  input  logic        rx_i,       // Линия приема по UART
  output logic        tx_o,       // Линия передачи по UART

  output logic [3:0]  vga_r_o,    // Красный канал vga
  output logic [3:0]  vga_g_o,    // Зеленый канал vga
  output logic [3:0]  vga_b_o,    // Синий канал vga
  output logic        vga_hs_o,   // Линия горизонтальной синхронизации vga
  output logic        vga_vs_o    // Линия вертикальной синхронизации vga
);

Что в итоге мы имеем в сравнении с целевым интерфейсом модуля lab_top:

  • кнопки, микрофон и динамик система не использует — их мы не подключаем;

  • линии UART — полностью совпадают, тут сложно в чём-либо разойтись;

  • сигналы переключателей, светодиодов, выбора семисегментных индикаторов и vga-каналов — lab_top ожидает их в виде параметризованной разрядности, в целевой системе их разрядность зафиксирована под конкретную плату;

  • в lab_top отсутствуют сигналы клавиатуры PS/2;

  • в lab_top используется внешний контроллер вывода изображения (не обязательно VGA), поэтому вместо выходов vga_hs/vga_vs используются входы x/y координат выводимого на текущий момент пикселя;

  • целевая система рассчитывает получить два тактовых синхроимпульса фиксированной частоты: 10 и 25.175МГц, в lab_top тактовая частота параметризована и не может гарантировать что придёт именно такая.

Проблема с несовпадением разрядностей решается довольно легко. Если разрядность порта в lab_top больше, чем в нашей системе, — подключаем все биты системы к младшей части порта, а старшие зануляем. Если меньше — берём только младшую часть сигнала системы, а старшие биты никуда не идут: на плате для них всё равно нет ни светодиодов, ни переключателей. Полярность сигналов для семисегментных индикаторов отличается, поэтому подключаемые биты сигнала hex_sel инвертируются. Для сигналов rgb-каналов подход несколько другой — лучше нормализовать значение под другую разрядность, нежели просто обрезать его. Т.е. если разрядность канала r в lab_top выше чем в АПС, мы сдвигаем значение влево. Если ниже — сдвигаем его вправо. Получается следующая форма адаптации совпадающих сигналов:

/*
=====================================================
LED adapter
=====================================================
*/
logic [15:0] aps_led;
generate
  if(w_led > 16) begin
    assign led[w_led-1:16] = '0;
    assign led[15:0] = aps_led;
  end
  else begin
    assign led = aps_led[0+:w_led];
  end
endgenerate
//===================================================

/*
=====================================================
SW adapter
=====================================================
*/
logic [15:0] aps_sw;
generate
  if(w_sw > 16) begin
    assign aps_sw=sw[15:0];
  end
  else begin
    assign aps_sw[0+:w_sw] = sw;
  end
endgenerate
//===================================================

/*
=====================================================
7seg adapter
=====================================================
*/
logic [6:0] hex_led;
logic [7:0] hex_sel;
assign abcdefgh   = ~{hex_led, 1'b0};

generate
  if(w_digit > 8) begin
    assign digit[w_digit-1:8] = '0;
    assign digit[7:0] = ~hex_sel;
  end else begin
    assign digit = ~hex_sel[0+:w_digit];
  end
endgenerate
//===================================================

/*
=====================================================
VGA adapter
=====================================================
*/
logic [3:0] vga_r;
logic [3:0] vga_g;
logic [3:0] vga_b;
logic vga_hs;
logic vga_vs;
generate
  if(w_red > 4) begin
    assign red      = vga_r << (w_red - 4);
  end else begin
    assign red      = vga_r >> (4 - w_red);
  end
  if(w_green > 4) begin
    assign green    = vga_g << (w_green - 4);
  end else begin
    assign green    = vga_g >> (4 - w_green);
  end
  if(w_blue > 4) begin
    assign blue     = vga_b << (w_blue - 4);
  end else begin
    assign blue     = vga_b >> (4 - w_blue);
  end
endgenerate
//===================================================

Далее — подстройка частоты тактовых синхроимпульсов. Поскольку проект может запускаться на разных платах от разных вендоров, нельзя просто подставить какой-то экземпляр PLL, куда проще сделать параметризованный делитель частоты. Однако тут есть нюанс: делитель частоты заработает только если исходная частота кратна целевой. И даже более того, если вы хотите ровную скважность сигнала, исходная частота должна быть в четное число раз выше целевой. Для наших целей не обязательно, чтобы скважность сигнала была ровной, поэтому был реализован следующий делитель частоты:

module clock_divider #(
  parameter int unsigned FAST_CLK_FREQ = 50_000_000,
  parameter int unsigned SLOW_CLK_FREQ = 10_000_000
)(
  input  logic clk_i,
  input  logic aresetn_i,

  output logic clk_o,
  output logic rst_o
);

  localparam int unsigned DIV = FAST_CLK_FREQ / SLOW_CLK_FREQ;

  /*===============
    Делитель клока
    ===============
  */

  /*
    Проверка параметров:
    - FAST_CLK_FREQ должна быть кратна SLOW_CLK_FREQ
    - FAST_CLK_FREQ должна быть выше SLOW_CLK_FREQ
  */
  generate
  if (FAST_CLK_FREQ % SLOW_CLK_FREQ != 0)
    fast_clk_is_not_multiple_of_slow_clk err1();

  if (FAST_CLK_FREQ <= SLOW_CLK_FREQ)
    fast_clk_must_be_greater_than_slow_clk err2();
  endgenerate

  localparam int unsigned HIGH_CYCLES = (DIV + 1) / 2; // ceil(DIV/2)
  localparam int unsigned LOW_CYCLES  = DIV / 2;       // floor(DIV/2)

  localparam int unsigned DIV_WIDTH = $clog2(DIV);

  logic [DIV_WIDTH-1:0] cnt;

  always_ff @(posedge clk_i or negedge aresetn_i) begin
    if (!aresetn_i) begin
      cnt   <= '0;
      clk_o <= 1'b1;
    end
    else begin
      if (clk_o) begin
        if (cnt == HIGH_CYCLES-1) begin
          cnt   <= '0;
          clk_o <= 1'b0;
        end
        else
          cnt <= cnt + 1'b1;
      end
      else begin
        if (cnt == LOW_CYCLES-1) begin
          cnt   <= '0;
          clk_o <= 1'b1;
        end
        else
          cnt <= cnt + 1'b1;
      end
    end
  end


  /*===================
    Буферизация сброса
    ===================
  */
  // Поскольку в системе используется синхронный сброс,
  // он не будет виден, ведь пока удерживается сброс,
  // генерируемый клок находится в нуле
  // Получается что входной сброс никогда не будет
  // зарегистрирован синхронной логикой.
  // Решением является буферизовать сброс, чтобы
  // он снялся уже после генерации первого такта.
  // Кроме того, входной сброс необходимо синхронизировать.

  logic [1:0] arstn_sync;
  logic rstn_synced;
  assign rstn_synced = arstn_sync[1];

  always_ff @(posedge clk_i or negedge aresetn_i) begin
    if(!aresetn_i) begin
      arstn_sync <= 2'b0;
    end
    else begin
      arstn_sync <= {arstn_sync[0], 1'b1};
    end
  end

  logic [3:0] sys_rstn_buf;
  assign rst_o = !sys_rstn_buf[3];

  always_ff @(posedge clk_o or negedge rstn_synced) begin
    if(!rstn_synced) begin
      sys_rstn_buf <= 2'b0;
    end
    else begin
      sys_rstn_buf <= {sys_rstn_buf[2:0], 1'b1};
    end
  end

endmodule

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

Но не всю адаптацию получилось произвести в модуле lab_top. СнК АПС и lab_top принципиально по-разному представляют себе видеовыход. АПС исходит из предположения что мы работаем только с vga: система сама генерирует сигналы синхронизации развёртки по горизонтали и вертикали, а также значения пикселей по каналам rgb. Модуль lab_top в свою очередь исходит из того, что картинка может выводиться на разные дисплеи: spi-дисплей, двумерную матрицу светодиодов, vga-монитор и т.п. И там и там используются выходы rgb, но lab_top использует входы [x;y], для которых в ответ устройство генерирует rgb-значение пикселя по полученным координатам. Данное расхождение не получится разрешить каким-то внешним адаптером, поэтому при интеграции системы пришлось протащить данные входы до vga-контроллера (к счастью там уже была подходящая логика, которая могла бы их воспринять).

Помимо прочего, не весь RTL-код может быть спокойно перенесён между разными САПР. В частности, описание блочной памяти. САПР распознаёт определённые паттерны RTL-кода, которые заменяет на BRAM, и эти паттерны могут отличаться — например потому, что сами блоки имеют некоторые отличия в поведении, либо же просто потому, что САПР заточен под немного другой паттерн.

Ценой данного расхождения будет то, что память в итоге будет синтезирована за счёт обычных регистров и LUT. Для небольших памятей это приемлемо, но не для памяти, скажем, в 4 КБ — а именно столько занимает каждая из трёх памятей символьного VGA-контроллера: карта символов, карта цветов и знакогенератор.

Поэтому для блочной памяти пришлось добавить условные блоки:

  always_ff @(posedge clka_i) begin
    for (int i = 0; i < NUM_COLS; ++i) begin
      if (wea_i[i]) mem[addra_i][i*COL_WIDTH+:COL_WIDTH] <= dina_i[i*COL_WIDTH+:COL_WIDTH];
`ifdef GOWIN_SYNTHESIS
      else douta_o[i*COL_WIDTH+:COL_WIDTH] <= mem[addra_i][i*COL_WIDTH+:COL_WIDTH];
`endif
    end
  end

`ifndef GOWIN_SYNTHESIS
  always_ff @(posedge clka_i) begin
    douta_o <= mem[addra_i];
  end
`endif

Важно уточнить, что эти две ветки — не одно и то же описание, записанное по-разному. Они отличаются поведением при одновременном чтении и записи:

  • в варианте без GOWIN_SYNTHESIS чтение стоит в отдельном always_ff и выполняется безусловно каждый такт, поэтому на выходе оказывается содержимое памяти до записи — это режим READ_FIRST;

  • в варианте для Gowin чтение уехало в else от условия записи, причём побайтно: в такте записи соответствующий байт douta_o вообще не обновляется и сохраняет предыдущее значение — это режим NO_CHANGE.

То есть выбор паттерна под вендора не бесплатен: вместе с паттерном меняется и режим разрешения коллизии чтения-записи. Такую замену можно делать только тогда, когда точно знаешь, что дизайн на эту разницу не завязан — в нашем случае значение douta_o в такте записи никем не используется. Показательно, что второй порт этой памяти (doutb_o <= mem[addrb_i]) никакими ifdef не обложен и одинаков для всех вендоров: он работает только на чтение, коллизии на нём не возникает, и подстраивать там нечего.

Отдельного упоминания заслуживает сам макрос GOWIN_SYNTHESIS. В отличие от XILINX_VIVADO или ALTERA_RESERVED_QIS, которые определяют сами САПР, этот макрос Gowin EDA не определяет — его определяет BGM. В репозитории есть файл define_gowin_or_nothing.svh, существующий в двух экземплярах: настоящий лежит в каталоге каждой Gowin-платы, а заглушка с единственной строчкой // Nothing — в общем каталоге peripherals. Общий для всех примеров config.svh подключает этот файл безусловно, а какая из копий выиграет, решает порядок путей поиска, в котором каталог выбранной платы идёт первым.

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

Откуда это всё растёт

Репозиторий BGM используется в Школе синтеза цифровых схем — открытой образовательной программе по RTL-разработке, функциональной верификации и основам проектирования микросхем. Именно она и порождает требование «один пример, много плат, много САПР»: занятия идут параллельно в университетских кластерах, и у каждого кластера своё железо и свой набор установленных инструментов.

Оттуда же и задача с интеграцией ядра АПС: курс МИЭТ разрабатывался сам по себе, под свою плату и свой САПР, а собираться его процессорная система теперь должна одинаково у всех.

Ссылки на упомянутые ресурсы

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.