В данной статье я попытался показать, как с помощью Matlab Simulink создать и отладить модуль на языке Verilog/VHDL, который можно загрузить в ПЛИС. Для проверки работоспособности сгенерированного кода используется отладочная плата CoreEP4CE6 с ПЛИС Altera (Intel) семейства Cyclone IV — EP4CE6E22C8, тактовым генератором на 50 МГц и четырьмя светодиодами. Созданный модуль должен при тактовой частоте 50 МГц управлять светодиодами, принимая по UART данные на скорости 115200 бод.

Сначала создадим источник-имитатор сигнала UART, затем — приёмный модуль, управляющий светодиодами. Запускаем Matlab, в нём — Simulink (нажав на кнопку «Start Simulink» или введя в командной строке «simulink»). Создаём новую модель («Blank Model»), сохраняем её (название латиницей, без пробелов) в удобное место. Запускаем браузер библиотек («Library Browser») нажатием соответствующей кнопки на панели окна модели, через меню «View» или нажатием комбинации клавиш Ctrl+Shift+L. Из раздела источников («Sources») перетаскиваем в окно модели источник повторяющейся последовательности («Repeating Sequence Stair»), из раздела приёмников («Sinks») — осциллограф («Scope»), мышкой соединяем выход первого со входом второго. Должно получиться как на рисунке 1.

Рисунок 1. Источник повторяющейся последовательности и осциллограф.

Запускаем симуляцию («Run») нажатием соответствующей кнопки на панели окна модели, через меню «Simulation» или нажатием комбинации клавиш Ctrl+T. После этого двойным щелчком по осциллографу открываем его и видим что-то похожее на рисунке 2: повторяющиеся значения «3», «1», «4», «2», «1».

Рисунок 2. Выход источника повторяющейся последовательности по умолчанию на осциллографе.

Двойным щелчком по источнику повторяющейся последовательности открываем его основные настройки и видим повторяющийся вектор значений [3 1 4 2 1] («Vector of output values») и период выдачи («Sample Time») равный «-1» (т.е. наследование («inherit»)) как на рисунке 3.

Рисунок 3. Настройки блока Repeating Sequence Stair по умолчанию, вкладка Main.

Заменим вектор значений на последовательность бит [1 0 1 0 0 1 0 1 1 0 1], где идут подряд слева направо: «1» — режим ожидания, «0» — стартовый бит, «10010110» — 8 бит данных, «1» — стоповый бит. Sample Time выставим примерно 115200 бит/с = 8,86e-6 (8,68 мкс). На вкладке свойств сигнала («Signal Attributes») тип выходных данных («Output data type») заменим с «double» на «boolean». На панели инструментов установим время окончания симуляции («Simulation stop time») равным 100 мкс (1e-4) как на рисунке 4.

Рисунок 4. Поле ввода времени симуляции.

Запустим симуляцию, откроем осциллограф и увидим что-то похожее на сигнал UART со скоростью 115200 бод как на рисунке 5.

Рисунок 5. Сигнал UART на осциллографе.

Отлично, теперь самое интересное: можно начинать собирать приёмник! Приёмник (пока что?) будет самый примитивный, с двумя счётчиками: на одном реализован генератор битовой скорости, на другом — счётчик принятых бит. Алгоритм работы будет примерно такой:

1) в режиме ожидания оба счётчика обнулены;

2) по спаду входного сигнала запускается счётчик битовой скорости;

3) в середине длительности бита фиксируется состояние линии и инкрементируется счётчик принятых бит;

4) после 9 принятых бит (1 стартовый + 8 бит данных) переход в режим ожидания (п. 1).

Из раздела Порты и подсистемы («Ports & Subsystems») браузера библиотек перетаскиваем в окно модели обычную подсистему («Subsystem»), мышкой подключаем её к источнику повторяющейся последовательности. Двойным щелчком мыши по подсистеме открываем её, выделяем мышкой и удаляем перемычку между входным и выходным портами («In1» и «Out1» соответственно). Из раздела Дискретное («Discrete») перетаскиваем в подсистему два элемента единичной задержки (триггер, «Unit Delay»), из раздела Логических и битовых операций («Logic and Bit Operations») — два логических оператора («Logical Operator»), по умолчанию это «И» («AND»). Двойным щелчком по одному из операторов открываем его свойства и меняем на инвертор («NOT») и собираем схему детектора спада как на рисунке 6. Добавляем в подсистему осциллограф, в его настройках указываем 2 канала («Number of input ports»), расположенные друг под другом («Layout...»), и подключаем ко входу подсистемы и выходу логического «И».

