Про внешний SPI флэш внутри чипов WCH уже везде написали, ситуацию с реальным объемом я описывал https://habr.com/ru/articles/859 054/. Но какая там скорость и как она влияет на производительность системы? WCH на эту тему официально ничего не писал, но на китайском саппорте информация есть. Общий смысл всех ответов саппорта: для кода используйте кэшируемый флэш zero‑wait, все остальное это для пользовательских данных, но если вы уж прям не влезаете в кэш, то можно использовать область non zero-wait для кода. Крайне содержательно. Чтобы окончательно раскрыть тему, я вооружился тестами производительности CoreMark и в целом получил ответы на 2 своих главных вопроса: какая частота доступа к физическому SPI флэшу и как стратегия его применения в реальных проектах. Сейчас расскажу.
Итак, исходные данные. Прошивка запускалась в zero-wait. Код бенчмарка, исполняемый между функциями замера времени - мапился в верхние адреса за пределы кэшируемого флэша. Компилятор GCC8.2.0, оптимизация -O3, все остальное по дефолту из IDE. Время считалось на SysTick. Тесты проводились для стандартных частоты 48,56,72,96,120 и 144Mhz. На каждой частоте бенчмарк прогонялся 3 раза: в zero-wait, non zero-wait и non zero-wait с включенным режимом ускорения чтения из флэша (Flash Enhanced Read Mode). Кол-во итераций во всех тестах 4000. Это важно, поскольку на разном кол-ве итераций результат бенчмарка немного отличается.
Для CH32V203C8T6 rev0 получилось:
zero-wait флэш | non-zero wait флэш | non zero-wait флэш (FERM) | ||||
Частота | Цикл CoreMark в сек | Цикл CoreMark на Mhz | Цикл CoreMark в сек | Цикл CoreMark на Mhz | Цикл CoreMark в сек | Цикл CoreMark на Mhz |
144Mhz | 375,768584 | 2,60950405 | 34,664655 | 0,24072677 | 37,351394 | 0,25938468 |
120Mhz | 313,140486 | 2,60950405 | 29,190354 | 0,24325295 | 31,126162 | 0,25938468 |
96Mhz | 250,512389 | 2,60950405 | 23,172174 | 0,24137681 | 24,90093 | 0,25938468 |
72Mhz | 187,88429 | 2,60950402 | 17,332327 | 0,24072676 | 18,675697 | 0,25938468 |
56Mhz | 146,132227 | 2,60950405 | 13,4806991 | 0,24072677 | 14,525542 | 0,25938467 |
48Mhz | 125,256195 | 2,60950406 | 11,554885 | 0,24072677 | 12,4504645 | 0,25938467 |
Репорт для 144Mhz zero-wait
SystemClk:144 000 000
ChipID:20 310 500
2K performance run parameters for coremark.
CoreMark Size: 666
Total ticks: 191 607 290
Total time (secs): 10.644 849
Iterations/Sec: 375.768 584
Iterations: 4000
Compiler version: GCC8.2.0
Compiler flags: o3
Memory location: STACK
seedcrc: 0xe9f5
[0]crclist: 0xe714
[0]crcmatrix: 0×1fd7
[0]crcstate: 0×8e3a
[0]crcfinal: 0×65c5
Correct operation validated. See README.md for run and reporting rules.
CoreMark 1.0: 375.768 584 / GCC8.2.0 o3 / STACK
Репорт для 144Mhz non zero-wait Flash Enhanced Read Mode
SystemClk:144000000
ChipID:20310500
2K performance run parameters for coremark.
CoreMark Size : 666
Total ticks : 1927638880
Total time (secs): 107.091049
Iterations/Sec : 37.351394
Iterations : 4000
Compiler version : GCC8.2.0
Compiler flags : o3
Memory location : STACK
seedcrc : 0xe9f5
[0]crclist : 0xe714
[0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a
[0]crcfinal : 0x65c5
Correct operation validated. See README.md
for run and reporting rules.
CoreMark 1.0 : 37.351394 / GCC8.2.0 o3 / STACK
Остальные репорты публиковать смысла не вижу.
Теперь V303 и V307. Результаты идентичны.
zero wait флэш | non zero wait флэш | non zero wait флэш (FERM) | ||||
Частота | Цикл в сек | Цикл на Mhz | Цикл в сек | Цикл на Mhz | Цикл в сек | Цикл на Mhz |
144 | 366,415588 | 2,54455269 | 60,813471 | 0,42231577 | 68,385467 | 0,47489907 |
120 | 305,346324 | 2,5445527 | 50,677894 | 0,42231578 | 56,987889 | 0,47489907 |
96 | 244,277059 | 2,54455269 | 40,542315 | 0,42231578 | 45,590311 | 0,47489907 |
72 | 183,207794 | 2,54455269 | 30,406736 | 0,42231577 | 34,192733 | 0,47489906 |
56 | 142,494953 | 2,54455273 | 23,649683 | 0,42231576 | 26,594348 | 0,47489907 |
48 | 122,13853 | 2,54455270 | 20,271157 | 0,42231577 | 22,795156 | 0,47489908 |
Репорт для СH32V303/V307 144Mhz zero-wait
SystemClk:144000000
ChipID:30330514
2K performance run parameters for coremark.
CoreMark Size : 666
Total ticks : 196498191
Total time (secs): 10.916566
Iterations/Sec : 366.415587
Iterations : 4000
Compiler version : GCC8.2.0
Compiler flags : o3
Memory location : STACK
seedcrc : 0xe9f5
[0]crclist : 0xe714
[0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a
[0]crcfinal : 0x65c5
Correct operation validated. See README.md
for run and reporting rules.
CoreMark 1.0 : 366.415587 / GCC8.2.0 o3 / STACK
Репорт для СH32V303/V307 non zero-wait Flash Enhanced Read Mode
SystemClk:144000000
ChipID:30330514
2K performance run parameters for coremark.
CoreMark Size : 666
Total ticks : 1052855275
Total time (secs): 58.491960
Iterations/Sec : 68.385467
Iterations : 4000
Compiler version : GCC8.2.0
Compiler flags : o3
Memory location : STACK
seedcrc : 0xe9f5
[0]crclist : 0xe714
[0]crcmatrix : 0x1fd7
[0]crcstate : 0x8e3a
[0]crcfinal : 0x65c5
Correct operation validated. See README.md
for run and reporting rules.
CoreMark 1.0 : 68.385467 / GCC8.2.0 o3 / STACK
Валидация результата
Для начала соотнесем результаты с тем, что известно на эту тему по CH32V307 в zero-wait 144Mhz.
https://gitee.com/elecb/ch32v307_core-mark - CoreMark 1.0: 380.662352 / GCC8.2.0 -o3 / СТЕК
https://zhuanlan.zhihu.com/p/496255489 - CoreMark 1.0: 379.982521 / GCC8.2.0 -o3 / STACK
https://bbs.21ic.com/icview-3164492-1-1.html CoreMark 1.0: 382.464221 / GCC8.2.0 -ofast -o3 / STACK
https://www.eembc.org/viewer/?benchmark_seq=13505. CoreMark 1.0: 459.16 / GCC12.1.0 --O3 -funroll-all-loops -finline-limit=600 -ftree-dominator-opts -fno-if-conversion2 -fselective-scheduling -fno-code-hoisting
Последние мое любимое, цифра даже выше чем у CortexM4F 160Mhz. Но это хардкорные ключи оптимизации по скорости (на них я получал аналогичный результат), просто люди погнались за красивой итоговой цифрой. Порядок сходиться, можно считать что для zero-wait я получил валидный результат. Разница объясняется количеством итераций и разрядностью счетчика времени. Я считаю время по SysTick с делителем 8 (т.е. 18Mhz). Во всех приведенных выше примерах - таймер на 1кHZ или около того. Т.е. инструмент измерения (таймер) на 5 и более порядков меньшей разрядности, чем измеряемый процесс. Что могло у них пойти не так ?:) Но для быстрого флэша это не так критично. Теперь интереснее.
Те, кто интересуется контроллерами WCH, могли встречать различные оценки скорости флэша в верхних адресах. Для V203 я таких данных не находил, а вот V307 тестировали. Я встречал оценки в 10 и даже 18 раз медленнее. Удалось найти репорт по этому поводу https://blog.csdn.net/mx1117/article/details/126094948
Из репорта видно, что компилируется бенчмарк на оптимизации -Os. Если залезть в ASM, то видно в чем отличие. Поcкольку компилятор при -O3 разворачивает циклы, поток команд теста по большей части состоит из команд работы с регистрами. А вот при компиляции с -Os большая часть команд - команды работы с операндами в тот же самом медленном флэше. При тестах на оптимизации -O3 замедление происходит на выборке команды и результат характеризует в большей степени производительность флэш. В случае тестов на оптимизации -Os замедление происходит еще и на выборке операнда, т.е. дважды. Что характеризуют результаты тестов с оптимизацией -Os аллах его знает. Формально производительность системы, но медленного флэша в WCH в 3 раза больше чем кэшируемого, какой практический смысл исполняться там с оптимизацией по размеру?
Проблем с валидостью тестов коллегам подкидывает использование для расчета времени таймера, а не SysTick. А еще, видимо что бы не ждать или что бы не переполнять таймер, они сокращают кол-во итераций теста при исполнении в верхних адресах. Все это приводит к некорректным результатам тестирования. Мне лень повторять тесты при таких же условиях. Но на оптимизации -Os я тем не менее эксперимент провел, результат для верхних адресов предсказуемо в 2 раза медленнее чем при оптимизации -O3.
Анализ результатов
С валидностью надеюсь разобрались. Теперь можно попробовать накинуть аналитики. Первое и самое неожиданное, V3 медленнее в тестах чем V2. Чуть чуть, но тем не менее. Для верности я сравнил оба проекта на тему настроек IDE, размера кода исполняемого бенчамарака и бегло глянул .lst. Все идентично рубль в рубль. Тогда какого? Может не прав, но тут 2 разных ядра QingKeV4B и QingKeV4F. Помимо FPU, который не используется в тестах, на 4F есть модуль защиты памяти. Т.е. по идее это дополнительный блок в цепочке ядро-память. Не однозначно, но MPU единственное видимое отличие, и вроде как логично объясняет небольшую разницу в скорости.
Но это мелочь, главное что же там за флэш то? Я думаю ответ тут. https://www.eembc.org/viewer/?benchmark_seq=13659 Получается что тесты для V203 144Mhz из верхних адресов и CH32X033F8P6 48Mhz дают очень близкие результаты. Оба чипа на ядре QingKeV4, в обоих случаях код исполняется из внешнего флэш, с частотой доступа меньше частоты ядра, т.е. по факту производительность ограничена шиной доступа к флэш. У CH32X033F8P6 однозначно определена частота доступа по системной шине к данным- это 20Mhz. Для частот ядра выше 20Мhz есть регистр Latensy, в котором нужно установкой wait-stait ограничивать скорость доступа к памяти. Не похоже на совпадение. Я думаю в случае с CH32V203 мы имеет дело с 20Mhz внешнем SPI флэшем ( имеется ввиду не скорость физического доступа к микросхеме, а что по итогу обеспечивается на шине ядра). И загадочным контроллером Flash, который копирует при старте часть флэша в SRAM, а в процессе работы, в случае обращения со стороны ядра за пределы кэша, автоматом накидывает условно 10 wait-stait. Поэтому мы получаем равномерное падение производительности в верхних адресах на всех частотах. Еще одно отличие от CH32X033F8P6 , это тактирование записи во флэш. Во всех младших семействах WCH тактирование идет от HSI напрямую, в случае CH32X033F8P6 от всторенного 8Mhz. А вот на V2 и V3 тактирование с системной шины APH, через делитель, но с ограничением в 60Mhz. Если принять, что в V203 частота обращения к флэш 20Mhz, то на кой черт контроллеру флэш 60, при том что аналогичное ядро справляется с тем же самым на 8? А видимо для синхронизации кэша при записи. Допустим спорное утверждение, но логика в этом есть.
А что с V3? Ну там падение производительности в 5 раз. Это в 2 раза меньше чем на V2. Если поискать в закромах WCH, то находиться CH32L103 на ядре QingKeV4C. Там есть регистр latency, но частота обращения к флэш 40Mhz.Так что очень похоже, что в случае с V303/ V307 мы имеем дело как раз с флэшем на 40Mhz.
Выводы
Помимо размещения данных в верхних адресах, волне можно придумать части кода для переноса в non zero-wait зону флэш. Разницы по сути нет, при размещение данных замедление идет на выборке операнда, при размещение кода - точно такое же при выборке команды. Поэтому для кода оптимизация по скорости ( желательно максимально хардкорная, см. ссылку выше, CoreMark 459 против 380 на просто O3). Ну и Flash Enhanced Read Mode для верхних адресов желателен, поскольку добавляет около 8% ускорения. В случае с V3 имеет падение скорости доступа в 5 раз, в случае с V2 - в 10 раз. Не стоит путать эти цифры с падением производительности. Она может быть выше если код исполняемый в верхних адресах использует данные из той же зоны флэш. Как в случае с оптимизацией по размеру. Тогда падение производительности будет выше чем разница в скорости доступа.
UPD: Рекомендации для проведения аналогичных экспериментов.
Ни когда не сравнивайте свои результаты с любыми другими, если не видите репорта и не понимайте в каких условиях проводились тесты. Люди творят дичь.
Желательно, а если вы в первый раз делайте тесты то обязательно, делать сравнения на разны частотах тактирования. Удельная производительность ( кол-во циклов на 1Mhz) наше все. В общем случае она одинакова до последнего знака на разных частотах . Если есть расхождения и вы не можете их объяснить, то либо вы творите дичь, либо наткнулись на какую-то новую для себя особенность МК. В обоих случая получите полезные знания. Когда нет опыта, без массива данных найти ошибки и валидировать свои результаты не реально.
Разное кол-во итераций бенчмарака дает разные результаты. Ждать пока оно прокрутиться 10 минут на низкой частоте трудно, но таков путь.
Соотносите разрядность таймера замера времени с задачей. Результат CoreMark - кол-во итераций теста деленое на кол-во тиков таймера. Прикиньте формулу, посмотрите как оно считается в бенчмарке. Нет ли где потери данных?