1 # Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
2 # Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
4 # Based on code from LambaConcept, from the gram example which is BSD-2-License
5 # https://github.com/jeanthom/gram/tree/master/examples
7 # Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
8 # under EU Grants 871528 and 957073, under the LGPLv3+ License
10 from nmigen
import (Module
, Elaboratable
, DomainRenamer
)
11 from nmigen
.lib
.cdc
import ResetSynchronizer
12 from nmigen_soc
import wishbone
, memory
13 from nmigen_soc
.memory
import MemoryMap
14 from nmigen_stdio
.serial
import AsyncSerial
16 from lambdasoc
.cpu
.minerva
import MinervaCPU
17 from lambdasoc
.periph
.intc
import GenericInterruptController
18 from lambdasoc
.periph
.sram
import SRAMPeripheral
19 from lambdasoc
.periph
.timer
import TimerPeripheral
20 from lambdasoc
.periph
import Peripheral
21 from lambdasoc
.soc
.base
import SoC
22 from soc
.bus
.uart_16550
import UART16550
# opencores 16550 uart
24 from gram
.core
import gramCore
25 from gram
.phy
.ecp5ddrphy
import ECP5DDRPHY
26 from gram
.modules
import MT41K256M16
27 from gram
.frontend
.wishbone
import gramWishbone
29 from nmigen_boards
.versa_ecp5
import VersaECP5Platform
30 from nmigen_boards
.ulx3s
import ULX3S_85F_Platform
31 from nmigen_boards
.arty_a7
import ArtyA7_100Platform
32 from nmigen_boards
.test
.blinky
import Blinky
34 from crg
import ECPIX5CRG
40 class DDR3SoC(SoC
, Elaboratable
):
43 ddrphy_addr
, dramcore_addr
,
44 ddr_addr
, fw_addr
=0x0000_0000,
48 # set up wishbone bus arbiter and decoder. arbiter routes,
49 # decoder maps local-relative addressed satellites to global addresses
50 self
._arbiter
= wishbone
.Arbiter(addr_width
=30, data_width
=32,
52 features
={"cti", "bte"})
53 self
._decoder
= wishbone
.Decoder(addr_width
=30, data_width
=32,
55 features
={"cti", "bte"})
57 # default firmware name
59 firmware
= "firmware/main.bin"
61 # set up clock request generator
62 self
.crg
= ECPIX5CRG()
64 # set up CPU, and interrupt interface
66 self
.cpu
= MinervaCPU(reset_address
=0)
67 self
._arbiter
.add(self
.cpu
.ibus
) # I-Cache Master
68 self
._arbiter
.add(self
.cpu
.dbus
) # D-Cache Master. TODO JTAG master
69 self
.intc
= GenericInterruptController(width
=len(self
.cpu
.ip
))
71 # SRAM (but actually a ROM, for firmware), at address 0x0
72 if fw_addr
is not None:
73 self
.rom
= SRAMPeripheral(size
=4096, writable
=False)
74 with
open(firmware
, "rb") as f
:
75 words
= iter(lambda: f
.read(self
.cpu
.data_width
// 8), b
'')
76 bios
= [int.from_bytes(w
, self
.cpu
.byteorder
) for w
in words
]
78 self
._decoder
.add(self
.rom
.bus
, addr
=fw_addr
) # ROM at fw_addr
80 # SRAM (read-writeable BRAM)
81 self
.ram
= SRAMPeripheral(size
=4096)
82 self
._decoder
.add(self
.ram
.bus
, addr
=0x8000000) # SRAM at 0x8000_000
85 self
.uart
= UART16550()
86 umap
= MemoryMap(addr_width
=7, data_width
=8, name
="uart_map")
87 #umap.add_resource(self._mem, name="mem", size=1<<5)
88 self
.uart
.bus
.memory_map
= umap
90 self
._decoder
.add(self
.uart
.bus
, addr
=0xc0002000) # 16550 UART address
93 self
.ddrphy
= DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins
,
95 self
._decoder
.add(self
.ddrphy
.bus
, addr
=ddrphy_addr
)
97 ddrmodule
= MT41K256M16(clk_freq
, "1:2") # match DDR3 ASIC P/N
99 drs
= DomainRenamer("dramsync")
100 dramcore
= gramCore(phy
=self
.ddrphy
,
101 geom_settings
=ddrmodule
.geom_settings
,
102 timing_settings
=ddrmodule
.timing_settings
,
104 self
.dramcore
= drs(dramcore
)
105 self
._decoder
.add(self
.dramcore
.bus
, addr
=dramcore_addr
)
107 # map the DRAM onto Wishbone
108 self
.drambone
= drs(gramWishbone(self
.dramcore
))
109 self
._decoder
.add(self
.drambone
.bus
, addr
=ddr_addr
)
111 self
.memory_map
= self
._decoder
.bus
.memory_map
113 self
.clk_freq
= clk_freq
115 def elaborate(self
, platform
):
119 # add the peripherals and clock-reset-generator
120 m
.submodules
.sysclk
= self
.crg
122 if hasattr(self
, "rom"):
123 m
.submodules
.rom
= self
.rom
124 m
.submodules
.ram
= self
.ram
125 m
.submodules
.uart
= self
.uart
127 m
.submodules
.intc
= self
.intc
128 m
.submodules
.cpu
= self
.cpu
129 m
.submodules
.arbiter
= self
._arbiter
130 m
.submodules
.decoder
= self
._decoder
131 m
.submodules
.ddrphy
= self
.ddrphy
132 m
.submodules
.dramcore
= self
.dramcore
133 m
.submodules
.drambone
= self
.drambone
135 # add blinky lights so we know FPGA is alive
136 m
.submodules
.blinky
= Blinky()
138 # connect the arbiter (of wishbone masters)
139 # to the decoder (addressing wishbone slaves)
140 comb
+= self
._arbiter
.bus
.connect(self
._decoder
.bus
)
143 # wire up the CPU interrupts
144 comb
+= self
.cpu
.ip
.eq(self
.intc
.ip
)
146 # add uart16550 verilog source. assumes a directory
147 # structure where ls2 has been checked out in a common
148 # subdirectory as https://github.com/freecores/uart16550
149 opencores_16550
= "../../uart16550/rtl/verilog"
150 pth
= os
.path
.split(__file__
)[0]
151 pth
= os
.path
.join(pth
, opencores_16550
)
152 fname
= os
.path
.abspath(pth
)
154 self
.uart
.add_verilog_source(fname
, platform
)
159 if __name__
== "__main__":
161 # create a platform selected from the toolchain. defaults to VERSA_ECP5
162 # only VERSA_ECP5 will work for now because of the DDR3 module
164 if len(sys
.argv
) >= 2:
166 platform_kls
= {'versa_ecp5': VersaECP5Platform
,
167 'ulx3s': ULX3S_85F_Platform
,
168 'arty_a7': ArtyA7_100Platform
,
170 toolchain
= {'arty_a7': "yosys_nextpnr",
171 'versa_ecp5': 'Trellis',
174 platform
= platform_kls(toolchain
=toolchain
)
176 # select a firmware file
179 if len(sys
.argv
) >= 3:
180 firmware
= sys
.argv
[2]
181 fw_addr
= 0x0000_0000
183 # get DDR and UART resource pins
184 ddr_pins
= platform
.request("ddr3", 0,
185 dir={"dq":"-", "dqs":"-"},
186 xdr
={"clk":4, "a":4, "ba":4, "clk_en":4,
187 "odt":4, "ras":4, "cas":4, "we":4})
188 uart_pins
= platform
.request("uart", 0)
191 soc
= DDR3SoC(ddrphy_addr
=0xff000000, # DRAM firmware init base
192 dramcore_addr
=0x80000000,
199 # build and upload it
200 platform
.build(soc
, do_program
=True)