examples/minerva_soc: fix typo.
[lambdasoc.git] / examples / minerva_soc.py
1 import argparse
2 from collections import OrderedDict
3
4 from nmigen import *
5 from nmigen.lib.cdc import ResetSynchronizer
6 from nmigen.build import *
7
8 from nmigen_soc import wishbone
9 from nmigen_soc.periph import ConstantMap
10
11 from nmigen_stdio.serial import AsyncSerial
12
13 from nmigen_boards.arty_a7 import ArtyA7_35Platform
14 from nmigen_boards.ecpix5 import ECPIX545Platform, ECPIX585Platform
15
16 from lambdasoc.cpu.minerva import MinervaCPU
17 from lambdasoc.periph.intc import GenericInterruptController
18 from lambdasoc.periph.serial import AsyncSerialPeripheral
19 from lambdasoc.periph.sram import SRAMPeripheral
20 from lambdasoc.periph.timer import TimerPeripheral
21 from lambdasoc.periph.sdram import SDRAMPeripheral
22 from lambdasoc.periph.eth import EthernetMACPeripheral
23
24 from lambdasoc.soc.cpu import CPUSoC, BIOSBuilder
25
26 from lambdasoc.cores.pll.lattice_ecp5 import PLL_LatticeECP5
27 from lambdasoc.cores.pll.xilinx_7series import PLL_Xilinx7Series
28 from lambdasoc.cores import litedram, liteeth
29 from lambdasoc.cores.utils import request_bare
30
31 from lambdasoc.sim.blackboxes.serial import AsyncSerial_Blackbox
32 from lambdasoc.sim.platform import CXXRTLPlatform
33
34
35 __all__ = ["MinervaSoC"]
36
37
38 class _ClockResetGenerator(Elaboratable):
39 def __init__(self, *, sync_clk_freq, with_sdram, with_ethernet):
40 if not isinstance(sync_clk_freq, (int, float)) or sync_clk_freq <= 0:
41 raise ValueError("Sync domain clock frequency must be a positive integer or float, "
42 "not {!r}"
43 .format(sync_clk_freq))
44 self.sync_clk_freq = sync_clk_freq
45 self.with_sdram = bool(with_sdram)
46 self.with_ethernet = bool(with_ethernet)
47
48 def elaborate(self, platform):
49 m = Module()
50
51 m.domains += [
52 ClockDomain("_ref", reset_less=platform.default_rst is None, local=True),
53 ClockDomain("sync"),
54 ]
55
56 m.d.comb += ClockSignal("_ref").eq(platform.request(platform.default_clk, 0).i)
57 if platform.default_rst is not None:
58 m.d.comb += ResetSignal("_ref").eq(platform.request(platform.default_rst, 0).i)
59
60 # On the Arty A7, the DP83848 Ethernet PHY uses a 25 MHz reference clock.
61 if isinstance(platform, ArtyA7_35Platform) and self.with_ethernet:
62 m.domains += ClockDomain("_eth_ref", local=True)
63 m.submodules += Instance("BUFGCE",
64 i_I = ClockSignal("_eth_ref"),
65 i_CE = ~ResetSignal("_eth_ref"),
66 o_O = platform.request("eth_clk25", 0).o,
67 )
68
69 # The LiteDRAM core provides its own PLL, which drives the litedram_user clock domain.
70 # We reuse this clock domain as the sync domain, in order to avoid CDC between LiteDRAM
71 # and the SoC interconnect.
72 if self.with_sdram:
73 m.domains += ClockDomain("litedram_input")
74 m.d.comb += ClockSignal("litedram_input").eq(ClockSignal("_ref"))
75 if platform.default_rst is not None:
76 m.d.comb += ResetSignal("litedram_input").eq(ResetSignal("_ref"))
77
78 m.domains += ClockDomain("litedram_user")
79 m.d.comb += [
80 ClockSignal("sync").eq(ClockSignal("litedram_user")),
81 ResetSignal("sync").eq(ResetSignal("litedram_user")),
82 ]
83
84 # On the Arty A7, we still use our own PLL to drive the Ethernet PHY reference clock.
85 if isinstance(platform, ArtyA7_35Platform) and self.with_ethernet:
86 eth_ref_pll_params = PLL_Xilinx7Series.Parameters(
87 i_domain = "_ref",
88 i_freq = platform.default_clk_frequency,
89 i_reset_less = platform.default_rst is None,
90 o_domain = "_eth_ref",
91 o_freq = 25e6,
92 )
93 m.submodules.eth_ref_pll = eth_ref_pll = PLL_Xilinx7Series(eth_ref_pll_params)
94
95 if platform.default_rst is not None:
96 eth_ref_pll_arst = ~eth_ref_pll.locked | ResetSignal("_ref")
97 else:
98 eth_ref_pll_arst = ~eth_ref_pll.locked
99
100 m.submodules += ResetSynchronizer(eth_ref_pll_arst, domain="_eth_ref")
101
102 # In simulation mode, the sync clock domain is directly driven by the platform clock.
103 elif isinstance(platform, CXXRTLPlatform):
104 assert self.sync_clk_freq == platform.default_clk_frequency
105 m.d.comb += ClockSignal("sync").eq(ClockSignal("_ref"))
106 if platform.default_rst is not None:
107 m.d.comb += ResetSignal("sync").eq(ResetSignal("_ref"))
108
109 # Otherwise, we use a PLL to drive the sync clock domain.
110 else:
111 if isinstance(platform, ArtyA7_35Platform):
112 sync_pll_params = PLL_Xilinx7Series.Parameters(
113 i_domain = "_ref",
114 i_freq = platform.default_clk_frequency,
115 i_reset_less = platform.default_rst is None,
116 o_domain = "sync",
117 o_freq = self.sync_clk_freq,
118 )
119 if self.with_ethernet:
120 sync_pll_params.add_secondary_output(domain="_eth_ref", freq=25e6)
121 m.submodules.sync_pll = sync_pll = PLL_Xilinx7Series(sync_pll_params)
122 elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)):
123 sync_pll_params = PLL_LatticeECP5.Parameters(
124 i_domain = "_ref",
125 i_freq = platform.default_clk_frequency,
126 i_reset_less = platform.default_rst is None,
127 o_domain = "sync",
128 o_freq = self.sync_clk_freq,
129 )
130 m.submodules.sync_pll = sync_pll = PLL_LatticeECP5(sync_pll_params)
131 else:
132 assert False
133
134 if platform.default_rst is not None:
135 sync_pll_arst = ~sync_pll.locked | ResetSignal("_ref")
136 else:
137 sync_pll_arst = ~sync_pll.locked
138
139 m.submodules += ResetSynchronizer(sync_pll_arst, domain="sync")
140 if isinstance(platform, ArtyA7_35Platform) and self.with_ethernet:
141 m.submodules += ResetSynchronizer(sync_pll_arst, domain="_eth_ref")
142
143 return m
144
145
146 class MinervaSoC(CPUSoC, Elaboratable):
147 def __init__(self,
148 sync_clk_freq,
149 cpu_core,
150 bootrom_addr,
151 bootrom_size,
152 scratchpad_addr,
153 scratchpad_size,
154 uart_core,
155 uart_addr,
156 uart_irqno,
157 timer_addr,
158 timer_width,
159 timer_irqno):
160 if not isinstance(sync_clk_freq, (int, float)) or sync_clk_freq <= 0:
161 raise ValueError("Sync domain clock frequency must be a positive integer or float, "
162 "not {!r}"
163 .format(sync_clk_freq))
164 self.sync_clk_freq = int(sync_clk_freq)
165
166 if not isinstance(cpu_core, MinervaCPU):
167 raise TypeError("CPU core must be an instance of MinervaCPU, not {!r}"
168 .format(cpu_core))
169 self.cpu = cpu_core
170
171 self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32, granularity=8,
172 features={"cti", "bte", "err"})
173 self._decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8,
174 features={"cti", "bte", "err"})
175
176 self._arbiter.add(self.cpu.ibus)
177 self._arbiter.add(self.cpu.dbus)
178
179 self.intc = GenericInterruptController(width=len(self.cpu.ip))
180
181 self.bootrom = SRAMPeripheral(size=bootrom_size, writable=False)
182 self._decoder.add(self.bootrom.bus, addr=bootrom_addr)
183
184 self.scratchpad = SRAMPeripheral(size=scratchpad_size)
185 self._decoder.add(self.scratchpad.bus, addr=scratchpad_addr)
186
187 self.uart = AsyncSerialPeripheral(core=uart_core)
188 self._decoder.add(self.uart.bus, addr=uart_addr)
189 self.intc.add_irq(self.uart.irq, index=uart_irqno)
190
191 self.timer = TimerPeripheral(width=timer_width)
192 self._decoder.add(self.timer.bus, addr=timer_addr)
193 self.intc.add_irq(self.timer.irq, index=timer_irqno)
194
195 self._sdram = None
196 self._sram = None
197 self._ethmac = None
198
199 @property
200 def memory_map(self):
201 return self._decoder.bus.memory_map
202
203 @property
204 def constants(self):
205 return super().constants.union(
206 SDRAM = self.sdram .constant_map if self.sdram is not None else None,
207 ETHMAC = self.ethmac.constant_map if self.ethmac is not None else None,
208 SOC = ConstantMap(
209 WITH_SDRAM = self.sdram is not None,
210 WITH_ETHMAC = self.ethmac is not None,
211 MEMTEST_ADDR_SIZE = 8192,
212 MEMTEST_DATA_SIZE = 8192,
213 ),
214 )
215
216 @property
217 def mainram(self):
218 assert not (self._sdram and self.sram)
219 return self._sdram or self._sram
220
221 @property
222 def sdram(self):
223 return self._sdram
224
225 @property
226 def sram(self):
227 return self._sram
228
229 def add_sdram(self, core, *, addr, cache_size):
230 if self.mainram is not None:
231 raise AttributeError("Main RAM has already been set to {!r}".format(self.mainram))
232 if core.config.user_clk_freq != self.sync_clk_freq:
233 raise ValueError("LiteDRAM user domain clock frequency ({} MHz) must match sync "
234 "domain clock frequency ({} MHz)"
235 .format(core.config.user_clk_freq / 1e6, self.sync_clk_freq / 1e6))
236 self._sdram = SDRAMPeripheral(core=core, cache_size=cache_size)
237 self._decoder.add(self._sdram.bus, addr=addr)
238
239 def add_internal_sram(self, *, addr, size):
240 if self.mainram is not None:
241 raise AttributeError("Main RAM has already been set to {!r}".format(self.mainram))
242 self._sram = SRAMPeripheral(size=size)
243 self._decoder.add(self._sram.bus, addr=addr)
244
245 @property
246 def ethmac(self):
247 return self._ethmac
248
249 def add_ethmac(self, core, *, addr, irqno, local_ip, remote_ip):
250 if self._ethmac is not None:
251 raise AttributeError("Ethernet MAC has already been set to {!r}"
252 .format(self._ethmac))
253 self._ethmac = EthernetMACPeripheral(core=core, local_ip=local_ip, remote_ip=remote_ip)
254 self._decoder.add(self._ethmac.bus, addr=addr)
255 self.intc.add_irq(self._ethmac.irq, index=irqno)
256
257 def elaborate(self, platform):
258 m = Module()
259
260 m.submodules.crg = _ClockResetGenerator(
261 sync_clk_freq = self.sync_clk_freq,
262 with_sdram = self.sdram is not None,
263 with_ethernet = self.ethmac is not None,
264 )
265
266 m.submodules.cpu = self.cpu
267 m.submodules.arbiter = self._arbiter
268 m.submodules.decoder = self._decoder
269 m.submodules.uart = self.uart
270 m.submodules.timer = self.timer
271 m.submodules.intc = self.intc
272 m.submodules.bootrom = self.bootrom
273 m.submodules.scratchpad = self.scratchpad
274
275 if self.sdram is not None:
276 m.submodules.sdram = self.sdram
277 if self.sram is not None:
278 m.submodules.sram = self.sram
279 if self.ethmac is not None:
280 m.submodules.ethmac = self.ethmac
281
282 m.d.comb += [
283 self._arbiter.bus.connect(self._decoder.bus),
284 self.cpu.ip.eq(self.intc.ip),
285 ]
286
287 return m
288
289
290 if __name__ == "__main__":
291 parser = argparse.ArgumentParser()
292 parser.add_argument("--build-dir", type=str,
293 default="build/minerva_soc",
294 help="local build directory (default: 'build/sdram_soc')")
295 parser.add_argument("--platform", type=str,
296 choices=("sim", "arty_a7", "ecpix5_45", "ecpix5_85"),
297 default="sim",
298 help="target platform")
299 parser.add_argument("--sync-clk-freq", type=int,
300 default=75,
301 help="SoC clock frequency, in MHz. (default: 75)")
302 parser.add_argument("--with-sdram", action="store_true",
303 help="enable SDRAM")
304 parser.add_argument("--internal-sram-size", type=int,
305 default=8192,
306 help="Internal RAM size, in bytes. Ignored if --with-sdram is provided. "
307 "(default: 8192)")
308 parser.add_argument("--baudrate", type=int,
309 default=9600,
310 help="UART baudrate (default: 9600)")
311 parser.add_argument("--with-ethernet", action="store_true",
312 help="enable Ethernet")
313 parser.add_argument("--local-ip", type=str,
314 default="192.168.1.50",
315 help="Local IPv4 address (default: 192.168.1.50)")
316 parser.add_argument("--remote-ip", type=str,
317 default="192.168.1.100",
318 help="Remote IPv4 address (default: 192.168.1.100)")
319 args = parser.parse_args()
320
321 # Platform selection
322
323 if args.platform == "sim":
324 platform = CXXRTLPlatform()
325 elif args.platform == "arty_a7":
326 platform = ArtyA7_35Platform()
327 elif args.platform == "ecpix5_45":
328 platform = ECPIX545Platform()
329 elif args.platform == "ecpix5_85":
330 platform = ECPIX585Platform()
331 else:
332 assert False
333
334 # LiteDRAM
335
336 if args.with_sdram:
337 if isinstance(platform, CXXRTLPlatform):
338 litedram_config = litedram.ECP5Config(
339 memtype = "DDR3",
340 module_name = "MT41K256M16",
341 module_bytes = 2,
342 module_ranks = 1,
343 input_clk_freq = int(platform.default_clk_frequency),
344 user_clk_freq = int(platform.default_clk_frequency),
345 init_clk_freq = int(1e6),
346 )
347 elif isinstance(platform, ArtyA7_35Platform):
348 litedram_config = litedram.Artix7Config(
349 memtype = "DDR3",
350 speedgrade = "-1",
351 cmd_latency = 0,
352 module_name = "MT41K128M16",
353 module_bytes = 2,
354 module_ranks = 1,
355 rtt_nom = 60,
356 rtt_wr = 60,
357 ron = 34,
358 input_clk_freq = int(platform.default_clk_frequency),
359 user_clk_freq = int(args.sync_clk_freq * 1e6),
360 iodelay_clk_freq = int(200e6),
361 )
362 elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)):
363 litedram_config = litedram.ECP5Config(
364 memtype = "DDR3",
365 module_name = "MT41K256M16",
366 module_bytes = 2,
367 module_ranks = 1,
368 input_clk_freq = int(platform.default_clk_frequency),
369 user_clk_freq = int(args.sync_clk_freq * 1e6),
370 init_clk_freq = int(25e6),
371 )
372 else:
373 assert False
374
375 if isinstance(platform, CXXRTLPlatform):
376 litedram_pins = None
377 else:
378 litedram_pins = request_bare(platform, "ddr3", 0)
379
380 litedram_core = litedram.Core(litedram_config, pins=litedram_pins)
381 litedram_core.build(litedram.Builder(), platform, args.build_dir,
382 sim=isinstance(platform, CXXRTLPlatform))
383 mainram_size = litedram_core.size
384 else:
385 litedram_core = None
386 mainram_size = args.internal_sram_size
387
388 # LiteEth
389
390 if args.with_ethernet:
391 if isinstance(platform, CXXRTLPlatform):
392 raise NotImplementedError("Ethernet is currently unsupported in simulation.")
393 elif isinstance(platform, ArtyA7_35Platform):
394 liteeth_config = liteeth.Artix7Config(
395 phy_iface = "mii",
396 clk_freq = int(25e6),
397 )
398 elif isinstance(platform, (ECPIX545Platform, ECPIX585Platform)):
399 liteeth_config = liteeth.ECP5Config(
400 phy_iface = "rgmii",
401 clk_freq = int(125e6),
402 )
403 else:
404 assert False
405
406 liteeth_pins = request_bare(platform, f"eth_{liteeth_config.phy_iface}", 0)
407 liteeth_core = liteeth.Core(liteeth_config, pins=liteeth_pins)
408 liteeth_core.build(liteeth.Builder(), platform, args.build_dir)
409 else:
410 liteeth_core = None
411
412 # UART
413
414 if isinstance(platform, CXXRTLPlatform):
415 uart_core = AsyncSerial_Blackbox(
416 data_bits = 8,
417 divisor = 1,
418 )
419 else:
420 uart_core = AsyncSerial(
421 data_bits = 8,
422 divisor = int(args.sync_clk_freq * 1e6 // args.baudrate),
423 pins = platform.request("uart", 0),
424 )
425
426 # SoC and BIOS
427
428 if isinstance(platform, CXXRTLPlatform):
429 sync_clk_freq = platform.default_clk_frequency
430 else:
431 sync_clk_freq = int(args.sync_clk_freq * 1e6)
432
433 soc = MinervaSoC(
434 sync_clk_freq = sync_clk_freq,
435
436 cpu_core = MinervaCPU(
437 reset_address = 0x00000000,
438 with_icache = True,
439 icache_nlines = 16,
440 icache_nwords = 4,
441 icache_nways = 1,
442 icache_base = 0x40000000,
443 icache_limit = 0x40000000 + mainram_size,
444 with_dcache = True,
445 dcache_nlines = 16,
446 dcache_nwords = 4,
447 dcache_nways = 1,
448 dcache_base = 0x40000000,
449 dcache_limit = 0x40000000 + mainram_size,
450 with_muldiv = True,
451 ),
452
453 bootrom_addr = 0x00000000,
454 bootrom_size = 0x8000,
455 scratchpad_addr = 0x00008000,
456 scratchpad_size = 0x1000,
457
458 uart_addr = 0x80000000,
459 uart_core = uart_core,
460 uart_irqno = 1,
461 timer_addr = 0x80001000,
462 timer_width = 32,
463 timer_irqno = 0,
464 )
465
466 if args.with_sdram:
467 soc.add_sdram(litedram_core, addr=0x40000000, cache_size=4096)
468 else:
469 soc.add_internal_sram(addr=0x40000000, size=args.internal_sram_size)
470
471 if args.with_ethernet:
472 soc.add_ethmac(liteeth_core, addr=0x90000000, irqno=2,
473 local_ip=args.local_ip, remote_ip=args.remote_ip)
474
475 soc.build(build_dir=args.build_dir, do_init=True)
476
477 if isinstance(platform, CXXRTLPlatform):
478 platform.build(soc, build_dir=args.build_dir, blackboxes={
479 "lambdasoc.sim.blackboxes.serial": "serial_pty",
480 })
481 else:
482 platform.build(soc, build_dir=args.build_dir, do_program=True)