Разработка цифровых ИС на примере микроконтроллерного ядра SCR1 - прgграммное обеспечение
Для сборки программы bootloader.S может использоваться следующий Makefile:
bootloader.o: bootloader.S # Build object file riscv64-unknown-elf-gcc -march=rv32im_zicsr -mabi=ilp32 -c bootloader.S -o bootloader.o build: bootloader.elf # Target build bootloader.elf: bootloader.o common/ram.ld # Link object to elf riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostartfiles -nodefaultlibs -nolibc -T \ common/ram.ld bootloader.o -o bootloader.elf dump: bootloader.elf riscv64-unknown-elf-objdump -w -x -s -S bootloader.elf > bootloader.dump clean: # Delete all compiled files rm bootloader.elf bootloader.o bootloader.dump -f
В предоставляемых примерах данного курса используется Makefile, который был создан на основе примеров из репозитория SCR1.
~/scr1-sdk-new/sw/my_boot/Makefile
APP += bootloader APP_SRC += bootloader.S COMMON_BASE = common include $(COMMON_BASE)/common.mk
Makefile в директории, содержащий исходные коды программы, подключает основной Makefile common.mk, содержащий нужные правила и цели.
Значения флагов GCC, указываемых при сборке:
- -march - Определяет целевую архитектуру. Позволяет указать доступные компилятору набор инструкций и регистры
- -mabi - Задает используемый ABI
- -O[X] - Уровень оптимизации. X = g, используемый нами по умолчанию, задаёт уровень оптимизации для отладки, при котором выходном файле сохраняются отладочные символы.
Сборка: Linker Script
Скрипт компоновщинка (Linker script) описывает то, как секции входных объектных файлов должны быть отображены в выходной файл.
Рассмотрим скрипт компоновщика, использующийся в нашем проекте:
RAM - название региона памяти; rwx - права на чтение, запись и исполнение; ORIGIN - базовый адрес региона (здесь 0 - 64K = 0xFFFF0000, базовый адрес RAM этой платформы); LENGTH - размер региона в байтах.
В исходном коде на языке ассемблера используется директива .section, разбивающая код на секции. Эти секции необходимо распределить по нужным адресам в памяти устройства.
Обычно секция .text содержит в себе исполняемый код, а секция .data - данные, используемые программой.
Ещё примеры секций:
Рассмотрим секцию, содержащую reset vector и trap vector, которые так же задаются в описании ядра.
Структура загрузчика
- Инициализация целочисленных регистров x1-x31 и CSR регистров;
- Установка вектора прерываний на адрес обработчика прерываний;
- Инициализация отображаемых в память регистров UART и контроллера прерываний;
- Обеспечение вывода отладочных сообщений.
Ядро будет принимать исполняемый код по UART, исполнять его и выводить сообщение о результате исполнения.
Bare-metal драйвера
MMR (memory-mapped registers), регистры, отображаемые в память - представляют собой адреса в памяти, запись и чтение значений из которых позволяет осуществлять управление некоторым устройством посредством работы с памятью.
В нашем случае такими устройствами являются контроллер прерываний IPIC и регистры UART. Для упрощения работы с ними в оригинальном репозитории SCR1 имеются заголовочные файлы, содержащие bare-metal драйвера. В этом проекте данные файлы были упрощены до нескольких определений (#define).
Определение MMR:
#define БАЗОВЫЙ_АДРЕС (адрес) #define ИМЯ_РЕГИСТРА (БАЗОВЫЙ_АДРЕС + смещение)
Bare-metal драйвера: IPIC
Примеры из драйвера IPIC (заголовочный файл ipic.h)
#ifndef IPIC_H #define IPIC_H // IPIC memory map #define PLF_IPIC_MBASE (0xbf0) #define IPIC_CISV (PLF_IPIC_MBASE + 0) #define IPIC_CICSR (PLF_IPIC_MBASE + 1) … // Status #define IPIC_IRQ_PENDING (1 << 0) #define IPIC_IRQ_ENABLE (1 << 1) … #endif