Создание Tcl-управляемого тестбенча для VHDL-модуля кодового замка
Большинство VHDL-симуляторов предоставляют Tool Command Language (Tcl) в качестве основного скриптового интерфейса. Когда вы вводите команду в консоли симулятора, вы используете Tcl, и также можете писать скрипты, которые управляют и наблюдают за вашим VHDL-проектом.
Эта статья проведет вас через создание самопроверяющего тестбенча, который использует Tcl — вместо VHDL — для верификации поведения простого модуля кодового замка. Результат — понятный, поддерживаемый тестовый стенд, который можно запускать, модифицировать и повторно использовать без перекомпиляции VHDL.
Смотрите также:
Скачайте код и файлы проекта ModelSim ниже.
DUT: Модуль кодового замка на VHDL
Прежде чем погрузиться в тестбенч, вспомним Device Under Test (DUT). Модуль эмулирует простой замок, который открывает сейф при вводе правильной числовой последовательности на клавиатуре PIN.
Хотя многие называют такое устройство «комбинационным замком», правильный термин — «перестановочный замок» — порядок цифр имеет значение. Для ясности мы будем называть его кодовым замком.

Иллюстрация выше показывает типичный отель сейф. Для простоты наш дизайн использует только числовые клавиши и опускает кнопки CLEAR и LOCK.
Как работает модуль кодового замка
Замок начинается в запертом состоянии. Когда пользователь вводит четыре последовательные цифры, совпадающие с секретным PIN-кодом, замок открывается. Ввод неверной последовательности снова запирает устройство. Это поведение реализовано как простой детектор последовательности на VHDL.
Волновая форма иллюстрирует тайминг модуля. Помимо тактового сигнала и сброса, модуль принимает два входа: input_digit (целое 0–9) и input_enable (std_logic). DUT считывает цифру на фронте тактового сигнала, когда enable высокий.
Единственный выход — unlock, std_logic, который становится высоким только когда четыре последовательные цифры совпадают с правильным PIN-кодом. В этом примере пароль — 1234.
Сущность
Следующее определение сущности жестко задает секретный PIN через generic-параметры. Каждый generic — целое в диапазоне 0–9, представляющее BCD-цифру.
entity code_lock is generic (pin0, pin1, pin2, pin3 : integer range 0 to 9); port ( clk : in std_logic; rst : in std_logic; input_digit : in integer range 0 to 9; input_enable : in std_logic; unlock : out std_logic ); end code_lock;
Декларативная область
Модуль использует один внутренний сигнал — 4-битный сдвиговый регистр, хранящий последние введенные цифры. Допускаем значения от –1 до 9. Специальное значение –1 используется для состояния сброса, чтобы PIN 0000 не открывал замок по умолчанию.
type pins_type is array (0 to 3) of integer range -1 to 9; signal pins : pins_type;
Реализация
Архитектура включает комбинационное присваивание, которое устанавливает unlock в высокий уровень, когда сдвиговый регистр совпадает со значениями generic PIN. Поскольку pins тактируется, unlock меняется только на фронте тактового сигнала.
unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';
Следующий процесс реализует сдвиговый регистр. При сбросе все записи устанавливаются в –1. Когда input_enable высокий на фронте такта, текущая цифра сохраняется в позиции 0, а предыдущие цифры сдвигаются вправо.
PINS_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pins <= (others => -1); else if input_enable = '1' then pins(0) <= input_digit; pins(1 to 3) <= pins(0 to 2); end if; end if; end if; end process;
VHDL-тестбенч
Хотя мы используем Tcl для логики верификации, нам все равно нужен базовый VHDL-тестбенч для генерации тактового сигнала и сброса. Ниже приведен полный файл VHDL-тестбенча.
library ieee; use ieee.std_logic_1164.all; entity code_lock_tb is end code_lock_tb; architecture sim of code_lock_tb is constant clk_hz : integer := 100e6; constant clock_period : time := 1 sec / clk_hz; signal clk : std_logic := '1'; signal rst : std_logic := '1'; signal input_digit : integer range 0 to 9; signal input_enable : std_logic := '0'; signal unlock : std_logic; begin clk <= not clk after clock_period; DUT : entity work.code_lock(rtl) generic map (1,2,3,4) port map ( clk => clk, rst => rst, input_digit => input_digit, input_enable => input_enable, unlock => unlock ); end architecture;
Tcl-тестбенч
Ниже приведен Tcl-скрипт, запускаемый внутри ModelSim. Если вы используете Vivado или другой симулятор, потребуются незначительные корректировки, так как некоторые команды специфичны для симулятора. Обратитесь к ModelSim Command Reference Manual для деталей.
Скачайте пример проекта через форму ниже.
Использование пространства имен
Инкапсуляция скрипта в пространство имен предотвращает случайные конфликты с глобальными переменными. Весь Tcl-код находится внутри пространства имен codelocktb.
namespace eval ::codelocktb { # All Tcl code here }
Инициализация симуляции
Запускаем симуляцию с vsim и загружаем файл волновой формы, если он существует.
# Load the simulation vsim work.code_lock_tb # Load the waveform if {[file exists wave.do]} { do wave.do }
Импорт VHDL-значений в Tcl
Используя команду ModelSim examine, считываем константу clock_period и generic-параметры PIN. Следующий фрагмент извлекает числовое значение и единицу времени.
variable clockPeriod [examine clock_period] variable clockPeriod [string trim $clockPeriod "{}"] variable timeUnits [lindex $clockPeriod 1] variable clockPeriod [lindex $clockPeriod 0] variable pinCode [examine dut.pin0 dut.pin1 dut.pin2 dut.pin3]
Также инициализируем переменную errorCount для отслеживания сбоев.
variable errorCount 0
Печать с временной меткой
Команда echo выводит сообщения в консоль ModelSim на всех платформах. Следующая процедура добавляет к каждому сообщению текущее симуляционное время.
proc printMsg { msg } { global now variable timeUnits echo $now $timeUnits: $msg }
Запуск заданного числа тактовых циклов
Поскольку DUT чувствителен к такту, мы продвигаем время кратными периодам такта.
proc runClockCycles { count } { variable clockPeriod variable timeUnits set t [expr {$clockPeriod * $count}] run $t $timeUnits }
Проверка значений сигналов
Процедура checkSignal сравнивает VHDL-сигнал с ожидаемым значением, сообщая об ошибке, если они отличаются.
proc checkSignal { signalName expectedVal } { variable errorCount set val [examine $signalName] if {$val != $expectedVal} { printMsg "ERROR: $signalName=$val (expected=$expectedVal)" incr errorCount } }
Симуляция ввода PIN
Эта процедура управляет входами DUT для симуляции пользователя, вводящего 4-значный PIN. Проверяет выход unlock после применения всех четырех цифр.
proc tryPin { digits } { variable pinCode set pinStatus "incorrect" if { $digits == $pinCode } { set pinStatus "correct" } printMsg "Entering $pinStatus PIN code: $digits" foreach i $digits { force input_digit $i -deposit force input_enable 1 -deposit runClockCycles 1 force input_enable 0 -deposit runClockCycles 1 } if { $pinStatus == "correct" } { checkSignal unlock 1 } else { checkSignal unlock 0 } }
Тестовая последовательность и отчет о результате
После короткого периода инициализации скрипт снимает сброс и запускает несколько тест-кейсов: два краевых случая (0000 и 9999), правильный PIN и обратную последовательность. Затем сообщает общий результат.
runClockCycles 10 force rst '0' -deposit runClockCycles 1 printMsg "Checking reset value" checkSignal unlock 0 tryPin {0 0 0 0} tryPin {9 9 9 9} tryPin $pinCode tryPin [lreverse $pinCode] if { $errorCount == 0 } { printMsg "Test: OK" } else { printMsg "Test: Failure ($errorCount errors)" }
Запуск тестбенча
Выполните скрипт следующей командой внутри ModelSim:
source code_lock/code_lock_tb.tcl
Успешный запуск дает вывод, похожий на следующий, с временными метками перед каждым сообщением.
VSIM> source code_lock/code_lock_tb.tcl # vsim # 110 ns: Checking reset value # 110 ns: Entering incorrect PIN code: 0 0 0 0 # 190 ns: Entering incorrect PIN code: 9 9 9 9 # 270 ns: Entering correct PIN code: 1 2 3 4 # 350 ns: Entering incorrect PIN code: 4 3 2 1 # 430 ns: Test: OK
Если DUT содержит дефект (например, опечатку в присваивании unlock), скрипт сообщает о неудаче и количестве ошибок.
VSIM> source code_lock/code_lock_tb.tcl # vsim # 110 ns: Checking reset value # 110 ns: Entering incorrect PIN code: 0 0 0 0 # 190 ns: Entering incorrect PIN code: 9 9 9 9 # 270 ns: Entering correct PIN code: 1 2 3 4 # 350 ns: ERROR: unlock=0 (expected=1) # 350 ns: Entering incorrect PIN code: 4 3 2 1 # 430 ns: Test: Failure (1 errors)
Заключительные мысли
На протяжении карьеры я писал множество Tcl-основанных тестбенчей. Они предлагают уникальные преимущества: интерактивное тестирование, возможность модифицировать тестовый стенд без перекомпиляции, и дополнительный слой верификации, защищающий от ошибок как в проекте, так и в тестбенче.
Однако есть компромиссы. Tcl-скрипты работают заметно медленнее чистых VHDL-тестбенчей, и язык может быть хрупким — ошибки трудно отловить рано, а отладка менее интуитивна, чем в высокоуровневых языках вроде Python.
Несмотря на эти недостатки, Tcl остается де-факто клеевым языком для FPGA-инструментов и симуляции. Если вы работаете с Xilinx Vivado, Intel Quartus или Mentor Graphics Questa, освоение Tcl принесет дивиденды. Делитесь своим опытом в комментариях ниже.
VHDL
- Создание списков строк в VHDL: лучшие практики и пример
- Корректное завершение симуляции VHDL-тестбенча
- Реализация PWM-контроллера в VHDL: проектирование, симуляция и FPGA-демо
- Проектирование надежного кольцевого буфера FIFO на VHDL в блочной ОЗУ
- Улучшение верификации оборудования с помощью интерактивного тестбенча на Tcl
- Как создать самопроверяющийся тестбенч в VHDL — практическое руководство
- Реализация динамического связного списка в VHDL с защищенными типами и указателями доступа
- Использование нечистых функций в VHDL: повышение читаемости и поддерживаемости FSM
- Освоение функций VHDL: практическое руководство по эффективному дизайну
- Бесплатный симулятор и редактор VHDL: руководство для студентов