alternative uart wishbone mapping which just takes 8-bit data and
[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=50e6):
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=0xc0000000) # 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 at 0xC000_2000, convert 32-bit bus down to 8-bit in an odd way
104 if uart_pins is not None:
105 # sigh actual UART in microwatt is 8-bit
106 self.uart = UART16550(data_width=8)
107 # but (see soc.vhdl) 8-bit regs are addressed at 32-bit locations
108 cvtuartbus = wishbone.Interface(addr_width=5, data_width=32,
109 granularity=8)
110 umap = MemoryMap(addr_width=7, data_width=8, name="uart_map")
111 cvtuartbus.memory_map = umap
112 self._decoder.add(cvtuartbus, addr=0xc0002000) # 16550 UART addr
113 self.cvtuartbus = cvtuartbus
114
115 # DRAM Module
116 if ddr_pins is not None:
117 self.ddrphy = DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins,
118 sys_clk_freq=100e6))
119 self._decoder.add(self.ddrphy.bus, addr=ddrphy_addr)
120
121 ddrmodule = MT41K256M16(clk_freq, "1:2") # match DDR3 ASIC P/N
122
123 drs = DomainRenamer("dramsync")
124 dramcore = gramCore(phy=self.ddrphy,
125 geom_settings=ddrmodule.geom_settings,
126 timing_settings=ddrmodule.timing_settings,
127 clk_freq=clk_freq)
128 self.dramcore = drs(dramcore)
129 self._decoder.add(self.dramcore.bus, addr=dramcore_addr)
130
131 # map the DRAM onto Wishbone
132 self.drambone = drs(gramWishbone(self.dramcore))
133 self._decoder.add(self.drambone.bus, addr=ddr_addr)
134
135 self.memory_map = self._decoder.bus.memory_map
136
137 self.clk_freq = clk_freq
138
139 def elaborate(self, platform):
140 m = Module()
141 comb = m.d.comb
142
143 # add the peripherals and clock-reset-generator
144 if platform is not None:
145 m.submodules.sysclk = self.crg
146
147 if hasattr(self, "bootmem"):
148 m.submodules.bootmem = self.bootmem
149 m.submodules.syscon = self.syscon
150 m.submodules.ram = self.ram
151 if hasattr(self, "uart"):
152 m.submodules.uart = self.uart
153 comb += self.uart.cts_i.eq(1)
154 comb += self.uart.dsr_i.eq(1)
155 comb += self.uart.ri_i.eq(0)
156 comb += self.uart.dcd_i.eq(1)
157 # sigh connect up the wishbone bus manually to deal with
158 # the mis-match on the data
159 uartbus = self.uart.bus
160 comb += uartbus.adr.eq(self.cvtuartbus.adr)
161 comb += uartbus.stb.eq(self.cvtuartbus.stb)
162 comb += uartbus.cyc.eq(self.cvtuartbus.cyc)
163 comb += uartbus.we.eq(self.cvtuartbus.we)
164 comb += uartbus.dat_w.eq(self.cvtuartbus.dat_w) # drops 8..31
165 comb += self.cvtuartbus.dat_r.eq(uartbus.dat_r) # drops 8..31
166 comb += self.cvtuartbus.ack.eq(uartbus.ack)
167 m.submodules.arbiter = self._arbiter
168 m.submodules.decoder = self._decoder
169 if hasattr(self, "ddrphy"):
170 m.submodules.ddrphy = self.ddrphy
171 m.submodules.dramcore = self.dramcore
172 m.submodules.drambone = self.drambone
173 if hasattr(self, "cpu"):
174 m.submodules.intc = self.intc
175 m.submodules.extcore = self.cpu
176 m.submodules.dbuscvt = self.dbusdowncvt
177 m.submodules.ibuscvt = self.ibusdowncvt
178
179 # add blinky lights so we know FPGA is alive
180 if platform is not None:
181 m.submodules.blinky = Blinky()
182
183 # connect the arbiter (of wishbone masters)
184 # to the decoder (addressing wishbone slaves)
185 comb += self._arbiter.bus.connect(self._decoder.bus)
186
187 if hasattr(self, "cpu"):
188 # wire up the CPU interrupts
189 comb += self.cpu.irq.eq(self.intc.ip)
190
191 # add uart16550 verilog source. assumes a directory
192 # structure where ls2 has been checked out in a common
193 # subdirectory as https://github.com/freecores/uart16550
194 if platform is not None:
195 opencores_16550 = "../../uart16550/rtl/verilog"
196 pth = os.path.split(__file__)[0]
197 pth = os.path.join(pth, opencores_16550)
198 fname = os.path.abspath(pth)
199 print (fname)
200 self.uart.add_verilog_source(fname, platform)
201
202 return m
203
204 def ports(self):
205 # puzzlingly the only IO ports needed are peripheral pins,
206 # and at the moment that's just UART tx/rx.
207 ports = []
208 ports += [self.uart.tx_o, self.uart.rx_i]
209 return ports
210
211 if __name__ == "__main__":
212
213 # create a platform selected from the toolchain. defaults to VERSA_ECP5
214 # only VERSA_ECP5 will work for now because of the DDR3 module
215 fpga = "versa_ecp5"
216 if len(sys.argv) >= 2:
217 fpga = sys.argv[1]
218 platform_kls = {'versa_ecp5': VersaECP5Platform,
219 'ulx3s': ULX3S_85F_Platform,
220 'arty_a7': ArtyA7_100Platform,
221 'sim': None,
222 }[fpga]
223 toolchain = {'arty_a7': "yosys_nextpnr",
224 'versa_ecp5': 'Trellis',
225 'ulx3s': 'Trellis',
226 'sim': None,
227 }.get(fpga, None)
228 if platform_kls is not None:
229 platform = platform_kls(toolchain=toolchain)
230 else:
231 platform = None
232
233 # select a firmware file
234 firmware = None
235 fw_addr = None
236 if len(sys.argv) >= 3:
237 firmware = sys.argv[2]
238 fw_addr = 0x0000_0000
239
240 if platform is not None:
241 # get DDR and UART resource pins
242 ddr_pins = platform.request("ddr3", 0,
243 dir={"dq":"-", "dqs":"-"},
244 xdr={"clk":4, "a":4, "ba":4, "clk_en":4,
245 "odt":4, "ras":4, "cas":4, "we":4})
246 uart_pins = platform.request("uart", 0)
247 else:
248 ddr_pins = None
249 uart_pins = Record([('tx', 1), ('rx', 1)], name="uart_0")
250
251 # set up the SOC
252 soc = DDR3SoC(ddrphy_addr=0xff000000, # DRAM firmware init base
253 dramcore_addr=0x80000000,
254 ddr_addr=0x10000000,
255 fw_addr=fw_addr,
256 ddr_pins=ddr_pins,
257 uart_pins=uart_pins,
258 firmware=firmware)
259
260 if platform is not None:
261 # build and upload it
262 platform.build(soc, do_program=True)
263 else:
264 # for now, generate verilog
265 vl = verilog.convert(soc, ports=soc.ports())
266 with open("ls2.v", "w") as f:
267 f.write(vl)
268