Рисунок 6. Детектор спада и осциллограф в подсистеме.

Двойным щелчком по первому триггеру открываем его свойства и явно указываем в Sample time величину обратную частоте его работы 50 МГц = 1/50e6 или период тактового сигнала 20 нс = 20e-9. Запускаем симуляцию, открываем осциллограф и видим, что на выходе логического «И» присутствуют единичные импульсы длительностью 1 такт (20 нс, «Zoom» в помощь) после спада входного сигнала, как на рисунке 7.

Рисунок 7. Осциллограмма работы схемы на рисунке 6.

Из раздела «HDL Coder» — «HDL Operations» браузера библиотек перетаскиваем в подсистему два HDL счётчика («HDL Counter»). Первый будет счётчиком принятых бит — двойным щелчком открываем его настройки и устанавливаем режим счёта от 0 («Initial value», значение по умолчанию = 0) с инкрементом на 1 («Step value», по умолчанию = 1) до 9 («Count to value»). Его разрядность («Word length») установим равной 4 (т.к. 9 < 2^4-1). Поскольку битовая скорость 115200 примерно в 434 раза ниже тактовой частоты 50 МГц, настроим второй счётчик (генератор битовой скорости) на счёт от 0 до 433 с разрядностью 9 бит (433 < 2^9-1), деактивируем вход разрешения («Count enable port») и активируем вход сброса («Local reset port»). Из библиотеки «Simulink» — «Logic and Bit Operations» добавим блок сравнения с нулём («Compare To Zero»), в его настройках установим условие равенства («==») и соединим выход первого счётчика со входом сброса второго через компаратор как на рисунке 8. В настройках осциллографа установим количество каналов равным 3 и подключим его к выходам счётчиков как на рисунке 8.

Рисунок 8. Детектор спада и два счётчика.

Запустим симуляцию, откроем окно осциллографа. Видно, что после первого спада входного сигнала значение на выходе первого счётчика меняется с «0» на «1», соответственно, выход компаратора меняется с «истина» («1») на «ложь» («0»), и второй счётчик начинает бесконечно считать от 0 до 433 как на рисунке 9.

Рисунок 9. Осциллограмма работы схемы на рисунке 8.

Добавим немного логики, чтобы первый счётчик срабатывал только на первый спад входного сигнала («Logical Operator2») и когда значение на выходе второго счётчика равно 217 (половина длительности бита, «Logical Operator3» и «Compare To Constant» с условием «== 217») как на рисунке 10 (количество каналов осциллографа увеличим до четырёх с расположением графиков друг под другом).

Рисунок 10. Схема тактирования с запуском и остановкой.

Запустим симуляцию, откроем окно осциллографа. Видно, что на выходе компаратора с константой (нижний график) мы имеем импульсы на середине информационных бит (и стартового бита), по которым мы можем фиксировать состояние входной линии — остаётся только добавить триггеры с разрешением записи. Кроме того, на середине 8-го бита данных счётчики обнуляются и останавливаются, система переходит в режим ожидания спада сигнала на входе (см. рис. 11).

Рисунок 11. Осциллограмма работы схемы на рисунке 10.

Добавляем цепочку из 8 триггеров с разрешением записи («Unit Delay Enabled») и блок объединения бит с восемью входами («Bit Concat», после его добавления в главном меню Matlab может потребоваться сменить текущую директорию «Current Folder» с «/Matlab/bin/» на любую другую) как на рисунке 12.

Рисунок 12. Законченный примитивный приёмник UART.

Вернёмся на уровень модели, подключим к выходу подсистемы дисплей («Display»), в настройках укажем двоичный формат данных («binary (Stored Integer)»), запустим симуляцию. Увидим на дисплее передаваемый младшим битом вперёд байт данных «0110 1001» (рисунок 13).

Рисунок 13. Модель с имитатором UART и примитивным приёмником.

Теперь всё готово к экспорту в HDL код. Открываем меню «Simulation» — «Model Configuration Parameters» (Ctrl+E) и переходим в раздел «HDL Code Generation». Там выбираем, для чего конкретно мы хотим сгенерировать код («Generate HDL for:») — в выпадающем меню ищем нашу подсистему «filename/Subsystem», выбираем желаемый язык («Language:») — «Verilog» или «VHDL» и куда сохранять файлы («Folder:», где будет создана папка с именем модели) как на рисунке 14.

Рисунок 14. Окно настройки генератора HDL кода.

Жмём кнопку «Generate» и… Упираемся в ошибку «For the block 'filename/Subsystem/Unit Delay' Different input and output sample times are not supported for this block. Consider either adding a rate transition block, or replacing this block with a rate transition block». Сейчас просто скажу как от неё избавиться: добавить 1 триггер («Unit Delay») с явным указанием тактовой частоты 50 МГц («Sample time» = 20e-9 = 1/50e6) в модель перед подсистемой как на рисунке 15.

Рисунок 15. Модель с имитатором UART, примитивным приёмником и дополнительным триггером.

Снова жмём кнопку «Generate», дожидаемся строки «### HDL code generation complete» в основном окне Matlab, переходим в указанный каталог и находим там HDL файл «Subsystem.v» или «Subsystem.vhd». Всё, Matlab можно закрывать (на самом деле, лучше не закрывать, пока всё не будет протестировано в железе) и открывать IDE ПЛИС — у меня это Quartus II: я добавляю сгенерированный файл в проект, создаю для него символ, добавляю его на схему, подключаю входы/выходы как на рисунке 16 и запускаю компиляцию.

Рисунок 16. Модуль примитивного приёмника в проекте Quartus II.

Видно, что Matlab добавил к подсистеме Subsystem тактовый вход («clk»), вход сброса («reset»), вход разрешения тактирования («clk_enable») и выход разрешения тактирования («ce_out»). На схеме на рисунке 16 «CLK» — такт 50 МГц, «RX» — линия UART, «Led4..Led1» — управление светодиодами на отладочной плате, подключенные анодом к питанию (поэтому сигналы пропущены через инверторы, чтобы светодиоды загорались при логической «1» в принятом байте). Управлять тактированием или сбрасывать приёмник я не собираюсь, так что подключил вход разрешения тактирования к «1», а вход сброса — к «0». Количество занятых логических элементов для проекта, содержащего только данный приёмник (результат компиляции, «Compilation Report»), представлено на рисунке 17.

Рисунок 17. Занимаемые ресурсы в ПЛИС EP4CE6E22C8.

Вот и всё. Загружаем прошивку в ПЛИС, с помощью любого терминала посылаем байт на скорости 115200 бод — младшие 4 бита будут подсвечены светодиодами на отладочной плате. Ещё раз, приёмник самый примитивный просто чтобы показать возможность генерирования HDL кода, proof of concept. Если эта статья зайдёт — в следующей части можно будет его улучшить, попутно объясняя различные нюансы и раскрывая функционал Simulink. Если кому интересно, текст сгенерированного файла «Subsystem.v» спрятал под спойлер.

Скрытый текст

// -------------------------------------------------------------
// File Name: \Subsystem.v
// Created: 2025
// Generated by MATLAB 9.1 and HDL Coder 3.9
// Module: Subsystem
// Source Path: /Subsystem
// Hierarchy Level: 0
// -------------------------------------------------------------

