Компилятор языка программирования Oberon-07/16 для микроконтроллеров MSP430x{1,2}xx. ------------------------------------------------------------------------------ Параметры командной строки Вход - текстовые файлы модулей с расширением ".ob07", кодировка ANSI или UTF-8 с BOM-сигнатурой. Выход - hex-файл прошивки. Параметры: 1) имя главного модуля 2) "msp430" 3) необязательные параметры-ключи -out имя результирующего файла; по умолчанию, совпадает с именем главного модуля, но с расширением ".hex" -ram размер ОЗУ в байтах (128 - 2048) по умолчанию 128 -rom размер ПЗУ в байтах (2048 - 24576) по умолчанию 2048 -tab размер табуляции (используется для вычисления координат в исходном коде), по умолчанию - 4 -nochk <"ptibcwra"> отключить проверки при выполнении -lower разрешить ключевые слова и встроенные идентификаторы в нижнем регистре (по умолчанию) -upper только верхний регистр для ключевых слов и встроенных идентификаторов -def <имя> задать символ условной компиляции -uses вывести список импортированных модулей параметр -nochk задается в виде строки из символов: "p" - указатели "t" - типы "i" - индексы "b" - неявное приведение INTEGER к BYTE "c" - диапазон аргумента функции CHR "s" - переполнение стэка "a" - все проверки Порядок символов может быть любым. Наличие в строке того или иного символа отключает соответствующую проверку. Например: -nochk it - отключить проверку индексов и охрану типа. -nochk a - отключить все отключаемые проверки. Например: Compiler.exe "C:\example.ob07" msp430 -ram 128 -rom 4096 -nochk pti Compiler.exe "C:\example.ob07" msp430 -out "C:\Ex1.hex" -ram 512 -rom 16384 В случае успешной компиляции, компилятор передает код завершения 0, иначе 1. ------------------------------------------------------------------------------ Отличия от оригинала 1. Расширен псевдомодуль SYSTEM 2. В идентификаторах допускается символ "_" 3. Усовершенствован оператор CASE (добавлены константные выражения в метках вариантов и необязательная ветка ELSE) 4. Расширен набор стандартных процедур 5. Семантика охраны/проверки типа уточнена для нулевого указателя 6. Добавлены однострочные комментарии (начинаются с пары символов "//") 7. Разрешено наследование от типа-указателя 8. "Строки" можно заключать также в одиночные кавычки: 'строка' 9. Добавлена операция конкатенации строковых и символьных констант 10. Добавлены кодовые процедуры 11. Не реализована вещественная арифметика 12. Возможен импорт модулей с указанием пути и имени файла 13. Добавлен специальный синтаксис для условной компиляции (см. CC.txt) 14. Имя процедуры в конце объявления (после END) необязательно 15. Разрешено использовать нижний регистр для ключевых слов ------------------------------------------------------------------------------ Особенности реализации 1. Основные типы Тип Диапазон значений Размер, байт INTEGER -32768 .. 32767 2 CHAR символ ASCII (0X .. 0FFX) 1 BOOLEAN FALSE, TRUE 1 SET множество из целых чисел {0 .. 15} 2 BYTE 0 .. 255 1 2. Максимальная длина идентификаторов - 255 символов 3. Максимальная длина строковых констант - 511 символов (UTF-8) 4. Максимальная размерность открытых массивов - 5 5. Процедура NEW заполняет нулями выделенный блок памяти 6. Локальные переменные инициализируются нулями 7. В отличие от многих Oberon-реализаций, сборщик мусора и динамическая модульность отсутствуют 8. Тип BYTE в выражениях всегда приводится к INTEGER 9. Контроль переполнения значений выражений не производится ------------------------------------------------------------------------------ Псевдомодуль SYSTEM Псевдомодуль SYSTEM содержит низкоуровневые и небезопасные процедуры, ошибки при использовании процедур псевдомодуля SYSTEM могут привести к повреждению данных времени выполнения и аварийному завершению программы. PROCEDURE ADR(v: любой тип): INTEGER v - переменная или процедура; возвращает адрес v PROCEDURE SADR(x: строковая константа): INTEGER возвращает адрес x PROCEDURE VAL(v: любой тип; T): T v - переменная; интерпретирует v, как переменную типа T PROCEDURE SIZE(T): INTEGER возвращает размер типа T PROCEDURE TYPEID(T): INTEGER T - тип-запись или тип-указатель, возвращает номер типа в таблице типов-записей PROCEDURE MOVE(Source, Dest, n: INTEGER) Копирует n байт памяти из Source в Dest, области Source и Dest не могут перекрываться PROCEDURE GET(a: INTEGER; VAR v: любой основной тип, PROCEDURE, POINTER) v := Память[a] PROCEDURE GET8(a: INTEGER; VAR x: INTEGER, SET, BYTE, CHAR) Эквивалентно SYSTEM.MOVE(a, SYSTEM.ADR(x), 1) PROCEDURE PUT(a: INTEGER; x: любой основной тип, PROCEDURE, POINTER) Память[a] := x; Если x: BYTE, то значение x будет расширено до 16 бит, для записи байтов использовать SYSTEM.PUT8 PROCEDURE PUT8(a: INTEGER; x: INTEGER, SET, BYTE, CHAR) Память[a] := младшие 8 бит (x) PROCEDURE CODE(word1, word2,... : INTEGER) Вставка машинного кода, word1, word2 ... - целочисленные константы (константные выражения) - машинные слова, например: SYSTEM.CODE(0D032H, 0010H) (* BIS #16, SR; CPUOFF *) Функции псевдомодуля SYSTEM нельзя использовать в константных выражениях. ------------------------------------------------------------------------------ Оператор CASE Синтаксис оператора CASE: CaseStatement = CASE Expression OF Case {"|" Case} [ELSE StatementSequence] END. Case = [CaseLabelList ":" StatementSequence]. CaseLabelList = CaseLabels {"," CaseLabels}. CaseLabels = ConstExpression [".." ConstExpression]. Например: CASE x OF |-1: DoSomething1 | 1: DoSomething2 | 0: DoSomething3 ELSE DoSomething4 END В метках вариантов можно использовать константные выражения, ветка ELSE необязательна. Если значение x не соответствует ни одному варианту и ELSE отсутствует, то программа прерывается с ошибкой времени выполнения. ------------------------------------------------------------------------------ Конкатенация строковых и символьных констант Допускается конкатенация ("+") константных строк и символов типа CHAR: str = CHR(39) + "string" + CHR(39); (* str = "'string'" *) newline = 0DX + 0AX; ------------------------------------------------------------------------------ Проверка и охрана типа нулевого указателя Оригинальное сообщение о языке не определяет поведение программы при выполнении охраны p(T) и проверки типа p IS T при p = NIL. Во многих Oberon-реализациях выполнение такой операции приводит к ошибке времени выполнения. В данной реализации охрана типа нулевого указателя не приводит к ошибке, а проверка типа дает результат FALSE. В ряде случаев это позволяет значительно сократить частоту применения охраны типа. ------------------------------------------------------------------------------ Дополнительные стандартные процедуры COPY (x: ARRAY OF CHAR; VAR v: ARRAY OF CHAR); v := x; Если LEN(v) < LEN(x), то строка x будет скопирована не полностью. LSR (x, n: INTEGER): INTEGER Логический сдвиг x на n бит вправо. MIN (a, b: INTEGER): INTEGER Минимум из двух значений. MAX (a, b: INTEGER): INTEGER Максимум из двух значений. BITS (x: INTEGER): SET Интерпретирует x как значение типа SET. Выполняется на этапе компиляции. LENGTH (s: ARRAY OF CHAR): INTEGER Длина 0X-завершенной строки s, без учета символа 0X. Если символ 0X отсутствует, функция возвращает длину массива s. s не может быть константой. ------------------------------------------------------------------------------ Импорт модулей с указанием пути и имени файла Примеры: IMPORT Math IN "./lib/math.ob07"; (* относительно текущего модуля *) IMPORT M1 IN "C:\lib\math.ob07"; (* абсолютный путь *) ------------------------------------------------------------------------------ Использование регистров общего назначения R4 - R15 R4 - R7: регистровый стэк (промежуточные значения выражений), волатильные регистры (не требуют сохранения) R8 - R11: не используются компилятором, могут использоваться в кодовых процедурах, неволатильные (требуется сохранять перед использованием и восстанавливать после) R12 - R14: зарезервированы для возможного специального назначения в будущем R15: указатель кучи, используется в стандартной процедуре NEW, а также для контроля переполнения стэка ------------------------------------------------------------------------------ Вызов процедур и кадр стэка Правила вызова похожи на соглашение cdecl (x86): - параметры передаются через стэк справа налево - результат, если есть, передается через регистр R4 - вызывающая процедура очищает стэк Состояние стэка при выполнении процедуры: меньшие адреса <- |var3|var2|var1|PC|arg1|arg2|arg3| -> бОльшие адреса PC - значение регистра PC перед вызовом (адрес возврата) argX - параметры в порядке объявления (слева направо) varX - локальные переменные в порядке использования в процедуре Размер каждого элемента в стэке (кроме локальных переменных структурных типов) - 1 машинное слово (2 байта). Структурные переменные (массивы и записи) занимают место в стэке в соответствии с их размером (с учетом выравнивания). Размещение локальных переменных зависит от их размеров и порядка использования, и в общем случае неопределенно. Если переменная не используется явно, то компилятор не выделяет для нее место в стэке. ------------------------------------------------------------------------------ Скрытые параметры процедур Некоторые процедуры могут иметь скрытые параметры, они отсутствуют в списке формальных параметров, но учитываются компилятором при трансляции вызовов. Это возможно в следующих случаях: 1. Процедура имеет формальный параметр открытый массив: PROCEDURE Proc (x: ARRAY OF ARRAY OF REAL); Вызов транслируется так: Proc(LEN(x), LEN(x[0]), SYSTEM.ADR(x)) 2. Процедура имеет формальный параметр-переменную типа RECORD: PROCEDURE Proc (VAR x: Rec); Вызов транслируется так: Proc(SYSTEM.TYPEID(Rec), SYSTEM.ADR(x)) ------------------------------------------------------------------------------ Кодовые процедуры Компилятор поддерживает процедуры, написаные в машинных кодах. Синтаксис: PROCEDURE "[code]" имя [ (параметры): ТипРезультата ] МашСлово, МашСлово,... МашСлово; ";" после заголовка и END "имя" в конце процедуры не ставятся. МашСлово - целочисленная константа (в том числе и константное выражение). Например: PROCEDURE [code] asr (n, x: INTEGER): INTEGER (* ASR(x, n) -> R4 *) 4115H, 2, (* MOV 2(SP), R5; R5 <- n *) 4114H, 4, (* MOV 4(SP), R4; R4 <- x *) 0F035H, 15, (* AND #15, R5 *) 2400H + 3, (* JZ L1 *) (* L2: *) 1104H, (* RRA R4 *) 8315H, (* SUB #1, R5 *) 2000H + 400H - 3; (* JNZ L2 *) (* L1: *) Компилятор автоматически добавляет к такой процедуре команду RET. Способ передачи параметров и результата не изменяется. Кодовые процедуры можно использовать также и для добавления в программу константных данных: PROCEDURE [code] sqr 0, 1, 4, 9, 16, 25, 36, 49, 64, 81; Получить адрес такой "процедуры": SYSTEM.ADR(sqr). Получить адрес n-го машинного слова: SYSTEM.ADR(sqr) + n * 2. Чтобы использовать кодовые процедуры, необходимо импортировать псевдомодуль SYSTEM. ------------------------------------------------------------------------------ Обработка прерываний При появлении запроса на прерывание, процессор: - помещает в стэк значение регистра PC - помещает в стэк значение регистра SR - очищает регистр SR - выполняет переход по адресу IV[priority], где IV - таблица векторов прерываний, priority - приоритет прерывания (номер элемента в таблице IV) (0..30) Компилятор генерирует код обработки прерываний: ; IV[0] = адрес следующей команды PUSH #0 ; поместить в стэк приоритет прерывания JMP Label ; перейти к обработчику ; IV[1] = адрес следующей команды PUSH #1 ; поместить в стэк приоритет прерывания JMP Label ; перейти к обработчику ... ; IV[priority] = адрес следующей команды PUSH #priority ; поместить в стэк приоритет прерывания JMP Label ; перейти к обработчику ... ; IV[30] = адрес следующей команды PUSH #30 ; поместить в стэк приоритет прерывания Label: PUSH R4 ; сохранить рабочие регистры (R4 - R7) ... PUSH R7 MOV SP, R4 ; настроить R4 на структуру данных прерывания (см. далее) ADD #8, R4 PUSH R4 ; передать параметр interrupt в обработчик (см. далее) PUSH @R4 ; передать параметр priority в обработчик (см. далее) CALL int ; вызвать обработчик (см. далее) ADD #4, SP ; удалить из стэка параметры обработчика POP R7 ; восстановить рабочие регистры (R7 - R4) ... POP R4 ADD #2, SP ; удалить из стэка значение priority RETI ; возврат из прерывания (восстановить SR и PC) ------------------------------------------------------------------------------ Обработка ошибок В случае возникновения ошибки при выполнении программы, будет вызван общий обработчик ошибок, который: - запрещает прерывания - сбрасывает стэк (во избежание переполнения в процессе обработки ошибки) - передает параметры в пользовательский обработчик (см. далее) - вызывает пользовательский обработчик (если он назначен) - повторно запрещает прерывания - выключает CPU и все тактовые сигналы Если выключать CPU не требуется, то пользовательский обработчик может, например, перезапустить программу. Коды ошибок: 1 ASSERT(x), при x = FALSE 2 разыменование нулевого указателя 3 целочисленное деление на неположительное число 4 вызов процедуры через процедурную переменную с нулевым значением 5 ошибка охраны типа 6 нарушение границ массива 7 непредусмотренное значение выражения в операторе CASE 8 ошибка копирования массивов v := x, если LEN(v) < LEN(x) 9 CHR(x), если (x < 0) OR (x > 255) 10 переполнение стэка 11 неявное приведение x:INTEGER к v:BYTE, если (x < 0) OR (x > 255) ------------------------------------------------------------------------------ Инициализация и финализация программы В начало программы компилятор вставляет код, который: - инициализирует регистры SP и R15 - выключает сторожевой таймер - назначает пустой обработчик прерываний и пустой обработчик ошибок В конец программы добавляет команду BIS #16, SR; выключить CPU ------------------------------------------------------------------------------ Структура ОЗУ (RAM) начало -> | куча/стэк | спец. переменные | глобальные переменные | <- конец Компилятор поддерживает размер ОЗУ 128..2048 байт. В верхних адресах располагаются пользовательские глобальные переменные и скрытые специальные переменные. Оставшаяся часть памяти отводится для кучи и стэка (не менее 40 байт, минимально необходимо для обработки прерываний и ошибок). При старте программы, в регистр R15 записывается адрес начала области кучи/стэка, а регистр SP настраивается на конец этой области (адрес спец. переменных). При выделении памяти процедурой NEW, значение регистра R15 увеличивается (если есть свободная память). Таким образом, стэк и куча растут навстречу друг другу. Проверка переполнения стэка производится только при входе в процедуру, если эта проверка не отключена при компиляции (-nochk s). Проверка стэка не производится: - в процессе выполнения процедуры - при входе в кодовую процедуру - при выполнении тела модуля ------------------------------------------------------------------------------ Структура ПЗУ (ROM) начало -> | свободная область | код | данные | векторы прерываний | <- конец Компилятор поддерживает размер ПЗУ 2048..24576 байт. В верхних адресах располагается таблица векторов прерываний (64 байта), адреса 0FFC0H..0FFFFH. Непосредственно перед ней размещаются данные (таблица типов, строки, множества) и перед данными - программный код. Адрес начала кода совпадает с точкой входа в программу (вектор сброса). Если размер ПЗУ больше, чем размер программы, то перед кодом останется свободная область. ============================================================================== MODULE MSP430 CONST биты регистра SR: GIE = {3} CPUOFF = {4} OSCOFF = {5} SCG0 = {6} SCG1 = {7} TYPE TInterrupt = RECORD priority: INTEGER; sr: SET; pc: INTEGER END структура данных прерывания priority - приоритет прерывания: адрес приоритет 0FFFEH 31 0FFFCH 30 0FFFAH 29 ... 0FFC0H 0 sr - сохраненное значение регистра SR pc - сохраненное значение регистра PC TTrapProc = PROCEDURE (modNum, modName, err, line: INTEGER); Процедура-обработчик ошибок. modNum - номер модуля (в отчете о компиляции: compiling (modNum) "modName" ) modName - адрес имени модуля err - номер ошибки line - номер строки TIntProc = PROCEDURE (priority: INTEGER; interrupt: TInterrupt) Процедура-обработчик прерываний. priority - приоритет прерывания interrupt - структура данных прерывания PROCEDURE SetTrapProc (TrapProc: TTrapProc) Назначить обработчик ошибок. PROCEDURE SetIntProc (IntProc: TIntProc) Назначить обработчик прерываний. PROCEDURE Restart Перезапустить программу. При этом: очищается регистр SR, повторно выполняется код инициализации программы (см. выше). Всё прочее состояние ОЗУ и регистров устройств сохраняется. PROCEDURE SetIntPC (interrupt: TInterrupt; NewPC: INTEGER) interrupt.pc := NewPC После возврата из прерывания, регистр PC получит значение NewPC. PROCEDURE SetIntSR (interrupt: TInterrupt; NewSR: SET) interrupt.sr := NewSR После возврата из прерывания, регистр SR получит значение NewSR. PROCEDURE DInt Запретить прерывания. PROCEDURE EInt Разрешить прерывания. PROCEDURE CpuOff Выключить CPU (установить бит CPUOFF регистра SR). PROCEDURE Halt Запретить прерывания, выключить CPU и все тактовые сигналы. PROCEDURE SetSR (bits: SET) Установить биты bits регистра SR. PROCEDURE ClrSR (bits: SET) Сбросить биты bits регистра SR. PROCEDURE Delay (n: INTEGER) Задержка выполнения программы на 1000*n тактов, но не менее чем на 2000 тактов. ==============================================================================