Available Languages?:

Язык SCL

Виктор Тимофеев, ноябрь 2009
osa@pic24.ru

Введение

Интегрированная среда MPLAB IDE имеет в своем составе встроенный симулятор, который позволяет отлаживать программу еще до программирования микросхемы. Для приближения к реальным условиям MPLAB предоставляет возможность имитировать входные сигналы с помощью встроенного инструмента Stimulus. С помощью него можно имитировать уровни на портах ввода вывода и значения регистров, причем можно привязывать эти события ко времени, к состояниям или изменениям состояний портов и регистров. Для этого интегрированной средой MPLAB предоставляется диалоговое окно (доступно через меню "Debugger/Stimulus"), в котором пользователю предлагается задать внешние воздействия.

Однако, когда нужно задать сигнал сложной формы, или довольно много внешних воздействий, связанных между собой во времени и состояниях, вводить имитируемые сигналы через диалоговое окно становится трудно и неудобно. Даже простая задача, например, имитация сигнала в виде манчестерского кода вызывает большие трудности из-за большего объема данных, которые требуется ввести (особенно трудности вызывает подсчет времен для асинхронной стимуляции).

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

Язык SCL довольно плохо описан (официального описания нет), поэтому все, что здесь приведено, - результат исследования и экспериментов, а также обмена опытом с другими исследователями-экспериментаторами. Данный материал не является точным описанием языка, а всего лишь содержит общие принципы написания программ на нем. Составляя его, я решал, как лучше изложить материал: чтобы он был наиболее полным, или чтобы он был простым пособием. Остановился на втором варианте, хотя, в результате получился, скорее, первый.

Возможно, при описании я что-то не учел, что-то описал неполно, а где-то допустил ошибку. Не стесняйтесь ругаться, если обнаружите неточности. Если у кого-то будет какое-то сущетвенное дополнение, я обязательно внесу его в это пособие (некоторые моменты, такие как определение пользовательских типов, я умышленно не стал здесь упоминать из-за их, на мой взгляд, ненадобности и малозначимости в SCL-программах).

Помимо самой этой статьи, я бы рекомендовал ознакомиться с двумя ветками на форуме microchip.com (на английском языке):

В том же форуме по разным темам разбросаны еще несколько примеров, но я в своей статье постарался объединить все затрагиваемые там вопросы.

Описание языка

Идентификаторы

Идентификаторы должны начинаться с латинской буквы и могут содержать латинские буквы, цифры и знак подчеркивания. Пример правильных идентификаторов:

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 поддерживает следующие типы данных:

Перечислимые

integer 32-битное целое знаковое
byte = integer
word = integer
boolean Логический тип, может принимать значения true или false
bit Битовый тип, может принимать значения '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 КГц.

Генератор программного UART

Большинство контроллеров PIC младшего и среднего семейства имеют на борту всего один аппаратный USART-модуль, но часто бывает нужно управлять двумя и более устройствами посредством этого интерфейса. При отладке программы хорошо бы убедиться в том, что прораммный UART-приемник работает корректно. Вот пример скрипта, который генерирует на вход ПИКу сигнал UART. Скорость выбирается пользователем (по умолчанию 9600), данные берутся из файла.

Пример исходного файла data_uart.txt:

baudrate 19200
wait 1 ms
10 20 30 40 50 60
wait 3 ms
55 66 77 88

Здесь baudrate - команда на изменение скорости, wait - команда на выдержку паузы. Числа даны в десятичной системе счисления (как их задавать в шестнадцатеричной или в символьном виде, - не знаю).

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

  1. байт может отправляется из разных мест процесса;
  2. байт может отправляться из разных процессов;
  3. во время отправки данных головной процесс должен заниматься своими делами.

Для синхронизации служит общая переменная UART_Work. Когда она = 0, процесс отправки байта свободен. Для отправки байта головной процесс должен подготовить данные для отправки (записать нужное значение в UART_Data), а потом установить UART_Work в "1", тем самым сообщив процессу отправки, что можно начинать работать. Процесс отправки сам обнулит эту переменную, когда байт будет полностью передан, сообщем тем самым основному процессу, что передатчик вновь свободен.