`timescale 1 ns / 1 ns

module Subsystem
(
clk,
reset,
clk_enable,
In1,
ce_out,
Out1
);
input clk;
input reset;
input clk_enable;
input In1;
output ce_out;
output [7:0] Out1; // uint8

wire enb;
reg Unit_Delay_out1;
wire Logical_Operator_out1;
reg Unit_Delay1_out1;
wire Logical_Operator1_out1;
reg [8:0] HDL_Counter1_out1; // ufix9
wire Compare_To_Zero_out1;
reg [8:0] HDL_Counter1_stepreg; // ufix9
reg [3:0] HDL_Counter_out1; // ufix4
wire Logical_Operator3_out1;
reg [3:0] HDL_Counter_stepreg; // ufix4
wire Logical_Operator2_out1;
wire Compare_To_Constant_out1;
reg Unit_Delay_Enabled_out1;
reg Unit_Delay_Enabled1_out1;
reg Unit_Delay_Enabled2_out1;
reg Unit_Delay_Enabled3_out1;
reg Unit_Delay_Enabled4_out1;
reg Unit_Delay_Enabled5_out1;
reg Unit_Delay_Enabled6_out1;
reg Unit_Delay_Enabled7_out1;
wire [7:0] Bit_Concat_out1; // uint8

assign enb = clk_enable;

always @(posedge clk or posedge reset)
begin : Unit_Delay_process
if (reset == 1'b1) begin
Unit_Delay_out1 <= 1'b0;
end
else begin
if (enb) begin
Unit_Delay_out1 <= In1;
end
end
end

assign Logical_Operator_out1 = ~ Unit_Delay_out1;

always @(posedge clk or posedge reset)
begin : Unit_Delay1_process
if (reset == 1'b1) begin
Unit_Delay1_out1 <= 1'b0;
end
else begin
if (enb) begin
Unit_Delay1_out1 <= Unit_Delay_out1;
end
end
end

assign Logical_Operator1_out1 = Logical_Operator_out1 & Unit_Delay1_out1;

// Count limited, Unsigned Counter
// initial value = 0
// step value = 1
// count to value = 433
always @(posedge clk or posedge reset)
begin : HDL_Counter1_step_process
if (reset == 1'b1) begin
HDL_Counter1_stepreg <= 9'b000000001;
end
else begin
if (enb) begin
if (Compare_To_Zero_out1 == 1'b1) begin
HDL_Counter1_stepreg <= 9'b000000001;
end
else if (HDL_Counter1_out1 == 9'b110110000) begin
HDL_Counter1_stepreg <= 9'b001001111;
end
else begin
HDL_Counter1_stepreg <= 9'b000000001;
end
end
end
end

// Count limited, Unsigned Counter
// initial value = 0
// step value = 1
// count to value = 9
always @(posedge clk or posedge reset)
begin : HDL_Counter_step_process
if (reset == 1'b1) begin
HDL_Counter_stepreg <= 4'b0001;
end
else begin
if (enb) begin
if (Logical_Operator3_out1 == 1'b1) begin
if (HDL_Counter_out1 == 4'b1000) begin
HDL_Counter_stepreg <= 4'b0111;
end
else begin
HDL_Counter_stepreg <= 4'b0001;
end
end
end
end
end

assign Logical_Operator2_out1 = Compare_To_Zero_out1 & Logical_Operator1_out1;

assign Logical_Operator3_out1 = Logical_Operator2_out1 | Compare_To_Constant_out1;

always @(posedge clk or posedge reset)
begin : HDL_Counter_process
if (reset == 1'b1) begin
HDL_Counter_out1 <= 4'b0000;
end
else begin
if (enb) begin
if (Logical_Operator3_out1 == 1'b1) begin
HDL_Counter_out1 <= HDL_Counter_out1 + HDL_Counter_stepreg;
end
end
end
end

assign Compare_To_Zero_out1 = HDL_Counter_out1 == 4'b0000;

always @(posedge clk or posedge reset)
begin : HDL_Counter1_process
if (reset == 1'b1) begin
HDL_Counter1_out1 <= 9'b000000000;
end
else begin
if (enb) begin
if (Compare_To_Zero_out1 == 1'b1) begin
HDL_Counter1_out1 <= 9'b000000000;
end
else begin
HDL_Counter1_out1 <= HDL_Counter1_out1 + HDL_Counter1_stepreg;
end
end
end
end

assign Compare_To_Constant_out1 = HDL_Counter1_out1 == 9'b011011001;

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled_process
if (reset == 1'b1) begin
Unit_Delay_Enabled_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled_out1 <= Unit_Delay_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled1_process
if (reset == 1'b1) begin
Unit_Delay_Enabled1_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled1_out1 <= Unit_Delay_Enabled_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled2_process
if (reset == 1'b1) begin
Unit_Delay_Enabled2_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled2_out1 <= Unit_Delay_Enabled1_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled3_process
if (reset == 1'b1) begin
Unit_Delay_Enabled3_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled3_out1 <= Unit_Delay_Enabled2_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled4_process
if (reset == 1'b1) begin
Unit_Delay_Enabled4_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled4_out1 <= Unit_Delay_Enabled3_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled5_process
if (reset == 1'b1) begin
Unit_Delay_Enabled5_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled5_out1 <= Unit_Delay_Enabled4_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled6_process
if (reset == 1'b1) begin
Unit_Delay_Enabled6_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled6_out1 <= Unit_Delay_Enabled5_out1;
end
end
end

always @(posedge clk or posedge reset)
begin : Unit_Delay_Enabled7_process
if (reset == 1'b1) begin
Unit_Delay_Enabled7_out1 <= 1'b0;
end
else begin
if (enb && Compare_To_Constant_out1) begin
Unit_Delay_Enabled7_out1 <= Unit_Delay_Enabled6_out1;
end
end
end

assign Bit_Concat_out1 = {Unit_Delay_Enabled_out1, Unit_Delay_Enabled1_out1, Unit_Delay_Enabled2_out1, Unit_Delay_Enabled3_out1, Unit_Delay_Enabled4_out1, Unit_Delay_Enabled5_out1, Unit_Delay_Enabled6_out1, Unit_Delay_Enabled7_out1};

assign Out1 = Bit_Concat_out1;

assign ce_out = clk_enable;

endmodule // Subsystem