У сопроцессора ULPFSM имеется 4 регистра общего назначения, каждый разрядности 16-бит: R0, R1, R2, R3. У него также есть 8-битный регистр счетчика (stage_cnt), который может использоваться для реализации циклов. К регистру stage_cnt осуществляется доступ с помощью специальных инструкций.
Сопроцессор ULP может обращаться к 8 килобайтам области памяти RTC_SLOW_MEM. Память адресуется 32-битными словами. Сопроцессор также может обращаться к регистрам периферийных устройств RTC_CNTL, RTC_IO и SENS.
Все инструкции сопроцессора 32-разрядные. Инструкции перехода (Jump), арифметических операций (ALU), доступа к регистрам периферийных устройств и памяти выполняются за 1 такт. Инструкции, которые работают с периферийными устройствами (TSENS, ADC, I2C) выполняются переменное количество тактов, в зависимости от операции периферийного устройства.
Синтаксис инструкций не чувствителен к регистру символов. Большие и маленькие буквы могут использоваться произвольным образом, в том числе и вперемежку. Это верно как для имен регистров, так и для имен инструкций.
Адресация. Инструкции JUMP, ST, LD ожидают аргумент адреса, выраженный следующим способом, в зависимости от типа адреса аргумента:
• Когда аргумент адреса представлен как метка, то инструкция ожидает адрес, выраженный как 32-битное слово.
Рассмотрим следующий пример программы:
entry:
NOP
NOP
NOP
NOP
loop:
MOVE R1, loop
JUMP R1
Когда эта программа ассемблируется и линкуется, адрес метки цикла будет равен 16 (в байтах). Однако инструкция JUMP ожидает, что адрес, сохраненный в R1, выражен в 32-битном слове. Чтобы учесть такой общий случай, ассемблер преобразует байтовый адрес метки цикла в номер слова, затем генерирует инструкцию MOVE. Генерируемый код будет эквивалентен следующему:
0000 NOP
0004 NOP
0008 NOP
000c NOP
0010 MOVE R1, 4
0014 JUMP R1
• Другой случай - когда аргумент инструкции MOVE не метка, а константа. В этом случае ассемблер будет использовать значение как есть, без преобразования:
.set val, 0x10
MOVE R1, val
В этом случае в регистр R1 будет загружено значение 0x10.
Однако когда непосредственное значение используется как смещение в инструкциях LD и ST, ассемблер считает аргумент адреса в байтах, и преобразует его в 32-битное слово перед выполнением инструкции:
В этом случае значение R1 сохраняется в ячейку памяти, указанную как [R2 + смещение / 4].
Рассмотрим следующий код:
.global array
array: .long0
.long0
.long0
.long0
MOVE R1, array
MOVE R2, 0x1234
ST R2, R1, 0// запись значения R2 в первый элемент массива,// т. е. в array[0]
ST R2, R1, 4// запись значения R2 во второй элемент массива,// со смещением 4 байта, т. е. в array[1]
ADD R1, R1, 2// эта команда инкрементирует адрес на 2 слова// (8 байт)
ST R2, R1, 0// запись значения R2 во третий элемент массива,// т. е. в array[2]
Время выполнения инструкции. Сопроцессор ULP тактируется с частотой RTC_FAST_CLK, которая обычно получается из внутреннего RC-генератора 8 МГц. Приложения, которым нужно знать точную частоту ULP, могут откалибровать её по частоте основных тактов XTAL:
Для выборки каждой инструкции сопроцессору ULP требуется определенное количество тактов, плюс некоторое количество тактов для её выполнения, в зависимости от инструкции. Конкретное время выполнения можно узнать далее в описании каждой инструкции.
Время выборки инструкции равно:
2 периода тактов — для инструкций, которые идут за инструкцией ALU и инструкции ветвления. 4 периода тактов — в остальных случаях.
Обратите внимание, что когда осуществляется доступ к ячейкам памяти RTC и регистрам RTC, у сопроцессора ULP приоритет ниже, чем у ядер CPU. Это значит, что выполнение инструкций сопроцессора ULP может быть приостановлено, когда основной CPU обращается к тому же самому региону памяти, что и ULP.
Rdst - регистр R[0..3] Rsrc1 - регистр R[0..3] Rsrc2 - регистр R[0..3] Imm - 16-разрядное значение со знаком
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция вычитает регистр источника из другого регистра источника, или вычитает 16-битное значение со знаком из регистра источника, и сохраняет результат в регистр назначения.
Rdst - регистр R[0..3] Rsrc1 - регистр R[0..3] Rsrc2 - регистр R[0..3] Imm - 16-разрядное значение со знаком
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция выполнит логическое И исходного регистра и другого исходного регистра, или 16-битного значения со знаком, и результат сохраняет в регистр назначения.
Rdst - регистр R[0..3] Rsrc1 - регистр R[0..3] Rsrc2 - регистр R[0..3] Imm - 16-разрядное значение со знаком
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция выполняет логическое ИЛИ исходного регистра и другого исходного регистра, или 16-битного значения со знаком, и результат сохраняет в регистр назначения.
Rdst - регистр R[0..3] Rsrc1 - регистр R[0..3] Rsrc2 - регистр R[0..3] Imm - 16-разрядное значение со знаком
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция делает логический сдвиг влево исходного регистра на указанное количество бит в другом исходном регистре или 16-битном значении со знаком, и сохраняет результат в регистр назначения.
Rdst - регистр R[0..3] Rsrc1 - регистр R[0..3] Rsrc2 - регистр R[0..3] Imm - 16-разрядное значение со знаком
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция делает логический сдвиг вправо исходного регистра на указанное количество бит в другом исходном регистре или 16-битном значении со знаком, и сохраняет результат в регистр назначения.
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция перемещает значение из регистра источника или из 16-битного значения со знаком в регистр назначения.
Обратите внимание, что когда метка используется для непосредственного значения, адрес метки будет преобразован из байтов в слова. Это потому, что инструкции LD, ST и JUMP ожидают значение адреса регистра, выраженное в словах, а не в байтах. Чтобы избежать этого, используйте дополнительную инструкцию.
Rsrc – R[0..3], хранит 16-битное значение для сохранения Rdst – R[0..3], адрес назначения, в 32-битных словах Offset – 10-битное значение со знаком, смещение в байтах
Время выполнения: 4 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция сохранит 16-битное значение Rsrc в младшую половину слова в памяти по адресу Rdst+offset. Старшая половина слова записывается текущим значением программного счетчика (PC), выраженным в словах, сдвинутым влево на 5 бит:
Rdst – R[0..3], содержащий адрес для перехода (выраженный в 32-битных словах) ImmAddr – 13-битный адрес (выраженный в байтах), выровненный на 4 байта Condition: EQ – переход, если результат операции ALU был равен 0 OV – переход, если последняя операция ALU установила флаг переполнения
Время выполнения: 2 такта на выполнение, 2 такта для выборки следующей инструкции.
Инструкция делает переход по указанному адресу. Это может быть либо безусловный переход, либо ветвление, основанное на флагах операции ALU.
Пример 1:
JUMP R1 // Переход по адресу в R1 (адрес в R1// выражен в 32-битных словах)
Пример 2:
JUMP 0x120, EQ // Переход по адресу 0x120 (значение// в байтах) если результат ALU ноль
Пример 3:
JUMP label // Переход по метке
...
label:
nop // Определение метки
Пример 4:
.global label // Декларация глобальной метки
MOVE R1, label // R1 = label (значение, загруженное в R1 выраженное в словах)
JUMP R1 // Переход по метке label
...
Step – относительное смещение от текущей позиции, в байтах Threshold – значение порога для условия ветвления Condition: EQ (equal) – переход, если значение в R0 равно threshold LT (less than) – переход, если значение в R0 меньше threshold LE (less or equal) – переход, если значение в R0 меньше или равно threshold GT (greater than) – переход, если значение в R0 больше threshold GE (greater or equal) – переход, если значение в R0 больше или равно threshold
Время выполнения:
Условия LT, GE, LE и GT: 2 цикла на выполнение, 2 цикла на выборку следующей инструкции. Условия LE и GT реализованы в ассемблере с использованием инструкций JUMPR:
Условие EQ реализовано в ассемблере с использованием двух инструкций JUMPR:
// JUMPR target, threshold, EQ реализовано как:
JUMPR next, threshold +1, GE
JUMPR target, threshold, GE
next:
Таким образом, время выполнения будет зависеть от того, какое происходит ветвление: либо 2 такта на выполнение + 2 такта на выборку инструкции, или 4 такта на выполнение + 4 такта на выборку.
Инструкция делает переход по относительному адресу, если условие true. Условие это результат сравнения R0 и значения threshold.
Пример 1:
pos: JUMPR 16, 20, GE // переход по адресу (позиция + 16 байт),// если значение в R0 >= 20
Пример 2:
// Цикл с декрементируемым счетчиком на основе регистра R0
MOVE R0, 16// загрузка 16 в R0
label:
SUB R0, R0, 1// R0--
NOP // какие-либо действия
JUMPR label, 1, GE // переход по метке label, если R0 >= 1
Step – относительное смещение от текущей позиции, в байтах Threshold – значение порога для условия ветвления Condition: EQ (equal) – переход, если stage_cnt равно threshold LT (less than) – переход, если stage_cnt меньше threshold LE (less or equal) - переход, если stage_cnt меньше или равно threshold GT (greater than) – переход, если stage_cnt больше threshold GE (greater or equal) — переход, если stage_cnt больше или равно
Время выполнения:
Условия LE, LT, GE: 2 цикла на выполнение, 2 цикла на выборку следующей инструкции. Условия EQ, GT реализованы в ассемблере с использованием двух инструкций JUMPS:
// JUMPS target, threshold, GT реализован как:
JUMPS next, threshold, LE
JUMPS target, threshold, GE
next:
Таким образом, время выполнения будет зависеть от того, какое происходит ветвление: либо 2 такта на выполнение + 2 такта на выборку инструкции, или 4 такта на выполнение + 4 такта на выборку.
Инструкция делает переход по относительному адресу, если условие true. Условие это результат сравнения регистра счетчика и значения threshold.
Пример 1:
pos: JUMPS 16, 20, EQ // переход на (позиция + 16 байт),// если stage_cnt == 20
Пример 2:
// Цикл, основанный на инкрементируемом счетчике в регистре stage_cnt
STAGE_RST // stage_cnt = 0
label:
STAGE_INC 1// stage_cnt++
NOP // какие-либо действия
JUMPS label, 16, LT // переход по метке label, если stage_cnt < 16
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция посылает прерывание от ULP контроллеру RTC.
Если SoC находится в режиме глубокого сна (deep sleep mode), и разрешено ULP wakeup, то это разбудит SoC.
Если SoC не в deep sleep mode, и бит прерывания ULP (RTC_CNTL_ULP_CP_INT_ENA) установлен в регистре RTC_CNTL_INT_ENA_REG, то произойдет прерывание RTC.
Обратите внимание, что перед использованием инструкции WAKE программе ULP может понадобиться подождать, пока контроллер RTC не будет готов тому, чтобы разбудить основной процессор. Это показывается битом RTC_CNTL_RDY_FOR_WAKEUP регистра RTC_CNTL_LOW_POWER_ST_REG. Если инструкция WAKE выполняется, когда RTC_CNTL_RDY_FOR_WAKEUP равен 0, то она не не дает эффекта (пробуждение не произойдет).
Пример:
is_rdy_for_wakeup:// Чтение бита RTC_CNTL_RDY_FOR_WAKEUP
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
AND r0, r0, 1
JUMP is_rdy_for_wakeup, eq // Повтор, пока этот бит не установится
WAKE // Запуск пробуждения
REG_WR 0x006, 24, 24, 0// Остановка таймера ULP (очистка// RTC_CNTL_ULP_CP_SLP_TIMER_EN)
HALT // Остановка программы ULP// После этой инструкции SoC выйдет из сна, и ULP не заработает опять,// пока не будет запущен основной программой.
sleep_reg – 0..4, выбор одного из регистров SENS_ULP_CP_SLEEP_CYCx_REG.
Время выполнения: 2 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция выберет, значение в каком из регистров SENS_ULP_CP_SLEEP_CYCx_REG (x = 0..4) будет использоваться для периода таймера пробуждения ULP. По умолчанию используется значение из SENS_ULP_CP_SLEEP_CYC0_REG.
Пример 1:
SLEEP 1// Используется период, установленный в SENS_ULP_CP_SLEEP_CYC1_REG
Пример 2:
.set sleep_reg, 4// Определение константы
SLEEP sleep_reg // Используется период, установленный в SENS_ULP_CP_SLEEP_CYC4_REG
Sub_addr – адрес подчиненного (slave) устройства I2C. High, Low — определяет диапазон считываемых бит. Биты, выходящие за диапазон [High, Low], маскируются. Slave_sel - индекс адреса I2C slave.
Время выполнения: время выполнения инструкции зависит главным образом от времени обмена I2C. 4 такта уходит на выборку следующей инструкции.
Инструкция I2C_RD считывает 1 байт из устройства I2C slave с индексом Slave_sel. Адрес (в 7-битном формате) должен быть предварительно установлен в поле регистра SENS_I2C_SLAVE_ADDRx, где x == Slave_sel. 8 бит результата чтения сохраняются в регистр R0.
Пример:
I2C_RD 0x10, 7, 0, 0// Чтение байта из подчиненного устройства// с адресом 0x10, с индексом адреса,// установленным в SENS_I2C_SLAVE_ADDR0
Sub_addr – адрес подчиненного (slave) устройства I2C. Value – 8-битное значение для записи. High, Low — определяет диапазон записываемых бит. Биты, выходящие за диапазон [High, Low], будут маскированы. Slave_sel - индекс адреса I2C slave.
Время выполнения: время выполнения инструкции зависит главным образом от времени обмена I2C. 4 такта уходит на выборку следующей инструкции.
Инструкция I2C_WR запишет 1 байт в I2C с индексом Slave_sel. Адрес (в 7-битном формате) должен быть предварительно установлен в поле регистра SENS_I2C_SLAVE_ADDRx, где x == Slave_sel.
Пример:
I2C_WR 0x20, 0x33, 7, 0, 1// Запись байта 0x33 в подчиненное устройство// с адресом 0x20, с индексом адреса,// установленным в SENS_I2C_SLAVE_ADDR1.
Addr – адрес регистра в 32-битных словах. High – номер последнего бита регистра. Low – номер первого бита регистра.
Время выполнения: 4 такта на выполнение, 4 такта для выборки следующей инструкции.
Инструкция читает до 16 бит из регистра периферийного устройства в регистр общего назначнения R0 = REG[Addr][High:Low].
Эта инструкция может обращаться к регистрам периферийных устройств RTC_CNTL, RTC_IO, SENS и RTC_I2C. Адрес регистра, как он видится ULP, может быть вычислен из адреса того же регистра на шине DPORT следующим образом:
Addr – адрес регистра в 32-битных словах. High – номер последнего бита регистра. Low – номер первого бита регистра. Data – значение для записи, 8 бит.
Время выполнения: 8 тактов на выполнение, 4 такта для выборки следующей инструкции.
Инструкция запишет до 8 из непосредственного значения данных в регистр периферийного устройства REG[Addr][High:Low] = данные.
Эта инструкция может обращаться к регистрам периферийных устройств RTC_CNTL, RTC_IO, SENS и RTC_I2C. Адрес регистра, как он видится ULP, может быть вычислен из адреса того же регистра на шине DPORT следующим образом:
[Макросы для доступа к регистрам периферийного устройства]
Перед тем, как исходный код для ULP попадет ассемблеру, он сначала проходит через препроцессор C. Это позволяет использовать определенные макросы для упрощения доступа к регистрам периферийных устройств.
Некоторые готовые макросы определены в заголовке soc/soc_ulp.h. Эти макросы позволяют получать доступ к полям регистра периферийного устройства по их именам. Имена регистров периферийных устройств, которые могут использоваться с этими макросами, определены в soc/rtc_cntl_reg.h, soc/rtc_io_reg.h, soc/sens_reg.h и soc/rtc_i2c_reg.h.