Моделируем поведение Quartus-проекта на Verilog в среде ModelSim

В прошлой статье мы сделали достаточно сложный модуль. Разумеется, я вставил в тело статьи уже отлаженный результат. Но мне показалось, что достаточно странно, когда автор говорит «делай, как я», но при этом не показывает очень важного процесса. Давайте я покажу, как вообще проводится отладка системы путём моделирования. Причём в следующей статье будут содержаться сведения, которые ещё неделю назад не знал даже я. Но, чтобы перейти к ним, надо разобраться с базовыми принципами. Итак. Давайте рассмотрим, как быстро подготовить и не менее быстро запустить процесс моделирования в среде ModelSim.

Моделируем поведение Quartus-проекта на Verilog в среде ModelSim

Предыдущие статьи цикла

  1. Разработка простейшей «прошивки» для ПЛИС, установленной в Redd, и отладка на примере теста памяти
  2. Разработка простейшей «прошивки» для ПЛИС, установленной в Redd. Часть 2. Программный код
  3. Разработка собственного ядра для встраивания в процессорную систему на базе ПЛИС
  4. Разработка программ для центрального процессора Redd на примере доступа к ПЛИС
  5. Первые опыты использования потокового протокола на примере связи ЦП и процессора в ПЛИС комплекса Redd
  6. Веселая Квартусель, или как процессор докатился до такой жизни
  7. Методы оптимизации кода для Redd. Часть 1: влияние кэша
  8. Методы оптимизации кода для Redd. Часть 2: некэшируемая память и параллельная работа шин
  9. Экстенсивная оптимизация кода: замена генератора тактовой частоты для повышения быстродействия системы
  10. Доступ к шинам комплекса Redd, реализованным на контроллерах FTDI
  11. Работа с нестандартными шинами комплекса Redd
  12. Практика в работе с нестандартными шинами комплекса Redd
  13. Проброс USB-портов из Windows 10 для удалённой работы
  14. Использование процессорной системы Nios II без процессорного ядра Nios II
  15. Практическая работа с ПЛИС в комплекте Redd. Осваиваем DMA для шины Avalon-ST и коммутацию между шинами Avalon-MM
  16. Разработка простейшего логического анализатора на базе комплекса Redd
  17. Разработка логического анализатора на базе Redd – проверяем его работу на практике
  18. Делаем голову шинного USB-анализатора на базе комплекса Redd

Как работает обычная программа для ЭВМ? Имеется некая внешняя среда (монитор и клавиатура с «мышкой» — самые типичные представители этой самой среды). Программа с ними взаимодействует. При отладке можно производить настоящие воздействия от внешней среды, а можно эмулировать их. У нас тестеры часто пишут всякие скрипты, которые как раз эмулируют внешние воздействия. После чего запускаются анализаторы логов, которые проверяют, чтобы ответы в среду уходили верные.

Что делать, если в этой программе для ЭВМ всё глючит? Можно поставить точки останова и изучать срез системы в момент, когда они сработали. Срез системы — это значения переменных. Может, состояния различных мьютексов и прочих объектов синхронизации. В общем, срез внутренних параметров отлаживаемой системы.

При отладке для ПЛИС можно сделать всё то же самое. Правда, если среда будет настоящая, то делать остановку и изучать срез системы проблематично, хоть и возможно. В рамках рассказа о Redd, я всё время продвигаю мысль, что всё должно быть просто и быстро. Мы не проектируем сложных систем. Мы делаем какие-то модули, вроде того, что был сделан в прошлой статье. Он навороченный, но весьма и весьма несложный. В общем, мы будем производить его поведенческое моделирование.

И здесь возникает вопрос о внешней среде. Как сымитировать её? Нам на помощь приходят модели. На языке Verilog (как и VHDL, и других похожих) вполне можно описать поведение чего угодно. Делаем мы систему, которая работает с микросхемой ULPI… Значит, чтобы проверить её работу, на том конце должно быть что-то, что ведёт себя именно, как ULPI. То есть, модель ULPI. Но этого мало. Наш блок реагирует на команды от шины ALAVON_MM. Именно эта шина заставляет блок жить. Поэтому надо ещё добавить модель шины AVALON_MM, причём эта модель должна быть активной. Именно она будет подавать тестовые воздействия.

В конечном итоге мы должны сделать именно такую систему. И тогда мы сможем снимать временные диаграммы сигналов на всех её шинах и даже внутри любых её модулей. Если будет возникать ошибка, мы сможем устанавливать точки останова и изучать срезы системы, чтобы найти врага. Хотя, лично я эти точки останова обычно не ставлю, чаще всего хватает анализа временных диаграмм. Дело в том, что сигналы можно смотреть не только интерфейсные, а любые внутренние. Вытянув десяток-другой внутренних сигналов на график, обычно можно догадаться, что в логике реализовано не так.

Цель сегодняшней статьи — не рассказать о том, что такое моделирование вообще (это — долгая история), а показать, как это моделирование провести быстрее всего. И рассмотрим мы это не на боевой задаче, а на простом примере. Сделаем совсем простенькую тестовую систему, чтобы в следующей статье уже понимать, откуда растут ноги у более сложного её варианта, ведь при чтении удобнее не сидеть и недоумевать: «Зачем он это делает?», а знать все базовые принципы, из которых уже вытекают усложнения. Кстати, недавно выяснилось, что один мой знакомый хоть и владеет мастерством моделирования, но не знал, что в среду Quartus встроены механизмы, которые позволяют делать это легко и непринуждённо. Он тратил на это намного больше усилий, чем требуется. Так что может, кто-то тоже сейчас узнает для себя что-то новое о возможностях, заложенных в Quartus. Итак, приступаем.

Создание простейшей модели на языке Verilog

Люди делятся на две категории. Те, кто любит создавать всё с нуля руками и те, кто любит делать это, повозив мышкой. Руками создавать всё — правильнее. Можно контролировать каждое действие и делать всё заведомо идеально. Но память — штука ненадёжная. Если всё время заниматься одним и тем же делом, она держит детали в уме, а если приходится всё время переключаться между языками, через месяц-другой приходится вспоминать, что же там надо сделать. Поэтому работа через вариант «повозить мышкой» имеет право на существование хотя бы из-за этого. Опять же, если у отлаживаемого модуля десяток-другой интерфейсных сигналов, мне всегда скучно делать рутинную работу по их переобъявлению и пробросу. Поэтому сейчас мы рассмотрим, как сделать модель при помощи «мышки». А дальше — каждый для себя решит, достаточно ему этого, или стоит переходить на ручную работу.

Итак, мы хотим промоделировать модуль. Что такое «промоделировать» выходит за рамки нашего цикла, можно на эту тему написать отдельный большой цикл. То есть, в рамках этого раздела, считаем, что вы знакомы с методикой разработки модели. Но дальше надо всё включить в проект… Или нет? Как ни странно, для моделирования модуля совершенно не нужно даже создавать собственный проект. Мы можем прицепиться в качестве паразита к любому проекту, не включая в него ничего нового, а только создав тестовый набор, который никак не будет участвовать в основной сборке.

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

module sum(
input         clk,
input [7:0]   data,
input         we,
input         sof,
output [15:0] sum
);

logic [15:0] temp;

always @ (posedge clk)
begin
     if (we) 
     begin
         if (sof)
             temp <= data;
         else
             temp <= temp + data;
     end
end

// В идеале - так
//assign sum = (~temp)+1;
// Но контролировать проще так:
assign sum = temp;
endmodule

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

Положим получившийся файл в каталог с нашим проектом, но не станем включать его в проект в Quartus. Вместо этого — создадим тестовый набор специально под него. Для этого выбираем пункт меню Assignments—>Settings:

и в появившемся дереве ищем пункт EDA Tools Settings—>Simulation:

Кстати, о типе моделирования, выделенном зелёной рамкой. Возможно, кто-то помнит, в первых статьях я говорил, что при создании проекта чисто по привычке выбираю ModelSim Altera? Это было то самое ружьё на сцене, которое рано или поздно должно было выстрелить. Но если при создании проекта тип моделирования не был выбран, его можно выбрать или изменить здесь.

Продолжаем создавать тестовый набор. Переключаем радиокнопку на Compile test bench (кстати, а как этот термин красиво переводится на русский? Я не могу заставить себя писать «тестовый стенд», так как не вижу никакого стенда) и нажимаем кнопку Test Benches:

В открывшемся диалоге нажимаем New:

Если делать тестовый набор вручную, то можно заполнить поля за один проход. Но так как мы делаем всё при помощи «мышки», то сейчас заполняем только часть полей, а остальные – дозаполним позже. В поле Test bench name я вбил слово Parazit (а как ещё назвать тест, который просто паразитирует на проекте?). Слово Parazit под ним заполнилось автоматически. Сейчас мы не будем его менять, но в будущем нам ещё предстоит это сделать. Также при помощи кнопки «...» я выбрал файл sum.sv с кодом отлаживаемого сумматора, после чего, при помощи кнопки Add, затолкнул его в список файлов теста. Пока — всё. Закрываем диалог…

Дальше мы продолжим формирование теста в среде ModelSim. Для этого выбираем пункт меню Tools—>Run Simulation Tools—>RTL Simulation:

Открывается окно ModelSim. Возможно, будут найдены ошибки в коде Verilog, тогда надо закрывать ModelSim, править ошибки, открывать вновь. Но рано или поздно, перечень ошибок станет чисто организационным. У меня он выглядит так:

Не найдено модуля верхнего уровня. Это нормально. Мы его ещё не создали просто. Поэтому идём в перечне библиотек к work и раскрываем её. Вот он, наш сумматор.

Наводимся на него, нажимаем правую кнопку «Мыши» и выбираем пункт меню Create Wave. Это в тексте всё так занудно, если бы я снимал видео, весь процесс занимал бы десятки секунд, так что не пугайтесь, а следите за руками дальше. Итак, Create Wave

Интерфейсные сигналы модуля автоматически переехали на график:

Надо назначить значение какого-нибудь из них. Не важно какого, важно назначить. Очень старая среда моделирования Квартуса умела красиво генерить тактовые сигналы. Увы, её давно изъяли из поставки, так как стали прилагать ModelSim, а тут с подобным всё не так красиво. Проку в формировании генератора здесь, я не увидел, поэтому даже показывать не буду. Так что… Ну, давайте линию we нулю присвоим. Наводимся на сигнал, нажимаем правую кнопку, выбираем пункт меню Edit—>Wave Editor—>Create/Modify WaveForm.

В появившемся диалоге выбираем Constant. И время заодно поменяем, скажем, на 100 микросекунд:

Далее — указываем значение 0:

Всё, минимально необходимый набор данных мы создали, а остальное проще будет ручками сделать. Экспортируем файл. Для этого выбираем пункт меню File—>Export—>Waveform:

Выбираем тип файла Verilog Testbench (кстати, очень жаль, что не SystemVerilog, но в будущем можно будет поправить и ручками). Также задаём имя файла. Я назвал его parazit_tb, по принципу «а почему бы и нет?».

Всё, ModelSim можно закрывать, времянку при этом сохранять не нужно.

Что делать с моделью дальше

Вот такой кривоватый, но всё-таки готовый Верилоговский файл нам создала система:

`timescale 1ns / 1ns
module parazit_tb  ; 
 
  reg    sof   ; 
  reg    we   ; 
  wire  [15:0]  sum   ; 
  reg  [7:0]  data   ; 
  reg    clk   ; 
  sum  
   DUT  ( 
       .sof (sof ) ,
      .we (we ) ,
      .sum (sum ) ,
      .data (data ) ,
      .clk (clk ) ); 



// "Constant Pattern"
// Start Time = 0 ns, End Time = 100 us, Period = 0 ns
  initial
  begin
  end

  initial
	#0 $stop;
endmodule

Автоматика избавила нас от написания стандартных блоков. Причём если бы интерфейсных сигналов было больше, автоматика бы послушно прописала бы и соединила все цепи. Лично меня при ручном создании тестовых наборов удручает именно процесс описания сигналов и их проброса. Теперь в этом файле мы сейчас создадим модель среды, которая будет воздействовать на отлаживаемый модуль sum.

Как видим, толку от задания констант, сделанного автогенератором никакого. Но всё-таки, созданы все цепи, подключён модуль, подлежащий тестированию, даже секция initial создана. Давайте облагородим код. Первое — выкинем точку останова, удалив строки:

  initial
	#0 $stop;

Дальше — добавим модель тактового генератора (как же мне не хватает замечательного генератора, который делали старинные Квартусы! Там можно было задать частоту в мегагерцах и не думать о пересчёте её в период, а тем более — полупериод).

  always 
  begin
      clk = 0;
      #5;
      clk = 1;
      #5;
  end

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

task SendByte (input reg[7:0] D);
    begin
        data = D;
        we = 1;
        @(posedge clk);
        #1
        we = 0;
   end
endtask

Ну, и впишу назначение констант и вызов циклов работы с шиной в блок initial. Напоминаю, что запись типа #123 означает «ждать 123 единицы времени». У нас это наносекунды. Также напоминаю, что так как присвоения идут последовательно, используем операцию «равно», а не «стрелка». Итого, имеем следующий основной код тестирования:

Смотреть здесь

  initial
  begin
     sof = 0;
     we = 0;
     data = 0;
     #13;
     // Первый байт кадра
     sof = 1;
     SendByte (1);
     // Остальные байты
     sof = 0;
     SendByte (5);
     SendByte (1);
     // А тут мы промоделируем небольшую задержечку
     #20;
     SendByte (1);
  end

Итого, у нас полный код модуля приобрёл такой вид:

Смотреть полный код модуля.

`timescale 1ns / 1ns
module parazit_tb  ; 
 
  reg    sof   ; 
  reg    we   ; 
  wire  [15:0]  sum   ; 
  reg  [7:0]  data   ; 
  reg    clk   ; 
  sum  
   DUT  ( 
       .sof (sof ) ,
      .we (we ) ,
      .sum (sum ) ,
      .data (data ) ,
      .clk (clk ) ); 


  always 
  begin
      clk = 0;
      #5;
      clk = 1;
      #5;
  end

