Теория по КМПУ | Готовые элементы систем | Технологии и хитрости | Прочее | Магазин | Контакты | |||
![]()
06.03.25
В данной заметке мы рассмотрим базовую архитектуру и возможности ядра LGT8XM, являющегося основой микроконтроллеров LGT8F328P, а также дадим общий обзор поддерживаемых им инструкций. Как известно, ядро отвечает за обеспечение корректного выполнения программы, т.е. является «мозгом» любого кирпича. В связи с этим, оно должно иметь возможность доступа к памяти программ и данных, уметь быстро и без ошибок выполнять математические операции, а также управлять периферией и обрабатывать различные прерывания. И для лучшего понимания работы китайского камня мы в той или иной степени рассмотрим ниже каждую из перечисленных задач. Общие сведения
Микроконтроллеры LGT8F328P построены по Гарвардской архитектуре, характеризующейся раздельной памятью программ и памятью данных. Каждая память имеет собственную шину для доступа к ней, что позволяет работать с обоими типами памяти одновременно, а также использовать при этом шины данных различной разрядности. В рассматриваемых кирпичах нет механизма DMA, обеспечивающего прямое общение периферии с ОЗУ – вся работа осуществляется через ядро. Вследствие этого, во внутреннем мире камней LGT8F328P оное занимает центральную позицию, ибо на него завязаны все остальные узлы микроконтроллера: ![]()
Основные особенности ядра LGT8XM перечислены ниже:
• использование высокоэффективной архитектуры RISC;
Внутренняя структура ядра LGT8XM показана на этом рисунке: ![]()
Из приведенной картинки видно, что ядро камней LGT8F328P состоит из конвейера команд (Pipeline) и исполнительного модуля (Execute Unit), который имеет доступ к пачке регистров общего назначения (РОН), объединенных в регистровый файл (Register File), а также к схеме управления конвейером (Pipeline Control). С остальными внутренностями кирпича ядро общается при помощи 16-битной шины программ и 8-битной шины данных. Кроме того, в китайские чипы добавлена дополнительная шина данных разрядностью 16 бит, при помощи которой исполнительный модуль может общаться с ускорителем вычислений, а также осуществлять обмен информацией между ускорителем и ОЗУ. На рисунке выше эта часть камня обозначена темно-рыжим цветом, причем, 16-разрядная шина, судя по всему, присутствует в кирпиче физически, а не хитро эмулируется при помощи 8-битной.
• если требуется выполнить логическую или арифметическую операцию, модуль обращается к своей части под названием «ALU»: мол, умножь-ка мне (сложи, вычти и т.д.) вот эти вот два регистра и положи результат в один из них. Отметим, что в зависимости от выполняемой команды, манипуляции могут производиться только над одним регистром, но суть остается та же – если требуются какие-либо вычисления, в дело вступает арифметико-логическое устройство (АЛУ);
Это, по большому счету, всё, чем занимается ядро микроконтроллера LGT8F328P. На первый взгляд, описанный алгоритм элементарен – вынимаем команду, декодируем ее, выполняем. Однако, нетрудно догадаться, что на каждом этапе возникнет много ньюансов, ибо ядро – штука достаточно сложная. Поэтому далее мы рассмотрим работу данного узла более подробно, в частности, уделим внимание особенностям выполнения программ в китайском кирпиче, а также инструкциям, из которых эти программы могут состоять. Арифметико-логическое устройство
Арифметико-логическое устройство (АЛУ) – это обязательный «математический» узел любого современного микроконтроллера, позволяющий выполнять арифметические, логические и битовые операции. Помимо этого, в камнях LGT8F328P АЛУ включает в себя аппаратный умножитель, дающий возможность работы с беззнаковым и знаковым содержимым 8-битных регистров, в том числе с поддержкой дробного формата. Обратите внимание на то, что операция умножения в китайских кирпичах длится всего один такт, в отличие от AVR-ок, где на это требуется два машинных цикла.
• один 8-битный операнд и один 8-битный результат операции (схема характерна для команд с одним регистром, например «побитовый сдвиг» или «смена тетрад в байте»);
Арифметико-логическое устройство подключено к регистровому файлу напрямую, благодаря чему обеспечивается максимальное быстродействие камня. Отметим, что в чипах LGT8F328P длительность любой операции АЛУ составляет всего один такт, в отличие от микроконтроллеров AVR, где для выполнения умножения и изменения значения регистровой пары требуется два машинных цикла. При этом арифметико-логическому устройству доступны все регистры общего назначения, что существенно сокращает время на различные промежуточные действия типа перекладывания значений из «общих» регистров в рабочие и обратно. Результат операции АЛУ автоматически заносится в регистровый файл; косвенно о нем также можно судить по состоянию специального регистра SREG. Регистр состояния (SREG)
Регистр состояния SREG представляет собой набор флагов, показывающих текущее состояние камня. Все разряды регистра состояния доступны как для чтения, так и для записи в любой момент времени; после сброса микроконтроллера содержимое этого регистра равно нулю: ![]()
Одной из задач регистра SREG является хранение общей информации о результате последней операции, выполненной АЛУ. После того, как арифметико-логическое устройство завершает очередное вычисление, шесть из восьми флагов в регистре состояния автоматически устанавливаются или сбрасываются в соответствии с полученным результатом. Эта информация может быть использована для изменения хода программы при помощи команд условного перехода (BRNE, BREQ и т.д.). Обратите внимание на то, что обновление регистра SREG после каждой операции АЛУ часто устраняет необходимость в дополнительных инструкциях сравнения, что позволяет сделать код программы более быстрым и компактным. Регистры общего назначения
Регистры общего назначения (РОН) – это основные рабочие лошади любого кирпича. Как говорилось выше, именно они используются арифметико-логическим устройством для хранения операндов и результатов, и только с их помощью мы можем общаться с памятью камня. Ядро LGT8XM включает в себя аж тридцать два (!) 8-битных регистра общего назначения R0…R31, которые объединены в единый регистровый файл. По факту данный файл является отдельной и самой быстрой памятью микроконтроллера LGT8F328P, оптимизированной под набор инструкций именно китайского ядра. При этом каждый регистр общего назначения также имеет свой собственный адрес в общем пространстве памяти данных, поэтому к РОН можно обращаться как к обычным ячейкам ОЗУ (несмотря на то, что физически эти регистры не являются частью SRAM). ![]()
1 – отметим, что указатели «X», «Y» и «Z», наряду с комбинацией R25:R24, могут выступать операндом в командах изменения содержимого регистровой пары (ADIW и SBIW), а также при 16-битном доступе ядра к ускорителю вычислений.
Более подробно этот вопрос будет рассмотрен в отдельной заметке, здесь же просто отметим, что при помощи данных указателей может быть адресована вообще любая ячейка памяти кирпича LGT8F328P – хоть SRAM, хоть FLASH, хоть EEPROM. При этом в китайских МК доступ к памяти программ возможен не только посредством инструкции LPM, но и при помощи обычных LD/LDD/LDS, поэтому здесь для чтения данных из флэша можно использовать все три указателя, а не только «Z» (как в AVR-ках). Инструкции (команды)
Программа для любого микроконтроллера представляет собой последовательность команд (инструкций), записанных в память МК. Большинство команд при выполнении изменяют содержимое регистров или ячеек ОЗУ, либо переносят нас в определенное место программы (в соответствии с каким-либо условием, или же просто на конкретный адрес). Кроме того, обычно ядро поддерживает также несколько специальных инструкций, выполняющих особые действия. В случае микроконтроллера LGT8F328P все команды можно разбить на пять групп:
• команды выполнения арифметических и логических действий. Позволяют складывать, вычитать и умножать регистры и константы, выполнять над ними логические операции «И», «ИЛИ», «исключающее ИЛИ», а также вычислять обратный и дополнительный код числа;
Полный перечень команд китайского камня, включая команды работы с 16-битной шиной данных, приведен в отдельной заметке. Отмечу, что в некотором роде этот перечень уникален, поскольку в нем исправлены все косяки, присутствующие в оригинальной документации (по крайней мере, подавляющее их большинство). Вообще говоря, в китайском даташыте в сводной таблице команд творится просто лютый пиздец – то флаги не те укажут (или не укажут нужные), то для инструкций LD в одном месте напишут «длительность 1 или 2 такта», а в другом – «1 такт», то вообще забудут привести команду (см. SEH и CLH) или, наоборот, приведут ее несколько раз (см. LD, LDD, LDS, NOP, SLEEP). В итоге пришлось проверять длительность выполнения всех инструкций вручную, в том числе, и тех, которые работают по 16-битной шине (задача оказалась, мягко говоря, нетривиальной). Результатом этих трудов и стала моя таблица команд, ссылка на которую дана выше (это мой дар Человечеству). Правда, я пока не проверял, правильно ли выполняются в китайском камне вообще все инструкции, но к тем командам, которые мне на данный момент приходилось использовать, вопросов нет – всё работает как надо.
• все кастомизированные команды условного перехода типа BRNE, BREQ, BRLO и т.д. (всего их 18 штук). Любая подобная инструкция может быть получена из универсальной команды BRBC или BRBS;
При компиляции программы вместо перечисленных выше «производных» инструкций будут подставлены их «уникальные» аналоги с необходимыми операндами. Например, команда «BRNE k» компилятором будет интерпретирована как «BRBC 1, k», а «SEI» – как «BSET 7». А вот фразы «LPM» и «LPM R0, Z» для ядра LGT8XM – это две разные команды, хотя обе они загружают байт из памяти программ в регистр R0 (адрес необходимой ячейки лежит в указателе «Z»). Так сложилось исторически, ибо камни AVR из семейства Classic не имели возможности загрузки данных из флэша в любой произвольный РОН (только в R0), и чтобы сохранить совместимость с ними, в более свежих моделях кирпичей решили оставить оба варианта.
1 – обратная сторона архитектуры RISC заключается в том, что для сложных задач «универсальных» простых команд может понадобиться очень много, поэтому в сумме они займут до хера места в памяти и будут выполняться слишком долго;
Время выполнения любой команды кирпича LGT8F328P составляет либо один, либо два машинных такта, при этом количество однотактных инструкций равно 119 (91,5%). Отмечу, что по сравнению с AVR-ками это достаточно много – у ATmega328P однотактных команд всего 84, т.е. 64,1% от общего количества. Кроме того, в 328-й Меге время выполнения команды может достигать трех и даже четырех машинных циклов, в китайском же камне, повторюсь, ни одна инструкция не выполняется дольше двух тактов Формально, конечно, к этому утверждению можно придраться – см. инструкции CPSE, SBRC/SBRS и SBIC/SBIS. Однако, на мой взгляд, здесь к самим командам вопросов нет, ибо в «чистом» виде (т.е. когда условие в них ложно) они длятся всего один такт. Увеличение длительности до трех циклов происходит лишь в том случае, когда размер следующей инструкции составляет четыре байта вместо двух, т.е. это обусловлено структурой программы, а не «плохими» командами. Кстати, в оригинальной документации на китайские кирпичи для команд CPSE, SBRC/SBRS и SBIC/SBIS указана длительность не более двух машинных тактов. Однако, я лично проверял – если после рассматриваемых инструкций идет команда размером 4 байта, то время их выполнения увеличивается до трех циклов. Конвейер
Как было сказано выше, при работе камня все команды программы проходят через двухуровневый конвейер – пока выполняется одна инструкция (уровень «Выполнение»), следующая предварительно выбирается из памяти программ (уровень «Выборка»): ![]()
Конвейерный подход позволяет сократить время выполнения большинства команд до одного такта, что в пределе делает производительность камня равной 1MIPS/МГц. Такого результата удается добиться благодаря тому, что не нужно отдельно реализовывать цикл «выборка + исполнение» для каждой команды. При этом китайское ядро имеет преимущество перед AVR-овским, ибо оно позволяет вытаскивать из памяти сразу две инструкции за один такт. Откуда берется эта возможность, в китайской документации не объясняется, но, думаю, она связана с тем, что память EFLASH, встроенная в микроконтроллеры LGT8FX8P, физически имеет 32-разрядную шину данных, а размер подавляющего большинства команд составляет всего 16 бит. Таким образом, за одну выборку мы чаще всего получим сразу две инструкции, поэтому в следующем машинном такте память можно не дергать. Отметим, что «удвоенная» выборка команд в пределе позволяет снизить частоту обращения ядра к памяти программ аж в два раза, что значительно уменьшает энергопотребление всей системы. ![]()
Из данного рисунка можно видеть, что АЛУ в AVR-ках и китайских камнях выполняет одну команду за один такт, благодаря подключению непосредственно к регистровому файлу. Тут, правда, есть один мутный момент – не совсем понятно, с чем синхронизован процесс чтения содержимого регистров-операндов, выполнения операции и записи ее результата в регистр-приемник, и синхронизован ли он с чем-либо вообще. Ответа на этот вопрос нет ни в китайской документации, ни в AVR-овских даташытах, поэтому просто поверим производителю на слово, что ядро успевает всё провернуть за один машинный цикл (тем более что практика этот факт действительно подтверждает). Совсем другая картина будет при работе с памятью – здесь камню сначала необходимо сформировать адрес ячейки, с которой он будет работать, и только потом он сможет выполнить требуемое действие (чтение или запись): ![]()
Очевидно, что при таких раскладах команда не может выполняться меньше двух машинных тактов – в первом такте ядро формирует адрес, во втором производит доступ к ячейке. Однако, факт остается фактом – время выполнения инструкций LD/LDD и ST/STD в китайских кирпичах составляет всего один машинный цикл. И на мой взгляд, это возможно только в том случае, когда на борту ядра есть какой-нибудь вспомогательный узел, который может заранее сформировать требуемый адрес, а при необходимости еще и увеличить/уменьшить его. То же самое касается команд относительного безусловного перехода (RJMP) и вызова подпрограммы (RCALL) – поскольку они не содержат никаких условий, то при наличии помощника конвейер может не тратить лишний цикл на выборку команды при переходе на требуемый адрес (см. ниже), а сделать это заранее. Да и вообще – если составить перечень «долгих» команд в AVR, то будет заметно, что все они связаны с какими-то дополнительными действиями, которые должно выполнить ядро для корректного завершения операции (по большей части – формирование адреса ячейки памяти и сохранение/извлечение данных из стека). И если предположить, что все эти дополнительные действия берет на себя помощник, становится ясно, почему в китайских камнях длительность команды не превышает двух тактов, хотя те же CALL, RET и RETI в AVR-ках выполняются целых четыре цикла. Счетчик команд (PC) и задержки в конвейере
При работе конвейера адрес инструкции, которая будет извлечена из памяти следующей, берется из счетчика команд (Program Counter, PC). Размер данного счетчика для китайских камней не приводится, однако, по аналогии с AVR-ками можно предположить, что его разрядность составляет 12 бит для LGT8F88P, 13 бит для LGT8F168P и 14 бит для LGT8F328P. Обратите внимание на то, что счетчик команд адресует именно команды, а поскольку размер большинства инструкций составляет 16 бит, он считает не отдельные байты, а слова́. Именно поэтому для адресации 32КБ памяти нам будет достаточно 14-битного значения, а не 15-битного, как это было бы при побайтовой адресации.
1 – значение счетчика команд увеличивается на 2 при выполнении инструкции, занимающей две ячейки памяти программ (4 байта). К таким инструкциям относятся команды, использующие прямую адресацию: LDS, STS, CALL и JMP.
В случае команд типа «Test & Skip» (CPSE, SBRC/SBRS и SBIC/SBIS) следующая инструкция не выполняется, если проверяемое условие истинно (регистры равны, либо требуемый бит регистра сброшен/установлен). Однако выборка пропускаемой команды уже произошла, и вследствие того, что она не выполняется, в конвейере образуется «дырка», которая заключается в пропуске одного или двух (в зависимости от размера пропускаемой инструкции1) машинных циклов. Соответственно команды типа «Test & Skip» выполняются за 1 такт, если условие, указанное в команде ложно, и за 2 или 3 такта, если указанное условие истинно.
1 – если размер команды, следующей за инструкцией типа «Test & Skip», составляет 2 байта, производится пропуск одного машинного такта. Если размер следующей команды равен 4 байта, будет пропущено два машинных такта.
Последняя группа команд, изменяющих содержимое счетчика PC и вызывающих задержки в работе конвейера, это команды безусловного перехода, вызова подпрограммы и возврата из подпрограммы (или из обработчика прерывания). Практически все они выполняются за два такта, т.е. являются «долгими», однако, по сравнению с AVR-ками китайцы и здесь довольно выгодно отличаются: ![]()
Из приведенной таблицы видно, что в ATmega328P время выполнения команд из рассматриваемой группы составляет от двух до четырех тактов. При этом прослеживается четкая связь – инструкции, работающие со стеком (RCALL, ICALL, CALL), выполняются ровно на один такт дольше своих «аналогов», которые стек не используют (RJMP, IJMP, JMP). Отсюда можно сделать вывод о том, что в ядре AVR работа со стеком отнимает один машинный цикл, а всё остальное «дополнительное» время – это задержка в работе конвейера из-за необходимости формирования адреса перехода и извлечения инструкции по этому адресу. Стек и указатель стека (SP)
Одной из важных составляющих микроконтроллеров LGT8F328P является стек – специальный кусок ОЗУ, используемый системой для вспомогательных задач. В подавляющем большинстве случаев этот кусок задействуется в качестве временного хранилища содержимого регистров, а также адресов возврата из подпрограмм и обработчиков прерываний. Если пользователю нужно на время сохранить текущее значение какого-либо регистра, для этого можно использовать стек. Если пользователь вызывает подпрограмму, в стеке автоматически сохранится адрес, на который нам потом нужно будет вернуться. Аналогичным образом ядро поступает и при возникновении разрешенного прерывания – содержимое счетчика команд PC кладется в стек, после чего программа переходит на соответствующий вектор в таблице прерываний. ![]()
Адрес этой ячейки лежит в указателе стека SP (Stack Pointer), в качестве которого выступает регистровая пара SPH:SPL, расположенная в пространстве ввода-вывода: ![]()
Отметим, что, в отличие от счетчика программ, с данной парой можно работать как с обычными регистрами ввода-вывода, т.е. как считывать ее содержимое, так и изменять его, причем, для этого допускается использовать команды IN и OUT. ![]()
Конечно, пользователь волен располагать стек в любом месте оперативной памяти кирпича, однако, обычно под его основание всё же отводят последнюю ячейку ОЗУ. Именно исходя из этой логики, после включения питания, а также после сброса микроконтроллера в регистровую пару SPH:SPL автоматически загружается значение RAMEND (в случае камня LGT8F328P оно равно 0x08FF, для младших моделей RAMEND = 0x04FF). Это дает возможность пользователю не инициализировать указатель стека специально, поскольку всё само собой получится так, как нужно. Если же вы хотите перенести стек в другое место памяти, необходимо будет самостоятельно записать требуемый адрес в регистровую пару SPH:SPL. ![]()
Кроме того, к изменению указателя стека приведет возникновение разрешенного прерывания, поскольку в этом случае в стеке будет сохранен адрес возврата. Обратите внимание на то, что после команд работы с байтами (PUSH/POP) содержимое указателя SP изменяется на 1, т.к. ОЗУ микроконтроллера имеет побайтовую организацию. По этой же причине выполнение «адресных» инструкций (RCALL/ICALL/CALL/RET/RETI) ведет к изменению регистровой пары SPH:SPL на 2 – размер адреса команды в МК LGT8F328P составляет 16 бит. Прерывания
При работе с периферией и внешними сигналами довольно часто используется такая вещь, как прерывания. Под прерыванием понимают прекращение нормального хода программы для выполнения какой-либо задачи, определяемой внутренним или внешним событием микроконтроллера. Таким событием может быть переполнение одного из таймеров, приход синхроимпульса на определенный порт МК, передача данных по SPI и т.д. Например, пользователь может настроить кирпич таким образом, что с каждым приходом байта по шине UART камень будет отвлекаться от своих текущих задач на обработку этого байта (под текущими здесь понимаются задачи, определяемые нормальным ходом программы).
|
Место для разного (сдается) |