Язык SCL (часть-1) — АСУ ТП: сообщество инженеров

Язык SCL (часть-1)

Введение

Интегрированная среда 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):

  1. выбрать в качестве отладчика MPLAB SIM (меню «Debugger/Select Tool»);
  2. создать рабочую книгу через меню «Debug/Stimulus/New Workbook» (или Open Workbook, если книга уже была создана);
  3. в открывшемся диалоговом окне «Stimulus» нажать кнопку «Advanced…» в нижнем левом углу; откроется маленькое диалоговое окно «Advanced operations»;
  4. в этом новом окне нажать кнопку «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

🔗 Язык SCL (часть-1)

Оставьте комментарий

Войти

Зарегистрироваться

Сбросить пароль

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

Прокрутить вверх