-------------------------------------------------------------------------------
--
-- Данный скрипт демонстрирует формирование программного UART сигнала на входе
-- контроллера PORTC, 5. Данные читаются из файла "data_uart.txt"
--
-------------------------------------------------------------------------------
-- Пример файла с данными:
-------------------------------------------------------------------------------
--  baudrate 19200
--  wait 1 ms
--  10 20 30 40 50 60
--  wait 3 ms
--  55 66 77 88
-------------------------------------------------------------------------------
 
testbench for "pic16f877a" is
begin
 
 
process is
begin
    report "======================== SCL loaded OK! ==========================";
    wait;
end process;
 
---------------------------------------------------
 
uart_send_byte: process is              -- Подпрограмма вывода одного байта по последовательному
                                        -- интерфейсу UART через вход RC5
 
    variable UART_Delay   : time;       -- Длительность одного бита
    variable UART_Data    : byte;       -- Передаваемый байт
    variable UART_Bit     : byte;       -- Для выделения бита при передаче
    variable UART_Counter : integer;    -- Счетчик битов
    variable UART_Work    : integer;    -- Установкой этой переменной в 1 из другого
                                        -- процесса мы инициируем начало передачи.
 
begin
 
    RC5 <= '1';
    UART_Work := 0;                     -- Сообщаем головной программе, что процесс свободен,
                                        -- моно передавать новый байт
 
    while UART_Work == 0 loop           -- Ожидаем команду на старт от основного процесса
        wait for 1 ic;
    end loop;
 
    RC5 <= '0';                         -- Формируем стартовый бит
    wait for UART_Delay;
 
    UART_Counter := 8;                  -- цикл для 8 бит данных
 
    while UART_Counter > 0 loop
 
        UART_Counter := UART_Counter - 1;
        UART_Bit := UART_Data / 2;      -- Выделяем младший бит
        UART_Bit := UART_Data - UART_Bit * 2;
        UART_Data := UART_Data / 2;
 
        if UART_Bit == 0 then           -- Отправляем его
            RC5 <= '0';
        else
            RC5 <= '1';
        end if;
 
        wait for UART_Delay;
 
    end loop;
 
    RC5 <= '1';                         -- Формируем стоповый бит
    wait for UART_Delay;
 
end process uart_send_byte;
 
---------------------------------------------------
 
uart: process is                            -- Читаем данные из файла и формируем
                                            -- из них сигнал для контроллера
 
    file     InFile : text;                 -- Рабочий файл
    variable Status : file_open_status;
    variable InLine : line;                 -- Строка, прочитанная из файла
    variable i      : integer;              -- Рабочая переменная
    variable Delay  : time;                 -- Рабочая задержка
 
begin
 
    UART_Delay := 1 sec / 9600;             -- скорость по умолчанию 9600
 
                                            -- Открываем файл с данными
    file_open(Status, InFile, "data_uart.txt", read_mode);
    if Status != open_ok then
        report "Ошибка открытия файла data_uart.txt" severity failure;
    end if;
 
 
    while endfile(InFile) == false loop     -- Обрабатываем, пока файл не пуст
 
        readline(InFile, InLine);
        print("Прочитана строка: ", InLine);
 
        while match(InLine, "") == false loop
 
            -- Проверяем строку на наличие команды
 
            if match(InLine, "baudrate") == true then   -- Изменение скорости
 
                read(InLine, i);            -- Удаляем из строки слово "baudrate"
                read(InLine, i);            -- Читаем значение скорости
 
                if i != 0 then
                    UART_Delay := 1 sec / i;            -- Устанавливаем новую скорость
                    print("Новая скорость UART = ", i);
                else
                    print("ОШИБКА в файле: Скорость не может быть = 0!");
                end if;
 
            elsif match(InLine, "wait") == true then    -- Задержка
 
                read(InLine, i);            -- Удаляем из строки слово "wait"
                read(InLine, Delay);        -- Читаем значение задержки
                print("Ждем ", Delay);
                wait for Delay;             -- формируем задержку
 
            else                                        -- Передача байта
 
                read(InLine, UART_Data);                -- Читаем байт
                print("Отправка байта ", UART_Data);
                UART_Work := 1;                         -- Даем команду на начало передачи
 
                while UART_Work == 1 loop               -- Ожидаем завершения передачи
                    wait for 1 ic;
                end loop;
 
            end if;
 
        end loop; -- match InLine
 
    end loop; -- endfile
 
    file_close(InFile);
 
end process uart;
 
end testbench;

Ниже приведен график генерируемого сигнала. На нем отчетливо видно, что данные передаются двумя пакетами с интервалом 3 мс.

Генератор аналогового сигнала

Симулятор в интегрированной среде MPLAB IDE не позволяет имитировать разные уровни напряжения на аналоговых входах. Однако он позволяет симулировать регистры ADRESH и ADRESL напрямую. Этим мы можем воспользоваться для имитации аналоговых сигналов.

Ниже приведен пример, который будет заполнять регистры ADRESH:ADRESL значениями из файла. Это будет происходить в тот момент, когда программа будет обращаться к АЦП. Это пример является только демонстрацией необходимых действия для формирования регитсров ADRES, он не подходит для практического применения, поскольку входные данные синхронизированы только с обращениями программы к АЦП. Т.е. если обращений не будет, то и сигналы формироваться не будут (или другими словами: когда бы мы не обратились к АЦП, мы всегда прочитаем один и тот же сигнал). На практике такое не встречается.

testbench for "pic16f877a" is
begin
 
process is
begin
    report "======================== SCL loaded OK! ==========================";
    wait;
end process;
 
---------------------------------------------------
 
ADC: process is
 
    file     InFile : text;                     -- Рабочий файл
    variable Status : file_open_status;
    variable InLine : line;                     -- Строка, прочитанная из файла
    variable Value  : integer;                  -- Рабочая переменная
 
    constant FileName : string := "data_adc.txt";
 
begin
 
    file_open(Status, InFile, FileName, read_mode);
 
    if Status != open_ok then
        report "Ошибка открытия файла!" severity failure;
    end if;
 
 
    while endfile(InFile) == false loop         -- Обрабатываем, пока файл не пуст
 
        readline(InFile, InLine);
        print("Прочитана строка: ", InLine);
 
        while match(InLine, "") == false loop   -- Обрабатываем строку, пока она не пуста
 
            wait until ADCON0.GO_nDONE == '1';  -- Ждем, когда программа начнет чтение ADC
            read(InLine, Value);
 
            if ADCON0.ADON == '1' then
 
                print("Формируем значение ", Value);
 
                while ADCON0.GO_nDONE == '1' loop -- Ждем завершения измерения (для 877a
                    wait for 1 ic;                -- этот бит сбрасывается MPLAB SIM'ом
                end loop;                         -- автоматически. Но, мо-моему, для неко-
                                                  -- торых контроллеров эту нужно делать
                                                  -- вручную)
 
                if ADCON1.ADFM == '0' then        -- Левое выравнивание
 
                    ADRESH <= Value /  4;
                    ADRESL <= Value * 64;
 
                else
                                                  -- Правое выравнивание
                    ADRESH <= Value / 256;
                    ADRESL <= Value;
 
                end if;
 
            end if; -- ADON
 
        end loop; -- match InLine
 
    end loop; -- endfile
 
    file_close(InFile);
    report "Конец скрипта";
    wait;
 
end process ADC;
 
end testbench;

Генератор DTMF

Итак, как уже было сказано, для практического применения предыдущий пример не очень подходит. Представьте, что нам нужно оцифровать и распознать DTMF сигнал. Для этого нам нужно точно вымерять моменты обращения к АЦП, а если скрипт синхронизирован только с самим обращением, то он будет работать при любых условиях. В новом примере мы создадим реальную модель аналогового сигнала, по которой уже можно будет отлаживать программу.

Напомню: DTMF сигнал - это сигнал, получаемый суммированием двух синусоидальных сигналов из двух наборов частот:

  1. 697 Hz, 770 Hz, 852 Hz, 941 Hz
  2. 1209 Hz, 1336 Hz, 1477 Hz, 1633 Hz

Скрипт будет состоять из 4-х процессов:

  1. FREQ1 - генератор синуса заданной частоты
  2. FREQ2 - генератор синуса заданной частоты
  3. WORK - Управляющий - будет читать файл с данными и формировать целеуказания для FREQ1 и FREQ2
  4. ADC - независимый процесс, который при обращении пользовательской программы к АЦП будет формировать значения регистров ADRESH:ADRESL по мгновенным значениям синусов, генерируемых процессами FREQ1 и FREQ2.

(Примечание: еще добавлены пятый процесс, в который для удобства вынесены объявления глобальных переменных, и шестой, который содержит строку "SCL loaded OK!")

Формат файла "data_dtmf.txt"

Этот файл содержит список команд для управления процессом. В самом простом случае он может состоять из чисел от 1 до 16 (0 - будет воспринят как 16), из которых будет генерироваться последовательность тонов. Однако, с помощью доолнительных команд можно менять параметры сигналов:

freq <c> <f>
Генерировать синус частоты f на канале c (c = 1 или 2)
stop
остановить генерацию (отключает оба канала)
duration <d>
задать длительность тонов (по умолчанию 100 мс)
pause <p>
задать паузу между тонами (по умолчанию 100 мс)
level
задать уровень сигнала от 0% до 100% (по умолчанию 100%)

Также допускается использование комментариев

Пример рабочего файла:

  
// Задаем временные параметры тонов
duration 30 ms
pause 30 ms

// Генерируем все тоны по очереди
1 2 3 4 5 6 7 8 

// Уменьшаем уровень сигнала втрое 
level 33

9 10 11 12 13 14 15 16

// Генерируем опорную частоту 1 КГц на первом канале
freq 1 1000
wait 100 ms
stop

// Генерируем опорную частоту 2 КГц на втором канале
freq 2 2000
wait 100 ms
stop

Вот отчет его обработки:

(0)  SIM-N0001 Note: ======================== SCL loaded OK! ==========================
(0)  SIM-N0001 Note: Line:  // Задаем временные параметры тонов
(0)  SIM-N0001 Note: Line:  duration 30 ms
(0)  SIM-N0001 Note: Установлено время гудка  30000000 ns
(0)  SIM-N0001 Note: Line:  pause 30 ms
(0)  SIM-N0001 Note: Установлено время паузы  30000000 ns
(0)  SIM-N0001 Note: Line:
(0)  SIM-N0001 Note: Line:  // Генерируем все тоны по очереди
(0)  SIM-N0001 Note: Line:  1 2 3 4 5 6 7 8
(0)  SIM-N0001 Note: Генерируется тон:  1
ADC-W0001:	Tad time is less than 1.60us
ADC-W0008: No stimulus file attached to ADRESL for A/D.
(60000)  SIM-N0001 Note: Генерируется тон:  2
(120000)  SIM-N0001 Note: Генерируется тон:  3
(180000)  SIM-N0001 Note: Генерируется тон:  4
(240000)  SIM-N0001 Note: Генерируется тон:  5
(300000)  SIM-N0001 Note: Генерируется тон:  6
(360000)  SIM-N0001 Note: Генерируется тон:  7
(420000)  SIM-N0001 Note: Генерируется тон:  8
(480000)  SIM-N0001 Note: Line:
(480000)  SIM-N0001 Note: Line:  // Уменьшаем уровень сигнала втрое
(480000)  SIM-N0001 Note: Line:  level 33
(480000)  SIM-N0001 Note: Установлен уровень сигнала =  33 %
(480000)  SIM-N0001 Note: Line:
(480000)  SIM-N0001 Note: Line:  9 10 11 12 13 14 15 16
(480000)  SIM-N0001 Note: Генерируется тон:  9
(540000)  SIM-N0001 Note: Генерируется тон:  10
(600000)  SIM-N0001 Note: Генерируется тон:  11
(660000)  SIM-N0001 Note: Генерируется тон:  12
(720000)  SIM-N0001 Note: Генерируется тон:  13
(780000)  SIM-N0001 Note: Генерируется тон:  14
(840000)  SIM-N0001 Note: Генерируется тон:  15
(900000)  SIM-N0001 Note: Генерируется тон:  16
(900000)  SIM-N0001 Note: Line:
(900000)  SIM-N0001 Note: Line:  // Генерируем опорную частоту 1 КГц на первом канале
(900000)  SIM-N0001 Note: Line:  freq 1 1000
(900000)  SIM-N0001 Note: Частота канала 1 =  1000
(900000)  SIM-N0001 Note: Sample rate =  976 ns
(900000)  SIM-N0001 Note: OK
(900000)  SIM-N0001 Note: Line:  wait 100 ms
(900000)  SIM-N0001 Note: Ждем  100000000 ns
(1000000)  SIM-N0001 Note: Line:  stop
(1000000)  SIM-N0001 Note: Line:
(1000000)  SIM-N0001 Note: Line:  // Генерируем опорную частоту 2 КГц на втором канале
(1000000)  SIM-N0001 Note: Line:  freq 2 2000
(1000000)  SIM-N0001 Note: Частота канала 2 =  2000
(1000000)  SIM-N0001 Note: OK
(1000000)  SIM-N0001 Note: Line:  wait 100 ms
(1000000)  SIM-N0001 Note: Ждем  100000000 ns
(1100000)  SIM-N0001 Note: Line:  stop
(1100000)  SIM-N0001 Note: Line:
(1100000)  SIM-F0001 Failure: Работа скрипта успешно завершена



