Интегрированная среда MPLAB IDE имеет в своем составе встроенный симулятор, который позволяет отлаживать программу еще до программирования микросхемы. Для приближения к реальным условиям MPLAB предоставляет возможность имитировать входные сигналы с помощью встроенного инструмента Stimulus. С помощью него можно имитировать уровни на портах ввода вывода и значения регистров, причем можно привязывать эти события ко времени, к состояниям или изменениям состояний портов и регистров. Для этого интегрированной средой MPLAB предоставляется диалоговое окно (доступно через меню «Debugger/Stimulus»), в котором пользователю предлагается задать внешние воздействия.
Однако, когда нужно задать сигнал сложной формы, или довольно много внешних воздействий, связанных между собой во времени и состояниях, вводить имитируемые сигналы через диалоговое окно становится трудно и неудобно. Даже простая задача, например, имитация сигнала в виде манчестерского кода вызывает большие трудности из-за большего объема данных, которые требуется ввести (особенно трудности вызывает подсчет времен для асинхронной стимуляции).
В таких случаях для задания внешних воздействий удобно пользоваться специальным встроенным языком SCL. SCL — это структурный VHDL-подобный язык высокого уровня (примечание: VHDL используется для описания аппаратуры), правда, по сравнению с ним гораздо менее функциональный. SCL представляет нам возможность организовывать циклы, формировать задержки, задавать уровни цифровых входов, имитировать аналоговые сигналы, работать с файлами.
Язык SCL довольно плохо описан (официального описания нет), поэтому все, что здесь приведено, — результат исследования и экспериментов, а также обмена опытом с другими исследователями-экспериментаторами. Данный материал не является точным описанием языка, а всего лишь содержит общие принципы написания программ на нем. Составляя его, я решал, как лучше изложить материал: чтобы он был наиболее полным, или чтобы он был простым пособием. Остановился на втором варианте, хотя, в результате получился, скорее, первый.
Возможно, при описании я что-то не учел, что-то описал неполно, а где-то допустил ошибку. Не стесняйтесь ругаться, если обнаружите неточности. Если у кого-то будет какое-то сущетвенное дополнение, я обязательно внесу его в это пособие (некоторые моменты, такие как определение пользовательских типов, я умышленно не стал здесь упоминать из-за их, на мой взгляд, ненадобности и малозначимости в SCL-программах).
Идентификаторы
Идентификаторы должны начинаться с латинской буквы и могут содержать латинские буквы, цифры и знак подчеркивания. Пример правильных идентификаторов:
MyVariable My_Variable My_Variable_1
Имена идентификаторов чувствительны к регистру. Имена идентификаторов не могут совпадать с зарезервированными словами.
Структура программы
Основное тело программы заключено в операторные скобки testbench:
configuration for "pic16f877a" is end configuration; testbench for "pic16f877a" is -- Тело программы end testbench;
(Для блока configuration я нашел только одно применение, о нем будет сказано в разделе Функции: Стимуляция регистров. Во всех остальных случаях его можно и не ставить.)
Само тело программы состоит из процессов, которые выполняются параллельно, но могут быть синхронизированы друг с другом:
Label: process [(sensitivity_list)] is
[Определения]
begin
[Тело процесса]
end process Label;Здесь:
Labelнеобязательное поле — идентификатор процессаprocess … isключевое слово, обозначающее блок описания процессаsensitivity_listнеобязательное поле — список сигналов, по изменению которых процесс запускается.begin … end process;операторные скобки, внутри которых заключается тело процессаОпределениянеобязательная секция, где определяются пользовательские типы, переменные и константыТело процессаэто уже последовательность действий, описанная операторами языка. После ключевой фразы «process is» идет необязательная секция описания типов, констант и переменных. Для программы на языке SCL типы, константы и переменные являются глобальными для всех процессов, описанных ниже (для процессов описанных выше они невидимы)
Примеры:
-- При каждом изменении состояния портов RA0 или RB1 переменная i будет увеличиваться на 1
TEST: process (RA0,RB1) is
variable i: integer;
begin
i := i + 1;
end process TEST;-- генерация меандра 1 KHz
process is
begin
RB0 := not RB0;
wait for 500 us;
end process;Тело процесса выполняется по кругу. Если его нужно выполнить только один раз, то в конце можно поставить оператор wait без параметров.
Комментарии
Комментарии в языке SCL могут начинаться с «—«, как в VHDL, или с «//», как в Си. Все, что следует за символами «—» или «//» считается комментарием и игнорируется при трансляции.
Типы
Язык SCL является строго типизированным, т.е. в нем не допускается смешение типов в одной операции (например, нельзя присваивать переменной типа integer переменную типа bit; или нельзя суммировать переменную типа byte с переменной типа time).
На данный момент язык SCL поддерживает следующие типы данных:
Перечислимые
integer32-битное целое знаковоеbyte= integerword= integerbooleanЛогический тип, может принимать значения true или falsebitБитовый тип, может принимать значения ‘0’ или ‘1’characterСимвольный.paddressАдрес в памяти программы. 24-битовое беззнаковое целое.daddressАдрес в памяти данных. 24-битовое беззнаковое целое.
Временные
timeВременной. Знаковое 64-битное целое, задается в пикосекундах. Однако можно указать другие единицы измерения: ns, us, ms, sec, min, hr.cycleСхожий с временным типом, только время задается в циклах. Задается в виде числа с указанием единиц измерения ic (instruction cycle).frequencyЧастотный. Задается в герцах (от 0 до 1e9). Может задаваться с явным указанием единиц измерения: hz, khz, mhz.
Строковые
stringТекстовая строка. Представляет собой массив character’ов (type string is array (integer range <>) of character;). Применяется для вывода информации по ходу симуляцииlineСтрока для обработки данных. Может быть прочитана из файла и обработана.
Файловые
textФайловый. Применяется для чтения файлов (запись через этот тип произвести не удается).file_open_kindТип открываемого файла. Может принимать значения: read_mode, write_mode, append_mode. (Удалось заставить работать только в режиме read_mode)file_open_statusСостояние открытого файла. Может принимать значения: open_ok, status_error, name_error, mode_error
Остальные
severity_levelтип сообщения в операторе report. Может принимать значения: note, warning, error, failure.arrayпозволяет определять массивы. Но в настоящее время толку от них мало, т.к. нет возможности обращаться к элементу массива по индексуbit_vectorмассив битов. Также нет возможности обращаться к элементам по индексуrecordСтруктура. Позволяет обращаться к битовым переменным SFR (например, ADCON.ADON).
Константы
Способы задания констант
Целые числа можно задавать с указанием системы счисления или без нее (по умолчанию число считается записанным в десятичной системе):
система_счисления#число# Причем поле «число» может содержать не только цифры (и буквы A,B,C,D,E,F), но и символ подчеркивания ‘_’, что очень удобно для более наглядного представления длинных чисел. Например, число 168 можно записать так:
168 10#168# 16#A8# 2#10101000# 2#1010_1000#
Времена можно задавать в виде целого или вещественного числа с указанием единиц измерения:
100 ic -- в тактах
10534 ps -- в пикосекундах
120 ns -- в наносекундах
83.3 us -- в микросекундах
0.11 ms -- в миллисекундах
5 sec -- в секундах
0.1 min -- в минутах
3 hr -- в часахпричем, как уже упоминалось, времена могут обозначаться как положительными, так и отрицательными числами.
Определение констант в программе
Константы определяются внутри процесса перед операторными скобками begin … end с ключевым словом constant и указанием типа и значения:
process is
constant Start : time := 100 us;
constant Stop : time := 300 us;
constant Max : integer := 16#7FFFFFFF#;
constant Hello : string := "Hello world!";
begin
--...
end process;Переменные
Переменные описываются с ключевым словом variable, после которого следует идентификатор переменной и через двоеточие — тип переменной. Так же при определении переменной может быть сразу задано ее значение:
process is
variable I: integer;
variable B: byte := 10;
variable S: string := "Hello world";
begin
--...
end process;Определение файловых переменных производится с ключевым словом file:
process is
file DataFile : text; -- переменная, которая будет связана с
-- конкретным файлом
variable FileStatus : file_open_status; -- переменная, куда будет записан результат
-- функции открытия файла
begin
--...
end process;Операторы
Операции
Математические операции: +, -, *, /
Логические операции: not
(остальные операции: ^, <<, >>, %, and, or и т.д. — не поддерживаются)
Операторы сравнения
>, <, >=, <=, ==, != (или /=)
Оператор присваивания
a := 10; s := "This is a string";
Оператор назначения
Оператор назначения «<=» служит для задания уровней сигналов на портах ввода/вывода:
RB0 <= '1'; RC0 <= '0';
Для языка SCL (в отличие от VHDL) этот оператор не отличается от оператора присваивания (разве что только его применение допустимо только к сигнальным линиям).
Условный оператор if
if <expression> then
...
elsif <expression2> then
...
elsif <expression3> then
...
else
...
end if;Здесь expression — простое выражение, результат которого может быть ‘true’ или ‘false’. Например:
if i == 10 then
RB0 <= '0';
else
RB0 <= '1';
end if;
if i + 3 >= 5 then
...
end if;
if s == "Hello world" then
...
elsif s == "Wellcome" then
...
else
...
end if;Оператор ожидания wait
Есть четыре применения этого оператора:
1. wait — вечное ожидание
2. wait for — ожидание заданного времени
wait for <time>;
Время может быть указано как в виде константы, так и в виде переменной.
wait for 100 ic;
MyTime := 150 us;
wait for MyTime;3. wait until — ожидание события
wait until <event> [for <time>];
выполняет ожидание события, заданного выражением event, с выходом по таймауту, указанному в time (необязательное поле).
Примеры:
wait until RB0 != RB1; -- ожидание неравенства RB0 и RB1
wait until RB0 == '1' for 1 ms; -- в течение 1 мс ждем положительного фронта на RB0Обращу внимание на то, что если на момент начала выполнения wait until условие выполняется, то это не считается событием. Например, «wait until RB0 == ‘1’» будет ожидать не единичное состояние, а переход ‘0’→’1′. Т.е. если на момент выполнения wait у нас RB0 уже в состоянии ‘1’, то это не считается уже случившимся событием и оператор wait будет ждать сначала перехода ‘1’→’0′, а потом ‘0’→’1′.
4. wait on — ожидание изменения сигнала
wait on <signals_list> [for <time>];
Параметры этого оператора указываются так же, как и в описании списка для process. Оператор переводит процесс в ожидание, пока не изменится состояние одного из сигналов, указанных в списке:
wait on RB0, RB1, RB2; -- Ждем изменения состояния на одном из трех выводов
wait on RA0, RA1 for 10 ms; -- В течение 10мс ждем изменения состояния RA0 или RA1Оператор цикла loop
Безусловный цикл:
loop
...
end loop;Цикл будет выполняться бесконечно. Прервать его выполнение можно оператором exit (безусловное прерывание) или exit when … (условное):
-- Генератор на RB0, пока на RB1 высокий уровень
loop
exit when RB1 == '0'; -- Прерываем цикл
RB0 <= not RB0;
wait for 1 us;
end loop;Т.е. exit является аналогом break в Си. Аналога continue в SCL, к сожалению, нет (вернее, он есть — next, — но он не поддерживается)
Внутри тела цикла должен быть хотя бы один оператор wait.
Оператор цикла while
while <expression> loop
...
end loop;Цикл похож на предыдущий, но с предустановленным условием выполнения. Например, цикл из предыдущего примера выглядел бы так:
-- Генератор на RB0, пока на RB1 высокий уровень
while RB1 == '1' loop
RB0 <= not RB0;
wait for 1 us;
end loop;Вывод информации report
Этот оператор выводит отладочную информацию в MPLAB IDE в окно «Output: MPLAB_SIM». Может использоваться совместно с оператором severity, который обозначает тип сообщения. Вывод информации служит для визуализации текущего состояния работы SCL-файла. В качестве параметра может быть как строковая константа, так и строковая переменная. Например:
process is
variable s : string;
begin
report "This is string-constant";
s := "This is string-variable";
report s;
wait;
end process;Выведет в окно:
(0) SIM-N0001 Note: This is string-constant (0) SIM-N0001 Note: This is string-variable
Здесь SIM-N0001 — номер процесса, Note — тип сообщения. По умолчанию все сообщения имеют тип Note. Однако, с помощью оператора severity можно изменять тип сообщений на: note, warning, error, failure. Причем, note, warning и error позволяют скрипту SCL выполняться дальше, в то время как failure прерывают выполнение скрипта. Код:
process is
begin
report "This is a note";
report "This is a warning" severity warning;
report "This is an error" severity error;
report "This is a failure" severity failure;
report "This text will not be reported";
wait;
end process;выведет следующую последовательность сообщений:
(0) SIM-N0001 Note: This is a note (0) SIM-W0001 Warning: This is a warning (0) SIM-E0001 Error: This is an error (0) SIM-F0001 Failure: This is a failure
Обратим внимание, что последняя строка уже не выводится, т.к. severity failure прервало работу скрипта.
Функции
На данный момент мне известны следующие функции:
- Стимуляция регистровaccessin — назначить файл для асинхронной записи в регистрtriggerin — назначить файл для синхронной записи в регистрpacketin — отправить данные
- История регистраaccessout — назначить файл для асинхронного сохранения значений регистраtriggerout — назначить файл для синхронного сохранения значений регистраtriggerout_gpr — назначить файл для синхронного сохранения значений произвольного регистра
- Чтение текстовых файловfile_open — открыть файлfile_close — закрыть файлendfile — проверка конца файлаreadline — читать строку из файла
- Обработка строкmatch — проверить совпадение строкread — читать параметр из строки
- Времяnow — получение текущего времениrandom_time — получить случайное время
- Для отладкиprint — вывод информации в отчет
Стимуляция регистров
accessin (FileName: string, FileMode: mode, Reg: register, Wrap: boolean)
- Назначить файл для асинхронной записи в регистр. Регистр при каждом к нему обращении (на чтение) будет заполняться данными из указанного файла.
Параметры:
FileName
Имя файла
FileMode
Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. ниже)
Reg
Стимулируемый регистр
Wrap
?
Типы файла:
hex_mode— Данные записаны в шестнадцатеричном ASCII представлении (на каждой строке по одномму числу)dec_mode— Данные записаны в десятичном ASCII представлении (на каждой строке по одномму числу)binary_mode— Двоичный файлformatted_mode— Форматированный файл (как он форматирован я пока не разобрался)
process is
begin
accessin("adc_data.txt", binary_mode, ADRESH, true);
wait;
end process;triggerin (FileName: string, FileMode: mode, Reg: register, PC: paddress, Wrap: boolean)
- Назначить файл для синхронной записи в регистр. Регистр будет заполняться данными из указанного файла при указанном значении программного счетчика.
Параметры:
FileName
Имя файла
FileMode
Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше)
Reg
Стимулируемый регистр
PC
Значение программного счетчика, при котором производить стимуляцию регистра (может быть указан в виде имени функции)
Wrap
?
Если значение PC указывается в виде имени функции, то это имя должно быт ьобъявлено в секции configuration с ключевыми словами shared label (нечто вроде extern в Си).
Пример:
configuration for "pic16f877a" is
shared label usart_getch; -- Объявляем скрипту имя функции
end configuration;
testbench for "pic16f877a" is
begin
process is
begin
-- формируем значение PORTB при каждом входе в прерывание
triggerin("portb.txt", hex_mode, PORTB, 16#0004#, true);
-- формируем значение приемного регистра USART при каждом
-- входе в функцию usart_getch()
triggerin("portb.txt", hex_mode, RCREG, usart_getch, true);
wait;
end process;
end testbench;packetin (Data: line, Reg: register, Wrap: boolean)
- Стимуляция значения регистра вручную. Регистр будет принимать значения, указанные в строке. Эта функция может быть применена тогда, когда регистр нужно стимулировать по более сложному алгоритму, чем допускают функции accessin и triggerin.
Параметры:
Data
Пакет данных (список значений, разделяемых пробелами), которые нужно занести в регистр
Reg
Имя регистра
Wrap
‘true’ — добавить в очередь к уже имеющимся в пакете данным; ‘false’ — затереть старые данные новым пакетом
Пример стимулирования принятых данных по UART:
process is
variable DataLine : line := "1 2 3 4 5 6 7 8 9 10";
begin
wait for 10 ms;
packetin(DataLine, RCREG, false); -- Стимулируем принимаемые данные
wait until RCREG_packet_done; -- Ожидание завершения приема
...
end process;История регистра
accessout (FileName: string, FileMode: mode, Reg: register, Wrap)
- Назначить файл для асинхронного сохранения значений регистра. Значение регистра при каждом к нему обращении (на запись) будет сохраняться в указанный файл.
Параметры:
FileName
Имя файла
FileMode
Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше)
Reg
Наблюдаемый регистр
Wrap
?
process is
begin
accessout("eeprom_data.txt", hex_mode, EEDATA);
wait;
end process;triggerout (FileName: string, FileMode: mode, Reg: register, PC: paddress)
- Назначить файл для синхронного сохранения значений регистра. Значение регистра будет записываться в указанный файл при каждой установке программного счетчика в указанное значение.
Параметры:
FileName
Имя файла
FileMode
Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше)
Reg
Наблюдаемый регистр
PC
Значение программного счетчика, при котором производить запись значения регистра в файл (может быть указан в виде имени функции)
Если значение PC указывается в виде имени функции, то это имя должно быт ьобъявлено в секции configuration с ключевыми словами shared label.
Пример:
configuration for "pic16f877a" is
shared variable eewrite; -- Объявляем скрипту имя функции
end configuration;
testbench for "pic16f877a" is
begin
process is
begin
-- Сохраняем все значения регистра PORTA на момент входа в прерывание
triggerout("porta.txt", hex_mode, PORTA, 16#0004#);
-- Сохраняем все записываемые в EEPROM данные
triggerout("eeprom.txt", hex_mode, EEDATA, eewrite);
wait;
end process;
end testench;triggerout_gpr (FileName: string, FileMode: mode, GPR: ???, PC: paddress, Size: integer)
- Назначить файл для синхронного сохранения значений регистра. Значение регистра будет записываться в указанный файл при каждой установке программного счетчика в указанное значение.
Параметры:
FileName
Имя файла
FileMode
Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше)
GPR
Имя переменной
PC
Значение программного счетчика, при котором производить запись значения регистра в файл (может быть указан в виде имени функции)
Size
Количество байт на одну запись (в текстовом режиме будут разделены пробелами)
Для того, чтобы скрипт знал имя используемой переменной, ему нужно его сообщеть в секции configuration c ключевыми словами shared variable:
Пример:
configuration for "pic16f877a" is
shared variable MyCounter; -- Объявляем скрипту имя переменной
end configuration;
testbench for "pic16f877a" is
begin
process is
begin
-- Сохраняем все значения переменной MyCounter при входе в прерывание
triggerout("counter.txt",dec_mode, MyCounter, 16#0004#);
wait;
end process;
end testbench;Чтение текстовых файлов
file_open (Status: file_open_status, F: text, Name: string, Kind: file_open_kind
- Открывает указанный файл и связывает его с файловой переменной.
Параметры:
Status
в эту переменную будет записан результат открытия файла (open_ok, status_error, name_error, mode_error)
F
файловая переменная
Name
имя файла в текстовом виде
Kind
режим работы файла (read_mode,write_mode,append_mode). Хоть SCL и позволяет открывать файлы в режимах write и append, и даже создает файлы с указанным именем на диске, но произвести запись в эти файлы так и не удалось. Так что на сегодняшний день реально из файлов можно только читать.
Пример:
process is
file MyFile : text;
variable Result : file_open_result;
begin
file_open(Result, MyFile, "data.txt", read_mode);
if Result != open_ok then
report "Error opening file data.txt!" severity failure;
end if;
end process;file_close (F: text)
- Закрывает открытый файл
Параметр:
F
файловая переменная
Пример:
process is
file MyFile : text;
variable Result : file_open_result;
begin
file_open(Result, MyFile, "data.txt", read_mode);
...
file_close(MyFile);
end process;endfile (F: text)
- Проверка конца файла. Возвращает ‘true’, если конец файла, иначе — ‘false’.
Параметр:
F
файловая переменная
Пример:
process is
file MyFile : text;
variable Result : file_open_result;
begin
file_open(Result, MyFile, "data.txt", read_mode);
while endfile(MyFile) == false loop
...
end loop;
end process;readline (F: text, Data: line)
- Читает строку из файла.
Параметр:
F
файловая переменная
Data
переменная, куда будет помещена прочитанная строка
Пример:
process is
file MyFile : text;
variable Result : file_open_result;
variable Data : line;
begin
file_open(Result, MyFile, "data.txt", read_mode);
while endfile(MyFile) == false loop
readline(MyFile, Data);
...
end loop;
end process;Обработка строк
match (Line: line, Pattern: string)
- Проверить совпадение строк. Вернее, совпадение начала первой строки со второй строкой. Функция возвращает ‘true’, если начало первой строки совпадает со второй строкой, и ‘false’, — если не совпадает.
Параметры:
Line
здесь содержится строка, начало которой мы проверяем
Pattern
здесь находится шаблон для сравнения
Пример:
process is
variable MyLine : line := "This is a line";
begin
if match(MyLine, "This") == true then -- условие выполнится
...
end if;
if match(MyLine, "This is ") == true then -- условие выполнится
...
end if;
if match(MyLine, "is") == true then -- условие не выполнится
...
end if;
if match(MyLine, "") == true then -- Проверка на пустую строку
...
end if;
end process;read (Line: line, Data: any type)
- читает слово из строки и заносит результат в переменную Data. Само слово затем из строки удаляется. Длина слова зависит от типа Data:string — будет прочитана вся строкаtime или cycle — будут прочитаны два слова, разделенные пробеламиinteger, byte, word — будет прочитано одно слово
Параметры:
Line
Исходная строка
Data
Переменная, куда будет помещен результат
Пример:
process is
variable Line : line := "wait 100 ms";
variable Delay : time;
variable Dummy : integer;
begin
if match(Line, "wait") == true then
read(Line, Dummy); -- Убираем слово "wait", теперь Line = "100 ms"
read(Line, Delay); -- Delay = 100 ms, Line = ""
wait for Delay;
end if;
...
wait;
end process;Обращу внимание на чтение в переменную Dummy. Когда мы попадаем внутрь блока if, у нас строка до сих пор содержит «wait 100 ms». Нам нужно убрать одно слово из нее, для чего мы выполняем функцию read, занося результат в любую переменную типа integer (т.е. читаем до первого пробела). После этого прочитанное слово удаляется из исходной строки. Далее выполняем read еще раз с параметром Delay, который имеет тип time, т.е. читаем два слова: «100 ms». После этого строка Line становится пустой.
Время
now ()
- Получение текущего времени. Возвращает текущее время симуляции. В зависимости от того, какого типа переменную мы используем для хранения результата (cycle или time), будут выбраны соответствующие единицы измерения.
Пример:
process is
variable Time : time;
variable Cycle : cycle;
begin
wait for 10 ms;
Time := now(); -- получение времени в пикосекундах
Cycle := now(); -- получение времени в тактах контроллера
end process;random_time (LowerLimit: integer, UpperLimit: integer, Units: string, Seed1: integer, Seed2: integer, Result: time)
- Формирует случайное значение для переменной типа time.
Параметры:
LowerLimit
Нижний предел
UpperLimit
Верхний предел
Units
Единицы измерения («»ps», «ns», «us», «ms», «sec», «min», «hr»)
Seed1, Seed2
параметры генератора случайных чисел (в самом простом случае можно задать любыми ненулевыми значениями)
Result
Результат работы функции
Пример (генератор шума):
testbench for "pic16f877a" is
begin
process is
variable RandTime : time;
variable RandSeed1, RandSeed2: integer;
begin
RandSeed1 := 1234;
RandSeed2 := 5678;
loop
random_time(1, 100, "us", RandSeed1, RandSeed2, RandTime);
wait for RandTime;
RB0 <= not RB0;
end loop;
end process;
end testbench;Для отладки
print (…)
- Эта функция похожа на оператор report, но в отличие от него она, помимо строк, может выводить значения переменных (целочисленных, битовых, булевых, временных, строковых), а также выводить по несколько значений на одной строке.
Параметры: эта функция может принимать не более 5 параметров различных типов: line, string, integer, character, boolean, bit, time, cycle.
Пример:
I := 25;
print("Current I = ", I);Выведет:
(0) SIM-N0001 Note: Current I = 25
Работа с файлами
Мы уже упоминали функции для стимуляции регистров значениями из файлов (assignin, triggerin). Эти функции при своей простоте имеют недостатки:
- неудобно стимулировать несколько регистров синхронно (актуально, например, для пары ADRESH:ADRESL);
- довольно бедный выбор условий обновления значений регистра (либо при обращении к нему, либо по значению PC);
- нет возможности менять правила стимуляции в ходе работы;
- нет возможности стимулировать отдельные биты регистра.
Избавиться от этих недостатков можно, читая данные из файлов напрямую, используя специальные функции работы с файлами и функции обработки строк. На сегодняшний день есть возможность работать только с текстовыми файлами. Это с одной стороны немного ограничивает нас в способе предоставления информации (нельзя обрабатывать бинарные), но с другой — позволяет снабдить файл сопроводительной информацией (например, паузы, комментарии).
Рассмотрим порядок обработки файла data.txt следующего содержания (задача взята наобум):
porta 0 portb 0 wait 10 ms rcreg 1 2 3 4 5 6 7 porta 16 portb 255 wait 10 ms porta 0 portb 0
Сначала мы обнуляем значения регистров PORTA и PORTB. Через 10 мс отправляем данные по USART, после чего устанавливаем порты в значения PORTA = 0x10, PORTB = 0xFF. А еще через 10 мс порты снова обнуляются.
testbench for "pic16f877a" is
begin
process is
file InFile : text; -- Файловая переменная для связи с файлом
variable Status : file_open_status; -- Для проверки правильности открытия файла
variable InLine : line; -- Для обработки прочитанных данных
variable Delay : time; -- Для формирования задержек
variable Work : integer; -- Рабочая переменная для обработки данных
begin
file_open(Status, InFile, "data.txt", read_mode);
if Status != open_ok then -- Обработка ошибки
report "Ошибка открытия файла data.txt!" severity failed;
end if;
-- Работаем с файлом, пока в нем есть данные
while endfile(InFile) == false loop
readline(InFile, InLine); -- Читаем строку
-- Обрабатываем строку, пока в ней есть данные
while match(InLine, "") == false loop
-- Обработка команды
if match(InLine, "porta") == true then
read(InLine, Work); -- Убираем из InLine слово "porta"
read(InLine, Work); -- Читаем параметр
PORTA <= Work;
elsif match(InLine, "portb") == true then
read(InLine, Work); -- Убираем из InLine слово "portb"
read(InLine, Work); -- Читаем параметр
PORTB <= Work;
elsif match(InLine, "rcreg") == true then
read(InLine, Work); -- Убираем из InLine слово "rcreg"
packetin(InLine, RCREG, true); -- Перенаправляем остаток строки в приемный буфер
elsif match(InLine, "wait") == true then
read(InLine, Work); -- Убираем из InLine слово "wait"
read(InLine, Delay); -- Читаем параметр задержки
wait for Delay; -- Выдерживаем задержку
else
report "Неизвестная команда";
end if;
loop end; -- line == ""
loop end; -- endfile
report "Готово!";
wait;
end process;
end testbench;В этом примере следует сосредоточить внимание на двух циклах: while endfile(InFile) == false и while match(InLine, «») == false. Эти два цикла — основа работы с файлами и присутствуют почти в любой программе на языке SCL, которая занимается обработкой входного файла. Внутри этих вложенных один в другой циклов и происходит вся обработка данных. Т.е. если файл еще содержит данные (проверяем функцией endfile), то читаем строку, а затем по одной достаем из строки команды (вспоминаем, что функция read из строки удаляет прочитанное слово) и их параметры, пока строка не закончится.
Отметим одно важное свойство: с одним и тем же файлом параллельно могут работать несколько процессов.
Программирование
В этом параграфе рассмотрим некоторые особенности и сложности работы с SCL файлами при работе с интегрированной средой MPLAB.
Подключение программы SCL к симулятору
После того, как программа генерации входных сигналов написана, нам нужно ее подключить. Для этого нужно (действия для версии MPLAB 8.xx):
- выбрать в качестве отладчика MPLAB SIM (меню «Debugger/Select Tool»);
- создать рабочую книгу через меню «Debug/Stimulus/New Workbook» (или Open Workbook, если книга уже была создана);
- в открывшемся диалоговом окне «Stimulus» нажать кнопку «Advanced…» в нижнем левом углу; откроется маленькое диалоговое окно «Advanced operations»;
- в этом новом окне нажать кнопку «Attach» (прикрепить SCL-файл); выбрать файл с программой SCL.
Если программа была написана правильно, то в поле «Override Workbook with Stimulus File» появится имя выбранного файла. Если нет, значит, в программе есть ошибки и их нужно исправлять. Как — описано в следующем параграфе.
Сложности отладки
Отладка SCL-файлов — дело довольно трудоемкое, поскольку сама среда MPLAB предоставляет довольно скудный инструментарий для отладки. Начать можно с того, что встроенный парсер SCL-файлов выдает очень лаконичные сообщения по поводу ошибок, и часто долго не понимаешь, что ему может не нравиться.
Работая с SCL-файлами, желательно, чтобы на виду было рабочее окно «Output» с открытой вкладкой «MPLAB SIM». Все сообщения об ошибках, а также вся отладочная информация, формируемая самим SCL-скриптом (print и report) будут выводиться в это окно. Чем нам MPLAB IDE помогает при отладке? Во-первых, он обеспечивает подсветку синтаксиса для файлов SCL, что позволяет избежать некоторых малозаметных помарок. Во-вторых, при прикреплении SCL-файла в окно «Output» выводится информация с номером строки, содержащей ошибку, и тип ошибки (здесь он довольно скуп на сообщения и чаще всего пишет «syntax error»); кроме того, двойным щелчком мышки по сообщению в окне «Output» мы попадаем на строку с ошибкой в исходном файле.
Я бы рекомендовал подключать файл SCL еще на начальном этапе программирования. Т.е., сначала создаем пустой файл:
testbench for "pic16f877a" is begin end testbench;
потом подключаем его по описанному выше алгоритму, убеждаемся, что он подключился (в окне Output должно появиться сообщение «Stimulus: SCL file attached successfully.»). Теперь можно писать SCL-скрипт, причем для проверки того, что все написано правильно уже не нужно делать «Detach/Attach», а достаточно всего лишь нажать кнопку F6 (Reset при отладке). По этой кнопке файл SCL автоматически перезагружается и проходит повторную проверку. В окне Output будут появляться сообщения об ошибках, если они есть, или ничего не будет появляться, если ошибок нет. (Когда ничего не появляется, это несколько настораживает, поэтому я во все свои SCL-скрипты встраиваю такой процесс:
process is
begin
report "======================== SCL loaded OK! ==========================";
wait;
end process;Если нет ошибок, то по нажатию F6 в окне Output будет появляться надпись «SCL-file loaded OK.»)
Надо отметить, что у нас нет возможности пройти по шагам SCL-скрипт. Поэтому даже если и нет ошибок синтаксических, которые обнаружит парсер, у нас могут быть ошибки алгоритмические. Их находить труднее, потому что мы не можем расставлять в SCL-программах точки останова и отслеживать в каком-то окне текущие значения переменных. Но здесь нам на помощь приходят report и print, использование которых может помочь нам проследить ход выполнения SCL-скрипта и значения переменных в определенных точках выполняемого скрипта.
Недостаток операторов
Язык SCL хоть и является подмножеством языка VHDL, но все-таки имеет далеко не все его возможности. Стоит отметить отсутствие некоторых операторов (case, for, after), типов (real, полноценных массивов), математических и логических операций (xor, and, or, %, сдвиги) и пр. Это все доставляет некоторые неудобства, но, тем не менее, большинство конструкций могут быть заменены.
Во-первых, у нас нет возможности использовать сложные выражения в условных операторах. Мы не можем написать:
if RA0 == '1' and RA1 == '1' then
...Нам придется делать вложенные условия:
if RA0 == '1' then
if RA1 == '1' then
...Это немного усложняет структуру программы, т.к. появляется очень много операторных блоков.
Кроме того, сильно огорчает отсутствие выделения битовых полей. Например, мы не можем воспользоваться операцией and:
i := j and 16#0E#;
потому, что такой операции нет в языке SCL. Или, что еще обиднее, нет простого механизма сдвига числа с извлечением младшего бита. Однако это не значит, что такое невозможно. Например, наш предыдущий пример с j & 0x0E может быть решен с помощью математических операций *, /, -:
i := j / 16;
i := j - i * 16; -- Отрезаем все биты выше 3-го
i := i / 2;
i := i * 2; -- Отрезаем нулевой битПримерно так же решается задача сдвига с извлечением младшего бита:
b := 65; -- Данные, которые надо последовательно втолкнуть на вход RB0
i := 8; -- обрабатываем 8 бит
while i > 0 loop
i := i - 1;
j := b / 2;
j := b - j * 2; -- Получили в j младший бит из переменой b
if j == 1 then -- Вталкиваем этот бит в RB0
RB0 <= '1';
else
RB0 <= '0';
end if;
b := b / 2; -- деление на 2 - эквивалент сдвига влево
wait for 100 us;
end loop;Доступ к регистрам контроллера
Во время выполнения SCL-скрипта у нас есть возможность обращаться к регистрам контроллера. Мы можем получить текущее значения программно счетчика, значение регистра FSR, значения таймеров, регистров управления модулем CCP и т.д. Здесь я особо глубоко исследования не проводил. Но с сожалением должен заметить, что не все регистры можно прочитать. Например, на это:
i := PORTA;
парсер ругается «Type error in assignment». В то время как значения остальных регистров прочитать таким образом удается. (Также не читаются TRISx, TXREG и еще некоторые периферийные регистры). В принципе, это не страшно, т.к., во-первых, операция чтения регистра PORTA довольно редкая, а во-вторых, ее можно заменить побитовым заполнением:
i := 0;
if RA0 == '1' then
i := i + 1;
end if;
if RA1 == '1' then
i := i + 2;
end if;
if RA2 == '1' then
i := i + 4;
end if;
if RA3 == '1' then
i := i + 8;
end if;
if RA4 == '1' then
i := i + 16;
end if;
if RA5 == '1' then
i := i + 32;
end if;Встроенный в MPLAB симулятор сам накладывает кое-какие ограничения. Их поиском я не занимался, а когда и натыкался на них, то старался обходить, при этом, к сожалению, не отмечая для себя на будущее. По отзывам с форума на microchip.com есть ограничения на стимуляцию регистров АЦП для dsPIC’ов, но я лично не проверял.
Примеры
Здесь приведу несколько примеров готовых скриптов. Сначала совсем простые, потом посложнее. Основная цель, которую я преследовал, выбирая задачи для скриптов, не в том, чтобы их можно было использовать для отладки ваших программ, а в том, чтобы, глядя на них, у вас сложилось более четкое представление о структуре скрипта, порядке его работы и о его возможностях.
Генератор 1 KHz
Для начала самый простой пример — генератор.
testbench for "pic16f877a" is
begin
process is
begin
RB0 <= '0';
loop
RB0 <= not RB0; -- каждые 0.5мс меняем состояние порта
wait for 500 us;
end loop;
end process;
end testbench;В результате работы данного скрипта мы получим такой сигнал на входе RB0:
Можем его немного усложнить, добавив управление из самой отлаживаемой программы:
-------------------------------------------------------------------------------
--
-- Данный скрипт демонстрирует управляемый генератор 1 KHz. Генерируемый сигнал
-- подается на вход RB0. Управляется выходом RB1 (=1 - есть генерация, =0 - нет)
--
-------------------------------------------------------------------------------
testbench for "pic16f877a" is
begin
process is
begin
RB0 <= '0';
loop
if RB1 == '1' then -- Меняем состояние порта, только если есть сигнал
RB0 <= not RB0; -- разрешения ("1" на выходе RB1)
else
RB0 <= '0';
end if;
wait for 500 us;
end loop;
end process;
end testbench;Теперь в программе для микроконтроллера, устанавливая порт RB1 в «1», мы включаем генерацию на RB0 из SCL-скрипта. Для примера была написана небольшая программа, которая формирует на выводе RB1 меандр частотой 50 Гц для управления генератором. На графике видно, что как только на RB1 появляется «1», скрипт начинает генерировать на вход RB0 меандр частотой 1 КГц.
Источник: http://pic24.ru/doku.php/osa/articles/scl
Больше интересных и полезных статей на нашем форуме по АСУ ТПhttps://p4b.su/forums
Тендера фриланс по АСУ ТП https://p4b.su/projects
Библиотека с технической литературой по промышленной автоматизации https://p4b.su/library
Группа ВК https://vk.com/p4b_su