CUPL, part II

Язык CUPL по сложности находится где-то между PALASM и Verilog. То есть, логические выражения на нём уже можно писать, и компилятор даже попытается их оптимизировать. Есть ещё некие “расширения”, которые позволяют описывать триггеры и разные аппаратные особенности. Плюс конечные автоматы, таблицы истинности и симулятор. А вот сделать простое сравнение больше/меньше простым путём уже не получится. На мой взгляд, самым большим преимуществом языка CUPL является его простота/понятность.

Программу можно разбить на три блока:
1. Описание программы, в котором есть один важный пункт – тип используемой ПЛИС.
2. Описание выводов.
3. Внутренняя логика.

Описание программы выглядит вот так:

Name ADDR_DECODER ;
PartNo 00 ;
Date 14.06.2015 ;
Revision 01 ;
Designer JM ;
Company Home ;
Assembly None ;
Location ;
Device g16v8a;

Можно подставлять сюда любой текст, который Вам понравится. Анализируется только поле Device, в него лучше вписать “реальное” название (скриншоты с диалогами и последовательностью кликов см. в предыдущем выпуске). Хелп говорит ещё о поле Format, но его придётся вписать руками (оно задаёт формат выходного файла). (Эти поля в дальнейшем сравниваются с аналогичными из симулятора, должны совпадать).

Описание выводов состоит из набора строк:

PIN номер = [!] имя ;

Номер – это просто номер вывода. Имя – это его обозначение (про допустимые идентификаторы чуть ниже).
Перед именем вывода можно поставить восклицательный знак, который обозначает инверсию (полярность). Это позволяет не усложнять запись логических выражений (инверсия указывается ровно один раз).
Документация говорит, что восклицательный знак задаёт полярность входа/выхода: “Концепция полярности может показаться странной. При разработке ПЛИС разработчик сосредоточен на том, что сигнал может принимать значения true/false. Разработчик не должен беспокоиться о том, что означает высокий или низкий уровень сигнала. По разным причинам дизайн платы может требовать сигнала true, при наличии низкого уровня сигнала (логического нуля) и наоборот. Этот сигнал считается “сигналом с активным низким уровнем”, если активируется, когда уровень низкий. Он также может быть назван low-true. Если сигнал изменяется с активного верхнего уровня на актиный низкий уровнень, то полярность должна быть изменена”.

Входы и выходы не различаются (по написанию), компилятор решает сам, какая ножка вход, а какая выход, по тому, как она используется. Чисто физически есть выводы, которые могут быть только входами, есть те, что могут быть входами/выходами.

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

a0
A0
8250_ENABLE
Real_time_clock_interrupt
_address

А вот так именовать переменные нельзя:

99
I/O enable
out 6a
tbl-2

В языке CUPL есть понятие индексных переменных. Опять таки, цитата из описания языка:
Имена переменных могут быть использованы для представления группы адресных линий, линий данных или других специфических нумерованных элементов. Например, следующие имена переменных могут обозначать 8 младших бит шины адреса микропроцессора:

A0 A1 A2 A3 A4 A5 A6 A7

Имена переменных, которые заканчиваются цифрами, как показано выше, подразумевают индексные переменные.
Примечание: лучше всего начинать нумерацию с нуля.
Индексы это всегда десятичные числа от 0 до 31. Когда они используются в битовых операциях (см. ниже) переменная с индексом 0 – это всегда младший двоичный разряд.
Переменные, которые заканчиваются на числа более 31 не являются индексными переменными.
Примеры:

a23
D07
D7
counter_bit_3

Переменные D07 и D7 – разные!

Такие переменные удобно использовать для сокращения записи. Пример:

PIN [2..9] = [A15..A8];

В результате пин 2 будет иметь имя A15. Порядок записи важен, если написать A8..A15, то пин 2 будет иметь имя A8.

Комментарии.
Комментарии записываются как в классическом Си: /* комменты */
К сожалению, WinCUPL не позволяет писать русские буквы в комментариях. Ни в win-1251, ни в utf-8 (видимо, код символа должен быть строго меньше 0x80).

Внутренняя логика.
Логические операции вполне привычны, если не считать странного знака #:

a & b операция И
a # b операция ИЛИ
a $ b операция исключающее ИЛИ
! b инверсия

Вся программа состоит из отдельных строк, которые должны заканчиваться символом точка с запятой. Допустима и многострочная запись.

В самом простом случае описываются логические операции над выводами.
Например:

Y = A & B;

В принципе, уже этого достаточно чтобы сделать какое-то достаточно сложное устройство. Например, декодер адреса для микропроцессора:

/* pin declaration */
PIN [2..9] = [A15..A8];
PIN 19 = !RAM1; /* Active low */
/* logic */


RAM1 = !A15 & (A14 # A13 # A12 # A11 # A10);

В результате сигнал RAM1 будет инверсным, и будет принимать значение 0 (физически, на ножке) в случае, когда адрес будет лежать в диапазоне 1КБ-32КБ (если адрес меньше 1КБ, то адресные линии A10…A14 будут нулевые, а для адресов выше 32КБ адресная линия A15 будет 1).
Небольшого количества логических элементов хватает, чтобы порезать адресное пространство на несколько независимых кусков.

To be continued…