Текст скрипта

Наконец, сам скрипт:

(файл с таблицей синуса прилагается sinus.rar)


testbench for "pic16f877a" is
begin
 
-------------------------------------------------------------------
 
process is
begin
    report "======================== SCL loaded OK! ==========================";
    wait;
end process;
 
 
-------------------------------------------------------------------
--
-- Определение глобальных переменных (использующиеся более чем одним
-- процессом) вынесены в отдельный пустой процесс для наглядности
--
-------------------------------------------------------------------
 
variables : process is
 
    constant DTMFFileName   : string  := "data_dtmf.txt";   -- Файл с командами
    constant SinusFileName  : string  := "sinus.txt";       -- Файл, содержащий 1024 выборки
                                                            -- одного периода синусоиды
    constant NumOfSinPoints : integer := 1024;
 
    variable SignalLevel    : integer := 100;               -- Уровень сигнала (в процентах)
 
                                                            -- ДЛЯ КАНАЛА 1:
    variable SampleRate_1   : time;                         -- Скорость выборки
    variable AnalogSignal_1 : integer := 0;                 -- Мгновенное значение сигнала
    variable FreqEnable_1   : boolean := false;             -- true - генерировать сигнал
                                                            -- false - молчать
 
                                                            -- ДЛЯ КАНАЛА 2:
    variable SampleRate_2   : time;                         -- Скорость выборки
    variable AnalogSignal_2 : integer := 0;                 -- Мгновенное значение сигнала
    variable FreqEnable_2   : boolean := false;             -- true - генерировать сигнал
                                                            -- false - молчать
 
begin
    SampleRate_1 := 1 sec / 697;
    SampleRate_1 := SampleRate_1 / NumOfSinPoints;
    SampleRate_2 := 1 sec / 1209;
    SampleRate_2 := SampleRate_2 / NumOfSinPoints;
 
    wait;
end process variables;
 
 
 
 
-------------------------------------------------------------------
--
-- Основной процесс. Читает из файла data_dtmf.txt команды и
-- выполняет их, выдавая целеуказания процессам FREQ1 и FREQ2.
--
-------------------------------------------------------------------
 
WORK: process is
 
    file     DTMF_File      : text;
    variable Status         : file_open_status;
    variable i, j           : integer;
    variable DTMF_Line      : line;
 
    variable DTMF_Pause     : time := 100 ms;
    variable DTMF_Duration  : time := 100 ms;
 
    variable WaitTime       : time;
 
