В данной статье я попытался показать, как с помощью 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.

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

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

Заменим вектор значений на последовательность бит [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.

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

Отлично, теперь самое интересное: можно начинать собирать приёмник! Приёмник (пока что?) будет самый примитивный, с двумя счётчиками: на одном реализован генератор битовой скорости, на другом — счётчик принятых бит. Алгоритм работы будет примерно такой:
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...»), и подключаем ко входу подсистемы и выходу логического «И».

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

Из раздела «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.

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

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

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

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

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

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

Жмём кнопку «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.

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

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

Вот и всё. Загружаем прошивку в ПЛИС, с помощью любого терминала посылаем байт на скорости 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
mozg37
Забавно. Код он сгенерил так себе - но я видел как контроллер блдс мотора так же из кубиков сгенеренный работает - фантастика да и только. Попытаюсь по сему мануалу регулятор какой нибудь сотворить.
Gudd-Head Автор
В коде он генерирует очень много лишнего, да. Но это лишнее не выливается в дополнительные занятые ресурсы.
Что там контроллер BLDC, у меня когерентный приёмник с АЦП на 300 Msps из кубиков сгенеренный крутится))
mozg37
Я как раз пытаю когерентный прием и пришел к выводу что надо через матлаб начинать.