======= Язык SCL ======= Виктор Тимофеев, ноябрь 2009\\ [[osa@pic24.ru]] ====== Введение ======= == == Интегрированная среда MPLAB IDE имеет в своем составе встроенный симулятор, который позволяет отлаживать программу еще до программирования микросхемы. Для приближения к реальным условиям MPLAB предоставляет возможность имитировать входные сигналы с помощью встроенного инструмента Stimulus. С помощью него можно имитировать уровни на портах ввода вывода и значения регистров, причем можно привязывать эти события ко времени, к состояниям или изменениям состояний портов и регистров. Для этого интегрированной средой MPLAB предоставляется диалоговое окно (доступно через меню "Debugger/Stimulus"), в котором пользователю предлагается задать внешние воздействия. Однако, когда нужно задать сигнал сложной формы, или довольно много внешних воздействий, связанных между собой во времени и состояниях, вводить имитируемые сигналы через диалоговое окно становится трудно и неудобно. Даже простая задача, например, имитация сигнала в виде манчестерского кода вызывает большие трудности из-за большего объема данных, которые требуется ввести (особенно трудности вызывает подсчет времен для асинхронной стимуляции). В таких случаях для задания внешних воздействий удобно пользоваться специальным встроенным языком SCL. SCL - это структурный VHDL-подобный язык высокого уровня (примечание: VHDL используется для описания аппаратуры), правда, по сравнению с ним гораздо менее функциональный. SCL представляет нам возможность организовывать циклы, формировать задержки, задавать уровни цифровых входов, имитировать аналоговые сигналы, работать с файлами. Язык SCL довольно плохо описан (официального описания нет), поэтому все, что здесь приведено, - результат исследования и экспериментов, а также обмена опытом с другими исследователями-экспериментаторами. Данный материал не является точным описанием языка, а всего лишь содержит общие принципы написания программ на нем. Составляя его, я решал, как лучше изложить материал: чтобы он был наиболее полным, или чтобы он был простым пособием. Остановился на втором варианте, хотя, в результате получился, скорее, первый. Возможно, при описании я что-то не учел, что-то описал неполно, а где-то допустил ошибку. Не стесняйтесь ругаться, если обнаружите неточности. Если у кого-то будет какое-то сущетвенное дополнение, я обязательно внесу его в это пособие (некоторые моменты, такие как определение пользовательских типов, я умышленно не стал здесь упоминать из-за их, на мой взгляд, ненадобности и малозначимости в SCL-программах). Помимо самой этой статьи, я бы рекомендовал ознакомиться с двумя ветками на форуме microchip.com (на английском языке): * [[http://www.microchip.com/forums/tm.aspx?m=111255|SCL PRIMER / TUTORIAL ]] - краткое описание языка SCL; * [[http://www.microchip.com/forums/tm.aspx?m=109149|SCL Code repository]] - несколько примеров скриптов на языке 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; Здесь: {| class = "fpl" |- |##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 поддерживает следующие типы данных: ==== Перечислимые ==== == == {| class = "fpl" |- |##integer## |32-битное целое знаковое |- |##byte## |= integer |- |##word## |= integer |- |##boolean## |Логический тип, может принимать значения ##true## или ##false## |- |##bit## |Битовый тип, может принимать значения '0' или '1' |- |##character## |Символьный. |- |##paddress## |Адрес в памяти программы. 24-битовое беззнаковое целое. |- |##daddress## |Адрес в памяти данных. 24-битовое беззнаковое целое. |} ==== Временные ==== == == {| class = "fpl" |- |##time## |Временной. Знаковое 64-битное целое, задается в пикосекундах. Однако можно указать другие единицы измерения: ns, us, ms, sec, min, hr. |- |##cycle## |Схожий с временным типом, только время задается в циклах. Задается в виде числа с указанием единиц измерения ic (instruction cycle). |- |##frequency## |Частотный. Задается в герцах (от 0 до 1e9). Может задаваться с явным указанием единиц измерения: hz, khz, mhz. |} ==== Строковые ==== == == {| class = "fpl" |- |##string## |Текстовая строка. Представляет собой массив character'ов (//type string is array (integer range <>) of character;//). Применяется для вывода информации по ходу симуляции |- |##line## |Строка для обработки данных. Может быть прочитана из файла и обработана. |} ==== Файловые ==== == == {| class = "fpl" |- |##text## |Файловый. Применяется для чтения файлов (запись через этот тип произвести не удается). |- |##file_open_kind## |Тип открываемого файла. Может принимать значения: read_mode, write_mode, append_mode. (Удалось заставить работать только в режиме read_mode) |- |##file_open_status## |Состояние открытого файла. Может принимать значения: open_ok, status_error, name_error, mode_error |} ==== Остальные ==== == == {| class = "fpl" |- |##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 then ... elsif then ... elsif 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 Время может быть указано как в виде константы, так и в виде переменной. wait for 100 ic; MyTime := 150 us; wait for MyTime; === 3. wait until - ожидание события === wait until [for выполняет ожидание события, заданного выражением ##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 [for Параметры этого оператора указываются так же, как и в описании списка для ##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 loop ... end loop; Цикл похож на предыдущий, но с предустановленным условием выполнения. Например, цикл из предыдущего примера выглядел бы так: -- Генератор на RB0, пока на RB1 высокий уровень while RB1 == '1' loop RB0 <= not RB0; wait for 1 us; end loop; ==== Вывод информации report ==== == == Этот оператор выводит отладочную информацию в MPLAB IDE в окно "Output: MPLAB_SIM". Может использоваться совместно с оператором ##severity##, который обозначает тип сообщения. Вывод информации служит для визуализации текущего состояния работы SCL-файла. В качестве параметра может быть как строковая константа, так и строковая переменная. Например: process is variable s : string; begin report "This is string-constant"; s := "This is string-variable"; report s; wait; end process; Выведет в окно: (0) SIM-N0001 Note: This is string-constant (0) SIM-N0001 Note: This is string-variable Здесь SIM-N0001 - номер процесса, Note - тип сообщения. По умолчанию все сообщения имеют тип Note. Однако, с помощью оператора ##severity## можно изменять тип сообщений на: note, warning, error, failure. Причем, note, warning и error позволяют скрипту SCL выполняться дальше, в то время как failure прерывают выполнение скрипта. Код: process is begin report "This is a note"; report "This is a warning" severity warning; report "This is an error" severity error; report "This is a failure" severity failure; report "This text will not be reported"; wait; end process; выведет следующую последовательность сообщений: (0) SIM-N0001 Note: This is a note (0) SIM-W0001 Warning: This is a warning (0) SIM-E0001 Error: This is an error (0) SIM-F0001 Failure: This is a failure Обратим внимание, что последняя строка уже не выводится, т.к. ##severity failure## прервало работу скрипта. ===== Функции ===== == == На данный момент мне известны следующие функции: * [[#Стимуляция регистров]] * **accessin** - назначить файл для асинхронной записи в регистр * **triggerin** - назначить файл для синхронной записи в регистр * **packetin** - отправить данные * [[#История регистра]] * **accessout** - назначить файл для асинхронного сохранения значений регистра * **triggerout** - назначить файл для синхронного сохранения значений регистра * **triggerout_gpr** - назначить файл для синхронного сохранения значений произвольного регистра * [[#Чтение текстовых файлов]] * **file_open** - открыть файл * **file_close** - закрыть файл * **endfile** - проверка конца файла * **readline** - читать строку из файла * [[#Обработка строк]] * **match** - проверить совпадение строк * **read** - читать параметр из строки * [[#Время]] * **now** - получение текущего времени * **random_time** - получить случайное время * [[#Для отладки]] * **print** - вывод информации в отчет ==== Стимуляция регистров ==== ---- \\ === === |\ \ \ ##accessin## (FileName: **string**, FileMode: **mode**, Reg: **register**, Wrap: **boolean**)\ \ \ | == == * Назначить файл для асинхронной записи в регистр. Регистр при каждом к нему обращении (на чтение) будет заполняться данными из указанного файла. **Параметры:** ; ''FileName'' : Имя файла ; ''FileMode'' : Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. ниже) ; ''Reg'' : Стимулируемый регистр ; ''Wrap'' : ? **Типы файла:** * ''hex_mode'' - Данные записаны в шестнадцатеричном ASCII представлении (на каждой строке по одномму числу) * ''dec_mode'' - Данные записаны в десятичном ASCII представлении (на каждой строке по одномму числу) * ''binary_mode'' - Двоичный файл * ''formatted_mode'' - Форматированный файл //(как он форматирован я пока не разобрался)// process is begin accessin("adc_data.txt", binary_mode, ADRESH, true); wait; end process; \\ ---- \\ === === |\ \ \ ##triggerin## (FileName: **string**, FileMode: **mode**, Reg: **register**, PC: **paddress**, Wrap: **boolean**)\ \ \ | == == * Назначить файл для синхронной записи в регистр. Регистр будет заполняться данными из указанного файла при указанном значении программного счетчика. **Параметры:** ; ''FileName'' : Имя файла ; ''FileMode'' : Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше) ; ''Reg'' : Стимулируемый регистр ; ''PC'' : Значение программного счетчика, при котором производить стимуляцию регистра (может быть указан в виде имени функции) ; ''Wrap'' : ? Если значение PC указывается в виде имени функции, то это имя должно быт ьобъявлено в секции ##configuration## с ключевыми словами ##shared label## (нечто вроде ##extern## в Си). **Пример:** configuration for "pic16f877a" is shared label usart_getch; -- Объявляем скрипту имя функции end configuration; testbench for "pic16f877a" is begin process is begin -- формируем значение PORTB при каждом входе в прерывание triggerin("portb.txt", hex_mode, PORTB, 16#0004#, true); -- формируем значение приемного регистра USART при каждом -- входе в функцию usart_getch() triggerin("portb.txt", hex_mode, RCREG, usart_getch, true); wait; end process; end testbench; \\ ---- \\ === === |\ \ \ ##packetin## (Data: **line**, Reg: **register**, Wrap: **boolean**)\ \ \ | == == * Стимуляция значения регистра вручную. Регистр будет принимать значения, указанные в строке. Эта функция может быть применена тогда, когда регистр нужно стимулировать по более сложному алгоритму, чем допускают функции ##accessin## и ##triggerin##. **Параметры:** ; ''Data'' : Пакет данных (список значений, разделяемых пробелами), которые нужно занести в регистр ; ''Reg'' : Имя регистра ; ''Wrap'' : 'true' - добавить в очередь к уже имеющимся в пакете данным; 'false' - затереть старые данные новым пакетом **Пример** стимулирования принятых данных по UART: process is variable DataLine : line := "1 2 3 4 5 6 7 8 9 10"; begin wait for 10 ms; packetin(DataLine, RCREG, false); -- Стимулируем принимаемые данные wait until RCREG_packet_done; -- Ожидание завершения приема ... end process; \\ ---- \\ ==== История регистра ==== ---- \\ === === |\ \ \ ##accessout## (FileName: **string**, FileMode: **mode**, Reg: **register**, Wrap)\ \ \ | == == * Назначить файл для асинхронного сохранения значений регистра. Значение регистра при каждом к нему обращении (на запись) будет сохраняться в указанный файл. **Параметры:** ; ''FileName'' : Имя файла ; ''FileMode'' : Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше) ; ''Reg'' : Наблюдаемый регистр ; ''Wrap'' : ? process is begin accessout("eeprom_data.txt", hex_mode, EEDATA); wait; end process; \\ ---- \\ === === |\ \ \ ##triggerout## (FileName: **string**, FileMode: **mode**, Reg: **register**, PC: **paddress**)\ \ \ | == == * Назначить файл для синхронного сохранения значений регистра. Значение регистра будет записываться в указанный файл при каждой установке программного счетчика в указанное значение. **Параметры:** ; ''FileName'' : Имя файла ; ''FileMode'' : Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше) ; ''Reg'' : Наблюдаемый регистр ; ''PC'' : Значение программного счетчика, при котором производить запись значения регистра в файл (может быть указан в виде имени функции) Если значение PC указывается в виде имени функции, то это имя должно быт ьобъявлено в секции ##configuration## с ключевыми словами ##shared label##. **Пример:** configuration for "pic16f877a" is shared variable eewrite; -- Объявляем скрипту имя функции end configuration; testbench for "pic16f877a" is begin process is begin -- Сохраняем все значения регистра PORTA на момент входа в прерывание triggerout("porta.txt", hex_mode, PORTA, 16#0004#); -- Сохраняем все записываемые в EEPROM данные triggerout("eeprom.txt", hex_mode, EEDATA, eewrite); wait; end process; end testench; \\ ---- \\ === === |\ \ \ ##triggerout_gpr## (FileName: **string**, FileMode: **mode**, GPR: ???, PC: **paddress**, Size: **integer**)\ \ \ | == == * Назначить файл для синхронного сохранения значений регистра. Значение регистра будет записываться в указанный файл при каждой установке программного счетчика в указанное значение. **Параметры:** ; ''FileName'' : Имя файла ; ''FileMode'' : Тип файла: hex_mode, dec_mode, binary_mode, formatted_mode (см. выше) ; ''GPR'' : Имя переменной ; ''PC'' : Значение программного счетчика, при котором производить запись значения регистра в файл (может быть указан в виде имени функции) ; ''Size'' : Количество байт на одну запись (в текстовом режиме будут разделены пробелами) Для того, чтобы скрипт знал имя используемой переменной, ему нужно его сообщеть в секции ##configuration## c ключевыми словами ##shared variable##: **Пример:** configuration for "pic16f877a" is shared variable MyCounter; -- Объявляем скрипту имя переменной end configuration; testbench for "pic16f877a" is begin process is begin -- Сохраняем все значения переменной MyCounter при входе в прерывание triggerout("counter.txt",dec_mode, MyCounter, 16#0004#); wait; end process; end testbench; \\ ---- \\ ==== Чтение текстовых файлов ==== ---- \\ === === |\ \ \ ##file_open## (Status: **file_open_status**, F: **text**, Name: **string**, Kind: **file_open_kind**\ \ \ | == == * Открывает указанный файл и связывает его с файловой переменной. **Параметры**: ; ''Status'' : в эту переменную будет записан результат открытия файла (open_ok, status_error, name_error, mode_error) ; ''F'' : файловая переменная ; ''Name'' : имя файла в текстовом виде ; ''Kind'' : режим работы файла (read_mode,write_mode,append_mode). Хоть SCL и позволяет открывать файлы в режимах write и append, и даже создает файлы с указанным именем на диске, но произвести запись в эти файлы так и не удалось. Так что на сегодняшний день реально из файлов можно только читать. **Пример:** process is file MyFile : text; variable Result : file_open_result; begin file_open(Result, MyFile, "data.txt", read_mode); if Result != open_ok then report "Error opening file data.txt!" severity failure; end if; end process; \\ ---- \\ === === |\ \ \ ##file_close## (F: **text**)\ \ \ | == == * Закрывает открытый файл **Параметр:** ; ''F'' : файловая переменная **Пример:** process is file MyFile : text; variable Result : file_open_result; begin file_open(Result, MyFile, "data.txt", read_mode); ... file_close(MyFile); end process; \\ ---- \\ === === |\ \ \ ##endfile## (F: **text**)\ \ \ | == == * Проверка конца файла. Возвращает 'true', если конец файла, иначе - 'false'. **Параметр:** ; ''F'' : файловая переменная **Пример:** process is file MyFile : text; variable Result : file_open_result; begin file_open(Result, MyFile, "data.txt", read_mode); while endfile(MyFile) == false loop ... end loop; end process; \\ ---- \\ === === |\ \ \ ##readline## (F: **text**, Data: **line**)\ \ \ | == == * Читает строку из файла. **Параметр:** ; ''F'' : файловая переменная ; ''Data'' : переменная, куда будет помещена прочитанная строка **Пример:** process is file MyFile : text; variable Result : file_open_result; variable Data : line; begin file_open(Result, MyFile, "data.txt", read_mode); while endfile(MyFile) == false loop readline(MyFile, Data); ... end loop; end process; ==== Обработка строк ==== ---- \\ === === |\ \ \ ##match## (Line: **line**, Pattern: **string**)\ \ \ | == == * Проверить совпадение строк. Вернее, совпадение начала первой строки со второй строкой. Функция возвращает 'true', если начало первой строки совпадает со второй строкой, и 'false', - если не совпадает. **Параметры:** ; ''Line'' : здесь содержится строка, начало которой мы проверяем ; ''Pattern'' : здесь находится шаблон для сравнения **Пример:** process is variable MyLine : line := "This is a line"; begin if match(MyLine, "This") == true then -- условие выполнится ... end if; if match(MyLine, "This is ") == true then -- условие выполнится ... end if; if match(MyLine, "is") == true then -- условие не выполнится ... end if; if match(MyLine, "") == true then -- Проверка на пустую строку ... end if; end process; \\ ---- \\ === === |\ \ \ ##read## (Line: **line**, Data: any type)\ \ \ | == == * читает слово из строки и заносит результат в переменную Data. Само слово затем из строки удаляется. Длина слова зависит от типа Data: * string - будет прочитана вся строка * time или cycle - будут прочитаны два слова, разделенные пробелами * integer, byte, word - будет прочитано одно слово **Параметры:** ; ''Line'' : Исходная строка ; ''Data'' : Переменная, куда будет помещен результат **Пример:** process is variable Line : line := "wait 100 ms"; variable Delay : time; variable Dummy : integer; begin if match(Line, "wait") == true then read(Line, Dummy); -- Убираем слово "wait", теперь Line = "100 ms" read(Line, Delay); -- Delay = 100 ms, Line = "" wait for Delay; end if; ... wait; end process; Обращу внимание на чтение в переменную Dummy. Когда мы попадаем внутрь блока ##if##, у нас строка до сих пор содержит "wait 100 ms". Нам нужно убрать одно слово из нее, для чего мы выполняем функцию ##read##, занося результат в любую переменную типа ##integer## (т.е. читаем до первого пробела). После этого прочитанное слово удаляется из исходной строки. Далее выполняем ##read## еще раз с параметром ##Delay##, который имеет тип ##time##, т.е. читаем два слова: "100 ms". После этого строка ##Line## становится пустой. ==== Время ==== ---- \\ === === |\ \ \ ##now## ()\ \ \ | == == * Получение текущего времени. Возвращает текущее время симуляции. В зависимости от того, какого типа переменную мы используем для хранения результата (##cycle## или ##time##), будут выбраны соответствующие единицы измерения. **Пример:** process is variable Time : time; variable Cycle : cycle; begin wait for 10 ms; Time := now(); -- получение времени в пикосекундах Cycle := now(); -- получение времени в тактах контроллера end process; \\ ---- \\ === === |##random_time## (LowerLimit: **integer**, UpperLimit: **integer**, Units: **string**, Seed1: **integer**, Seed2: **integer**, Result: **time**)| == == * Формирует случайное значение для переменной типа ##time##. **Параметры:** ; ''LowerLimit'' : Нижний предел ; ''UpperLimit'' : Верхний предел ; ''Units'' : Единицы измерения (""ps", "ns", "us", "ms", "sec", "min", "hr") ; ''Seed1, Seed2'' : параметры генератора случайных чисел (в самом простом случае можно задать любыми ненулевыми значениями) ; ''Result'' : Результат работы функции **Пример** (генератор шума): testbench for "pic16f877a" is begin process is variable RandTime : time; variable RandSeed1, RandSeed2: integer; begin RandSeed1 := 1234; RandSeed2 := 5678; loop random_time(1, 100, "us", RandSeed1, RandSeed2, RandTime); wait for RandTime; RB0 <= not RB0; end loop; end process; end testbench; ==== Для отладки ==== ---- \\ === === |\ \ \ ##print## (...)\ \ \ | == == * Эта функция похожа на оператор ##report##, но в отличие от него она, помимо строк, может выводить значения переменных (целочисленных, битовых, булевых, временных, строковых), а также выводить по несколько значений на одной строке. **Параметры**: эта функция может принимать не более 5 параметров различных типов: line, string, integer, character, boolean, bit, time, cycle. **Пример:** I := 25; print("Current I = ", I); Выведет: (0) SIM-N0001 Note: Current I = 25 \\ ---- \\ ===== Работа с файлами ===== == == Мы уже упоминали функции для стимуляции регистров значениями из файлов (assignin, triggerin). Эти функции при своей простоте имеют недостатки: * неудобно стимулировать несколько регистров синхронно (актуально, например, для пары ADRESH:ADRESL); * довольно бедный выбор условий обновления значений регистра (либо при обращении к нему, либо по значению PC); * нет возможности менять правила стимуляции в ходе работы; * нет возможности стимулировать отдельные биты регистра. Избавиться от этих недостатков можно, читая данные из файлов напрямую, используя специальные [[#Чтение текстовых файлов|функции работы с файлами]] и функции [[#Обработка строк|обработки строк]]. На сегодняшний день есть возможность работать только с текстовыми файлами. Это с одной стороны немного ограничивает нас в способе предоставления информации (нельзя обрабатывать бинарные), но с другой - позволяет снабдить файл сопроводительной информацией (например, паузы, комментарии). Рассмотрим порядок обработки файла data.txt следующего содержания (задача взята наобум): porta 0 portb 0 wait 10 ms rcreg 1 2 3 4 5 6 7 porta 16 portb 255 wait 10 ms porta 0 portb 0 Сначала мы обнуляем значения регистров PORTA и PORTB. Через 10 мс отправляем данные по USART, после чего устанавливаем порты в значения PORTA = 0x10, PORTB = 0xFF. А еще через 10 мс порты снова обнуляются. testbench for "pic16f877a" is begin process is file InFile : text; -- Файловая переменная для связи с файлом variable Status : file_open_status; -- Для проверки правильности открытия файла variable InLine : line; -- Для обработки прочитанных данных variable Delay : time; -- Для формирования задержек variable Work : integer; -- Рабочая переменная для обработки данных begin file_open(Status, InFile, "data.txt", read_mode); if Status != open_ok then -- Обработка ошибки report "Ошибка открытия файла data.txt!" severity failed; end if; -- Работаем с файлом, пока в нем есть данные while endfile(InFile) == false loop readline(InFile, InLine); -- Читаем строку -- Обрабатываем строку, пока в ней есть данные while match(InLine, "") == false loop -- Обработка команды if match(InLine, "porta") == true then read(InLine, Work); -- Убираем из InLine слово "porta" read(InLine, Work); -- Читаем параметр PORTA <= Work; elsif match(InLine, "portb") == true then read(InLine, Work); -- Убираем из InLine слово "portb" read(InLine, Work); -- Читаем параметр PORTB <= Work; elsif match(InLine, "rcreg") == true then read(InLine, Work); -- Убираем из InLine слово "rcreg" packetin(InLine, RCREG, true); -- Перенаправляем остаток строки в приемный буфер elsif match(InLine, "wait") == true then read(InLine, Work); -- Убираем из InLine слово "wait" read(InLine, Delay); -- Читаем параметр задержки wait for Delay; -- Выдерживаем задержку else report "Неизвестная команда"; end if; loop end; -- line == "" loop end; -- endfile report "Готово!"; wait; end process; end testbench; В этом примере следует сосредоточить внимание на двух циклах: ##while endfile(InFile) == false## и ##while match(InLine, "") == false##. Эти два цикла - основа работы с файлами и присутствуют почти в любой программе на языке SCL, которая занимается обработкой входного файла. Внутри этих вложенных один в другой циклов и происходит вся обработка данных. Т.е. если файл еще содержит данные (проверяем функцией endfile), то читаем строку, а затем по одной достаем из строки команды (вспоминаем, что функция read из строки удаляет прочитанное слово) и их параметры, пока строка не закончится. **Отметим одно важное свойство**: с одним и тем же файлом параллельно могут работать несколько процессов. ====== Программирование ====== == == В этом параграфе рассмотрим некоторые особенности и сложности работы с SCL файлами при работе с интегрированной средой MPLAB. ===== Подключение программы SCL к симулятору ===== == == После того, как программа генерации входных сигналов написана, нам нужно ее подключить. Для этого нужно (действия для версии MPLAB 8.xx): - выбрать в качестве отладчика MPLAB SIM (меню "Debugger/Select Tool"); - создать рабочую книгу через меню "Debug/Stimulus/New Workbook" (или Open Workbook, если книга уже была создана); - в открывшемся диалоговом окне "Stimulus" нажать кнопку "Advanced..." в нижнем левом углу; откроется маленькое диалоговое окно "Advanced operations"; - в этом новом окне нажать кнопку "Attach" (прикрепить SCL-файл); выбрать файл с программой SCL. Если программа была написана правильно, то в поле "Override Workbook with Stimulus File" появится имя выбранного файла. Если нет, значит, в программе есть ошибки и их нужно исправлять. Как - описано в следующем параграфе. ===== Сложности отладки ===== == == Отладка SCL-файлов - дело довольно трудоемкое, поскольку сама среда MPLAB предоставляет довольно скудный инструментарий для отладки. Начать можно с того, что встроенный парсер SCL-файлов выдает очень лаконичные сообщения по поводу ошибок, и часто долго не понимаешь, что ему может не нравиться. Работая с SCL-файлами, желательно, чтобы на виду было рабочее окно "Output" с открытой вкладкой "MPLAB SIM". Все сообщения об ошибках, а также вся отладочная информация, формируемая самим SCL-скриптом (print и report) будут выводиться в это окно. Чем нам MPLAB IDE помогает при отладке? Во-первых, он обеспечивает подсветку синтаксиса для файлов SCL, что позволяет избежать некоторых малозаметных помарок. Во-вторых, при прикреплении SCL-файла в окно "Output" выводится информация с номером строки, содержащей ошибку, и тип ошибки (здесь он довольно скуп на сообщения и чаще всего пишет "syntax error"); кроме того, двойным щелчком мышки по сообщению в окне "Output" мы попадаем на строку с ошибкой в исходном файле. Я бы рекомендовал подключать файл SCL еще на начальном этапе программирования. Т.е., сначала создаем пустой файл: testbench for "pic16f877a" is begin end testbench; потом подключаем его по описанному выше алгоритму, убеждаемся, что он подключился (в окне Output должно появиться сообщение "Stimulus: SCL file attached successfully."). Теперь можно писать SCL-скрипт, причем для проверки того, что все написано правильно уже не нужно делать "Detach/Attach", а достаточно всего лишь нажать кнопку F6 (Reset при отладке). По этой кнопке файл SCL автоматически перезагружается и проходит повторную проверку. В окне Output будут появляться сообщения об ошибках, если они есть, или ничего не будет появляться, если ошибок нет. (Когда ничего не появляется, это несколько настораживает, поэтому я во все свои SCL-скрипты встраиваю такой процесс: process is begin report "======================== SCL loaded OK! =========================="; wait; end process; Если нет ошибок, то по нажатию F6 в окне Output будет появляться надпись "SCL-file loaded OK.") Надо отметить, что у нас нет возможности пройти по шагам SCL-скрипт. Поэтому даже если и нет ошибок синтаксических, которые обнаружит парсер, у нас могут быть ошибки алгоритмические. Их находить труднее, потому что мы не можем расставлять в SCL-программах точки останова и отслеживать в каком-то окне текущие значения переменных. Но здесь нам на помощь приходят ##report## и ##print##, использование которых может помочь нам проследить ход выполнения SCL-скрипта и значения переменных в определенных точках выполняемого скрипта. ===== Недостаток операторов ===== == == Язык SCL хоть и является подмножеством языка VHDL, но все-таки имеет далеко не все его возможности. Стоит отметить отсутствие некоторых операторов (##case##, ##for##, ##after##), типов (##real##, полноценных массивов), математических и логических операций (##xor##, ##and##, ##or##, ##%##, сдвиги) и пр. Это все доставляет некоторые неудобства, но, тем не менее, большинство конструкций могут быть заменены. Во-первых, у нас нет возможности использовать сложные выражения в условных операторах. Мы не можем написать: if RA0 == '1' and RA1 == '1' then ... Нам придется делать вложенные условия: if RA0 == '1' then if RA1 == '1' then ... Это немного усложняет структуру программы, т.к. появляется очень много операторных блоков. Кроме того, сильно огорчает отсутствие выделения битовых полей. Например, мы не можем воспользоваться операцией ##and##: i := j and 16#0E#; потому, что такой операции нет в языке SCL. Или, что еще обиднее, нет простого механизма сдвига числа с извлечением младшего бита. Однако это не значит, что такое невозможно. Например, наш предыдущий пример с j & 0x0E может быть решен с помощью математических операций %%*, /, -%%: i := j / 16; i := j - i * 16; -- Отрезаем все биты выше 3-го i := i / 2; i := i * 2; -- Отрезаем нулевой бит Примерно так же решается задача сдвига с извлечением младшего бита: b := 65; -- Данные, которые надо последовательно втолкнуть на вход RB0 i := 8; -- обрабатываем 8 бит while i > 0 loop i := i - 1; j := b / 2; j := b - j * 2; -- Получили в j младший бит из переменой b if j == 1 then -- Вталкиваем этот бит в RB0 RB0 <= '1'; else RB0 <= '0'; end if; b := b / 2; -- деление на 2 - эквивалент сдвига влево wait for 100 us; end loop; ===== Доступ к регистрам контроллера ===== == == Во время выполнения SCL-скрипта у нас есть возможность обращаться к регистрам контроллера. Мы можем получить текущее значения программно счетчика, значение регистра FSR, значения таймеров, регистров управления модулем CCP и т.д. Здесь я особо глубоко исследования не проводил. Но с сожалением должен заметить, что не все регистры можно прочитать. Например, на это: i := PORTA; парсер ругается "Type error in assignment". В то время как значения остальных регистров прочитать таким образом удается. (Также не читаются TRISx, TXREG и еще некоторые периферийные регистры). В принципе, это не страшно, т.к., во-первых, операция чтения регистра PORTA довольно редкая, а во-вторых, ее можно заменить побитовым заполнением: i := 0; if RA0 == '1' then i := i + 1; end if; if RA1 == '1' then i := i + 2; end if; if RA2 == '1' then i := i + 4; end if; if RA3 == '1' then i := i + 8; end if; if RA4 == '1' then i := i + 16; end if; if RA5 == '1' then i := i + 32; end if; Встроенный в MPLAB симулятор сам накладывает кое-какие ограничения. Их поиском я не занимался, а когда и натыкался на них, то старался обходить, при этом, к сожалению, не отмечая для себя на будущее. По отзывам с форума на microchip.com есть ограничения на стимуляцию регистров АЦП для dsPIC'ов, но я лично не проверял. ====== Примеры ====== == == Здесь приведу несколько примеров готовых скриптов. Сначала совсем простые, потом посложнее. Основная цель, которую я преследовал, выбирая задачи для скриптов, не в том, чтобы их можно было использовать для отладки ваших программ, а в том, чтобы, глядя на них, у вас сложилось более четкое представление о структуре скрипта, порядке его работы и о его возможностях. ===== Генератор 1 KHz ===== == == Для начала самый простой пример - генератор. testbench for "pic16f877a" is begin process is begin RB0 <= '0'; loop RB0 <= not RB0; -- каждые 0.5мс меняем состояние порта wait for 500 us; end loop; end process; end testbench; В результате работы данного скрипта мы получим такой сигнал на входе RB0: {{:osa:articles:graph_1khz.png|}} Можем его немного усложнить, добавив управление из самой отлаживаемой программы: ------------------------------------------------------------------------------- -- -- Данный скрипт демонстрирует управляемый генератор 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 КГц. {{:osa:articles:graph_1khz_controlled.png|}} ===== Генератор программного 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## - команда на выдержку паузы. Числа даны в десятичной системе счисления (как их задавать в шестнадцатеричной или в символьном виде, - не знаю). В добавок, данный пример является некоей иллюстрацией синхронизации двух процессов через внутреннюю переменную. Скрипт разбит на два процесса: один читает файлы и формирует данные для генерации сигнала, а второй формирует сам сигнал. Процессы выполняются параллельно. В конкрентом случае можно было обойтись и без разделения программы на два процесса, т.к. у нас всего один процесс (и только в одном месте) запускает второй. Но в общем случае это может оказаться полезным, когда: - байт может отправляется из разных мест процесса; - байт может отправляться из разных процессов; - во время отправки данных головной процесс должен заниматься своими делами. Для синхронизации служит общая переменная 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 мс. {{:osa:articles:graph_uart.png|}} ===== Генератор аналогового сигнала ===== == == Симулятор в интегрированной среде 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 сигнал - это сигнал, получаемый суммированием двух синусоидальных сигналов из двух наборов частот: - 697 Hz, 770 Hz, 852 Hz, 941 Hz - 1209 Hz, 1336 Hz, 1477 Hz, 1633 Hz Скрипт будет состоять из 4-х процессов: - ##FREQ1## - генератор синуса заданной частоты - ##FREQ2## - генератор синуса заданной частоты - ##WORK## - Управляющий - будет читать файл с данными и формировать целеуказания для FREQ1 и FREQ2 - ##ADC## - независимый процесс, который при обращении пользовательской программы к АЦП будет формировать значения регистров ADRESH:ADRESL по мгновенным значениям синусов, генерируемых процессами FREQ1 и FREQ2. (Примечание: еще добавлены пятый процесс, в который для удобства вынесены объявления глобальных переменных, и шестой, который содержит строку "SCL loaded OK!") === Формат файла "data_dtmf.txt" === == == Этот файл содержит список команд для управления процессом. В самом простом случае он может состоять из чисел от 1 до 16 (0 - будет воспринят как 16), из которых будет генерироваться последовательность тонов. Однако, с помощью доолнительных команд можно менять параметры сигналов: ; **freq** : Генерировать синус частоты f на канале c (c = 1 или 2) ; **stop** : остановить генерацию (отключает оба канала) ; **duration** : задать длительность тонов (по умолчанию 100 мс) ; **pause**

: задать паузу между тонами (по умолчанию 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: Работа скрипта успешно завершена \\ \\ === Текст скрипта === == == Наконец, сам скрипт: (файл с таблицей синуса прилагается {{:osa:articles: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 Гц): {{:osa:articles:graph_dtmf.png|}} ====== Заключение ====== == == Итак, в статье были описаны известные мне конструкции языка SCL, а также приведены несколько небольших примеров использования этого языка для имитации цифровых и аналоговых сигналов в симуляторе. Я надеюсь, что описанное здесь поможет упростить процесс отладки, а главное - поможет вам утвердиться во мнении, что симулятор в MPLAB IDE - это действительно мощное средство, и не такое оно неудобное, как может показаться, если пользоваться только диалоговым окном Stimulus, пренебрегая самим языком SCL. Так что теперь, я думаю, такие задачи, как имитация 128-битного манчестерского кода, трудностей больше не вызовут. Тем, кто раньше пользовался диалоговым окном ##Stimulus## для задания входных сигналов, а после прочтения статьи задумался: "А как бы мне то же самое сделать на SCL?" - напомню, что в MPLAB IDE есть возможность сформировать файл SCL для заданных в диалоговом окне сигналов. Для этого нужно в диалоговом окне "Stimulus" нажать кнопку "Advanced..." и в открывшемся окне нажать кнопку "Generate SCL File". Все заданные вами воздействия будут автоматически переведены в скрипт на языке SCL. Виктор Тимофеев, ноябрь, 2009\\ [[osa@pic24.ru]]