begin
 
    file_open(Status, DTMF_File, DTMFFileName, read_mode);
    if Status != open_ok then
        print("Ошибка открытия файла ", DTMFFileName);
        report "Скрипт приостановлен" severity failure;
    end if;
 
    while endfile(DTMF_File) == false loop
 
        readline(DTMF_File, DTMF_Line);
        print("Line: ",  DTMF_Line);
 
        while match(DTMF_Line, "") == false loop
 
            -----------------------------------------------------
            -- Пропускаем комментарии
            -----------------------------------------------------
 
            if match(DTMF_Line, "//") == true then
 
                exit;                               -- выходим из цикла
 
            -----------------------------------------------------
            -- Пауза в обработке скрипта
            -----------------------------------------------------
 
            elsif match(DTMF_Line, "wait") == true then  --
 
                read(DTMF_Line, i);                 -- Удаляем команду из строки
                read(DTMF_Line, WaitTime);
                print("Ждем ", WaitTime);
                wait for WaitTime;
 
            -----------------------------------------------------
            -- Длительность сигнала
            -----------------------------------------------------
 
            elsif match(DTMF_Line, "duration") == true then
 
                read(DTMF_Line, i);                 -- Удаляем команду из строки
                read(DTMF_Line, DTMF_Duration);
                print("Установлено время гудка ", DTMF_Duration);
 
            -----------------------------------------------------
            -- Длительность паузы
            -----------------------------------------------------
 
            elsif match(DTMF_Line, "pause") == true    then
 
                read(DTMF_Line, i);                 -- Удаляем команду из строки
                read(DTMF_Line, DTMF_Pause);
                print("Установлено время паузы ", DTMF_Pause);
 
            -----------------------------------------------------
            -- Выбор произвольной частоты
            -----------------------------------------------------
 
            elsif match(DTMF_Line, "freq") == true     then
 
                read(DTMF_Line, i);                 -- Удаляем команду из строки
                read(DTMF_Line, i);                 -- Читаем номер канала
 
                if i == 1 then
 
                    read(DTMF_Line, i);
                    if i > 0 then
 
                        SampleRate_1 := 1 sec / i;
                        SampleRate_1 := SampleRate_1 / NumOfSinPoints;
                        FreqEnable_1 := true;
                        print("Частота канала 1 = ", i);
                        print("Sample rate = ", SampleRate_1);
 
                    end if;
 
                elsif i == 2 then
 
                    read(DTMF_Line, i);
                    if i > 0 then
 
                        SampleRate_2 := 1 sec / i;
                        SampleRate_2 := SampleRate_2 / NumOfSinPoints;
                        FreqEnable_2 := true;
                        print("Частота канала 2 = ", i);
 
                    end if;
 
                else
                    print("В команде freq неправильно задан номер канала: ", i, "(должен быть 1 или 2)");
                    read(DTMF_Line, i);
                end if;
 
                report "OK";
 
            -----------------------------------------------------
            -- Прекратить генерацию обоих синусов
            -----------------------------------------------------
 
            elsif match(DTMF_Line, "stop") == true then
 
                read(DTMF_Line, i);                 -- Удаляем команду из строки
                FreqEnable_1 := false;
                FreqEnable_2 := false;
 
            -----------------------------------------------------
            -- Задать уровень сигнала (в процентах)
            -----------------------------------------------------
 
            elsif match(DTMF_Line, "level") == true then
 
                read(DTMF_Line, i);                 -- Удаляем команду из строки
                read(DTMF_Line, i);
 
                if i >= 0 then
                    if i <= 100 then
                        SignalLevel := i;
                        print("Установлен уровень сигнала = ", SignalLevel, "%");
                    end if;
                end if;
 
 
            -----------------------------------------------------
            -- Выдать DTMF-тон
            -----------------------------------------------------
 
            else
 
                read(DTMF_Line, i);
                if i < 0 then
                    print("Неправильный тон: ", i);
                elsif i > 16 then
                    print("Неправильный тон: ", i);
                else
 
                    print("Генерируется тон: ", i);
 
                    if    j == 1 then                   -- 1
                        SampleRate_1 := 1 sec / 697;
                        SampleRate_2 := 1 sec / 1209;
                    elsif j == 2 then                   -- 2
                        SampleRate_1 := 1 sec / 697;
                        SampleRate_2 := 1 sec / 1336;
                    elsif j == 3 then                   -- 3
                        SampleRate_1 := 1 sec / 697;
                        SampleRate_2 := 1 sec / 1477;
                    elsif j == 4 then                   -- 4
                        SampleRate_1 := 1 sec / 770;
                        SampleRate_2 := 1 sec / 1209;
                    elsif j == 5 then                   -- 5
                        SampleRate_1 := 1 sec / 770;
                        SampleRate_2 := 1 sec / 1336;
                    elsif j == 6 then                   -- 6
                        SampleRate_1 := 1 sec / 770;
                        SampleRate_2 := 1 sec / 1477;
                    elsif j == 7 then                   -- 7
                        SampleRate_1 := 1 sec / 852;
                        SampleRate_2 := 1 sec / 1209;
                    elsif j == 8 then                   -- 8
                        SampleRate_1 := 1 sec / 852;
                        SampleRate_2 := 1 sec / 1336;
                    elsif j == 9 then                   -- 9
                        SampleRate_1 := 1 sec / 852;
                        SampleRate_2 := 1 sec / 1477;
                    elsif j == 10 then                  -- 0
                        SampleRate_1 := 1 sec / 941;
                        SampleRate_2 := 1 sec / 1209;
                    elsif j == 11 then                  -- *
                        SampleRate_1 := 1 sec / 941;
                        SampleRate_2 := 1 sec / 1336;
                    elsif j == 12 then                  -- #
                        SampleRate_1 := 1 sec / 941;
                        SampleRate_2 := 1 sec / 1477;
                    elsif j == 13 then                  -- A
                        SampleRate_1 := 1 sec / 697;
                        SampleRate_2 := 1 sec / 1633;
                    elsif j == 14 then                  -- B
                        SampleRate_1 := 1 sec / 770;
                        SampleRate_2 := 1 sec / 1633;
                    elsif j == 15 then                  -- C
                        SampleRate_1 := 1 sec / 852;
                        SampleRate_2 := 1 sec / 1633;
                    else                                -- D
                        SampleRate_1 := 1 sec / 941;
                        SampleRate_2 := 1 sec / 1633;
                    end if;
 
                    SampleRate_1 := SampleRate_1 / NumOfSinPoints;
                    SampleRate_2 := SampleRate_2 / NumOfSinPoints;
 
                    FreqEnable_1 := true;               -- Включаем звук
                    FreqEnable_2 := true;
 
                    wait for DTMF_Duration;
 
                    FreqEnable_1 := false;              -- Выключаем звук
                    FreqEnable_2 := false;
 
                    wait for DTMF_Pause;
 
                end if;
 
            end if;
 
        end loop; -- while DTMF_Line != ""
 
    end loop; -- while not eof
 
    file_close(DTMF_File);
    report "Работа скрипта успешно завершена";
    wait;
 
end process WORK;
 
 
 
-------------------------------------------------------------------
--
-- Процесс формирования регистров ADRESH:ADRESL. Когда пользовательская
-- программа устанавливает бит GODONE, этот процесс берет мгновенные
-- значения сигнала, складывает их, умножает на переменную, показывающую
-- уровень сигнала, и отправляет в регистры ADRESH:ADRESL, соблюдая
-- выравнивание (левое или правое)
--
-------------------------------------------------------------------
 
ADC: process is
    variable AnalogSignal : integer;
begin
 
    wait until ADCON0.GO_nDONE == '1';  -- Ждем, когда программа начнет чтение ADC
 
    if ADCON0.ADON == '1' then
 
        while ADCON0.GO_nDONE == '1' loop
            wait for 1 ic;
        end loop;
 
        AnalogSignal := AnalogSignal_1/2  + AnalogSignal_2/2; -- Сумма мгновенных
                                                              -- значений сигналов
        AnalogSignal := AnalogSignal * SignalLevel; -- Вычисление уровня сигнала
        AnalogSignal := AnalogSignal / 100;         -- (в процентах)
        AnalogSignal := AnalogSignal + 512;         -- Средняя точка (Vdd/2)
 
        if AnalogSignal < 0 then                    -- Ограничение снизу
            AnalogSignal := 0;
        end if;
 
        if AnalogSignal > 1023 then                 -- Ограничение сверху
            AnalogSignal := 1023;
        end if;
 
        if ADCON1.ADFM == '0' then                  -- Левое выравнивание
            ADRESH <= AnalogSignal /  4;
            ADRESL <= AnalogSignal * 64;
        else                                        -- Правое выравнивание
            ADRESH <= AnalogSignal / 256;
            ADRESL <= AnalogSignal;
        end if;
 
    end if; -- ADON
 
end process ADC;
 
 
 
 
 
-------------------------------------------------------------------
--
-- Генерация синуса 1. Период выборки значений из таблицы синуса
-- оределяется переменной SampleRate_1. Файл со значениями синуса
-- читается по кругу.
--
-- FreqEnable_1 разрешает/запрещает генерацию синуса
--
-------------------------------------------------------------------
 
FREQ1: process is
 
    file     InFile_1 : text;
    variable Status_1 : file_open_status;
    variable InLine_1 : line;
    variable i_1      : integer;
    variable Time_1   : time;
    variable Now_1    : time;
 
