Comments 88
Похвально, что Вы не бросили свою затею. Обычно обещающие здесь многотомную эпопею, первой публикацией и ограничиваются.
однотактное умножение не так уж и нужно
Да, если Вы посчитаете , сколько в коремарке выполнилось команд умножения и сколько на них потратилось тактов (для многотактной версии), то получится где-то менее 7 тактов на умножение. В тесте очень удобные числа для Вашей реализации.
И не только в тесте. Для чего обычно используется умножение в общем коде? Если надо превратить arr[i] в arr + i * sizeof(arr[0]). Очевидно, в таком умножении много тактов и не понадобится, потому что i редко за 1000 переваливает, а sizeof тем более.
Что скажете насчёт быстрого деления? Быстрее флагманов получится?
Для чего обычно используется умножение в общем коде?
А если взять какую нибудь криптографию...
Деление у Вас просто огонь. В тактах, конечно. Вот только не знаю какой бы тест подобрать чтобы надрать задницу флагманам :)
Ещё важный пункт, деления почти не вызываются во время теста, так что влияние нового алгоритма лучше проверять на чём-то другом.
Шикарная работа, но в CoreMark вроде нет деления?
Однотактное умножение в ПЛИС влазит (но оно там есть и так ) и занимает 2000LUT и тактовая 50MHz.
Но шибко лютый CoreMark получился, 2.7CM/MHz выглядит уж круто , как у Cortex M0. у PicoRV32 0.57, но умножитель на на ПЛИСе всегда однотактовый.
Хорошая статья, есть надо чем подумать... (Вообще бесконечно можно делать три вещи- смотреть как течет вода, смотреть как горит огонь, и запускать CoreMark))
На скриншоте x = 7D0, y = 3, это вычисление размера теста, 2000/3=666. Ещё форматирование %d вызывает 2 деления на цифру. Если ворочать json, наверное там бы кэширование деления пригодилось, чтобы частное и остаток два раза не вычислять.
С появлением конвейера CM просядет совсем немножко, об этом в следующей статье, если соберусь.
Дак это на результат не влияет, stop_time() вызывается до вывода. Там в настройка есть сколько тиков в секунде. Но в любом случае спасибо, класная работа, пойду свое ядро мучать, смотреть где мои 2.7 Cm/Mhz
PS. Хотя нет, я извиняюсь за занудство.
Результат теста во втрой статье $finish дает количество тиков 1,629,813 это развне не должно биться с Total ticks? Применяя аппаратное умножение $finish 802303 в два раза быстрее. В тестбенче `timescale 1ns / 1ns
. Если бы тактовая была 1Mhz то в первом случае итерация бы заняла 1,6 секунды, вовтором 0.8. В первом случае у Вас 0,625 Cm/Mhz во втором 1, 25 как у всех однотактников. Где я ошибся?
1 наносекунда на низ меандра, одна наносекунда на верх, так что число тиков надо делить на два.
со всеми однотактными командами у меня одна итерация 313 263 такта. Это 3.1922
В чём запускаете? У меня одна итерация однотактного варианта в икарусе занимает 9 минут, поэтому по умолчанию отключена.
Стойте стойте, а почему вы собираете %gcc_bin%-gcc %ccflags% -c src/core_list_join.c -o build/core_list_join.o
А где matrix?
Но шибко лютый CoreMark получился, 2.7CM/MHz выглядит уж круто
Модель интересна тем, что она однотактовая. В ней напрочь отсутствует конвейер. Любая команда исполняется за 1 такт. Вы не теряете ничего на переходах, на загрузке данных. т.е. это какой-то идеал , к которому нужно стремиться. :)
Подначиваете следующую статью писать. Удивляться надо будет, если ядра через Ctrl+C, Ctrl+V накопируются и начнут поток параллельно крутить, вот там можно i7 по числу команд за такт обогнать.
В пределе с одним набором регистров (32 штуки) можно за такт 10 команд выполнять. А если вторую копию завести и учесть float, то ещё больше.
С удовольствием посмотрю и на такую Вашу модель.
Интересно посмотреть как изменится CM когда появитс три стадии.
Лучше точно не будет :)
Но на мой взгляд, нужно еще доделывать текущую однотактную модель. Сейчас к ней невозможно подключить обычную синхронную статическую память оставаясь в рамках однотактного исполнения. Также можно добавить В-расширение. Например zba,zbb,zbs команды. Это уже поддерживается компилятором, просто добавляется и улучшит немного коремарк :)
Далее, оставаясь с однотактной моделью, можно попробовать реализовать fusion для комбинаций команд. Это тоже улучшит коремарк на МГц. Конвейеры только добавят проблем и загубят хорошие показатели :)
Я за хорошими показателями на fpga гнался, а не просто в вакууме ) . Для fusion надо ведь отбрасывание команд после джампов делать, чтобы лишнего не выполнилось, так что в любом случае добавлять флаги блокировки "конвейера". Хотя может и правда так легче начать.
Немного ошибся. Почитал статью про fusion, и это не out-of-order, там хотят сливать инструкции в том случае, если целевой регистр совпадает, но ведь это удлиняет путь сигнала. Причём путь удлинится для всех инструкций, а оптимизация получится только для некоторых, так что больше на пессимизацию похоже.
но ведь это удлиняет путь сигнала
У Вас сейчас, например, для команды LD тактой путь сигнала: РС адрес из регистра в память, чтение памяти, декодирование команды, определение что команда LD, вычисление адреса чтения, отправка адреса в память, чтение памяти, прием и преобразование данных , запись в РОН. И все это ОДИН такт :) "тяжело" (с)
Так ведь синхронная память адрес получает на одном такте, а значение выдаёт на другом, так что однотактовым процессором уже не обойтись. Потому и пришлось конвейер делать.
Насчёт B-расширения, может и добавлю, только оно не выглядит простым. zba + zbb + zbs - это 24 команды (без учёта immediate), и все такие разные.
А смысл подключать синхронную память если мы про SRAM. Отладок с синхронной памятью всего две шутки и одну из них делает @checkpoint
Запись сделана также как в YRV через маску Byte line enable, на ПЛИСе должно работать, там единственное что не все [31:0] умеют синтезировать, но это решаемо.
Quartus автоматом подключает, так что без вариантов. А ждать 3 такта на инструкцию, чтобы сначала адрес инструкции задать, потом инструкция приехала, потом для неё данные - слишком большие потери времени.
Ничего не понял. Блок M9k у Cyclone IV двухпортовый и такая конструкция на нем должна синтезироваться:
always_ff @(posedge clk)
<TAB> if(we) mem[addr]<=din;
assign dout= mem[addr];
Другое дело что на некоторых Cyclone Вы не сможете синтезировать [31:0] mem [1:1024]; Надо делать четыре штуки по [7:0]. В MIPSFpga и примерах на Cortex M0 так и сделано. Это в schoolRisc reg [31:0] rom [SIZE - 1:0];
Вы привели пример памяти у которой запись синхронная, а чтение асинхронное. Уже для меня непонятно что :) Синхронная запись означает, что реальная запись будет не до фронта клока, а после него, т.е. в следующем такте. Но в следующем такте у Вас по этому же адресу возможно уже асинхронное чтение.
Как это возможно на RV32? Атомиков же нет?
И у него же также сделано
if
(data_w)
begin ?
В икарусе работает. Если предположить, что память - это массив регистров, то всё будет нормально выглядеть, на выходе регистра значение всегда есть, только надо с помощью адреса значение нужного получить через комбинаторику, а на входе регистров значение подаётся везде, а флаг записи в регистр выставляется шиной адреса.
Для плиса код будет в следующей статье. Процесс по этапам идёт, сначала простое ядро, чтобы легко понять, как это работает, потом его тестирование в симуляторе, потом ускорение, потом запуск на плис.
Выражение always_ff @(posedge clk) if(we) mem[addr]<=din; означает, что Вы имеете дело с синхронной памятью, т.к. в выражении присутствует clk. В этом случае вся информация для записи защелкивается по фронту клока в буфере памяти, а сама запись делается в следующем такте. Выражение assign dout= mem[addr]; означает асинхронное чтение памяти. Может ли быть память одновременно синхронной и асинхронной? Вот что по этому поводу мне написал ИИ:
Нет, SRAM не может быть одновременно синхронной и асинхронной. SRAM, или статическая память с произвольным доступом, существует в двух основных типах: асинхронная и синхронная. Асинхронная SRAM не имеет тактового сигнала, а ее операции чтения/записи управляются непосредственно сигналами адреса, данных и управления. Синхронная SRAM, в свою очередь, использует тактовый сигнал для синхронизации операций чтения/записи, что обеспечивает более высокую скорость работы.
Синхронный вариант работает, а так:
wire [31:0] i_data;
assign i_data = rom[i_data[31:2]];
выдаёт ошибку:
Ram logic "rom" is uninferred due to asynchronous read logic
Cannot convert all sets of registers into RAM megafunctions when creating nodes. The resulting number of registers remaining in design exceeds the number of registers in the device or the number specified by the assignment max_number_of_registers_from_uninferred_rams.
Это можно отключить, синтезировать 128 байт памяти и посмотреть максимальную частоту.
А смысл подключать синхронную память если мы про SRAM.
И я про синхронную SRAM, Этот тип памяти обычно подключается к ядру (ТСМ память называется) чтобы получить наибольшее число коремарков на МГц :)
Лучше точно не будет :)
А разве разделение на стадии не должно радикально увеличить тактовую частоту, ну то есть увеличить IPS ?
Чтобы добавить внешнюю память требуется хотя бы самый простейший арбитр, а это уже холостые циклы в ожидании. Чтобы их минимизировать нужно вводить I и D кэши. А чтобы минимизировать провалы у кэшей на ветвлениях нужно вводить предсказатель. В общем, работы еще очень много. :)
Увеличть то он ее увеличит, а где он техпроцесс то найдет? )))) У него судя по всему Cyclone iV или MAX10
Тактовую увеличит, но сейчас разговор про коремарк на МГц. Мы обгоняем всех по этому показателю :)
А что толку от этого показателя если Fmax = 5 МГц ? Разделение на стадии даст условные 50 МГц. Нужно измерять IPS, а не Coremark/MHz. А то получается как в мультфильме - "в попугаях то я существенно длиннее". :-)
Ну и как только появится арбитраж, вся эта производительность однотактового поцессора сольется в канализацию. Конвейерное ядро, теоритически, может выполнять хоть какую-то полезную работу, пока шина занята.
А точно что Fmax = 5МГц ? Он же его не синтезировал вроде еще.
Это условно. Но я предполагаю, что будет примерно где-то так. :)
А если 25? Умножитель 32х32 на Cyclone 4 в 50 МГц влазит, тут вроде не сильно разлапистей
нужно смотреть не умножитель , а критический путь. Он начинается сейчас от регистра РС, далее память, чтение команды, дешифрация команды, чтение операндов из РОН, выполнение операции умножения, запись результата в РОН.
Не про умножение речь, если вы сделаете комбинационный умножитель 32х32 через generate то на Cyclone 4 он займет 2КLUT и максимальная тактовая будет где-то 50МГц. (тоже интересовался победой в Coremark и честным умножением) Ну у него так и получилось.
Sorry за вмешательство дилетанта в этой области - но чтение и выполнение команды вроде и на однотактной микроархитектуре можно на разные такты разнести. Т.е. на конкретном такте у нас в отдельном регистре лежит текущая команда, битики которой определяют что делает АЛУ, в PC - адрес следующей команды, по которому выдаётся значение из памяти инструкций (для простоты возьмём гарвард) на вход регистра с инструкцией - соответственно по фронту защёлкнется следующая инструкция, которая будет выполняться на следующем такте.
Вы описали конвейерную архитектуру и в ней действительно все команды могут исполняться за один такт. Но от момента начала чтения команды Х , до момента записи результата этой команды в GPR, пройдет несколько тактов. Длина (глубина) конвейера. Сейчас у нас эта длина равна 1. И лично я вижу в такой модели интересность :)
Понял, просто считал что "однотактовость" архитектуры относится к собственно выполнению, вынос зачитывания в отдельный такт не считается ) Харрисов пока дочитал только до основ верилога, всё ленюсь ручками что то поделать.
Это у первых Харрисов глава 7.5
Возможно Вы правы. Я так вообще никогда не читал этого автора и в правильных названиях могу ошибаться. Слово "выполнение" мне непонятно. Получается, что если умножение не за 1 такт, то уже многотактовая архитектура? А если выполнение умножения поделено на две части: одна на одной стадии конвейера, а вторая на другой. Это однотактовая? Нужно как-то определиться с правильными названиями, чтобы любой мог понять о чем здесь разговор.
если умножение не за 1 такт, то уже многотактовая архитектура?
Насколько понимаю, это уж точно. Книжка имеется в виду эта (я себе купил таки бумажную).
Спасибо. Особенно , что на русском. А то я со своим испанским ни беса, ни муччо
Оказывается, там только оглавление и мусор. Ну да ладно. Все равно спасибо :)
https://is.ifmo.ru/books/2016/digital-design-and-computer-architecture-russian-translation_July16_2016.pdf
Вот только у меня второе издание, но там тираж 200 экз ))
Да, не заметил; вот тут вроде как полный вариант последнего издания.
Вы как будто статью не читали (или я недостаточно понятно написал). Для ПЛИС и нет смысла включать однотактовое деление, точно так же как делать однотактовое умножение вручную, это скорее для ASIC. А если с многотактовым, синтезируется примерно 40 МГц.
Ну и если смотреть на процент заполнения флага обращения к памяти, там меньше 20%, так что сольётся, но не прямо в канализацию ) .
Для ПЛИС и нет смысла включать однотактовое деление, точно так же как делать однотактовое умножение вручную, это скорее для ASIC.
Вопрос же чисто исследовательский ? Понятное дело, что в ПЛИС есть блоки DSP с умножением и делением, но почему бы не синтезировать схему как есть, из дискретных элементов, и посмотреть каков будет результат ? Далее можно прикинуть как оно будет работать в ASIC. Есть некая эмпирическая формула: схема на ПЛИС примерно во 20 раз медленнее этой же схемы на ASIC при одном и том же техпроцессе.
Так какой Fmax у Вас получился на Циклоне или Максе для однотактовой схемы ?
Если все 32 бита делить, то 5 МГц. Давайте считать, 5 * 20 = 100 МГц для 40 нм. Для 2 ГГц получается максимум 20 тактов. И как я уже в статье писал, есть ещё потенциал для оптимизации.
Да где ж их проводить, это надо ASIC если не делать, то хотя бы моделировать, а я с такими вещами не знаком.
Учитывая, что современная оперативка хоть и заявляет высокие рабочие частоты, но внутри работает на всё тех же 150 МГц (надо перемножить частоту на ожидание в тактах), тут любой процессор сольётся, если кучей злоупотреблять. А если кэша хватает, тогда и проблемы нет.
Чтобы добавить внешнюю память требуется
можно добавить 2-хпортовую синхронную память. при этом вместо instruction_address = pc; нужно instruction_address = pc_result; Это для чтения команд. Чтение данных будет отложенное и нужно будет сделать bypass. Тогда будет реально синхронная 2-хпортовая SRAM и однотактная модель.
Можно просто поставить две микросхемы памяти, одну для инструкций, вторую для данных. Либо инструкции хранить в QSPI (NOR flash). Либо в синтезируемой памяти внутри ПЛИС, если программа небольшая и у пользователя нет необходимости её менять.
Хоть я и новичок, но на pc[i] = pc_result[i-1] вы меня не разведёте. Очевидно, надо делать для следующего такта pc[i] <= pc_next + i*4, а дальше если повезло и бранчей не было, значит выполнять инструкцию, нет - тогда выбрасывать.
низя, конвейер получится
на pc[i] = pc_result[i-1] вы меня не разведёте.
Вы меня развеселили. Очень уж торопитесь. Прочитайте еще раз что я посоветовал. В РС пишется то, что и ранее. А вот наружу выдается pc_result и там защелкивается в буфере синхронной памяти. Так работает память. Для данных вы читаемые данные не получите в текущем такте, только в следующем. И нужен будет дополнительный порт записи в РОНы. Еще нужен будет bypass для загружаемых данных чтобы следующая команда не тормозила, если эти данные нужны ей.
И у Вас будет и синхронная стандартная память, и однотактная модель.
Провел интересные эксперименты с моделью автора и коремарком. Как уже автор писал, у него показатель см/мгц равен 3.2 при однотактном исполнении всех команд. Вчера я добавил в его модель В-расширение . Получил 3.292 за счет набора zba_zbb_zbs. Сегодня я скачал с сайта Syntacore свежий инструмент с версией компилятора 14.2 . Прежняя версия была 13.2 . На новой версии компилятора показатель 3.3556. Видно, что прогресс у RISC-V не стоит на месте.
А я только пошёл разбираться с B расширением и структуру команд переписать успел. У вас готовый модуль был? Получается, не шибко битовое расширение ускоряет. Ещё бы посмотреть, сколько ячеек отъест.
Вы можете и в сети найти такой модуль, но лучше самому добавить. Там простые команды. Когда-то в этом расширении были сложные команды, но в окончательной редакции их повыбрасывали. Обратите внимание на деление В расширения на группы. Можете добавлять по частям zba+zbb+zbs и смотреть влияние каждой подгруппы на результат.
GCC 14.2 уже очень сильно оптимизирует по сравнению с 12й версией (я на ней до сих пор сижу)
В этой версии работает даже векторизация. Я был удивлен , когда увидел, что при выполнении коремарка шевелятся векторные регистры. Прибавка от векторного расширения составила аж 0.006 :)
С векторным расширением это уже совсем другая модель и она проигрывает по коремаркам модели автора :)
Кстати, автору нужно как-то назвать ядро. А то без имени уже третью часть.
Если добавить unroll-loops да inline , то даже до 3.72 добираемся :) Правда кода в 3 раза больше.
Процессор на коленке ч.3. Алгоритм быстрого деления