task SendByte (input reg[7:0] D);
    begin
        data = D;
        we = 1;
        @(posedge clk);
        #1
        we = 0;
   end
endtask

// "Constant Pattern"
// Start Time = 0 ns, End Time = 100 us, Period = 0 ns
  initial
  begin
     sof = 0;
     we = 0;
     data = 0;
     #13;
     // Первый байт кадра
     sof = 1;
     SendByte (1);
     // Остальные байты
     sof = 0;
     SendByte (5);
     SendByte (1);
     // А тут мы промоделируем небольшую задержечку
     #20;
     SendByte (1);
  end

endmodule

Завершение подготовки тестового набора

Пришла пора добавить этот текст к тестовому набору. Для этого идём в уже известный нам диалог

Но теперь наш набор не создаём, а выбираем в списке. В будущем список будет расти по мере добавления наборов… Выбрав, нажимаем кнопку Edit. Я внёс в настройки три правки:

  1. Добавил файл parazit_tb.v в список.
  2. Так как файле parazit_tb.v модуль верхнего уровня имеет имя parazit_tb (можете убедиться, глянув исходник из предыдущего раздела), я вписал это имя в строку Top level module in test bench.
  3. Я сказал вести моделирование в течение 10 микросекунд, после чего приостановиться. Если что — я домоделирую через нажатие кнопок ручного управления.

Итого

Закрываем всё. Снова запускаем ModelSim. Видим, что всё работает верно. Данные приходят и учитываются в сумме. Если же на такте нет данных (we в нуле) – сумма не увеличивается.

Как пользоваться самой средой моделирования — это тема на несколько статей. Причём скорее в видеоформате. Но в целом — мы познакомились с методикой быстрой подготовки и запуска тестов на языке Verilog из среды Quartus.

Теперь, зная, как быстро запустить моделирование, мы можем набросать модели среды для нашей головы USB-анализатора и проверять её работу. При этом мы не запоминали ни одного заклинания ModelSim, так как Квартус позволяет всё настроить при помощи «мышки». Все необходимые скрипты он генерит сам и среду ModelSim вызывает тоже сам. Базу для модели нам также создали в автоматическом режиме, хоть её и пришлось затем доработать вручную.

Увы и ах. Один из элементов внешней среды — модуль ULPI. Чтобы разработать его модель самостоятельно, надо, во-первых, тщательно разобраться в логике работы той микросхемы. А в предыдущей статье я говорил, что она очень заковыристая. Ну и, во-вторых, надо затратить уйму времени на разработку кода модели. И устранение ошибок в нём… Понятно, что проще найти что-то готовое. Но готовую модельку удалось найти только на языке SystemC. Поэтому в следующей статье мы будем учиться моделировать систему с использованием этого языка.

 

Источник

FPGA, ModelSim, Quartus, Redd, verilog, моделирование на Verilog, ПЛИС

Читайте также