begin
 
    file_open(Status_1, InFile_1, SinusFileName, read_mode);
    if Status_1 != open_ok then
        print("Ошибка открытия файла ", SinusFileName);
        report "Скрипт приостановлен" severity failure;
    end if;
 
 
    while endfile(InFile_1) == false loop           -- Читаем весь файл
        readline(InFile_1, InLine_1);               -- построчно
 
        while match(InLine_1, "") == false loop     -- Выбираем значения из строки
            if FreqEnable_1 == true then            -- Генерация разрешена
                read(InLine_1, AnalogSignal_1);
            else                                    -- Генерация запрещена
                read(InLine_1, i_1);
                AnalogSignal_1 := 0;
                wait for 1 us;
            end if;
 
            Now_1  := now();                        -- Компенсация ошибки, вносимой
            Time_1 := Time_1 + SampleRate_1;        -- несоответствием длительности
                                                    -- машинного цикла и SampleRate
            if  Time_1  < Now_1 then
                Time_1 := Now_1;
            end if;
 
            wait for Time_1 - Now_1;                  -- Пауза, равная периоду выборки
 
        end loop;
 
    end loop;
 
    file_close(InFile_1);
 
end process FREQ1;
 
 
 
-------------------------------------------------------------------
--
-- Генерация синуса 2. Период выборки значений из таблицы синуса
-- оределяется переменной SampleRate_2. Файл со значениями синуса
-- читается по кругу.
--
-- FreqEnable_2 разрешает/запрещает генерацию синуса
--
-------------------------------------------------------------------
 
FREQ2: process is
 
    file     InFile_2 : text;               -- Файл со значениями синуса
    variable Status_2 : file_open_status;
    variable InLine_2 : line;               -- Рабочая строка для чтения файла
    variable Time_2   : time;               --
    variable Now_2    : time;
    variable i_2      : integer;
 
begin
 
    file_open(Status_2, InFile_2, SinusFileName, read_mode);
    if Status_2 != open_ok then
        print("Ошибка открытия файла ", SinusFileName);
        report "Скрипт приостановлен" severity failure;
    end if;
 
 
    while endfile(InFile_2) == false loop           -- Читаем весь файл
        readline(InFile_2, InLine_2);               -- построчно
 
        while match(InLine_2, "") == false loop     -- Выбираем значения из строки
            if FreqEnable_2 == true then            -- Генерация разрешена
                read(InLine_2, AnalogSignal_2);
            else                                    -- Генерация запрещена
                read(InLine_2, i_2);
                AnalogSignal_2 := 0;
                wait for 1 us;
            end if;
 
            Now_2  := now();                        -- Компенсация ошибки, вносимой
            Time_2 := Time_2 + SampleRate_2;        -- несоответствием длительности
                                                    -- машинного цикла и SampleRate
            if  Time_2  < Now_2 then
                Time_2 := Now_2;
            end if;
 
            wait for Time_2 - Now_2;                -- Пауза, равная периоду выборки
 
        end loop;
 
    end loop;
 
    file_close(InFile_2);
 
end process FREQ2;
 
 
end testbench;

Он, хоть и сравнительно большой, но понятный. Не составит труда дабавить еще один процесс, который будет подмешивать к сигналу шум. Для этого нужно будет воспользоваться функцией random_time, а в процессе ADC вычислять сумму не двух сигналов, а трех. Уровень шума удобно будет регулировать через входной файл.

Ниже приведен фрагмент графика сигнала, сформированного данным скриптом для команды "1" (697 Гц + 1209 Гц):

Заключение

Итак, в статье были описаны известные мне конструкции языка SCL, а также приведены несколько небольших примеров использования этого языка для имитации цифровых и аналоговых сигналов в симуляторе. Я надеюсь, что описанное здесь поможет упростить процесс отладки, а главное - поможет вам утвердиться во мнении, что симулятор в MPLAB IDE - это действительно мощное средство, и не такое оно неудобное, как может показаться, если пользоваться только диалоговым окном Stimulus, пренебрегая самим языком SCL. Так что теперь, я думаю, такие задачи, как имитация 128-битного манчестерского кода, трудностей больше не вызовут.

Тем, кто раньше пользовался диалоговым окном Stimulus для задания входных сигналов, а после прочтения статьи задумался: "А как бы мне то же самое сделать на SCL?" - напомню, что в MPLAB IDE есть возможность сформировать файл SCL для заданных в диалоговом окне сигналов. Для этого нужно в диалоговом окне "Stimulus" нажать кнопку "Advanced…" и в открывшемся окне нажать кнопку "Generate SCL File". Все заданные вами воздействия будут автоматически переведены в скрипт на языке SCL.

Виктор Тимофеев, ноябрь, 2009
osa@pic24.ru

 
osa/articles/scl.txt · Последние изменения: 28.05.2010 09:44 От osa_chief
 
Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki