Часть 3: Почти что грузим Linux с SD-карты на RocketChip

    В предыдущей части был реализован более-менее работающий контроллер памяти, а точнее — обёртка над IP Core из Quartus, являющаяся переходником на TileLink. Сегодня же в рубрике «Портируем RocketChip на малоизвестную китайскую плату с Циклоном» вы увидите работающую консоль. Процесс несколько затянулся: я уже было думал, что сейчас по-быстрому запущу Linux, и пойдём дальше, но не тут то было. В этой части предлагаю посмотреть на процесс запуска U-Boot, BBL, и робкие попытки Linux kernel инициализироваться. Но консоль есть — U-Boot-овская, и довольно-таки продвинутая, имеющая многое из того, что вы ожидаете от полноценной консоли.


    В аппаратной части добавится SD-карта, подключённая по интерфейсу SPI, а также UART. В программной части BootROM будет заменён с xip на sdboot и, собственно, добавлены следующие стадии загрузки (на SD-карте).


    Допиливание аппаратной части


    Итак, задача: нужно перейти на «большое» ядро и подключить UART (от Raspberry) и SD-адаптер (использовалась некая платка от Catalex с шестью пинами: GND, VCC, MISO, MOSI, SCK, CS).


    В принципе, всё было довольно просто. Но перед тем, как это осознать, меня немного побросало из стороны в сторону: после предыдущего раза я решил, что снова нужно просто подмешать в System что-то вроде HasPeripheryUART (и в реализацию соответственно), то же для SD-карты — и всё будет готово. Потом я решил посмотреть, а как же оно реализовано в «серьёзном» дизайне. Так, что у нас тут из серьёзного? Arty, видимо, не подходит — остаётся монстр unleahshed.DevKitConfigs. И вдруг обнаружилось, что там повсюду какие-то оверлеи, которые добавляются через параметры по ключам. Я догадываюсь, что это, наверное, очень гибко и конфигурируемо, но мне бы хоть что-то для начала запустить… А у вас нет такого же, только попроще-покостыльнее?.. Тут-то я и наткнулся на vera.iofpga.FPGAChip для ПЛИС Microsemi и тут же растащил на цитаты попробовал сделать свою реализацию по аналогии, благо тут более-менее вся «разводка системной платы» в одном файле.


    Оказалось, действительно, нужно просто добавить в System.scala строчки


    class System(implicit p: Parameters) extends RocketSubsystem
    ...
      with HasPeripherySPI
      with HasPeripheryUART
    ...
    {
      val tlclock = new FixedClockResource("tlclk", p(DevKitFPGAFrequencyKey))
      ...
    }
    
    class SystemModule[+L <: System](_outer: L)
      extends RocketSubsystemModuleImp(_outer)
    ...
        with HasPeripheryUARTModuleImp
        with HasPeripheryGPIOModuleImp
    ...

    Строчка в теле класса System добавляет информацию о частоте, на которой работает эта часть нашего SoC, в dts-файл. Насколько я понимаю, DTS/DTB — это такой статичный аналог технологии plug-and-play для встраиваемых устройств: дерево dts-описания компилируется в бинарный dtb-файл и передаётся загрузчиком ядру, чтобы оно могло правильно настроить аппаратуру. Что интересно, без строчки с tlclock всё прекрасно синтезируется, но скомпилировать BootROM (напомню, теперь это будет уже sdboot) не получится — в процессе компиляции он парсит dts-файл и создаёт хедер с макросом TL_CLK, благодаря которому он сможет корректно настроить делители частоты для внешних интерфейсов.


    Также потребуется немного поправить «разводку»:


    Platform.scala:


    class PlatformIO(implicit val p: Parameters) extends Bundle {
    
    ...
    
      // UART
      io.uart_tx := sys.uart(0).txd
      sys.uart(0).rxd := RegNext(RegNext(io.uart_rx))
    
      // SD card
      io.sd_cs := sys.spi(0).cs(0)
      io.sd_sck := sys.spi(0).sck
      io.sd_mosi := sys.spi(0).dq(0).o
      sys.spi(0).dq(0).i := false.B
      sys.spi(0).dq(1).i := RegNext(RegNext(io.sd_miso))
      sys.spi(0).dq(2).i := false.B
      sys.spi(0).dq(3).i := false.B
    }

    Цепочки регистров, честно говоря, добавлены просто по аналогии с некоторыми другими местами изначального кода. Скорее всего, они должны защищать от метастабильности. Возможно, в некоторых блоках уже есть своя защита, но для начала хочется запустить хотя бы «на качественном уровне». Более интересный для меня вопрос — почему MISO и MOSI висят на разных dq? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.


    Физически, я просто назначил выводы дизайна на свободные контакты на колодке и переставил джампер выбора напряжения в 3.3V.


    SD-адаптер

    Вид сверху:



    Вид снизу:



    Отладка программной части: инструменты


    Для начала поговорим об имеющихся инструментах отладки и их ограничениях.


    Minicom


    Во-первых, нам будет нужно как-то читать то, что выводит загрузчик и ядро. Для этого на Linux (в данном случае — на том, что на RaspberryPi) нам потребуется программа Minicom. Вообще говоря, подойдёт любая программа для работы с последовательны портом.


    Обратите внимание, что при запуске имя устройства порта нужно указывать как -D /dev/ttyS0 — после опции -D. Ну и главная информация: для выхода используйте Ctrl-A, X. У меня правда был случай, когда эта комбинация не сработала — тогда можно из соседнего сеанса SSH просто сказать killall -KILL minicom.


    Есть и ещё одна особенность. Конкретно на RaspberryPi есть два UART, и оба порта могут быть уже для чего-то приспособлены: один для Bluetooth, через другой по умолчанию выводится консоль ядра. К счастью, это поведение можно перенастроить по этому мануалу.


    Переписывание памяти


    При отладке, для проверки гипотезы мне иногда приходилось загрузить загрузчик (извините) в оперативную память непосредственно с хоста. Может, это можно сделать прямо из GDB, но я в итоге пошёл по простому пути: скопировал на Raspberry необходимый файл, пробросил через SSH также порт 4444 (telnet от OpenOCD) и воспользовался командой load_image. Когда вы её выполняете, кажется что всё зависло, но на самом деле «оно не спит, оно просто медленно моргает»: оно грузит файл, просто делает это со скорость пару килобайт в секунду.


    Особенности установки breakpoint-ов


    Вероятно, многим об этом не приходилось задумываться при отладке обычных программ, но точки останова не всегда ставятся аппаратно. Иногда постановка breakpoint-а заключается во временном записывании специальной инструкции в нужное место прямо в машинный код. Например, так у меня действовала стандартная команда b в GDB. Вот, что из этого следует:


    • нельзя поставить точку внутри BootROM, потому что ROM
    • поставить точку останова на код, загруженный в оперативку с SD-карты, можно, но нужно дождаться, когда он будет загружен. В противном случае не мы перепишем кусочек кода, а загрузчик перепишет наш breakpoint

    Уверен, можно явно попросить использовать аппаратные точки останова, но их в любом случае ограниченное число.


    Быстрая подмена BootROM


    На начальном этапе отладки нередко возникает желание поправить BootROM и попробовать ещё разок. Но есть проблема: BootROM является частью дизайна, загружаемого в ПЛИС, а его синтез — дело нескольких минут (и это-то после почти мгновенной компиляции самого образа BootROM из C и Assembler...). К счастью, на самом деле всё намного быстрее: последовательность действий такая:


    • перегенерировать bootrom.mif (я перешёл на MIF вместо HEX, потому что с HEX у меня вечно были какие-то проблемы, а MIF — родной Альтеровский формат)
    • в Quartus сказать Processing -> Update Memory Initialization File
    • на пункте Assembler (в левой колонке Tasks) скомандовать Start again

    На всё про всё — пара десятков секунд.


    Подготовка SD-карты


    Тут всё относительно просто, но нужно запастись терпением и около 14Gb места на диске:


    git clone https://github.com/sifive/freedom-u-sdk
    git submodule update --recursive --init
    make

    После чего нужно вставить чистую, а точнее, не содержащую ничего нужного, SD-карту, и выполнить


    sudo make DISK=/dev/sdX format-boot-loader

    … где sdX — устройство, назначенное карте. ВНИМАНИЕ: данные на карте будут удалены, перезаписаны и вообще! Вряд ли стоит делать всю сборку из-под sudo, потому что тогда все артефакты сборки будут принадлежать root, и сборку придётся делать из-под sudo постоянно.


    В итоге получается карточка, размеченная в GPT с четырьмя разделами, на одном из которых FAT с uEnv.txt и загружаемым образом в формате FIT (он содержит несколько подобразов, каждый со своим адресом загрузки), другой раздел — чистый, его предполагается отформатировать в Ext4 для Линукса. Ещё два раздела — загадочные: на одном живёт U-Boot (его смещение, насколько я понимаю, зашито в BootROM), на другом, похоже, живут его переменные окружения, но я их пока не использую.


    Уровень первый, BootROM


    Народная мудрость гласит: «Если в программировании бывают пляски с бубном, то в электронике — ещё и с огнетушителем». Речь даже не о том, что один раз я чуть не спалил плату, решив, что «Ну GND — это же тот же низкий уровень» (видимо, резистор всё-таки не помешал бы...) Речь скорее о том, что если руки растут не оттуда, то электроника не перестаёт приносить сюрпризы: припаивая разъём на плату, я так и не сумел нормально пропаять контакты — на видео показывают, как припой прямо сам растекается по всему соединению, только паяльник приложи, у меня же он «нашлёпывался» как попало. Ну, может, припой не подходил для температуры паяльника, может, ещё что… В общем, увидев, что десяток контактов у меня уже есть, я плюнул, и начал отлаживать. И тут началось загадочное: подключил RX/TX от UART-а, загружаю прошивку — оно пишет


    INIT
    CMD0
    ERROR

    Ну, всё логично — модуль SD-карты я не подключил. Исправляем ситуацию, грузим прошивку… И тишина… Чего я только не передумал, а ларчик-то просто открывался: один из выводов модуля нужно было подключить на VCC. В моём случае модуль поддерживал 5V для питания, поэтому я, недолго думая, воткнул провод, тянувшийся от модуля, на противоположную сторону платы. В итоге криво пропаянный разъём перекосился, и просто потерялся контакт UART. facepalm.jpg В общем, «дурная голова ногам покоя не даёт», а кривые руки — голове...


    В итоге я увидил в Minicom долгожданное


    INIT
    CMD0
    CMD8
    ACMD41
    CMD58
    CMD16
    CMD18
    LOADING /

    Более того, оно шевелится крутится индикатор загрузки. Прямо вспоминаются школьные годы и неспешная загрузка MinuetOS с дискеты. Разве что дисковод не скрежещет.


    Проблема в том, что после сообщения BOOT не происходит ничего. Значит, самое время подключиться через OpenOCD на Raspberry, к нему GDB на хосте, и посмотреть, что же это такое.


    Во-первых, подключение с помощью GDB тут же показало, что $pc (program counter, адрес текущей инструкции) улетает в 0x0 — вероятно, это происходит после множественной ошибки. Поэтому, сразу после выдачи сообщения BOOT добавим бесконечный цикл. Это его ненадолго задержит...


    diff --git a/bootrom/sdboot/sd.c b/bootrom/sdboot/sd.c
    index c6b5ede..bca1b7f 100644
    --- a/bootrom/sdboot/sd.c
    +++ b/bootrom/sdboot/sd.c
    @@ -224,6 +224,8 @@ int main(void)
    
            kputs("BOOT");
    
    +    while(*(volatile char *)0x10000){}
    +
            __asm__ __volatile__ ("fence.i" : : : "memory");
            return 0;
     }

    Такой хитрый код используется «для надёжности»: я где-то слышал, что, вроде бы, бесконечный цикл — это Undefined Behavior, а тут компилятор вряд ли догадается (Напоминаю, что по 0x10000 находится BootROM).


    Отладчик есть, исходников нет


    Казалось бы, а что ещё ожидать — суровый embedded, какие уж тут исходники. Но ведь в той статье автор отлаживал сишный код… Крекс-фекс-пекс:


    (gdb) file builds/zeowaa-e115/sdboot.elf
    A program is being debugged already.
    Are you sure you want to change the file? (y or n) y
    Reading symbols from builds/zeowaa-e115/sdboot.elf...done.

    Есть исходники!


    Только нужно грузить не MIF-файл и не bin, а оригинальную версию в формате ELF.


    Теперь можно с энной попытки угадать адрес, где выполнение продолжится (это ещё одна причина, почему компилятор не должен был догадаться, что цикл — бесконечный). Команда


    set variable $pc=0xADDR

    позволяет поменять значение регистра на ходу (в данном случае — адрес текущей инструкции). С её же помощью можно менять значения, записанные в память (и memory-mapped регистры).


    В конечном итоге я пришёл к выводу (не уверен, что правильному), что у нас «образ sd-карты не той системы», и переходить нужно не на самое начало загруженных данных, а на 0x89800 байтов дальше:


    diff --git a/bootrom/sdboot/head.S b/bootrom/sdboot/head.S
    index 14fa740..2a6c944 100644
    --- a/bootrom/sdboot/head.S
    +++ b/bootrom/sdboot/head.S
    @@ -13,7 +13,7 @@ _prog_start:
       smp_resume(s1, s2)
       csrr a0, mhartid
       la a1, dtb
    -  li s1, PAYLOAD_DEST
    +  li s1, (PAYLOAD_DEST + 0x89800)
       jr s1
    
       .section .rodata

    Возможно, на этом также сказалось то, что не имея под рукой ненужной карты на 4Gb, я взял на 2Gb и методом тыка заменил в Makefile DEMO_END=11718750 на DEMO_END=3078900 (не ищите смысл в конкретном значении — его нет, просто теперь образ помещается на карточку).


    Уровень второй, U-Boot


    Теперь мы всё ещё «падаем», но оказываемся уже по адресу 0x0000000080089a84. Тут я вынужден признаться: на самом деле, изложение идёт не «со всеми остановками», а частично пишется уже «опосля», поэтому здесь я уже успел подложить правильный dtb-файл от нашего SoC, поправить в настройках HiFive_U-Boot переменную CONFIG_SYS_TEXT_BASE=0x80089800 (вместо 0x08000000), чтобы адрес загрузки совпадал с фактическим. Загружаем теперь уже карту следующего уровня другой образ:


    (gdb) file ../freedom-u-sdk/work/HiFive_U-Boot/u-boot
    (gdb) tui en

    И видим:


       │304     /*                                               │
       │305      * trap entry                                    │
       │306      */                                              │
       │307     trap_entry:                                      │
       │308         addi sp, sp, -32*REGBYTES                    │
      >│309         SREG x1, 1*REGBYTES(sp)                      │
       │310         SREG x2, 2*REGBYTES(sp)                      │
       │311         SREG x3, 3*REGBYTES(sp)                      │

    Причём мы прыгаем между строчками 308 и 309. И неудивительно, учитывая, что в $sp лежит значение 0xfffffffe31cdc0a0. Увы, оно ещё и постоянно «убегает» из-за строчки 307. Поэтому попробуем поставить точку останова на trap_entry, а потом снова перейти на 0x80089800 (точку входа U-Boot), и будем надеяться, что оно не требует правильного выставления регистров перед переходом… Похоже, работает:


    (gdb) b trap_entry
    Breakpoint 1 at 0x80089a80: file /hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot/arch/riscv/cpu/HiFive/start.S, line 308.
    (gdb) set variable $pc=0x80089800
    (gdb) c
    Continuing.
    
    Breakpoint 1, trap_entry () at /hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot/arch/riscv/cpu/HiFive/start.S:308
    (gdb) p/x $sp
    $4 = 0x81cf950

    Так себе указатель стека, прямо скажем: указывает вообще мимо оперативки (если, конечно, у нас ещё нет трансляции адресов, но будем надеяться на простой вариант).


    Попробуем заменить указатель на 0x881cf950. В итоге приходим к тому, что handle_trap вызывается и вызывается, при этом уходим в _exit_trap с аргументом epc=2148315240 (в десятичном виде):


    (gdb) x/10i 2148315240
       0x800cb068 <strnlen+12>:     lbu     a4,0(a5)
       0x800cb06c <strnlen+16>:     bnez    a4,0x800cb078 <strnlen+28>
       0x800cb070 <strnlen+20>:     sub     a0,a5,a0
       0x800cb074 <strnlen+24>:     ret
       0x800cb078 <strnlen+28>:     addi    a5,a5,1
       0x800cb07c <strnlen+32>:     j       0x800cb064 <strnlen+8>
       0x800cb080 <strdup>: addi    sp,sp,-32
       0x800cb084 <strdup+4>:       sd      s0,16(sp)
       0x800cb088 <strdup+8>:       sd      ra,24(sp)
       0x800cb08c <strdup+12>:      li      s0,0

    Ставим breakpoint на strnlen, продолжаем и видим:


    (gdb) bt
    #0  strnlen (s=s@entry=0x10060000 "", count=18446744073709551615) at lib/string.c:283
    #1  0x00000000800cc14c in string (buf=buf@entry=0x881cbd4c "", end=end@entry=0x881cc15c "", s=0x10060000 "", field_width=<optimized out>, precision=<optimized out>, flags=<optimized out>) at lib/vsprintf.c:265
    #2  0x00000000800cc63c in vsnprintf_internal (buf=buf@entry=0x881cbd38 "exception code: 5 , ", size=size@entry=1060, fmt=0x800d446e "s , epc %08x , ra %08lx\n", fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lx\n", args=0x881cc1a0,
        args@entry=0x881cc188) at lib/vsprintf.c:619
    #3  0x00000000800cca54 in vsnprintf (buf=buf@entry=0x881cbd38 "exception code: 5 , ", size=size@entry=1060, fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lx\n", args=args@entry=0x881cc188) at lib/vsprintf.c:710
    #4  0x00000000800cca68 in vscnprintf (buf=buf@entry=0x881cbd38 "exception code: 5 , ", size=size@entry=1060, fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lx\n", args=args@entry=0x881cc188) at lib/vsprintf.c:717
    #5  0x00000000800ccb50 in printf (fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lx\n") at lib/vsprintf.c:792
    #6  0x000000008008a9f0 in _exit_trap (regs=<optimized out>, epc=2148315240, code=<optimized out>) at arch/riscv/lib/interrupts.c:92
    #7  handle_trap (mcause=<optimized out>, epc=<optimized out>, regs=<optimized out>) at arch/riscv/lib/interrupts.c:55
    #8  0x0000000080089b10 in trap_entry () at /hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot/arch/riscv/cpu/HiFive/start.S:343
    Backtrace stopped: frame did not save the PC

    Похоже, _exit_trap хочет выдать отладочную информацию про произошедшее исключение, но у него не получается. Так, что-то у нас исходники опять не отображаются. set directories ../freedom-u-sdk/HiFive_U-Boot/ О! Теперь отображаются!


    Что же, запустим ещё раз, и увидим по стек-трейсу причину исходной проблемы, вызвавшей первую ошибку (mcause == 5). Если я правильно понял, что написано здесь на стр. 37, то это исключение означает Load access fault. Причина, по-видимому, в том, что вот здесь


    arch/riscv/cpu/HiFive/start.S:


    call_board_init_f:
        li  t0, -16
        li  t1, CONFIG_SYS_INIT_SP_ADDR
        and sp, t1, t0  /* force 16 byte alignment */
    
    #ifdef CONFIG_DEBUG_UART
        jal debug_uart_init
    #endif
    
    call_board_init_f_0:
        mv  a0, sp
        jal board_init_f_alloc_reserve
        mv  sp, a0
        jal board_init_f_init_reserve
    
        mv  a0, zero    /* a0 <-- boot_flags = 0 */
        la t5, board_init_f
        jr t5       /* jump to board_init_f() */
    

    $sp имеет то самое некорректное значение, и внутри board_init_f_init_reserve возникает ошибка. Похоже, вот и виновник: переменная с недвусмысленным названием CONFIG_SYS_INIT_SP_ADDR. Она определена в файле HiFive_U-Boot/include/configs/HiFive-U540.h. В какой-то момент я даже подумал, а может, ну его, допиливать загрузчик под процессор — может, легче чуть поправить процессор? Но потом я увидел, что это больше похоже на артефакт от не до конца за-#if 0-енных настроек под другую конфигурацию памяти, и можно попробовать сделать так:


    diff --git a/include/configs/HiFive-U540.h b/include/configs/HiFive-U540.h
    index ca89383..245542c 100644
    --- a/include/configs/HiFive-U540.h
    +++ b/include/configs/HiFive-U540.h
    @@ -65,12 +65,9 @@
     #define CONFIG_SYS_SDRAM_BASE  PHYS_SDRAM_0
     #endif
     #if 1
    -/*#define CONFIG_NR_DRAM_BANKS 1*/
    +#define CONFIG_NR_DRAM_BANKS   1
     #define PHYS_SDRAM_0   0x80000000              /* SDRAM Bank #1 */
    -#define PHYS_SDRAM_1   \
    -       (PHYS_SDRAM_0 + PHYS_SDRAM_0_SIZE)      /* SDRAM Bank #2 */
    -#define PHYS_SDRAM_0_SIZE      0x80000000      /* 2 GB */
    -#define PHYS_SDRAM_1_SIZE      0x10000000      /* 256 MB */
    +#define PHYS_SDRAM_0_SIZE      0x40000000      /* 1 GB */
     #define CONFIG_SYS_SDRAM_BASE  PHYS_SDRAM_0
     #endif
     /*
    @@ -81,7 +78,7 @@
     #define CONSOLE_ARG                            "console=ttyS0,115200\0"
    
     /* Init Stack Pointer */
    -#define CONFIG_SYS_INIT_SP_ADDR                (0x08000000 + 0x001D0000 - \
    +#define CONFIG_SYS_INIT_SP_ADDR                (0x80000000 + 0x001D0000 - \
                                            GENERATED_GBL_DATA_SIZE)
    
     #define CONFIG_SYS_LOAD_ADDR           0xa0000000      /* partway up SDRAM */

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


    Ну, приблизительно, вот столечко
    trosinenko@trosinenko-pc:/hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot$ git show --name-status
    commit 39cd67d59c16ac87b46b51ac1fb58f16f1eb1048 (HEAD -> zeowaa-1gb)
    Author: Anatoly Trosinenko <anatoly.trosinenko@gmail.com>
    Date:   Tue Jul 2 17:13:16 2019 +0300
    
        Initial support for Zeowaa A-E115FB board
    
    M       arch/riscv/Kconfig
    A       arch/riscv/cpu/zeowaa-1gb/Makefile
    A       arch/riscv/cpu/zeowaa-1gb/cpu.c
    A       arch/riscv/cpu/zeowaa-1gb/start.S
    A       arch/riscv/cpu/zeowaa-1gb/timer.c
    A       arch/riscv/cpu/zeowaa-1gb/u-boot.lds
    M       arch/riscv/dts/Makefile
    A       arch/riscv/dts/zeowaa-1gb.dts
    A       board/Zeowaa/zeowaa-1gb/Kconfig
    A       board/Zeowaa/zeowaa-1gb/MAINTAINERS
    A       board/Zeowaa/zeowaa-1gb/Makefile
    A       board/Zeowaa/zeowaa-1gb/Zeowaa-A-E115FB.c
    A       configs/zeowaa-1gb_defconfig
    A       include/configs/zeowaa-1gb.h

    Подробности можно посмотреть в репозитории.


    Как оказалось, на этой SiFive-овской плате регистры некоторых устройств имеют другие адреса. А ещё оказалось, что U-Boot конфигурируется уже знакомым по ядру Linux механизмом Kconfig — например, можно скомандовать make menuconfig, и перед вами появится удобный текстовый интерфейс с показом описаний параметров по ? и т.д. В общем, слепив из описаний двух плат описание третьей, выкинув оттуда всякие пафосные перенастройки PLL (видимо, это как-то связано с управлением с хостового компьютера по PCIe, но это не точно), я получил некоторую прошивку, которая при правильной погоде на Марсе выдавала мне по UART сообщение о том, из какого хеша коммита она собрана, и о том, сколько у меня DRAM (но эту информацию я сам же в хедере и прописал).


    Жаль только, что после этого плата обычно переставала отвечать по процессорному JTAG, а загрузка с SD-карты — дело, увы, в моей конфигурации не быстрое. С другой стороны, иногда BootROM выдавал сообщение, что ERROR, не удалось загрузиться, и тут же выскакивал U-Boot. Тут-то до меня и дошло: видимо, после перезагрузки bitstream в ПЛИС память не перетирается, не успевает «растренироваться» и т.д. Короче, можно просто при появлении сообщения LOADING / подключаться отладчиком и командовать set variable $pc=0x80089800, минуя тем самым эту долгую загрузку (конечно, в предположении, что оно в прошлый раз сломалось достаточно рано, и не успело поверх оригинального кода что-то загрузить).


    Кстати, а это вообще нормально, что процессор напрочь виснет, и к нему не может подключиться JTAG-отладчик с сообщениями


    Error: unable to halt hart 0
    Error:   dmcontrol=0x80000001
    Error:   dmstatus =0x00030c82

    Так, постойте! Я это уже видел! Что-то подобное происходит при дедлоке TileLink, а автору контроллера памяти я как-то не доверяю — сам же писал… Внезапно, после первой же удачной пересборки процессора после редактирования контроллера я увидел:


    INIT
    CMD0
    CMD8
    ACMD41
    CMD58
    CMD16
    CMD18
    LOADING
    BOOT
    
    U-Boot 2018.09-g39cd67d-dirty (Jul 03 2019 - 13:50:33 +0300)
    
    DRAM:  1 GiB
    MMC:
    BEFORE LOAD ENVBEFORE FDTCONTROLADDRBEFORE LOADADDRIn:    serial
    Out:   serial
    Err:   serial
    Hit any key to stop autoboot:  3

    На эту странную строчку перед In: serial не обращайте внимания — это я пытался на виснущем процессоре понять, корректно ли оно работает с environment. Что значит, «Уже десять минут так висит»? Оно хотя бы сумело релоцироваться и перейти к загрузочному меню! Небольшое отступление: хоть U-Boot и грузится в числе первых 2^24 байт с SD-карты, запустившись, он копирует себя куда подальше по адресу, то ли записанному в конфигурационном хедере, то ли просто в старшие адреса оперативной памяти, производит релокацию ELF-символов, и передаёт туда управление. Так вот: похоже, этот уровень прошли и бонусом получили процессор, не виснущий намертво после этого.


    Итак, почему не работает таймер? Похоже, часы в принципе почему-то не идут...


    (gdb) x/x 0x0200bff8
    0x200bff8:      0x00000000

    А что, если стрелки вручную покрутить?


    (gdb) set variable *0x0200bff8=310000000
    (gdb) c

    Тогда:


    Hit any key to stop autoboot:  0
    MMC_SPI: 0 at 0:1 hz 20000000 mode 0

    Вывод: часы не идут. Вероятно, из-за этого же и не работает ввод с клавиатуры:


    HiFive_U-Boot/cmd/bootmenu.c:


    static void bootmenu_loop(struct bootmenu_data *menu,
            enum bootmenu_key *key, int *esc)
    {
        int c;
    
        while (!tstc()) {
            WATCHDOG_RESET();
            mdelay(10);
        }
    
        c = getc();
    
        switch (*esc) {
        case 0:
            /* First char of ANSI escape sequence '\e' */
            if (c == '\e') {
                *esc = 1;
                *key = KEY_NONE;
            }
            break;
        case 1:
            /* Second char of ANSI '[' */
            if (c == '[') {
    ...

    Проблема оказалась в том, что я малость перемудрил: я добавил в конфиг процессора ключ:


      case DTSTimebase => BigInt(0)

    … ориентируясь на то, что в комментарии было сказано «если не знаете — оставьте 0». И ведь WithNBigCores как раз проставляло его в 1MHz (как, кстати, и было указано в конфиге U-Boot). Но я же, блин, аккуратный и дотошный: там я не знаю, тут 25MHz! В итоге ничего не работает. Убрал свои «улучшения» и...


    Hit any key to stop autoboot:  0
    MMC_SPI: 0 at 0:1 hz 20000000 mode 0
    ## Unknown partition table type 0
    libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
    ** No partition table - mmc 0 **
    ## Info: input data size = 34 = 0x22
    Running uEnv.txt boot2...
    ## Error: "boot2" not defined
    HiFive-Unleashed #

    Можно даже вводить команды! Например, немного поковырявшись, можно, наконец, догадаться ввести mmc_spi 1 10000000 0; mmc part, уменьшив частоту SPI с 20MHz до 10MHz. Почему? Ну, в конфиге была написана максимальная частота 20MHz, она же там и сейчас написана. Но, насколько я понял, интерфейсы, по крайней мере здесь, работают так: код делит частоту аппаратного блока (у меня — везде 25MHz) на целевую, и выставляет получившееся значение в качестве делителя в соответствующий управляющий регистр. Проблема в том, что если для 115200Hz UART-а будет приблизительно то, что нужно, то если нацело поделить 25000000 на 20000000 получится 1, т.е. работать оно будет на 25MHz. Может, это и нормально, но если ограничения выставляют, значит, это кому-нибудь нужно (но это не точно)… В общем, легче проставить и пойти дальше — далеко и, увы, надолго. 25MHz — это вам не Core i9.


    Вывод консоли
    HiFive-Unleashed # env edit mmcsetup
    edit: mmc_spi 1 10000000 0; mmc part
    HiFive-Unleashed # boot
    MMC_SPI: 1 at 0:1 hz 10000000 mode 0
    
    Partition Map for MMC device 0  --   Partition Type: EFI
    
    Part    Start LBA       End LBA         Name
            Attributes
            Type GUID
            Partition GUID
      1     0x00000800      0x0000ffde      "Vfat Boot"
            attrs:  0x0000000000000000
            type:   ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
            type:   data
            guid:   76bd71fd-1694-4ff3-8197-bfa81699c2fb
      2     0x00040800      0x002efaf4      "root"
            attrs:  0x0000000000000000
            type:   0fc63daf-8483-4772-8e79-3d69d8477de4
            type:   linux
            guid:   9f3adcc5-440c-4772-b7b7-283124f38bf3
      3     0x0000044c      0x000007e4      "uboot"
            attrs:  0x0000000000000000
            type:   5b193300-fc78-40cd-8002-e86c45580b47
            guid:   bb349257-0694-4e0f-9932-c801b4d76fa3
      4     0x00000400      0x0000044b      "uboot-env"
            attrs:  0x0000000000000000
            type:   a09354ac-cd63-11e8-9aff-70b3d592f0fa
            guid:   4db442d0-2109-435f-b858-be69629e7dbf
    libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
    2376 bytes read in 0 ms
    Running uEnv.txt boot2...
    15332118 bytes read in 0 ms
    ## Loading kernel from FIT Image at 90000000 ...
       Using 'config-1' configuration
       Trying 'bbl' kernel subimage
         Description:  BBL/SBI/riscv-pk
         Type:         Kernel Image
         Compression:  uncompressed
         Data Start:   0x900000d4
         Data Size:    74266 Bytes = 72.5 KiB
         Architecture: RISC-V
         OS:           Linux
         Load Address: 0x80000000
         Entry Point:  0x80000000
         Hash algo:    sha256
         Hash value:   28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
       Verifying Hash Integrity ... sha256+ OK
    ## Loading fdt from FIT Image at 90000000 ...
       Using 'config-1' configuration
       Trying 'fdt' fdt subimage
         Description:  unavailable
         Type:         Flat Device Tree
         Compression:  uncompressed
         Data Start:   0x90e9d31c
         Data Size:    6911 Bytes = 6.7 KiB
         Architecture: RISC-V
         Load Address: 0x81f00000
         Hash algo:    sha256
         Hash value:   10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
       Verifying Hash Integrity ... sha256+ OK
       Loading fdt from 0x90e9d31c to 0x81f00000
       Booting using the fdt blob at 0x81f00000
    ## Loading loadables from FIT Image at 90000000 ...
       Trying 'kernel' loadables subimage
         Description:  Linux kernel
         Type:         Kernel Image
         Compression:  uncompressed
         Data Start:   0x900123e8
         Data Size:    10781356 Bytes = 10.3 MiB
         Architecture: RISC-V
         OS:           Linux
         Load Address: 0x80200000
         Entry Point:  unavailable
         Hash algo:    sha256
         Hash value:   72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
       Verifying Hash Integrity ... sha256+ OK
       Loading loadables from 0x900123e8 to 0x80200000
    ## Loading loadables from FIT Image at 90000000 ...
       Trying 'ramdisk' loadables subimage
         Description:  buildroot initramfs
         Type:         RAMDisk Image
         Compression:  gzip compressed
         Data Start:   0x90a5a780
         Data Size:    4467411 Bytes = 4.3 MiB
         Architecture: RISC-V
         OS:           Linux
         Load Address: 0x82000000
         Entry Point:  unavailable
         Hash algo:    sha256
         Hash value:   883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
       Verifying Hash Integrity ... sha256+ OK
       Loading loadables from 0x90a5a780 to 0x82000000

    Окей, мы прошли на новый уровень, но оно всё ещё зависает. А иногда ещё и сыплет эксепшенами. Увидеть mcause можно, подкараулив код по указанному адресу $pc и после si оказаться на trap_entry. Сам обработчик из U-Boot умеет выводить только для mcause = 0..4, поэтому готовьтесь зациклиться на некорректной загрузке. Тут я полез в конфиг, стал смотреть, что же я менял, и вспомнил: там же в conf/rvboot-fit.txt написано:


    fitfile=image.fit
    # below much match what's in FIT (ugha)

    Что же, приведём все файлы в соответствие, заменим командную строку ядра приблизительно так, поскольку есть подозрения, что SIF0 — это вывод куда-то по PCIe:


    -bootargs=console=ttySIF0,921600 debug
    +bootargs=console=ttyS0,125200 debug

    И до кучи поменяем алгоритм хеширования с SHA-256 на MD5: криптостойкости мне не нужно (тем более, при отладке), считается оно жутко долго, а для отлова ошибок целостности при загрузке и MD5 — за глаза. Что же в итоге? Проходить предыдущий уровень мы стали заметно быстрее (за счёт более простого хеширования), и открылся следующий:


    ...
       Verifying Hash Integrity ... md5+ OK
       Loading loadables from 0x90a5a758 to 0x82000000
    libfdt fdt_check_header(): FDT_ERR_BADMAGIC
    chosen {
            linux,initrd-end = <0x00000000 0x83000000>;
            linux,initrd-start = <0x00000000 0x82000000>;
            riscv,kernel-end = <0x00000000 0x80a00000>;
            riscv,kernel-start = <0x00000000 0x80200000>;
            bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
    };
    libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
    chosen {
            linux,initrd-end = <0x00000000 0x83000000>;
            linux,initrd-start = <0x00000000 0x82000000>;
            riscv,kernel-end = <0x00000000 0x80a00000>;
            riscv,kernel-start = <0x00000000 0x80200000>;
            bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
    };
       Loading Kernel Image ... OK
    Booting kernel in
    3

    Вот только часы не тикают...


    (gdb) x/x 0x0200bff8
    0x200bff8:      0x00000000

    Упс, похоже, исправление хода часов оказалось плацебо, хотя мне тогда и показалось, что помогло. Нет, починить, конечно надо, но давайте для начала покрутим стрелки вручную и посмотрим, что получится:


    0x00000000bff6dbb0 in ?? ()
    (gdb) set variable *0x0200bff8=1000000
    (gdb) c
    Continuing.
    ^C
    Program received signal SIGINT, Interrupt.
    0x00000000bff6dbb0 in ?? ()
    (gdb) set variable *0x0200bff8=2000000
    (gdb) c
    Continuing.
    ^C
    Program received signal SIGINT, Interrupt.
    0x00000000bff6dbb0 in ?? ()
    (gdb) set variable *0x0200bff8=3000000
    (gdb) c
    Continuing.

    Тем временем...


       Loading Kernel Image ... OK
    Booting kernel in
    3
    2
    1
    0
    ## Starting application at 0x80000000 ...

    Нет уж, пойду автоматизировать ход часов — а то, может, он там таймер калибровать вздумает!


    А адрес текущей инструкции тем временем указывает куда-то в


    0000000080001c20 <poweroff>:
        80001c20:   1141                    addi    sp,sp,-16
        80001c22:   e022                    sd      s0,0(sp)
        80001c24:   842a                    mv      s0,a0
        80001c26:   00005517                auipc   a0,0x5
        80001c2a:   0ca50513                addi    a0,a0,202 # 80006cf0 <softfloat_countLeadingZeros8+0x558>
        80001c2e:   e406                    sd      ra,8(sp)
        80001c30:   f7fff0ef                jal     ra,80001bae <printm>
        80001c34:   8522                    mv      a0,s0
        80001c36:   267000ef                jal     ra,8000269c <finisher_exit>
        80001c3a:   00010797                auipc   a5,0x10
        80001c3e:   41e78793                addi    a5,a5,1054 # 80012058 <htif>
        80001c42:   639c                    ld      a5,0(a5)
        80001c44:   c399                    beqz    a5,80001c4a <poweroff+0x2a>
        80001c46:   72c000ef                jal     ra,80002372 <htif_poweroff>
        80001c4a:   45a1                    li      a1,8
        80001c4c:   4501                    li      a0,0
        80001c4e:   dc7ff0ef                jal     ra,80001a14 <send_ipi_many>
        80001c52:   10500073                wfi
        80001c56:   bff5                    j       80001c52 <poweroff+0x32>

    внутри загрузившегося Berkeley Boot Loader. Лично меня в этом смущает упоминание htif — host interface, используемого для tethered-запуска ядра (то есть в кооперации с хостовым ARM), я-то предполагал standalone. Впрочем, если найти эту функцию в исходниках, то видно, что не всё так плохо:


    void poweroff(uint16_t code)
    {
      printm("Power off\r\n");
      finisher_exit(code);
      if (htif) {
        htif_poweroff();
      } else {
        send_ipi_many(0, IPI_HALT);
        while (1) { asm volatile ("wfi\n"); }
      }
    }

    Квест: запусти часы


    Поиск регистров в CLINT выводит нас к


        val io = IO(new Bundle {
          val rtcTick = Bool(INPUT)
        })
    
        val time = RegInit(UInt(0, width = timeWidth))
        when (io.rtcTick) { time := time + UInt(1) }

    Который подключается в RTC, либо в загадочном MockAON, про который я изначально рассудил: «Так, что это у нас тут? Непонятно? Отключаем!» Поскольку мне до сих пор непонятно, что это за тактовая магия там творится, поэтому просто перереализую эту логику в System.scala:


      val rtcDivider = RegInit(0.asUInt(16.W)) // на всякий случай поддержу до 16ГГц, я оптимист :)
      val mhzInt = p(DevKitFPGAFrequencyKey).toInt
      // Преположим, частота равна целому числу мегагерц
      rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
      outer.clintOpt.foreach { clint =>
        clint.module.io.rtcTick := rtcDivider === 0.U
      }

    Пробираясь к Linux kernel


    Тут повествование уже и без того затянулось и стало малость однообразным, поэтому опишу по верхам:


    BBL предполагал наличие FDT по адресу 0xF0000000, а я ведь уже исправлял! Ну что же, поищем ещё… Нашёл в HiFive_U-Boot/arch/riscv/lib/boot.c, заменил на 0x81F00000, указанное в конфигурации загрузки U-Boot.


    Потом BBL жаловался, что нет памяти. Мой путь лежал в функцию mem_prop, что в riscv-pk/machine/fdt.c: оттуда я узнал, что нужно пометить узел fdt ram как device_type = "memory" — потом, возможно, нужно будет генератор процессора поправить, но пока просто впишу руками — всё равно я этот файл вручную переносил.


    Теперь я получил сообщение (приведено в отформатированном виде, с возвратами каретки):


    This is bbl's dummy_payload.  To boot a real kernel, reconfigure bbl
    with the flag --with-payload=PATH, then rebuild bbl. Alternatively,
    bbl can be used in firmware-only mode by adding device-tree nodes
    for an external payload and use QEMU's -bios and -kernel options.

    Вроде, и указываются как нужно опции riscv,kernel-start и riscv,kernel-end в DTB, но парсятся нули. Отладка query_chosen показала, что BBL пытается парсить 32-битный адрес, а ему попадается пара <0x0 0xADDR>, и первое значение, похоже, младшие разряды. Дописал в секцию chosen


    chosen {
          #address-cells = <1>;
          #size-cells = <0>;
          ...
    }

    и поправил генерацию значений: не дописывать 0x0 первым элементом.


    Эти 100500 простых шагов позволят легко и просто посмотреть, как падает пингвин:


    Скрытый текст
       Verifying Hash Integrity ... md5+ OK
       Loading loadables from 0x90a5a758 to 0x82000000
    libfdt fdt_check_header(): FDT_ERR_BADMAGIC
    chosen {
            linux,initrd-end = <0x83000000>;
            linux,initrd-start = <0x82000000>;
            riscv,kernel-end = <0x80a00000>;
            riscv,kernel-start = <0x80200000>;
            #address-cells = <0x00000001>;
            #size-cells = <0x00000000>;
            bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
            stdout-path = "uart0:38400n8";
    };
    libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
    chosen {
            linux,initrd-end = <0x83000000>;
            linux,initrd-start = <0x82000000>;
            riscv,kernel-end = <0x80a00000>;
            riscv,kernel-start = <0x80200000>;
            #address-cells = <0x00000001>;
            #size-cells = <0x00000000>;
            bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
            stdout-path = "uart0:38400n8";
    };
       Loading Kernel Image ... OK
    Booting kernel in
    3
    2
    1
    0
    ## Starting application at 0x80000000 ...
    bbl loader
    
                    SIFIVE, INC.
    
             5555555555555555555555555
            5555                   5555
           5555                     5555
          5555                       5555
         5555       5555555555555555555555
        5555       555555555555555555555555
       5555                             5555
      5555                               5555
     5555                                 5555
    5555555555555555555555555555          55555
     55555           555555555           55555
       55555           55555           55555
         55555           5           55555
           55555                   55555
             55555               55555
               55555           55555
                 55555       55555
                   55555   55555
                     555555555
                       55555
                         5
    
               SiFive RISC-V Core IP
    [    0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
    [    0.000000] Linux version 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Wed Jul 3 21:29:21 MSK 2019
    [    0.000000] bootconsole [early0] enabled
    [    0.000000] Initial ramdisk at: 0x(____ptrval____) (16777216 bytes)
    [    0.000000] Zone ranges:
    [    0.000000]   DMA32    [mem 0x0000000080200000-0x00000000bfffffff]
    [    0.000000]   Normal   [mem 0x00000000c0000000-0x00000bffffffffff]
    [    0.000000] Movable zone start for each node
    [    0.000000] Early memory node ranges
    [    0.000000]   node   0: [mem 0x0000000080200000-0x00000000bfffffff]
    [    0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x00000000bfffffff]
    [    0.000000] On node 0 totalpages: 261632
    [    0.000000]   DMA32 zone: 3577 pages used for memmap
    [    0.000000]   DMA32 zone: 0 pages reserved
    [    0.000000]   DMA32 zone: 261632 pages, LIFO batch:63
    [    0.000000] software IO TLB: mapped [mem 0xbb1fc000-0xbf1fc000] (64MB)

    (эмблему выводит BBL, а то что с метками времени — ядро).


    К счастью, не знаю, как везде, но на RocketChip при подключении отладчика по JTAG можно ловить trap-ы из коробки — отладчик остановится ровно в этой точке.


    Program received signal SIGTRAP, Trace/breakpoint trap.
    0xffffffe0000024ca in ?? ()
    (gdb) bt
    #0  0xffffffe0000024ca in ?? ()
    Backtrace stopped: previous frame identical to this frame (corrupt stack?)
    (gdb) file work/linux/vmlinux
    A program is being debugged already.
    Are you sure you want to change the file? (y or n) y
    Reading symbols from work/linux/vmlinux...done.
    (gdb) bt
    #0  0xffffffe0000024ca in setup_smp () at /hdd/trosinenko/fpga/freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:75
    #1  0x0000000000000000 in ?? ()
    Backtrace stopped: frame did not save the PC

    freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:


    void __init setup_smp(void)
    {
        struct device_node *dn = NULL;
        int hart;
        bool found_boot_cpu = false;
        int cpuid = 1;
    
        while ((dn = of_find_node_by_type(dn, "cpu"))) {
            hart = riscv_of_processor_hartid(dn);
            if (hart < 0)
                continue;
    
            if (hart == cpuid_to_hartid_map(0)) {
                BUG_ON(found_boot_cpu);
                found_boot_cpu = 1;
                continue;
            }
    
            cpuid_to_hartid_map(cpuid) = hart;
            set_cpu_possible(cpuid, true);
            set_cpu_present(cpuid, true);
            cpuid++;
        }
    
        BUG_ON(!found_boot_cpu); // < ВЫ НАХОДИТЕСЬ ЗДЕСЬ
    }

    Как говорилось в старом анекдоте, CPU not found, running software emulation. Ну или не running. Заблудились в единственном ядре процессора.


    /* The lucky hart to first increment this variable will boot the other cores */
    atomic_t hart_lottery;
    unsigned long boot_cpu_hartid;

    Хороший комментарий в linux/arch/riscv/kernel/setup.c — этакая покраска забора по методу Тома Сойера. В общем, сегодня победителей почему-то не нашлось, приз переносится на следующий тираж...


    На этом предлагаю закончить и без того затянувшуюся статью.


    Продолжение следует. В нём будет бой с хитрой ошибкой, которая успевает спрятаться, если к ней медленно подкрадываться singlestep-ом.


    Текстовый скринкаст загрузки (внешняя ссылка):
    asciicast

    Поделиться публикацией

    Похожие публикации

    Комментарии 12

      +2
      Какая суровая магия! Особенно описание аппаратуры на Scala (впервые в жизни такое вижу, я знаю только VHDL и Verilog).
      Как раз в выходные думал, не вспомнить ли молодость, не заказать ли отладочную плату с FPGA (в университете был курс, работали с платой на Spartan-3E от Digilent), но понял, что я же абсолютно не знаю, как сделать что-то сложнее счётчика, где взять готовый модуль контроллера для той же DDR SDRAM. А тут аж целый процессор, уважуха!

      я где-то слышал, что, вроде бы, бесконечный цикл — это Undefined Behavior
      Это в С++. В С, насколько я помню, нет понятия undefined behavior.
      И это undefined behavior только в том случае, если цикл не делает ничего из нижеперечисленного:
      • Вызывает библиотечную I/O функцию
      • Читает или модифицирует volatile объект
      • Выполняет atomic операцию либо операцию с объектом синхронизации.
        0

        Спасибо! :) На всякий случай уточню: моего кода здесь и 0.1% не наберётся — я его только портирую на нестандартную плату


        Если надумаете покупать плату, рекомендую попробовать для начала синтезировать целевой дизайн: я вот сначала думал вообще на втором "Марсоходе" попробовать RocketChip, а оказалось, он даже минимальный приблизительно 16K ячеек (против 10 у платы). А когда я переключился с Tiny ядра на Small, использование подскочило до ~30K (и 60K у Big)! А я ещё думал, не купить ли что поизвестнее да попроще!..


        Кстати, сейчас на чём только аппаратуру не пишут: Haskell, Scala, даже Python. Но на Scala добрые люди целый процессор написали!

          0
          Это ещё вопрос, что сложнее: написать с нуля код под целевую платформу, или портировать готовый, но неподходящий.

          Спасибо за совет, тоже была такая мысль, что неплохо бы иметь проект, синтезировать его, посмотреть, сколько нужно ресурсов. Но как же я его напишу, если даже не знаю, какого производителя лучше взять FPGA :) Но вы правы: для того, чтобы погрузиться в проектирование и синтез, что-то напроектировать и даже верифицировать, железка не нужна.
            +1

            Есть вообще чудо техники: FireSim — ему даётся маленький инстанс на Amazon, ключ API и исходник на Chisel, а он сам развернёт сборочные инстансы, FPGA-инстансы, прогонит тесты. Правда смущает вот что:


            • после прочтения этой статьи я как-то начал опасаться сочетания постоплатной системы и API-ключей. Впрочем, с тех пор, возможно, всё десять раз поменялось
            • не знаю, нет ли риска "спалить" FPGA-ускоритель аппаратно (а стоит он, наверное, весьма немало)

            А вообще было бы забавно (даже без FireSim и API-ключей): "На AWS нет нужной архитектуры процессора — сейчас исправим!" — ПЛИСины там стоят огромные и стоимость аренды гуманная (подсказка: спотовые инстансы существенно дешевле, если подходят в плане доступности под задачу — не "всегда on-line", а "когда-нибудь посчитать подешевле").

          0

          Кстати, как говорится, на правах рекламы: если вдруг будет интересно из серии "написать с нуля", тут я экспериментировал с генерацией композитного сигнала как раз на "Марсоходе", но предупреждаю, там редкостный гов^Wнеидиоматичный код с точки зрения HDL, зато с нуля. :)

            +2
            В С, насколько я помню, нет понятия undefined behavior.
            Хаха
            список более чем на 10 страниц в PDF
            The behavior is undefined in the following circumstances:
            — A ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint is violated(clause 4).
            — A nonempty source file does not end in a new-line character which is not immediatelypreceded by a backslash character or ends in a partial preprocessing token orcomment (5.1.1.2).
            — Token concatenation produces a character sequence matching the syntax of auniversal character name (5.1.1.2).
            — A program in a hosted environment does not define a function namedmainusing oneof the specified forms (5.1.2.2.1).
            — The execution of a program contains a data race (5.1.2.4).
            — A character not in the basic source character set is encountered in a source file, exceptin an identifier, a character constant, a string literal, a header name, a comment, or apreprocessing token that is never converted to a token (5.2.1).
            — An identifier, comment, string literal, character constant, or header name contains aninvalid multibyte character or does not begin and end in the initial shift state (5.2.1.2).
            — The same identifier has both internal and external linkage in the same translation unit(6.2.2).
            — An object is referred to outside of its lifetime (6.2.4).
            ..........
              0
              Хм, спасибо, не знал. В общем-то, эти все условия понятные, для совсем тяжелых случаев, больше disclaimer, чем особенность языка. В С++ много случаев undefined behavior разрешены для оптимизации (хотя случай с бесконечным циклом вряд ли относится к этой категории).
                0

                Я бы не рекомендовал пытаться разделить UB на логичный и "да к этому же никто не придерётся" :)


                Например, как может повести себя приблизительно такой код? (пример скопирован со StackOverflow)


                // Zero-filled global buffer of 16 characters
                char destBuffer[16];
                
                void Serialize(bool boolValue) {
                    // Determine which string to print based on boolValue
                    const char* whichString = boolValue ? "true" : "false";
                
                    // Compute the length of the string we selected
                    const size_t len = strlen(whichString);
                
                    // Copy string into destination buffer, which is zero-filled (thus already null-terminated)
                    memcpy(destBuffer, whichString, len);
                }

                Отгадка

                  +1
                  Вы самую интересную часть опустили:
                  int main()
                  {
                      // Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
                      FStruct structInstance;
                  
                      // Output "true" or "false" to stdout
                      Serialize(structInstance.uninitializedBool);
                      return 0;
                  }


                  Да, классный пример, спасибо, не видел его. Лично я считаю требование инициализировать переменные (то есть, запрет использовать неинициализированные) абсолютно логичным. И вот реальный пример, как это помогает в оптимизации кода. Си так не смог бы :)
                    0

                    Ой, главное, написал же какой-то пример с неинициализированной переменной, потом думаю: "Вдруг там не воспроизведётся, лучше скопипасчу" :) Вот и докопипастился не посмотрев, извините. :) Только то ли я туплю, то ли это корректный сишный код тоже?

                  0
                  В общем-то, эти все условия понятные, для совсем тяжелых случаев, больше disclaimer, чем особенность языка. В С++ много случаев undefined behavior разрешены для оптимизации
                  Например, как я помню, переполнение signed int — undefined behavior и в C, и в C++, одинаково используется компиляторами для оптимизации.
                  if (counter++ == INT_MAX) { ... }
                  Вот простой примерчик, выражение может быть оптимизировано C компилятором в:
                  if (0) { ... }
                  и в итоге в ничто.
              0

              -

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

              Самое читаемое