Блог

Развертывание LiteX SoC с процессором VexRiscv на отладочной плате VE-10CL025.

Продолжая цикл статей, посвященных RISC-V, в новой статье расскажем как создать собственную SOC с ядром процессора RISC-V, используя систему LiteX. LiteX это генератор Core/SoC основанный на Migen/MiSoC , который предоставляет инфраструктуру для легкого создания ядер SoC (с процессором или без него). Общие компоненты SoC предоставляются непосредственно: Шины и потоки (Wishbone, AXI, Avalon-ST), интерконнект, общие ядра (RAM, ROM, Timer, UART и т. д...), Процессорные оболочки/интеграция и т. д... а возможности создания SoC могут быть значительно расширены с помощью экосистемы ядер LiteX (DRAM, PCIe, Ethernet, SATA и т. д...), которые могут быть легко интегрированы/смоделированы/построены с помощью LiteX. Он также предоставляет бэкенды сборки для цепочек инструментов с открытым исходным кодом.

В общем случае Migen используется для создания дизайнов ПЛИС на языке Python, а LiteX как генератор SOC для создания/разработки/отладки систем на кристалле ПЛИС на языке Python.

Репозиторий проекта находится по адресу: https://github.com/enjoy-digital/litex. Для наших экспериментов будем использовать WSL (Windows Subsystem for Linux). Для начала запустим WSL и перейдем в домашний каталог:

Bash Code:
  1. $cd /home/vise

Консоль WSL

Далее создадим каталог для развертывания LiteX, и перейдем в него:

Bash Code:
  1. $ mkdir risc-v
  2. cd risc-v

На следующем шаге необходимо установить Python 3.6+ в WSL, а в самой windows среду разработки для целевой FPGA. В нашем случае это Quartus V19.1.

Устанавливаеи Migen/LiteX и ядра LiteX'а:

Bash Code:
  1. $ wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py
  2. $ chmod +x litex_setup.py
  3. $ sudo ./litex_setup.py init install --user (--user to install to user directory)

Далее, если необходимо, обновляем репозитории:

Bash Code:
  1. $ ./litex_setup.py update

Если вы используете MacOS, убедитесь, что пакет HomeBrew установлен. После выполните, brew install wget.

Если вы используете Windows, Возможно вам придется установить переменную SHELL в SHELL=cmd.exe.

Устанавливаем RISC-V тулчейн (Если вы собираетесь тестировать/создавать SoC с процессором):

Bash Code:
  1. $ ./litex_setup.py gcc

Добавляем путь до нашего компилятора в переменную PATH:

Bash Code:
  1. $ export PATH=$PATH:$(echo $PWD/riscv64-*/bin/)

В принципе, если бы вы использовали плату, которую поддерживает LiteX, после этого шага, мы уже могли бы создать целевую SOC. Но нашей платы пока в репозитории нет. Поэтому нам надо изменить некоторые файлы. Сначала по пути /home/vise/risc-v/litex-boards/litex_boards/platforms мы создадим файл ve10cl025.py описывающий архитектуру нашей платы:

Python Code:
  1. # This file is Copyright (c) 2020 Dmitriy Kiryanov <v2016e@gmail.com>
  2. # License: BSD
  3.  
  4. from litex.build.generic_platform import *
  5. from litex.build.altera import AlteraPlatform
  6. from litex.build.altera.programmer import USBBlaster
  7.  
  8. # IOs ----------------------------------------------------------------------------------------------
  9. _io = [
  10. ("clk50", 0, Pins("23"), IOStandard("3.3-V LVTTL")),
  11.  
  12. ("key", 0, Pins("24"), IOStandard("3.3-V LVTTL")),
  13. ("key", 1, Pins("25"), IOStandard("3.3-V LVTTL")),
  14. ("key", 0, Pins("52"), IOStandard("3.3-V LVTTL")),
  15. ("key", 1, Pins("53"), IOStandard("3.3-V LVTTL")),
  16.  
  17. ("serial", 0,
  18. Subsignal("rx", Pins("10"), IOStandard("3.3-V LVTTL")),
  19. Subsignal("tx", Pins("11"), IOStandard("3.3-V LVTTL")),
  20.  
  21. ),
  22.  
  23. ("hdmi_out", 0,
  24. Subsignal("clk_p", Pins("105"), Inverted(), IOStandard("LVDS")),
  25. Subsignal("clk_n", Pins("106"), Inverted(), IOStandard("LVDS")),
  26. Subsignal("data0_p", Pins("7"), IOStandard("LVDS")),
  27. Subsignal("data0_n", Pins("98"), IOStandard("LVDS")),
  28. Subsignal("data1_p", Pins("99"), IOStandard("LVDS")),
  29. Subsignal("data1_n", Pins("100"), IOStandard("LVDS")),
  30. Subsignal("data2_p", Pins("101"), IOStandard("LVDS")),
  31. Subsignal("data2_n", Pins("103"), IOStandard("LVDS")),
  32. Misc("DRIVE=4"),
  33. ),
  34.  
  35. # sdram (MT48LC8M8A2P-6A)
  36. ("sdram_clock", 0, Pins("76"), IOStandard("3.3-V LVTTL")),
  37. ("sdram", 0,
  38. Subsignal("a", Pins("28 31 32 33 39 42 43 44 46 49 50 51")),
  39. Subsignal("dq", Pins("58 59 60 65 66 67 68 69")),
  40. Subsignal("we_n", Pins("77")),
  41. Subsignal("ras_n", Pins("83")),
  42. Subsignal("cas_n", Pins("80")),
  43. Subsignal("ba", Pins("71 72")),
  44. Subsignal("dm", Pins("85")),
  45. IOStandard("3.3-V LVTTL")
  46. ),
  47.  
  48. # epcs (EPCS16SI8N)
  49. ("epcs", 0,
  50. Subsignal("data0", Pins("13")),
  51. Subsignal("dclk", Pins("12")),
  52. Subsignal("ncs0", Pins("8")),
  53. Subsignal("asd0", Pins("6")),
  54. IOStandard("3.3-V LVTTL")
  55. ),
  56.  
  57. # ethernet (KSZ9031)
  58. ("eth_clocks", 0,
  59. Subsignal("tx", Pins("114")),
  60. Subsignal("rx", Pins("135")),
  61. IOStandard("3.3-V LVTTL"),
  62. ),
  63.  
  64. ("eth", 0,
  65. Subsignal("rst_n", Pins("113")),
  66. Subsignal("mdio", Pins("111")),
  67. Subsignal("mdc", Pins("112")),
  68. Subsignal("rx_dv", Pins("136")),
  69. Subsignal("rx_er", Pins("")),
  70. Subsignal("rx_data", Pins("137 141 142 143")),
  71. Subsignal("tx_en", Pins("115")),
  72. Subsignal("tx_data", Pins("119 120 121 125")),
  73. Subsignal("col", Pins("")),
  74. Subsignal("crs", Pins("")),
  75. IOStandard("3.3-V LVTTL"),
  76. ),
  77.  
  78. ]
  79.  
  80. # Platform -----------------------------------------------------------------------------------------
  81.  
  82. class Platform(AlteraPlatform):
  83. default_clk_name = "clk50"
  84. default_clk_period = 1e9/12e6
  85.  
  86. def __init__(self):
  87. AlteraPlatform.__init__(self, "10CL025YE144A7G", _io)
  88.  
  89. def create_programmer(self):
  90. return USBBlaster()
  91.  
  92. def do_finalize(self, fragment):
  93. AlteraPlatform.do_finalize(self, fragment)
  94. self.add_period_constraint(self.lookup_request("clk50", loose=True), 1e9/50e6)

Далее по пути /home/vise/risc-v/litex-boards/litex_boards/targets/ мы также создадим файл ve10cl025.py описывающий архитектуру нашей SOC:

Python Code:
  1. #!/usr/bin/env python3
  2.  
  3. # This file is Copyright (c) 2020 Dmitriy Kiryanov <v2016e@gmail.com>
  4. # License: BSD
  5.  
  6. import os
  7. import argparse
  8.  
  9. from migen import *
  10. from migen.genlib.resetsync import AsyncResetSynchronizer
  11.  
  12. from litex.build.io import DDROutput
  13.  
  14. from litex_boards.platforms import ve10cl025
  15.  
  16. from litex.soc.cores.clock import Cyclone10LPPLL
  17. from litex.soc.integration.soc_core import *
  18. from litex.soc.integration.soc_sdram import *
  19. from litex.soc.integration.builder import *
  20.  
  21. from litedram.modules import MT48LC8M8
  22. from litedram.phy import GENSDRPHY
  23.  
  24. from liteeth.phy.s7rgmii import LiteEthPHYRGMII
  25. from litevideo.output import VideoOut
  26.  
  27. # CRG ----------------------------------------------------------------------------------------------
  28.  
  29. class _CRG(Module):
  30. def __init__(self, platform, sys_clk_freq):
  31. self.clock_domains.cd_sys = ClockDomain()
  32. self.clock_domains.cd_sys_ps = ClockDomain(reset_less=True)
  33.  
  34. # # #
  35.  
  36. # Clk / Rst
  37. clk50 = platform.request("clk50")
  38.  
  39. # PLL
  40. self.submodules.pll = pll = Cyclone10LPPLL(speedgrade="-A7")
  41. pll.register_clkin(clk50, 50e6)
  42. pll.create_clkout(self.cd_sys, sys_clk_freq)
  43. pll.create_clkout(self.cd_sys_ps, sys_clk_freq, phase=90)
  44.  
  45. # SDRAM clock
  46. self.comb += platform.request("sdram_clock").eq(self.cd_sys_ps.clk)
  47.  
  48. # BaseSoC ------------------------------------------------------------------------------------------
  49.  
  50. class BaseSoC(SoCCore):
  51. def __init__(self, sys_clk_freq=int(50e6), with_ethernet=True, with_hdmi=False, **kwargs):
  52. platform = ve10cl025.Platform()
  53.  
  54. # SoCCore ----------------------------------------------------------------------------------
  55. SoCCore.__init__(self, platform, sys_clk_freq,
  56. ident = "LiteX SoC on VE-10CL025",
  57. ident_version = True,
  58. **kwargs)
  59.  
  60. # CRG --------------------------------------------------------------------------------------
  61. self.submodules.crg = _CRG(platform, sys_clk_freq)
  62.  
  63. # SDR SDRAM --------------------------------------------------------------------------------
  64. if not self.integrated_main_ram_size:
  65. self.submodules.sdrphy = GENSDRPHY(platform.request("sdram"))
  66. self.add_sdram("sdram",
  67. phy = self.sdrphy,
  68. module = MT48LC8M8(sys_clk_freq, "1:1"),
  69. origin = self.mem_map["main_ram"],
  70. size = kwargs.get("max_sdram_size", 0x04000000),
  71. l2_cache_size = kwargs.get("l2_size", 8192),
  72. l2_cache_min_data_width = kwargs.get("min_l2_data_width", 128),
  73. l2_cache_reverse = True
  74. )
  75.  
  76. # Ethernet ---------------------------------------------------------------------------------
  77. if with_ethernet:
  78. self.submodules.ethphy = LiteEthPHYRGMII(
  79. clock_pads = self.platform.request("eth_clocks"),
  80. pads = self.platform.request("eth"))
  81. self.add_csr("ethphy")
  82. self.add_ethernet(phy=self.ethphy)
  83.  
  84. # hdmi out ---------------------------------------------------------------------------------
  85. if with_hdmi:
  86. hdmioutdramport = self.sdram.crossbar.getport(
  87. mode="read",
  88. dw=8,
  89. cd="hdmi_out0_pix" ,
  90. reverse=True)
  91.  
  92. self.submodules.hdmi_out0 = VideoOut(
  93. platform.device,
  94. platform.request("hdmi_out", 0),
  95. hdmi_out0_dram_port,
  96. "ycbcr422",
  97. fifodepth = 4096)
  98. # Build --------------------------------------------------------------------------------------------
  99.  
  100. def main():
  101. parser = argparse.ArgumentParser(description="LiteX SoC on VE-10CL025")
  102. parser.add_argument("--build", action="store_true", help="Build bitstream")
  103. parser.add_argument("--load", action="store_true", help="Load bitstream")
  104. builder_args(parser)
  105. soc_sdram_args(parser)
  106. parser.add_argument("--with-ethernet", action="store_true", help="Enable Ethernet support")
  107. parser.add_argument("--with-hdmi", action="store_true", help="Enable HDMI support")
  108. args = parser.parse_args()
  109.  
  110. soc = BaseSoC(with_ethernet=args.with_ethernet,
  111. **soc_sdram_argdict(args))
  112. builder = Builder(soc, **builder_argdict(args))
  113. builder.build(run=args.build)
  114.  
  115. if args.load:
  116. prog = soc.platform.create_programmer()
  117. prog.load_bitstream(os.path.join(builder.gateware_dir, soc.build_name + ".sof"))
  118.  
  119. if __name__ == "__main__":
  120. main()

В файл по адресу /home/vise/risc-v/litedram/litedram/modules.py добавляем модель памяти, установленной на нашей плате:

Python Code:
  1. class MT48LC8M8(SDRModule):
  2. # geometry
  3. nbanks = 4
  4. nrows = 4096
  5. ncols = 1024
  6. # timings
  7. technology_timings = _TechnologyTimings(tREFI=64e6/8192, tWTR=(2, None), tCCD=(1, None), tRRD=(None, 15))
  8. speedgrade_timings = {"default": _SpeedgradeTimings(tRP=20, tRCD=20, tWR=15, tRFC=(None, 66), tFAW=None, tRAS=44)}

Заходим в директорию с нашей целевой платой:

Bash Code:
  1. $ cd /home/vise/risc-v/litex-boards/litex_boards/targets

Теперь можно запустить сборку нашего SOC!

Bash Code:
  1. $ ./ve10cl025.py

Мы должны увидеть подобную картину с консолью сборки:

Консоль WSL

После этих действий, по адресу /home/vise/risc-v/litex-boards/litex_boards/targets/build/ve10cl025/ появится две папки gateware и software. Папка gateware содержит дизайн для нашей платы, а папка software содержит программное обеспечение, для сгенерированной SOC. В принципе если установить Quartus в WSL, можно запустить скрипт gateware/build_ve10cl025.sh для генерации прошивки для нашей FPGA, а можно создать проект в Quartus для Windows:

LiteX проект Quartus

Ну и по традиции видео работы, и исходники:

Проект: litex_riscv