54d4f4d9f7308adee97c2ffcb8723d2d364001c7
[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.periph.intc import GenericInterruptController
19 from lambdasoc.periph.sram import SRAMPeripheral
20 from lambdasoc.periph.timer import TimerPeripheral
21 from lambdasoc.periph import Peripheral
22 from lambdasoc.soc.base import SoC
23 from soc.bus.uart_16550 import UART16550 # opencores 16550 uart
24 from soc.bus.external_core import ExternalCore # external libresoc/microwatt
25 from soc.bus.wb_downconvert import WishboneDownConvert
26 from soc.bus.syscon import MicrowattSYSCON
27
28 from gram.core import gramCore
29 from gram.phy.ecp5ddrphy import ECP5DDRPHY
30 from gram.modules import MT41K256M16, MT41K64M16
31 from gram.frontend.wishbone import gramWishbone
32
33 from nmigen_boards.versa_ecp5 import VersaECP5Platform
34 from nmigen_boards.ulx3s import ULX3S_85F_Platform
35 from nmigen_boards.arty_a7 import ArtyA7_100Platform
36 from nmigen_boards.test.blinky import Blinky
37
38 from crg import ECPIX5CRG
39
40 import sys
41 import os
42
43
44 class DDR3SoC(SoC, Elaboratable):
45 def __init__(self, *,
46 dram_cls,
47 uart_pins, ddr_pins,
48 ddrphy_addr, dramcore_addr,
49 ddr_addr, fw_addr=0x0000_0000,
50 firmware=None,
51 clk_freq=50e6,
52 add_cpu=True):
53
54 # set up wishbone bus arbiter and decoder. arbiter routes,
55 # decoder maps local-relative addressed satellites to global addresses
56 self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32,
57 granularity=8,
58 features={"cti", "bte"})
59 self._decoder = wishbone.Decoder(addr_width=30, data_width=32,
60 granularity=8,
61 features={"cti", "bte"})
62
63 # default firmware name
64 if firmware is None:
65 firmware = "firmware/main.bin"
66
67 # set up clock request generator
68 self.crg = ECPIX5CRG(clk_freq)
69
70 # set up CPU, with 64-to-32-bit downconverters
71 if add_cpu:
72 self.cpu = ExternalCore(name="ext_core")
73 cvtdbus = wishbone.Interface(addr_width=30, data_width=32,
74 granularity=8)
75 cvtibus = wishbone.Interface(addr_width=30, data_width=32,
76 granularity=8)
77 self.dbusdowncvt = WishboneDownConvert(self.cpu.dbus, cvtdbus)
78 self.ibusdowncvt = WishboneDownConvert(self.cpu.ibus, cvtibus)
79 self._arbiter.add(cvtibus) # I-Cache Master
80 self._arbiter.add(cvtdbus) # D-Cache Master. TODO JTAG master
81
82 # CPU interrupt controller
83 self.intc = GenericInterruptController(width=len(self.cpu.irq))
84
85 # SRAM (but actually a ROM, for firmware), at address 0x0
86 if fw_addr is not None:
87 sram_width = 32
88 self.bootmem = SRAMPeripheral(size=0x8000, data_width=sram_width,
89 writable=True)
90 if firmware is not None:
91 with open(firmware, "rb") as f:
92 words = iter(lambda: f.read(sram_width // 8), b'')
93 bios = [int.from_bytes(w, "little") for w in words]
94 self.bootmem.init = bios
95 self._decoder.add(self.bootmem.bus, addr=fw_addr) # ROM at fw_addr
96
97 # System Configuration info
98 self.syscon = MicrowattSYSCON(sys_clk_freq=clk_freq,
99 has_uart=(uart_pins is not None))
100 self._decoder.add(self.syscon.bus, addr=0xc0000000) # at 0xc000_0000
101
102 if False:
103 # SRAM (read-writeable BRAM)
104 self.ram = SRAMPeripheral(size=4096)
105 self._decoder.add(self.ram.bus, addr=0x8000000) # at 0x8000_0000
106
107 # UART at 0xC000_2000, convert 32-bit bus down to 8-bit in an odd way
108 if uart_pins is not None:
109 # sigh actual UART in microwatt is 8-bit
110 self.uart = UART16550(data_width=8, pins=uart_pins)
111 # but (see soc.vhdl) 8-bit regs are addressed at 32-bit locations
112 cvtuartbus = wishbone.Interface(addr_width=5, data_width=32,
113 granularity=8)
114 umap = MemoryMap(addr_width=7, data_width=8, name="uart_map")
115 cvtuartbus.memory_map = umap
116 self._decoder.add(cvtuartbus, addr=0xc0002000) # 16550 UART addr
117 self.cvtuartbus = cvtuartbus
118
119 # SDRAM module using opencores sdr_ctrl
120 """
121 class MT48LC16M16(SDRModule):
122 # geometry
123 nbanks = 4
124 nrows = 8192
125 ncols = 512
126 # timings
127 technology_timings = _TechnologyTimings(tREFI=64e6/8192,
128 tWTR=(2, None),
129 tCCD=(1, None),
130 tRRD=(None, 15))
131 speedgrade_timings = {"default": _SpeedgradeTimings(tRP=20,
132 tRCD=20,
133 tWR=15,
134 tRFC=(None, 66),
135 tFAW=None,
136 tRAS=44)}
137 """
138
139 # DRAM Module
140 if ddr_pins is not None:
141 self.ddrphy = DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins,
142 sys_clk_freq=clk_freq))
143 self._decoder.add(self.ddrphy.bus, addr=ddrphy_addr)
144
145 ddrmodule = MT41K256M16(clk_freq, "1:2") # match DDR3 ASIC P/N
146
147 drs = DomainRenamer("dramsync")
148 dramcore = gramCore(phy=self.ddrphy,
149 geom_settings=ddrmodule.geom_settings,
150 timing_settings=ddrmodule.timing_settings,
151 clk_freq=clk_freq)
152 self.dramcore = drs(dramcore)
153 self._decoder.add(self.dramcore.bus, addr=dramcore_addr)
154
155 # map the DRAM onto Wishbone
156 self.drambone = drs(gramWishbone(self.dramcore))
157 self._decoder.add(self.drambone.bus, addr=ddr_addr)
158
159 self.memory_map = self._decoder.bus.memory_map
160
161 self.clk_freq = clk_freq
162
163 def elaborate(self, platform):
164 m = Module()
165 comb = m.d.comb
166
167 # add the peripherals and clock-reset-generator
168 if platform is not None:
169 m.submodules.sysclk = self.crg
170
171 if hasattr(self, "bootmem"):
172 m.submodules.bootmem = self.bootmem
173 m.submodules.syscon = self.syscon
174 if hasattr(self, "ram"):
175 m.submodules.ram = self.ram
176 if hasattr(self, "uart"):
177 m.submodules.uart = self.uart
178 comb += self.uart.cts_i.eq(1)
179 comb += self.uart.dsr_i.eq(1)
180 comb += self.uart.ri_i.eq(0)
181 comb += self.uart.dcd_i.eq(1)
182 # sigh connect up the wishbone bus manually to deal with
183 # the mis-match on the data
184 uartbus = self.uart.bus
185 comb += uartbus.adr.eq(self.cvtuartbus.adr)
186 comb += uartbus.stb.eq(self.cvtuartbus.stb)
187 comb += uartbus.cyc.eq(self.cvtuartbus.cyc)
188 comb += uartbus.we.eq(self.cvtuartbus.we)
189 comb += uartbus.dat_w.eq(self.cvtuartbus.dat_w) # drops 8..31
190 comb += self.cvtuartbus.dat_r.eq(uartbus.dat_r) # drops 8..31
191 comb += self.cvtuartbus.ack.eq(uartbus.ack)
192 m.submodules.arbiter = self._arbiter
193 m.submodules.decoder = self._decoder
194 if hasattr(self, "ddrphy"):
195 m.submodules.ddrphy = self.ddrphy
196 m.submodules.dramcore = self.dramcore
197 m.submodules.drambone = self.drambone
198 if hasattr(self, "cpu"):
199 m.submodules.intc = self.intc
200 m.submodules.extcore = self.cpu
201 m.submodules.dbuscvt = self.dbusdowncvt
202 m.submodules.ibuscvt = self.ibusdowncvt
203 # create stall sigs, assume wishbone classic
204 ibus, dbus = self.cpu.ibus, self.cpu.dbus
205 comb += ibus.stall.eq(ibus.stb & ~ibus.ack)
206 comb += dbus.stall.eq(dbus.stb & ~dbus.ack)
207
208 # add blinky lights so we know FPGA is alive
209 if platform is not None:
210 m.submodules.blinky = Blinky()
211
212 # connect the arbiter (of wishbone masters)
213 # to the decoder (addressing wishbone slaves)
214 comb += self._arbiter.bus.connect(self._decoder.bus)
215
216 if hasattr(self, "cpu"):
217 # wire up the CPU interrupts
218 comb += self.cpu.irq.eq(self.intc.ip)
219
220 if platform is None:
221 return m
222
223 # add uart16550 verilog source. assumes a directory
224 # structure where ls2 has been checked out in a common
225 # subdirectory as https://github.com/freecores/uart16550
226 opencores_16550 = "../../uart16550/rtl/verilog"
227 pth = os.path.split(__file__)[0]
228 pth = os.path.join(pth, opencores_16550)
229 fname = os.path.abspath(pth)
230 print (fname)
231 self.uart.add_verilog_source(fname, platform)
232
233 # add the main core
234 pth = os.path.split(__file__)[0]
235 pth = os.path.join(pth, '../external_core_top.v')
236 fname = os.path.abspath(pth)
237 with open(fname) as f:
238 platform.add_file(fname, f)
239
240 return m
241
242 def ports(self):
243 # puzzlingly the only IO ports needed are peripheral pins,
244 # and at the moment that's just UART tx/rx.
245 ports = []
246 ports += [self.uart.tx_o, self.uart.rx_i]
247 if hasattr(self, "ddrphy"):
248 ports += list(self.ddrphy.pads.fields.values())
249 return ports
250
251 if __name__ == "__main__":
252
253 # create a platform selected from the toolchain. defaults to VERSA_ECP5
254 # only VERSA_ECP5 will work for now because of the DDR3 module
255 fpga = "versa_ecp5"
256 if len(sys.argv) >= 2:
257 fpga = sys.argv[1]
258 platform_kls = {'versa_ecp5': VersaECP5Platform,
259 'ulx3s': ULX3S_85F_Platform,
260 'arty_a7': ArtyA7_100Platform,
261 'sim': None,
262 }[fpga]
263 toolchain = {'arty_a7': "yosys_nextpnr",
264 'versa_ecp5': 'Trellis',
265 'ulx3s': 'Trellis',
266 'sim': None,
267 }.get(fpga, None)
268 dram_cls = {'arty_a7': None,
269 'versa_ecp5': MT41K64M16,
270 'ulx3s': None,
271 'sim': None,
272 }.get(fpga, None)
273 if platform_kls is not None:
274 platform = platform_kls(toolchain=toolchain)
275 else:
276 platform = None
277
278 # select a firmware file
279 firmware = None
280 fw_addr = None
281 if len(sys.argv) >= 3:
282 firmware = sys.argv[2]
283 fw_addr = 0x0000_0000
284
285 # get UART resource pins
286 if platform is not None:
287 uart_pins = platform.request("uart", 0)
288 else:
289 uart_pins = Record([('tx', 1), ('rx', 1)], name="uart_0")
290
291 # get DDR resource pins
292 ddr_pins = None
293 if platform is not None and fpga in ['versa_ecp5', 'arty_a7']:
294 ddr_pins = platform.request("ddr3", 0,
295 dir={"dq":"-", "dqs":"-"},
296 xdr={"clk":4, "a":4, "ba":4, "clk_en":4,
297 "odt":4, "ras":4, "cas":4, "we":4})
298
299 # set up the SOC
300 soc = DDR3SoC(dram_cls,
301 ddrphy_addr=0xff000000, # DRAM firmware init base
302 dramcore_addr=0x80000000,
303 ddr_addr=0x10000000,
304 fw_addr=fw_addr,
305 #fw_addr=None,
306 ddr_pins=ddr_pins,
307 uart_pins=uart_pins,
308 firmware=firmware,
309 clk_freq=50e6,
310 add_cpu=True)
311
312 #if toolchain == 'Trellis':
313 # add -abc9 option to yosys synth_ecp5
314 # os.environ['NMIGEN_synth_opts'] = '-abc9'
315
316 if platform is not None:
317 # build and upload it
318 platform.build(soc, do_program=True)
319 else:
320 # for now, generate verilog
321 vl = verilog.convert(soc, ports=soc.ports())
322 with open("ls2.v", "w") as f:
323 f.write(vl)
324