Блог

Создание процессора со свободная архитектурой RISC-V. Часть 2.

В предидущей статье Создание процессора со свободная архитектурой RISC-V. Часть 1. мы рассказали об истории появления, и основных архитектурных решениях микропроцессорной архитектуры со свободным набором комманд RISC-V. Во второй части мы покажем, как можно реализовать FPGA версию микропроцессора RISC-V на языке SystemVerilog. Так-же мы получим в свое распоряжение программы для компиляции ассемблерного кода процессора, и возможность отладки с выводом информации на VGA дисплей и USART консоль. Полученная реализация имеет следующие особенности:

  • CPU*: 5-стадийный конвейер RISC-V, который может выполнять большинство инструкций из набора инструкций RV32I 
  • Шина*: Простой и интуитивно понятный, механизм арбитража, 32-битныая ширина шины адреса и 32-битная ширина шины данных
  • Арбитраж шины*: может быть изменен с помощью определения макросов для облегчения расширения периферийных устройств, DMA, многоядерных реализаций и т. д
  • Интерактивная отладка UART*: поддержка использования Putty на ПК, написанной на C# программы загрузчика через последовательный порт, minicom и другого программного обеспечения для выполнения следующих действий: сброс системы, загрузка программы, просмотр памяти и т. д.
  • Чистая реализация RTL*: Полностью используется SystemVerilog, не используются IP-ядра, легко переносится и эмулируется RAM и ROM, их описание соответствуют описанию на языке Verilog, которые автоматически синтезируются в BLOCK RAM

Структура SOC

pic1

Рисунок выше показывает структуру SoC. Арбитр шины bus_router является центральным элементом SoC, который имеет три ведущих интерфейса и пять подчиненных интерфейсов. Шина, используемая этим SoC, не относится к какому либо стандарту (например, к шине AXI или APB), это простая авторская шина, которая называется naive_bus.

Каждый подчиненный интерфейс занимает адресное пространство. Когда первичный интерфейс обращается к шине, bus_router определяет, к какому адресному пространству принадлежит адрес, и затем направляет его в соответствующий подчиненный интерфейс. В следующей таблице показано адресное пространство для пяти подчиненных интерфейсов.

Тип перефирииНачальный адресКонечный адрес
ROM Инструкций 0x00000000 0x00007fff
RAM Инструкций 0x00008000 0x00008fff
RAM Данных 0x00010000 0x00010fff
Память VGA 0x00020000 0x00020fff
Память UART 0x00030000 0x00030003

Описание компонентов арбитра памяти.

  • Арбитр с несколькими подчиненными шинами: соответствующий файл naive_bus_router.sv. Адресное пространство каждого подчиненного устройства разделяется, и запросы на чтение и запись шины ведущего стройства направляются на подчиненное устройство. Когда несколько ведущих устройств одновременно получают доступ к подчиненному устройству, конфликтный арбитраж также может выполняться в соответствии с риоритетом ведущего устройства.
  • RV32I Core: соответствующий файл core_top.sv. Включает два основных интерфейса. Один используется для получения инструкций, а другой - для чтения и записи данных.
  • Отладчик UART: соответствующий файл isp_uart.sv. Объедините функцию отладки UART с пользователем UART. Включает основной интерфейс и дополнительный интерфейс. Он получает команду, отправленную хост-компьютером из UART, на чтение и запись шины. Он может быть использован для онлайн-программирования и онлайн-отладки. Также возможно получать команды от CPU для отправки данных пользователю.
  • Командное ПЗУ: соответствующий файл instr_rom.sv. По умолчанию процессор получает инструкции отсюда.Поток команд фиксируется, когда аппаратный код компилируется и синтезируется, и не может быть изменен во время выполнения. Единственный способ изменить его - отредактировать код в instr_rom.sv, а затем перекомпилировать логику синтеза и программирования ПЛИС. Поэтому instr_rom в основном используется для симуляции. 
  • Инструкция RAM: соответствующий файл ram_bus_wrapper.sv. Пользователь использует для этого здесь инструкцию онлайн-программирования isp_uart, затем указывает адрес загрузки здесь, и после сброса SoC ЦПУ начинает отсюда выполнять поток инструкций. 
  • Данные RAM: Соответствующий файл ram_bus_wrapper.sv. Храните данные во время выполнения.
  • Память VGA: Соответствующий файл video_ram.sv. На экране отображаются 86 столбцов * 32 строки = 2752 символа. 4096B оперативной памяти разделено на 32 блока, по одному на каждый блок, 128B, и первые 86 байтов для 86 столбцов. На экране отображается символ, соответствующий каждому коду ASCII.

Характеристики процессора

Поддержка: все команды Load, Store, Arithmetic, Logic, Shift, Compare, Jump из набора RV32I.

Не поддерживается: синхронизация, состояние управления, вызов среды и инструкции класса точки останова

Все поддерживаемые инструкции включают в себя: LB, LH, LW, LBU, LHU, SB, SH, SW, ADD, ADDI, SUB, LUI, AUIPC, XOR, XORI, OR, ORI, AND, ANDI, SLL, SLLI, SRL, SRL, SRA, SRAI, SLT, SLTI, SLTU, SLTIU, BEQ, BNE, BLT, BGE, BLTU, BGEU, JAL, JALR

Для набора команд вы можете рассмотреть возможность добавления команд умножения и деления в RV32IM в будущем.

Процессор использует 5-стадийный конвейер. В настоящее время поддерживаются следующие функции конвейера: Forward, Loaduse, Bus wait

С точки зрения трубопроводов, характеристики, которые будут добавлены в будущем: Предсказание ветвлений, прерывания

Пример кода на языке ассемблера, отправляющий данные в usart:

ASM Code:
  1. .org 0x0
  2. .global _start
  3. _start:
  4. # Step 1: Let t0 register = 0x00030000, the address of the user_uart peripheral
  5. or t0, zero,zero # t0 Clear
  6. lui t0, 0x00030 # t0 Register high 20bit=0x00020
  7.  
  8. # Step 2: Write hello! to the user_uart peripheral character by character, ie print hello! to uart
  9. print_hello:
  10. ori t1, zero, 0x068 # t1='h'ASCII code
  11. sb t1, (t0) # T1 write t0 address
  12. ori t1, zero, 0x065 # t1='e'ASCII code
  13. sb t1, (t0) # T1 write t0 address
  14. ori t1, zero, 0x06c # t1='l'ASCII code
  15. sb t1, (t0) # T1 write t0 address
  16. ori t1, zero, 0x06c # t1='l'ASCII code
  17. sb t1, (t0) # T1 write t0 address
  18. ori t1, zero, 0x06f # t1='o'ASCII code
  19. sb t1, (t0) # T1 write t0 address
  20. ori t1, zero, 0x00a # t1='\n'ASCII code
  21. sb t1, (t0) # T1 write t0 address
  22.  
  23. # The third step: delay, through the way of large air circulation
  24. lui t2, 0x00c00 # t2 = 0x00c00000
  25. big_loop:
  26. addi t2, t2, -1 # t2 = t2-1
  27. bne t2, zero, big_loop # if t2!=0, jmp to big_loop
  28. jal zero, print_hello # The end of the big loop, jump to print_hello, repeat printing
  29.  

Утилита для компиляции исходного кода:

pic2

Утилита отладчика для последовательного порта:

pic3