Я всегда хотел собрать свой процессор. Не просто написать эмулятор или покопаться в чужих репозиториях, а пройти путь «от нуля»: описать RTL, прогнать через симуляцию, а потом оживить всё это на FPGA. В этой статье расскажу, как я к этому подошёл, какие инструменты использовал и на какие грабли наступил. Будет и Verilog-код, и опыт работы с симуляторами, и пара советов тем, кто захочет повторить эксперимент.

Честно говоря, идея «собрать свой процессор» долго казалась мне чем-то академическим. Мол, есть же готовые ядра: Rocket, BOOM, PicoRV32… Зачем плодить сущности? Но однажды я поймал себя на мысли: я могу запустить свой код на куске кремния, который я сам описал строчка за строчкой. Разве это не круто?

И вот я открыл текстовый редактор, выбрал Verilog, и начал писать. Да, граблей было предостаточно, да, дебаг занимал больше времени, чем разработка, но зато в конце на FPGA-плате мигнул светодиод, управляемый моим процессором. И ради этого стоило.

Архитектура RISC-V: минимум, чтобы стартовать

RISC-V — открытая архитектура, и именно это делает её идеальной для экспериментов. Я взял за основу RV32I — минимальный набор инструкций для 32-битного ядра. Без умных расширений, без FPU, без компрессий инструкций.

Что это значит на практике:

  • 32 регистра общего назначения по 32 бита.

  • Простые инструкции загрузки/выгрузки.

  • Арифметика и логика.

  • Управление переходами.

Вот честный вопрос к вам, читатель: разве для первого ядра нужно больше? Всё остальное можно добавить потом, если останется азарт.

Я начал с ALU (арифметико-логического устройства). Это сердце процессора, и без него дальше двигаться бессмысленно. Код вышел довольно компактным, но в нём я сознательно избегал слишком умных конструкций — лучше пусть будет длиннее, но понятнее.

module alu (
    input  wire [31:0] a,
    input  wire [31:0] b,
    input  wire [3:0]  op,
    output reg  [31:0] result
);

always @(*) begin
    case (op)
        4'b0000: result = a + b;   // ADD
        4'b0001: result = a - b;   // SUB
        4'b0010: result = a & b;   // AND
        4'b0011: result = a | b;   // OR
        4'b0100: result = a ^ b;   // XOR
        4'b0101: result = a << b;  // SLL
        4'b0110: result = a >> b;  // SRL
        default: result = 32'hDEADBEEF;
    endcase
end

endmodule

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

Симуляция: первые грабли

Для симуляции я использовал Icarus Verilog и GTKWave. Кто работал — тот знает: это простые, но мощные инструменты.

Я написал тестбенч для ALU:

module alu_tb;
    reg [31:0] a, b;
    reg [3:0]  op;
    wire [31:0] result;

    alu uut (
        .a(a), .b(b), .op(op), .result(result)
    );

    initial begin
        $dumpfile("alu_tb.vcd");
        $dumpvars(0, alu_tb);

        a = 10; b = 5; op = 4'b0000; #10; // ADD
        a = 10; b = 5; op = 4'b0001; #10; // SUB
        a = 10; b = 5; op = 4'b0010; #10; // AND
        $finish;
    end
endmodule

Запустил — и получил первые красивые графики. Но потом заметил странность: сдвиги работали неправильно. Оказалось, что b использовался целиком, а нужно было брать только младшие биты. Вот он, классический случай, когда «на бумаге гладко, а на практике всё иначе».

Регистровый файл и декодер инструкций

Следующим шагом стал регистровый файл. Тут тоже всё выглядит просто: 32 регистра, чтение/запись. Но если не добавить проверку на x0 (регистр, который всегда равен нулю), можно получить очень неприятные баги.

module regfile (
    input  wire clk,
    input  wire we,
    input  wire [4:0] ra1, ra2, wa,
    input  wire [31:0] wd,
    output wire [31:0] rd1, rd2
);

reg [31:0] regs [0:31];

assign rd1 = (ra1 == 0) ? 0 : regs[ra1];
assign rd2 = (ra2 == 0) ? 0 : regs[ra2];

always @(posedge clk) begin
    if (we && wa != 0)
        regs[wa] <= wd;
end

endmodule

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

Пайплайн или нет?

Здесь у меня был внутренний спор: стоит ли сразу заморачиваться с конвейером (pipeline)? С одной стороны, это интереснее и ближе к реальным CPU. С другой — отладка усложнится в разы.

Я решил начать без пайплайна, с простого пошагового исполнения. И знаете, не пожалел: багов и так хватало, а если бы я добавил ещё и forwarding, hazard-детекцию и прочее — я бы, наверное, бросил это дело на середине.

Синтез под FPGA

Когда RTL наконец-то более-менее заработал в симуляции, пришло время синтеза. Я использовал плату на базе Xilinx Artix-7 и Vivado.

Честно: первый синтез — это как первый запуск программы «Hello World». Только тут вместо текста на экране — мигающий светодиод. Я загрузил прошивку, написал простейший код на ассемблере RISC-V:

addi x1, x0, 42
sw   x1, 0(x0)

И, о чудо, на отладочном пине действительно появилось число 42. Вот в такие моменты забываешь про бессонные ночи.

Выводы и что дальше

Собрать свой RISC-V процессор оказалось реально. Да, путь непростой. Да, отладка выжирает кучу нервов. Но ощущение, что у тебя в руках крутится кусочек железа, написанный тобой, стоит того.

Что можно улучшить:

  • Добавить конвейеризацию.

  • Реализовать прерывания.

  • Попробовать расширение M (умножение/деление).

  • Сделать кэш и поддержку памяти.

А теперь вопрос к вам: а вы пробовали когда-нибудь написать процессор с нуля?

Комментарии (28)


  1. ParaParadox
    26.09.2025 17:10

    Странные чувства от Вашей публикации...

    Лучшее, что я видел по Risc-V на FPGA для начинающих, это
    https://github.com/BrunoLevy/learn-fpga/tree/master/FemtoRV

    или очень подробно

    https://github.com/BrunoLevy/learn-fpga/blob/master/FemtoRV/TUTORIALS/FROM_BLINKER_TO_RISCV/README.md


    1. checkpoint
      26.09.2025 17:10

      Картинка точно сгенерирована нейросетью.


      1. DrMefistO
        26.09.2025 17:10

        Прям позорище.


  1. HepoH
    26.09.2025 17:10

    Возможно вам будет интересно посмотреть на лабораторный практикум из МИЭТ, где как раз последовательно и с нуля пишется процессор RV32IZicsr. Он сопровождается очень подробными методическими материалами где как раз рассказывается вроде нюансов про использование младших битов операнда B при сдвигах. Кроме того, тестбенчи там даются уже готовые, потому что написать тестбенч для модулей — это тоже целая наука и у вас они довольно простые, можно не все отловить.


    1. checkpoint
      26.09.2025 17:10

      Совершенно не интересно смотреть за тем, как кто-то уже всё сделал и решил за тебя все проблемы. Автора всецело поддерживаю в его велосипедостроении. Считаю, что всякий программист обязан за свою карьеру хоть раз выделить время и реализовать какой-то вычислитель, пусть даже самый простой. После этого на природу сморишь совсем другими глазами. Глазами Создателя чтоли. :-)


  1. Krenodator
    26.09.2025 17:10

    Классный путь от ALU до мигающего диода. Это тот самый щелчок когда RTL оживает


  1. nv13
    26.09.2025 17:10

    Мне вот интересно.. такой чип стоит 12 тысяч рублей или такого порядка. Имеет ли смысл в него встраивать хоть что то микропроцессорное, если даже существенно более производительные процессоры и контроллеры стоят намного дешевле?)


    1. checkpoint
      26.09.2025 17:10

      В целом, это начинает приобретать какой-то смысл если Ваш синтезируемый вычислитель умеет делать что-то специфическое, например быстро перемножать матрицы, вычислять Фурье или имеет какие-то редкие интерфейсы. Автор же взял в руки первую попавшуюся плату и начал эксперименты.

      PS: Существуют ПЛИС и платы с ними за цену в менее 1500 руб (Tangnano-9K), на которых можно сделать всё то же самое, и даже в большем обьеме.


      1. nv13
        26.09.2025 17:10

        Именно. Но встраивание микропроцессоров жрёт очень много недешёвых ресурсов и не упрощает никак конечную разработку, поэтому мне всегда были непонятны разного рода nios-ы при наличии аналогичных по характеристикам dsp и контроллеров по цене на порядок или около того ниже.


        1. sintech
          26.09.2025 17:10

          Как же не упрощает?

          Для процессора вы пишите код на языке высокого уровня в привычной среде, реализовать тоже самое на verilog в RTL будет намного сложнее и дольше.


          1. nv13
            26.09.2025 17:10

            А почему бы рядом с fpga не поставить обычный процессор на котором всё легко? Вот для sdr процессор непосредственно нужен? Не нужен. Почему же в который раз за историю его снова пытаются запихать в плис со всеми очевидными последствиями? Чтобы интерфейс не придумывать? Так он займёт чуть меньше, чем ничего по сравнению с таким же процессором в fpga.

            Мне правда непонятен текущий подход к архитектуре подобных устройств. 20 лет назад тоже говорили что fpga заменит всё, однако рынок всё расставил по местам - fpga это дорого, долго и трудно поддерживаемо в массовых устройствах. А сейчас, такое ощущение, что что то в этих обстоятельствах изменилось


            1. sintech
              26.09.2025 17:10

              fpga это дорого, долго и трудно поддерживаемо в массовых устройствах

              С этим тезисом никто и не спорит, но для небольших партий (относительно) или устройств специального назначения вполне логичной выглядит использование одной микросхемы fpga с софт-процессором.

              Для любителей привычного подхода есть целый класс гибридных микросхем, где ядра cortex соседствуют с программируемой матрицей на одном кристалле. Но в современных, жирных fpga проще использовать софт risc-v например.


              1. nv13
                26.09.2025 17:10

                А чем проще? Раньше можно было получить разрешение на stratex или любой xilinx, а вот на тмс или ад уже нет. Это из этого разряда простота?))

                Но этот путь он того, чреватый достаточно - слишком много политики и практически нет техники..


            1. nixtonixto
              26.09.2025 17:10

              А почему бы рядом с fpga не поставить обычный процессор на котором всё легко

              Ну вот понадобится вам производить 20 умножений ОДНОвременно - вы поставите 20 обычных процессоров? Или реализовать сложную логику, собирая сигналы с сотни ног и в реалтайме на частоте 50 МГц обрабатывать их. ПЛИС - вершина цифрового мира, когда процессора становится недостаточно - выход только один: ПЛИС и по результатам заказывать АСИК или пустить в серию ПЛИС, если тираж небольшой.


              1. nv13
                26.09.2025 17:10

                И как в такой задаче поможет программируемый процессор внутри плис?) Там же будет несколько умножителей и автомат, переключающий входы выходы - типичный препроцессор не требующий ничего похожего на программируемое устройство


                1. nixtonixto
                  26.09.2025 17:10

                  Внутри ПЛИС можно разместить процессор, у которого будет 20 аппаратных умножителей.


                  1. nv13
                    26.09.2025 17:10

                    И он будет программируемый, с системой команд, как описано в данной статье? Ему же тогда компилятор надо разработать, отладчик какой никакой.. и непонятно, зачем он нужен для 20 умножений на 50 МГц


                    1. nixtonixto
                      26.09.2025 17:10

                      и непонятно, зачем он нужен для 20 умножений на 50 МГц

                      Простейший пример - видеокарта, внутри которой процессор с тысячами вычислительных ядер. Можно ведь графику и на одноядерном процессоре обсчитывать, просто подняв его рабочую частоту в тысячи раз? Вот что-то такое и вынуждает создавать кастомные процессоры на ПЛИС.


                      1. nv13
                        26.09.2025 17:10

                        Да я собственно об этом и говорил - препроцессоры - да, как программируемые слегка, так и нет, типа фильтров и прочего, а вот процессоры общего назначения, в которых утилизация не очень близка к 1 и тратятся не дешёвые транзисторы, а дорогие ячейки - непонятно


  1. kmatveev
    26.09.2025 17:10

    a = 10; b = 5; op = 4'b0000; #10;

    что означает последнее #10 в этой строке?

    Есть подозрение, что статья не настоящая, а нейросетевая.


    1. HepoH
      26.09.2025 17:10

      Задержку в 10 отсчётов моделирования. Без нее сигналы на входе алу менялись бы мгновенно и на временной диаграмме было бы видно только результат последнего тестового вектора.


    1. PriFak
      26.09.2025 17:10

      это не по коду можно отловить. код то может и его, но реплики в тексте....человек так не разговаривает и не пишет


    1. laviol
      26.09.2025 17:10

      Вполне обычная задержка, которую используют в тестбенчах: объявляешь timeunit и используешь. Конструкция не синтезируема, но и используется только при моделировании. Довольно удобно, если есть какой-нибудь clocking block, смену фронта клока в котором, кстати, тоже можно задать через #, а потом сам клок можно использовать уже через двойную решетку ##.


  1. PriFak
    26.09.2025 17:10

    в качестве проекта под FPGA в унике я тоже сделал процессор. только 16 битный, основанный и собранный на процессоре с игры nandgame. и пока сама имплементация логических гейтов шла неплохо, я очень много времени тратил на тестбенчи и отлов неопределенных состояний - потому что оказывается, если твой логический вентиль не имеет неопределенного состяния, то это вовсе не значит что тот же АЛУ собраный из этих элементов будет работать безукорызненно. благо в самой игре были аналоги тестбенчей и я знал конкретно чего ожидать.
    В итоге на не совсем полностью моем процессоре удалось и диодом поморгать и числа фибоначчи посчитать
    а саму игру nandgame вообще всем могу посоветовать, залипнуть можно надолго


  1. checkpoint
    26.09.2025 17:10

    А где всё остальное ? Где декодер команд ? Где шина и арбитраж ? :-)


  1. gxcreator
    26.09.2025 17:10

    Напомню как снизить количество мусорных бессодержательных ИИ-статей с рекламой

    - минус статье

    - минус в карму автора

    - не оставлять комментарий для подогрева срача


    1. artmel
      26.09.2025 17:10

      Сам же и нарушил третье правило.


  1. here-we-go-again
    26.09.2025 17:10

    Есть игра Turing Complete, советую тем кому интересно повторить путь автора без железа для начала. Там тоже шаг за шагом соберешь компьютер. 32 регистра кстати оверкил, так-то можно и всего парочку иметь в простом процессоре + арифметика + логика + условный переход и это уже можно программировать.

    Там не дает при чем сразу готовые компоненты, а начинаешь с буквально отдельных гейтов.

    Мой компьютер из игры
    По центру блок регистров,  справа-вверху арифметический блок, справа-внизу логика и условные переходы, слева стек
    По центру блок регистров, справа-вверху арифметический блок, справа-внизу логика и условные переходы, слева стек