VPR (произносится как Viper) — первый процессор компании Nordic Semiconductor на архитектуре RISC-V, представленный в новых версиях nRF54H и nRF54L линеек SoC после их первоначальных анонсов в апреле и октябре 2023 года соответственно. Читателям этого блога знаком мой давний, одержимый интерес к RISC-V (см. мои серии статей Советы по RISC-V и Байты RISC-V.) Тем не менее внедрение процессора RISC-V компанией Nordic особенно интересно для меня, ведь их линейка микроконтроллеров крайне популярна в беспроводных доменах с низким энергопотреблением. А это обычный случай применения для клиентов Golioth.
Естественно, мне хотелось изучить VPR в деталях по мере поступления дополнительной информации. Базовое описание регистров и инициализации можно найти в документации nRF54L. VPR — процессор RV32E. Это означает, что он использует 32-битные регистры, но реализует только младшие 16 регистров, требуемые спецификацией встраиваемости (E — embedded), а не 32, определенные спецификацией полного 32-битного целого числа (RV32I). В процессоре также реализованы операции умножения и деления (M) и расширение сжатых инструкций (C), которое добавляет поддержку 16-битных вариантов инструкций, чтобы повысить плотность кода. Все инструкции выполняются в машинном режиме (M) — единственном реализованном уровнем привилегий.
Помимо прикладного процессора Arm Cortex-M33, микроконтроллеры nRF54L содержат один процессор VPR, называемый быстрым легковесным периферийным процессором (FLPR, произносится как flipper — «флиппер»). Тогда как nRF54H20 включает в себя и FLPR, и периферийный процессор (PPR, произносится как pepper — пеппер) вместе с двухъядерным приложением Arm Cortex-M33 и сетевыми процессорами. PPR предназначен для периферийного обслуживания с низким энергопотреблением и работает на частоте 16 МГц. FLPR работает на частоте 320 МГц и предназначен для программно-определяемых периферийных устройств.
Процессоры VPR должны быть настроены и запущены управляющим процессором приложений. Эти процессы заключаются в установке счетчика программ VPR в регистре INITPC и последующей записи в регистр CPURUN, чтобы запустить процессор. В nRF54H20 PPR VPR выполняет код из медленной глобальной оперативной памяти (RAM3x). Поэтому управляющий процессор перед обновлением счетчика программ и запуском процессора VPR должен скопировать код в соответствующую область.
Ранее я писал о процессе загрузки Zephyr и особенно об инициализации оборудования. Zephyr использует devicetree, чтобы описать оборудование и периферийные устройства поддерживаемых процессоров и плат. Во включаемом файле дерева устройств — nrf54h20.dtsi есть узел, представляющий процессор PPR VPR.
cpuppr: cpu@d {
compatible = "nordic,vpr";
reg = <13>;
device_type = "cpu";
clocks = <&fll16m>;
clock-frequency = <DT_FREQ_M(16)>;
riscv,isa = "rv32emc";
nordic,bus-width = <32>;
cpuppr_vevif_rx: mailbox {
compatible = "nordic,nrf-vevif-task-rx";
status = "disabled";
interrupt-parent = <&cpuppr_clic>;
interrupts = <0 NRF_DEFAULT_IRQ_PRIORITY>,
<1 NRF_DEFAULT_IRQ_PRIORITY>,
<2 NRF_DEFAULT_IRQ_PRIORITY>,
<3 NRF_DEFAULT_IRQ_PRIORITY>,
<4 NRF_DEFAULT_IRQ_PRIORITY>,
<5 NRF_DEFAULT_IRQ_PRIORITY>,
<6 NRF_DEFAULT_IRQ_PRIORITY>,
<7 NRF_DEFAULT_IRQ_PRIORITY>,
<8 NRF_DEFAULT_IRQ_PRIORITY>,
<9 NRF_DEFAULT_IRQ_PRIORITY>,
<10 NRF_DEFAULT_IRQ_PRIORITY>,
<11 NRF_DEFAULT_IRQ_PRIORITY>,
<12 NRF_DEFAULT_IRQ_PRIORITY>,
<13 NRF_DEFAULT_IRQ_PRIORITY>,
<14 NRF_DEFAULT_IRQ_PRIORITY>,
<15 NRF_DEFAULT_IRQ_PRIORITY>;
#mbox-cells = <1>;
nordic,tasks = <16>;
nordic,tasks-mask = <0xfffffff0>;
};
};
Определены ранее упомянутые атрибуты, такие как поддержка rv32emc RISC-V ISA, и тактовая частота 16 МГц (DT_FREQ_M(16)). Существует также узел в soc, определяющий PPR как периферийное устройство сопроцессора.
cpuppr_vpr: vpr@908000 {
compatible = "nordic,nrf-vpr-coprocessor";
reg = <0x908000 0x1000>;
status = "disabled";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0x908000 0x1000>;
power-domains = <&gpd NRF_GPD_SLOW_ACTIVE>;
cpuppr_vevif_tx: mailbox@0 {
compatible = "nordic,nrf-vevif-task-tx";
reg = <0x0 0x1000>;
status = "disabled";
#mbox-cells = <1>;
nordic,tasks = <16>;
nordic,tasks-mask = <0xfffffff0>;
};
};
Свойство compatible связывает PPR с соответствующим драйвером, который управляющий процессор, — в данном это случае процессор приложений nRF54H20 (cpuapp), — может использовать для его настройки и запуска. В составе прочих драйверов есть nordic_vpr_launcher, который определяет совместимость устройства.
#define DT_DRV_COMPAT nordic_nrf_vpr_coprocessor
Драйвер относительно прост: он отвечает только за инициализацию устройства. Функция инициализации выполняет операции, описанные в документации.
static int nordic_vpr_launcher_init(const struct device *dev)
{
const struct nordic_vpr_launcher_config *config = dev->config;
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(source_memory)
if (config->size > 0U) {
LOG_DBG("Loading VPR (%p) from %p to %p (%zu bytes)", config->vpr,
(void *)config->src_addr, (void *)config->exec_addr, config->size);
memcpy((void *)config->exec_addr, (void *)config->src_addr, config->size);
}
#endif
#if defined(CONFIG_SOC_NRF54L_CPUAPP_COMMON) && !defined(CONFIG_TRUSTED_EXECUTION_NONSECURE)
nrf_spu_periph_perm_secattr_set(NRF_SPU00, nrf_address_slave_get((uint32_t)config->vpr),
true);
#endif
LOG_DBG("Launching VPR (%p) from %p", config->vpr, (void *)config->exec_addr);
nrf_vpr_initpc_set(config->vpr, config->exec_addr);
nrf_vpr_cpurun_set(config->vpr, true);
return 0;
}
Различные функции nrf_vpr_* предоставляются в hal_nordic как inline — встроеные. Конфигурация устройства, которая определяет используемые средством запуска адреса источника и назначения памяти, инстанцируются с использованием различных макросов DT_* дерева устройств. Эти макросы выполняются для каждого совместимого узла с помощью DT_INST_FOREACH_STATUS_OKAY.
/* получить адрес VPR либо из памяти, либо из раздела */
#define VPR_ADDR(node_id) \
(DT_REG_ADDR(node_id) + \
COND_CODE_0(DT_FIXED_PARTITION_EXISTS(node_id), (0), (DT_REG_ADDR(DT_GPARENT(node_id)))))
#define NORDIC_VPR_LAUNCHER_DEFINE(inst) \
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, source_memory), \
(BUILD_ASSERT((DT_REG_SIZE(DT_INST_PHANDLE(inst, execution_memory)) <= \
DT_REG_SIZE(DT_INST_PHANDLE(inst, source_memory))), \
"Execution memory exceeds source memory size");)) \
\
static const struct nordic_vpr_launcher_config config##inst = { \
.vpr = (NRF_VPR_Type *)DT_INST_REG_ADDR(inst), \
.exec_addr = VPR_ADDR(DT_INST_PHANDLE(inst, execution_memory)), \
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, source_memory), \
(.src_addr = VPR_ADDR(DT_INST_PHANDLE(inst, source_memory)), \
.size = DT_REG_SIZE(DT_INST_PHANDLE(inst, execution_memory)),))}; \
\
DEVICE_DT_INST_DEFINE(inst, nordic_vpr_launcher_init, NULL, NULL, &config##inst, \
POST_KERNEL, CONFIG_NORDIC_VPR_LAUNCHER_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(NORDIC_VPR_LAUNCHER_DEFINE)
Мы можем увидеть инициализацию в действии, собрав приложения как для процессора приложений, так и для PPR. [Инструмент сборки] Zephyr sysbuild позволяет создавать несколько целевых объектов. Пример hello_world демонстрирует это сборкой приложения, которое просто выводит Hello world from {target} для всех указанных целей.
west build -p -b nrf54h20dk/nrf54h20/cpuapp -T sample.sysbuild.hello_world.nrf54h20dk_cpuapp_cpuppr .
Аргумент -T указывает одну из тестовых конфигураций примера, которая автоматически добавит в сборку дополнительные аргументы.
sample.sysbuild.hello_world.nrf54h20dk_cpuapp_cpuppr:
platform_allow:
- nrf54h20dk/nrf54h20/cpuapp
integration_platforms:
- nrf54h20dk/nrf54h20/cpuapp
extra_args:
SB_CONF_FILE=sysbuild/nrf54h20dk_nrf54h20_cpuppr.conf
hello_world_SNIPPET=nordic-ppr
SB_CONF_FILE определяет конфигурацию sysbuild. Для комплекта разработки nRF24H20 (DK) файл конфигурации служит, только чтобы указать целевую плату варианта приложения REMOTE, которым является PPR.
SB_CONFIG_REMOTE_BOARD="nrf54h20dk/nrf54h20/cpuppr"
hello_world_SNIPPET указывает фрагмент, который следует включить в сборку. Он включает в себя два файла наложения дерева устройств. Один, nordic-ppr.overlay, включает узел cpuppr_vpr для любых плат, включающих PPR.
&cpuppr_vpr {
status = "okay";
};
Внимательные читатели заметят, что предстоящий nRF9280 от Nordic также содержит PPR.
Второй, nrf54h20_cpuapp.overlay, включает область памяти, которая совместно используется PPR и процессором приложения.
&cpuppr_ram3x_region {
status = "okay";
};
После сборки образ процессора приложения будет находиться в build/hello_world, а образ PPR — в build/remote. Можно использовать соответствующие тулчейны Arm и RISC-V из Zephyr SDK, чтобы проверить файлы ELF и понять, как конфигурация преобразуется в инструкции в бинарном файле.
Чтобы дизассемблировать образ процессора приложения, используйте objdump из тулчейна arm-zephyr-eabi.
$ZEPHYR_SDK_PATH/arm-zephyr-eabi/bin/arm-zephyr-eabi-objdump -D build/hello_world/zephyr/zephyr.elf
Для образа PPR можно использовать вариант riscv64-zephyr-elf.
$ZEPHYR_SDK_PATH/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-objdump -D build/remote/zephyr/zephyr.elf
В показанной ранее инициализации драйвера nordic_vpr_launcher макрос DEVICE_DT_INST_DEFINE используется для определения периферийного устройства PPR с уровнем инициализации POST_KERNEL. Это означает, что устройство должно быть инициализировано после загрузки ядра. Это обрабатывается вызовом z_sys_init_run_level с уровнем POST_KERNEL.
static void z_sys_init_run_level(enum init_level level)
{
static const struct init_entry *levels[] = {
__init_EARLY_start,
__init_PRE_KERNEL_1_start,
__init_PRE_KERNEL_2_start,
__init_POST_KERNEL_start,
__init_APPLICATION_start,
#ifdef CONFIG_SMP
__init_SMP_start,
#endif /* CONFIG_SMP */
/* Маркер конца */
__init_end,
};
const struct init_entry *entry;
for (entry = levels[level]; entry < levels[level+1]; entry++) {
const struct device *dev = entry->dev;
int result;
sys_trace_sys_init_enter(entry, level);
if (dev != NULL) {
result = do_device_init(entry);
} else {
result = entry->init_fn.sys();
}
sys_trace_sys_init_exit(entry, level, result);
}
}
Дизасемблированный код можно посмотретть в образе процессора приложения.
0e0a9ca4 <z_sys_init_run_level>:
e0a9ca4: b538 push {r3, r4, r5, lr}
e0a9ca6: 4b09 ldr r3, [pc, #36] ; (e0a9ccc <z_sys_init_run_level+0x28>)
e0a9ca8: f853 4020 ldr.w r4, [r3, r0, lsl #2]
e0a9cac: 3001 adds r0, #1
e0a9cae: f853 5020 ldr.w r5, [r3, r0, lsl #2]
e0a9cb2: 42a5 cmp r5, r4
e0a9cb4: d800 bhi.n e0a9cb8 <z_sys_init_run_level+0x14>
e0a9cb6: bd38 pop {r3, r4, r5, pc}
e0a9cb8: 6863 ldr r3, [r4, #4]
e0a9cba: b123 cbz r3, e0a9cc6 <z_sys_init_run_level+0x22>
e0a9cbc: 4620 mov r0, r4
e0a9cbe: f003 f81f bl e0acd00 <do_device_init>
e0a9cc2: 3408 adds r4, #8
e0a9cc4: e7f5 b.n e0a9cb2 <z_sys_init_run_level+0xe>
e0a9cc6: 6823 ldr r3, [r4, #0]
e0a9cc8: 4798 blx r3
e0a9cca: e7fa b.n e0a9cc2 <z_sys_init_run_level+0x1e>
e0a9ccc: 0e0ae818 mcreq 8, 0, lr, cr10, cr8, {0}
Вторая инструкция, ldr r3, [pc, #36], загружает адрес со смещением в 36 байтов от текущего счетчика программы (pc), этот счетчик — последняя запись дизассемблированной функции: 0e0ae818.
Игнорируйте попытку декодирования инструкций с помощью
objdumpдля этого адреса и всех адресов сохраненной памяти, указанных ниже.
Это адрес массива levels, который содержит элементы init_entry.
0e0ae818 <levels.0>:
e0ae818: 0e0ad3cc cdpeq 3, 0, cr13, cr10, cr12, {6}
e0ae81c: 0e0ad3cc cdpeq 3, 0, cr13, cr10, cr12, {6}
e0ae820: 0e0ad40c cdpeq 4, 0, cr13, cr10, cr12, {0}
e0ae824: 0e0ad414 cfmvdlreq mvd10, sp
e0ae828: 0e0ad464 cdpeq 4, 0, cr13, cr10, cr4, {3}
e0ae82c: 0e0ad474 mcreq 4, 0, sp, cr10, cr4, {3}
e0ae830: 3566726e strbcc r7, [r6, #-622]! ; 0xfffffd92
e0ae834: 30326834 eorscc r6, r2, r4, lsr r8
e0ae838: 30406b64 subcc r6, r0, r4, ror #22
e0ae83c: 302e392e eorcc r3, lr, lr, lsr #18
e0ae840: 66726e2f ldrbtvs r6, [r2], -pc, lsr #28
e0ae844: 32683435 rsbcc r3, r8, #889192448 ; 0x35000000
e0ae848: 70632f30 rsbvc r2, r3, r0, lsr pc
e0ae84c: 70706175 rsbsvc r6, r0, r5, ror r1
e0ae850: 6c654800 stclvs 8, cr4, [r5], #-0
e0ae854: 77206f6c strvc r6, [r0, -ip, ror #30]!
e0ae858: 646c726f strbtvs r7, [ip], #-623 ; 0xfffffd91
e0ae85c: 6f726620 svcvs 0x00726620
e0ae860: 7325206d ; <UNDEFINED> instruction: 0x7325206d
e0ae864: 6146000a cmpvs r6, sl
e0ae868: 64656c69 strbtvs r6, [r5], #-3177 ; 0xfffff397
e0ae86c: 206f7420 rsbcs r7, pc, r0, lsr #8
e0ae870: 6f626572 svcvs 0x00626572
e0ae874: 203a746f eorscs r7, sl, pc, ror #8
e0ae878: 6e697073 mcrvs 0, 3, r7, cr9, cr3, {3}
e0ae87c: 676e696e strbvs r6, [lr, -lr, ror #18]!
e0ae880: 646e6520 strbtvs r6, [lr], #-1312 ; 0xfffffae0
e0ae884: 7373656c cmnvc r3, #108, 10 ; 0x1b000000
e0ae888: 2e2e796c vnmulcs.f16 s14, s28, s25 ; <UNPREDICTABLE>
e0ae88c: 69000a2e stmdbvs r0, {r1, r2, r3, r5, r9, fp}
e0ae890: 322d6370 eorcc r6, sp, #112, 6 ; 0xc0000001
e0ae894: 0032312d eorseq r3, r2, sp, lsr #2
e0ae898: 2d637069 stclcs 0, cr7, [r3, #-420]! ; 0xfffffe5c
e0ae89c: 00332d32 eorseq r2, r3, r2, lsr sp
e0ae8a0: 736d6369 cmnvc sp, #-1543503871 ; 0xa4000001
e0ae8a4: 6f775f67 svcvs 0x00775f67
e0ae8a8: 00716b72 rsbseq r6, r1, r2, ror fp
Четвертая запись, 0e0ad414, представляет собой init_entry для периферийного устройства PPR. По этому адресу мы найдем адрес его init_function (0e0ac7cb) и указатель на структуру устройства (0e0ad474).
0e0ad414 <__init___device_dts_ord_60>:
e0ad414: 0e0ac7cb cdpeq 7, 0, cr12, cr10, cr11, {6}
e0ad418: 0e0ad474 mcreq 4, 0, sp, cr10, cr4, {3}
Как и ожидалось, адрес init_function — это местоположение функции nordic_vpr_launcher_init. Или почти. В адресе, который хранится в init_entry (0e0ac7cb), имеется смещение в один байт от адреса функции запуска (0e0ac7ca). Через мгновение мы увидим, почему:
0e0ac7ca <nordic_vpr_launcher_init>:
e0ac7ca: b510 push {r4, lr}
e0ac7cc: 6844 ldr r4, [r0, #4]
e0ac7ce: 68e2 ldr r2, [r4, #12]
e0ac7d0: b11a cbz r2, e0ac7da <nordic_vpr_launcher_init+0x10>
e0ac7d2: e9d4 0101 ldrd r0, r1, [r4, #4]
e0ac7d6: f000 fd56 bl e0ad286 <memcpy>
e0ac7da: e9d4 3200 ldrd r3, r2, [r4]
e0ac7de: f8c3 2808 str.w r2, [r3, #2056] ; 0x808
e0ac7e2: 2201 movs r2, #1
e0ac7e4: 6823 ldr r3, [r4, #0]
e0ac7e6: 2000 movs r0, #0
e0ac7e8: f8c3 2800 str.w r2, [r3, #2048] ; 0x800
e0ac7ec: bd10 pop {r4, pc
z_sys_init_run_level вызывает do_device_init на периферийном устройстве PPR.
static int do_device_init(const struct init_entry *entry)
{
const struct device *dev = entry->dev;
int rc = 0;
if (entry->init_fn.dev != NULL) {
rc = entry->init_fn.dev(dev);
/* Отметка, что устройство инициализировано. Если инициализация
* не удалась, запись состояния этой ошибки.
*/
if (rc != 0) {
if (rc < 0) {
rc = -rc;
}
if (rc > UINT8_MAX) {
rc = UINT8_MAX;
}
dev->state->init_res = rc;
}
}
dev->state->initialized = true;
if (rc == 0) {
/* Запуск автоматического включения устройства во время выполнения */
(void)pm_device_runtime_auto_enable(dev);
}
return rc;
}
Он загружает адрес init_function и указатель устройства из переданного init_entry в r3 и r4 соответственно (ldrd r3, r4, [r0]). Затем, если функция инициализации устройства не равна NULL, вызывает функцию (blx r3) после перемещения указателя устройства в r0 для передачи ( mov r0, r4).
0e0acd00 <do_device_init>:
e0acd00: b510 push {r4, lr}
e0acd02: e9d0 3400 ldrd r3, r4, [r0]
e0acd06: b933 cbnz r3, e0acd16 <do_device_init+0x16>
e0acd08: 2000 movs r0, #0
e0acd0a: 68e2 ldr r2, [r4, #12]
e0acd0c: 7853 ldrb r3, [r2, #1]
e0acd0e: f043 0301 orr.w r3, r3, #1
e0acd12: 7053 strb r3, [r2, #1]
e0acd14: bd10 pop {r4, pc}
e0acd16: 4620 mov r0, r4
e0acd18: 4798 blx r3
e0acd1a: 2800 cmp r0, #0
e0acd1c: d0f4 beq.n e0acd08 <do_device_init+0x8>
e0acd1e: 2800 cmp r0, #0
e0acd20: bfb8 it lt
e0acd22: 4240 neglt r0, r0
e0acd24: 28ff cmp r0, #255 ; 0xff
e0acd26: bfa8 it ge
e0acd28: 20ff movge r0, #255 ; 0xff
e0acd2a: 68e3 ldr r3, [r4, #12]
e0acd2c: 7018 strb r0, [r3, #0]
e0acd2e: e7ec b.n e0acd0a <do_device_init+0xa>
Использование инструкции blx («Ветвь со ссылкой и обменом») — причина смещения адреса на 1 байт. Cortex-M33 реализует архитектуру Armv8-M, которая использует набор инструкций T32 (ранее Thumb2.) Armv8 поддерживает «взаимодействие» (interworking), которое позволяет динамически переключаться между наборами инструкций A32 и T32, используя для применимых инструкций этого взаимодействия младший бит адреса назначения, указывающий применяемый вызывающей стороной ISA. Однако Armv8-M поддерживает только T32, поэтому младший бит всегда должен быть 1. Справочное руководство по архитектуре Armv8-M содержит следующее описание инструкции blx.
Bit[0] соответствует правилам взаимодействия архитектуры Arm для переключения между наборами инструкций A32 и T32. Однако Armv8-M поддерживает только набор инструкций T32, поэтому bit[0] должен быть равен 1. Если bit[0] равен 0, PE для инструкции по целевому адресу принимает исключение INVSTATE UsageFault.
В разделе device_area бинарного файла процессора приложения имеется структура device для периферийного устройства PPR. Его адрес соответствует второму члену init_entry, который был передан в do_device_init (0e0ad474.)
0e0ad474 <__device_dts_ord_60>:
e0ad474: 0e0ae95e ; <UNDEFINED> instruction: 0x0e0ae95e
e0ad478: 0e0ae5e8 cfsh32eq mvfx14, mvfx10, #-8
e0ad47c: 00000000 andeq r0, r0, r0
e0ad480: 2f011404 svccs 0x00011404
e0ad484: 00000000 andeq r0, r0, r0
Второй элемент device — это указатель на config периферийного устройства PPR (0e0ae5e8).
0e0ae5e8 <config0>:
e0ae5e8: 5f908000 svcpl 0x00908000
e0ae5ec: 2fc00000 svccs 0x00c00000
e0ae5f0: 0e0e4000 cdpeq 0, 0, cr4, cr14, cr0, {0}
e0ae5f4: 0000f800 andeq pc, r0, r0, lsl #16
nordic_vpr_launcher определяет config следующим образом:
struct nordic_vpr_launcher_config {
NRF_VPR_Type *vpr;
uintptr_t exec_addr;
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(source_memory)
uintptr_t src_addr;
size_t size;
#endif
};
В документации по структуре памяти nRF54H20 медленная глобальная оперативная память (RAM3x), из которой выполняется PRR, имеет диапазон адресов от 2fc00000 до 2fc14000. exec_addr соответствует верхней части диапазона 2fc00000. src_addr — это 0e0e4000, который находится в диапазоне адресов MRAM_10 (от 0e000000 до 0e100000< /g10>). MRAM\_10 — энергонезависимая память, используемая для хранения образов прошивки. Средство запуска VPR копирует прошивку PPR из MRAM\_10 в RAM3x, устанавливает счетчик программы PPR на начальный адрес RAM3x, а затем запускает PPR. В дампе образа прошивки PPR (сейчас мы смотрим на инструкции RISC-V) видно, что начальный адрес соответствует ожидаемому символу__start`.
2fc00000 <__start>:
2fc00000: 00001297 auipc t0,0x1
2fc00004: 90028293 addi t0,t0,-1792 # 2fc00900 <_isr_wrapper>
2fc00008: 00328293 addi t0,t0,3
2fc0000c: 30529073 csrw mtvec,t0
2fc00010: 00000297 auipc t0,0x0
2fc00014: 0f028293 addi t0,t0,240 # 2fc00100 <_irq_vector_table>
2fc00018: 30729073 csrw 0x307,t0
2fc0001c: 0a50006f j 2fc008c0 <_vector_end>
Однако перед копированием образ прошивки PPR должен присутствовать в регионе MRAM_10. Это обрабатывается sysbuild на west flash, поскольку порядок flash определен в сгенерированном файле domains.yaml.
default: hello_world
build_dir: zephyr/samples/sysbuild/hello_world/build
domains:
- name: hello_world
build_dir: zephyr/samples/sysbuild/hello_world/build/hello_world
- name: remote
build_dir: zephyr/samples/sysbuild/hello_world/build/remote
flash_order:
- remote
- hello_world
После успешного программирования процессор приложения загрузится, инициализирует и запустит PPR, а затем выведет данные на настроенную консоль, в /dev/ttyACM0.
*** Booting nRF Connect SDK v2.8.0-a2386bfc8401 ***
*** Using Zephyr OS v3.7.99-0bc3393fb112 ***
Hello world from [email protected]/nrf54h20/cpuapp
После запуска процессором приложения PPR загрузится и выведет данные на настроенную консоль, в /dev/ttyACM1.
*** Booting nRF Connect SDK v2.8.0-a2386bfc8401 ***
*** Using Zephyr OS v3.7.99-0bc3393fb112 ***
Hello world from [email protected]/nrf54h20/cpuppr
Приятно видеть применение RISC-V для конкретных операций наряду со знакомыми процессорами Arm, присутствующими во многих микроконтроллерах. С внедрением процессоров VPR во многие новые SoC Nordic становится ясно, что в ближайшие годы мы продолжим наблюдать больше гетерогенных вычислительных ресурсов. Понимание архитектуры системы и взаимодействия между компонентами позволяет более полно использовать возможности этих продуктов. Продолжая изучать процессоры PPR и FLPR VPR, мы увидим, как их можно применять, чтобы расширить функциональность и повысить производительность.
Читайте также:
- Путешествие строки скомпилированного кода
- Зачем писать компилятор Rust на C — личный опыт
- Архитектура виртуальной машины Java: объяснение для начинающих
Читайте нас в Telegram, VK и Дзен
Перевод статьи Daniel Mangum: VPR: Nordic’s First RISC-V Processor





