constexpr применительно к функции не гарантирует её вычисление в
компайлтайме. Только для переменной. Вот смотрите несколько
интересных примеров: 1. Функция не помечена ни const, ни constexpr, однако компилятор вычислил её значение на этапе компиляции.

2. Функция помечена constexpr, но вычисляется в рантайме.

3. В C++20 появился consteval, который запрещает вычисление функции в рантайме.

Отсюда вывод: const, constexpr и consteval это не про оптимизацию. Если компилятор может что-то вычислить на этапе компиляции, то он это сделает. Если не может, то никакие ключевые слова ему в этом не помогут.
Теперь по поводу размещения в ОЗУ-ПЗУ. Опять же, ключевые слова ничего не говорят о размещении в ПЗУ. Понятно, что переменные компилятор размещает в сегментах .data или .bss, а константы в сегменте .rodata. А уже в скрипте линкера написано где эти данные размещать и как инициализировать. При отладке из ОЗУ, например, .rodata тоже в ОЗУ располагается. А .data при этом в стартапе не копируется из ПЗУ, так как сразу загружается отладчиком в ОЗУ где и должна быть. Можно также вспомнить хороший пример из IAR для AVR. Он константы, помеченные просто const, размещает в SRAM. Помеченные __flash const - во flash. А помеченные __eeprom const - в EEPROM.
Отсюда ещё один вывод: const и constexpr это не про размещении данных в памяти, а про свойства этих данных в понятиях языка программирования. Размещение ОЗУ-ПЗУ это лишь побочный эффект вызванный свойствами данных.
Доклад окончен!