add microwatt SYSCON peripheral at 0xc000_0000
[ls2.git] / src / ls2.py
1 # Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
2 # Copyright (c) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
3 #
4 # Based on code from LambaConcept, from the gram example which is BSD-2-License
5 # https://github.com/jeanthom/gram/tree/master/examples
6 #
7 # Modifications for the Libre-SOC Project funded by NLnet and NGI POINTER
8 # under EU Grants 871528 and 957073, under the LGPLv3+ License
9
10 from nmigen import (Module, Elaboratable, DomainRenamer, Record)
11 from nmigen.cli import verilog
12 from nmigen.lib.cdc import ResetSynchronizer
13 from nmigen_soc import wishbone, memory
14 from nmigen_soc.memory import MemoryMap
15
16 from nmigen_stdio.serial import AsyncSerial
17
18 from lambdasoc.cpu.minerva import MinervaCPU
19 from lambdasoc.periph.intc import GenericInterruptController
20 from lambdasoc.periph.sram import SRAMPeripheral
21 from lambdasoc.periph.timer import TimerPeripheral
22 from lambdasoc.periph import Peripheral
23 from lambdasoc.soc.base import SoC
24 from soc.bus.uart_16550 import UART16550 # opencores 16550 uart
25 from soc.bus.external_core import ExternalCore # external libresoc/microwatt
26 from soc.bus.wb_downconvert import WishboneDownConvert
27 from soc.bus.syscon import MicrowattSYSCON
28
29 from gram.core import gramCore
30 from gram.phy.ecp5ddrphy import ECP5DDRPHY
31 from gram.modules import MT41K256M16
32 from gram.frontend.wishbone import gramWishbone
33
34 from nmigen_boards.versa_ecp5 import VersaECP5Platform
35 from nmigen_boards.ulx3s import ULX3S_85F_Platform
36 from nmigen_boards.arty_a7 import ArtyA7_100Platform
37 from nmigen_boards.test.blinky import Blinky
38
39 from crg import ECPIX5CRG
40
41 import sys
42 import os
43
44
45 class DDR3SoC(SoC, Elaboratable):
46 def __init__(self, *,
47 uart_pins, ddr_pins,
48 ddrphy_addr, dramcore_addr,
49 ddr_addr, fw_addr=0x0000_0000,
50 firmware=None,
51 clk_freq=40e6):
52
53 # set up wishbone bus arbiter and decoder. arbiter routes,
54 # decoder maps local-relative addressed satellites to global addresses
55 self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32,
56 granularity=8,
57 features={"cti", "bte"})
58 self._decoder = wishbone.Decoder(addr_width=30, data_width=32,
59 granularity=8,
60 features={"cti", "bte"})
61
62 # default firmware name
63 if firmware is None:
64 firmware = "firmware/main.bin"
65
66 # set up clock request generator
67 self.crg = ECPIX5CRG()
68
69 # set up CPU, with 64-to-32-bit downconverters
70 self.cpu = ExternalCore(name="ext_core")
71 cvtdbus = wishbone.Interface(addr_width=30, data_width=32,
72 granularity=8)
73 cvtibus = wishbone.Interface(addr_width=30, data_width=32,
74 granularity=8)
75 self.dbusdowncvt = WishboneDownConvert(self.cpu.dbus, cvtdbus)
76 self.ibusdowncvt = WishboneDownConvert(self.cpu.ibus, cvtibus)
77 self._arbiter.add(cvtibus) # I-Cache Master
78 self._arbiter.add(cvtdbus) # D-Cache Master. TODO JTAG master
79
80 # CPU interrupt controller
81 self.intc = GenericInterruptController(width=len(self.cpu.irq))
82
83 # SRAM (but actually a ROM, for firmware), at address 0x0
84 if fw_addr is not None:
85 sram_width = 32
86 self.bootmem = SRAMPeripheral(size=0x10000, data_width=sram_width,
87 writable=True)
88 with open(firmware, "rb") as f:
89 words = iter(lambda: f.read(sram_width // 8), b'')
90 bios = [int.from_bytes(w, "little") for w in words]
91 self.bootmem.init = bios
92 self._decoder.add(self.bootmem.bus, addr=fw_addr) # ROM at fw_addr
93
94 # System Configuration info
95 self.syscon = MicrowattSYSCON(sys_clk_freq=clk_freq,
96 has_uart=(uart_pins is not None))
97 self._decoder.add(self.syscon.bus, addr=0xc000000) # at 0xc000_0000
98
99 # SRAM (read-writeable BRAM)
100 self.ram = SRAMPeripheral(size=4096)
101 self._decoder.add(self.ram.bus, addr=0x8000000) # SRAM at 0x8000_0000
102
103 # UART
104 if uart_pins is not None:
105 self.uart = UART16550()
106 umap = MemoryMap(addr_width=7, data_width=8, name="uart_map")
107 #umap.add_resource(self._mem, name="mem", size=1<<5)
108 self.uart.bus.memory_map = umap
109 self._decoder.add(self.uart.bus, addr=0xc0002000) # 16550 UART addr
110
111 # DRAM Module
112 if ddr_pins is not None:
113 self.ddrphy = DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins,
114 sys_clk_freq=100e6))
115 self._decoder.add(self.ddrphy.bus, addr=ddrphy_addr)
116
117 ddrmodule = MT41K256M16(clk_freq, "1:2") # match DDR3 ASIC P/N
118
119 drs = DomainRenamer("dramsync")
120 dramcore = gramCore(phy=self.ddrphy,
121 geom_settings=ddrmodule.geom_settings,
122 timing_settings=ddrmodule.timing_settings,
123 clk_freq=clk_freq)
124 self.dramcore = drs(dramcore)
125 self._decoder.add(self.dramcore.bus, addr=dramcore_addr)
126
127 # map the DRAM onto Wishbone
128 self.drambone = drs(gramWishbone(self.dramcore))
129 self._decoder.add(self.drambone.bus, addr=ddr_addr)
130
131 self.memory_map = self._decoder.bus.memory_map
132
133 self.clk_freq = clk_freq
134
135 def elaborate(self, platform):
136 m = Module()
137 comb = m.d.comb
138
139 # add the peripherals and clock-reset-generator
140 if platform is not None:
141 m.submodules.sysclk = self.crg
142
143 if hasattr(self, "bootmem"):
144 m.submodules.bootmem = self.bootmem
145 m.submodules.syscon = self.syscon
146 m.submodules.ram = self.ram
147 m.submodules.uart = self.uart
148 m.submodules.arbiter = self._arbiter
149 m.submodules.decoder = self._decoder
150 if hasattr(self, "ddrphy"):
151 m.submodules.ddrphy = self.ddrphy
152 m.submodules.dramcore = self.dramcore
153 m.submodules.drambone = self.drambone
154 if hasattr(self, "cpu"):
155 m.submodules.intc = self.intc
156 m.submodules.extcore = self.cpu
157 m.submodules.dbuscvt = self.dbusdowncvt
158 m.submodules.ibuscvt = self.ibusdowncvt
159
160 # add blinky lights so we know FPGA is alive
161 if platform is not None:
162 m.submodules.blinky = Blinky()
163
164 # connect the arbiter (of wishbone masters)
165 # to the decoder (addressing wishbone slaves)
166 comb += self._arbiter.bus.connect(self._decoder.bus)
167
168 if hasattr(self, "cpu"):
169 # wire up the CPU interrupts
170 comb += self.cpu.irq.eq(self.intc.ip)
171
172 # add uart16550 verilog source. assumes a directory
173 # structure where ls2 has been checked out in a common
174 # subdirectory as https://github.com/freecores/uart16550
175 if platform is not None:
176 opencores_16550 = "../../uart16550/rtl/verilog"
177 pth = os.path.split(__file__)[0]
178 pth = os.path.join(pth, opencores_16550)
179 fname = os.path.abspath(pth)
180 print (fname)
181 self.uart.add_verilog_source(fname, platform)
182
183 return m
184
185 def ports(self):
186 # puzzlingly the only IO ports needed are peripheral pins,
187 # and at the moment that's just UART tx/rx.
188 ports = []
189 ports += [self.uart.tx_o, self.uart.rx_i]
190 return ports
191
192 if __name__ == "__main__":
193
194 # create a platform selected from the toolchain. defaults to VERSA_ECP5
195 # only VERSA_ECP5 will work for now because of the DDR3 module
196 fpga = "versa_ecp5"
197 if len(sys.argv) >= 2:
198 fpga = sys.argv[1]
199 platform_kls = {'versa_ecp5': VersaECP5Platform,
200 'ulx3s': ULX3S_85F_Platform,
201 'arty_a7': ArtyA7_100Platform,
202 'sim': None,
203 }[fpga]
204 toolchain = {'arty_a7': "yosys_nextpnr",
205 'versa_ecp5': 'Trellis',
206 'ulx3s': 'Trellis',
207 'sim': None,
208 }.get(fpga, None)
209 if platform_kls is not None:
210 platform = platform_kls(toolchain=toolchain)
211 else:
212 platform = None
213
214 # select a firmware file
215 firmware = None
216 fw_addr = None
217 if len(sys.argv) >= 3:
218 firmware = sys.argv[2]
219 fw_addr = 0x0000_0000
220
221 if platform is not None:
222 # get DDR and UART resource pins
223 ddr_pins = platform.request("ddr3", 0,
224 dir={"dq":"-", "dqs":"-"},
225 xdr={"clk":4, "a":4, "ba":4, "clk_en":4,
226 "odt":4, "ras":4, "cas":4, "we":4})
227 uart_pins = platform.request("uart", 0)
228 else:
229 ddr_pins = None
230 uart_pins = Record([('tx', 1), ('rx', 1)], name="uart_0")
231
232 # set up the SOC
233 soc = DDR3SoC(ddrphy_addr=0xff000000, # DRAM firmware init base
234 dramcore_addr=0x80000000,
235 ddr_addr=0x10000000,
236 fw_addr=fw_addr,
237 ddr_pins=ddr_pins,
238 uart_pins=uart_pins,
239 firmware=firmware)
240
241 if platform is not None:
242 # build and upload it
243 platform.build(soc, do_program=True)
244 else:
245 # for now, generate verilog
246 vl = verilog.convert(soc, ports=soc.ports())
247 with open("ls2.v", "w") as f:
248 f.write(vl)
249