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
, 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
16 from nmigen_stdio
.serial
import AsyncSerial
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
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
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
38 from crg
import ECPIX5CRG
44 class DDR3SoC(SoC
, Elaboratable
):
48 ddrphy_addr
, dramcore_addr
,
49 ddr_addr
, fw_addr
=0x0000_0000,
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,
58 features
={"cti", "bte"})
59 self
._decoder
= wishbone
.Decoder(addr_width
=30, data_width
=32,
61 features
={"cti", "bte"})
63 # default firmware name
65 firmware
= "firmware/main.bin"
67 # set up clock request generator
68 self
.crg
= ECPIX5CRG(clk_freq
)
70 # set up CPU, with 64-to-32-bit downconverters
72 self
.cpu
= ExternalCore(name
="ext_core")
73 cvtdbus
= wishbone
.Interface(addr_width
=30, data_width
=32,
75 cvtibus
= wishbone
.Interface(addr_width
=30, data_width
=32,
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
82 # CPU interrupt controller
83 self
.intc
= GenericInterruptController(width
=len(self
.cpu
.irq
))
85 # SRAM (but actually a ROM, for firmware), at address 0x0
86 if fw_addr
is not None:
88 self
.bootmem
= SRAMPeripheral(size
=0x8000, data_width
=sram_width
,
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
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
103 # SRAM (read-writeable BRAM)
104 self
.ram
= SRAMPeripheral(size
=4096)
105 self
._decoder
.add(self
.ram
.bus
, addr
=0x8000000) # at 0x8000_0000
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,
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
119 # SDRAM module using opencores sdr_ctrl
121 class MT48LC16M16(SDRModule):
127 technology_timings = _TechnologyTimings(tREFI=64e6/8192,
131 speedgrade_timings = {"default": _SpeedgradeTimings(tRP=20,
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
)
145 ddrmodule
= MT41K256M16(clk_freq
, "1:2") # match DDR3 ASIC P/N
147 drs
= DomainRenamer("dramsync")
148 dramcore
= gramCore(phy
=self
.ddrphy
,
149 geom_settings
=ddrmodule
.geom_settings
,
150 timing_settings
=ddrmodule
.timing_settings
,
152 self
.dramcore
= drs(dramcore
)
153 self
._decoder
.add(self
.dramcore
.bus
, addr
=dramcore_addr
)
155 # map the DRAM onto Wishbone
156 self
.drambone
= drs(gramWishbone(self
.dramcore
))
157 self
._decoder
.add(self
.drambone
.bus
, addr
=ddr_addr
)
159 self
.memory_map
= self
._decoder
.bus
.memory_map
161 self
.clk_freq
= clk_freq
163 def elaborate(self
, platform
):
167 # add the peripherals and clock-reset-generator
168 if platform
is not None:
169 m
.submodules
.sysclk
= self
.crg
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
)
208 # add blinky lights so we know FPGA is alive
209 if platform
is not None:
210 m
.submodules
.blinky
= Blinky()
212 # connect the arbiter (of wishbone masters)
213 # to the decoder (addressing wishbone slaves)
214 comb
+= self
._arbiter
.bus
.connect(self
._decoder
.bus
)
216 if hasattr(self
, "cpu"):
217 # wire up the CPU interrupts
218 comb
+= self
.cpu
.irq
.eq(self
.intc
.ip
)
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
)
231 self
.uart
.add_verilog_source(fname
, platform
)
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
)
243 # puzzlingly the only IO ports needed are peripheral pins,
244 # and at the moment that's just UART tx/rx.
246 ports
+= [self
.uart
.tx_o
, self
.uart
.rx_i
]
247 if hasattr(self
, "ddrphy"):
248 ports
+= list(self
.ddrphy
.pads
.fields
.values())
251 if __name__
== "__main__":
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
256 if len(sys
.argv
) >= 2:
258 platform_kls
= {'versa_ecp5': VersaECP5Platform
,
259 'ulx3s': ULX3S_85F_Platform
,
260 'arty_a7': ArtyA7_100Platform
,
263 toolchain
= {'arty_a7': "yosys_nextpnr",
264 'versa_ecp5': 'Trellis',
268 dram_cls
= {'arty_a7': None,
269 'versa_ecp5': MT41K64M16
,
273 if platform_kls
is not None:
274 platform
= platform_kls(toolchain
=toolchain
)
278 # select a firmware file
281 if len(sys
.argv
) >= 3:
282 firmware
= sys
.argv
[2]
283 fw_addr
= 0x0000_0000
285 # get UART resource pins
286 if platform
is not None:
287 uart_pins
= platform
.request("uart", 0)
289 uart_pins
= Record([('tx', 1), ('rx', 1)], name
="uart_0")
291 # get DDR resource pins
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})
300 soc
= DDR3SoC(dram_cls
=dram_cls
,
301 # check microwatt_soc.h for these
302 ddrphy_addr
=0xff000000, # DRAM_INIT_BASE firmware base
303 dramcore_addr
=0xc8000000, # DRAM_CTRL_BASE
304 ddr_addr
=0x40000000, # DRAM_BASE
313 if toolchain
== 'Trellis':
314 # add -abc9 option to yosys synth_ecp5
315 #os.environ['NMIGEN_synth_opts'] = '-abc9 -nowidelut'
316 os
.environ
['NMIGEN_synth_opts'] = '-abc9'
318 if platform
is not None:
319 # build and upload it
320 platform
.build(soc
, do_program
=True)
322 # for now, generate verilog
323 vl
= verilog
.convert(soc
, ports
=soc
.ports())
324 with
open("ls2.v", "w") as f
: