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
.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
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
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
39 from crg
import ECPIX5CRG
45 class DDR3SoC(SoC
, Elaboratable
):
48 ddrphy_addr
, dramcore_addr
,
49 ddr_addr
, fw_addr
=0x0000_0000,
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,
57 features
={"cti", "bte"})
58 self
._decoder
= wishbone
.Decoder(addr_width
=30, data_width
=32,
60 features
={"cti", "bte"})
62 # default firmware name
64 firmware
= "firmware/main.bin"
66 # set up clock request generator
67 self
.crg
= ECPIX5CRG()
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,
73 cvtibus
= wishbone
.Interface(addr_width
=30, data_width
=32,
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
80 # CPU interrupt controller
81 self
.intc
= GenericInterruptController(width
=len(self
.cpu
.irq
))
83 # SRAM (but actually a ROM, for firmware), at address 0x0
84 if fw_addr
is not None:
86 self
.bootmem
= SRAMPeripheral(size
=0x10000, data_width
=sram_width
,
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
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
99 # SRAM (read-writeable BRAM)
100 self
.ram
= SRAMPeripheral(size
=4096)
101 self
._decoder
.add(self
.ram
.bus
, addr
=0x8000000) # SRAM at 0x8000_0000
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,
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
116 if ddr_pins
is not None:
117 self
.ddrphy
= DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins
,
119 self
._decoder
.add(self
.ddrphy
.bus
, addr
=ddrphy_addr
)
121 ddrmodule
= MT41K256M16(clk_freq
, "1:2") # match DDR3 ASIC P/N
123 drs
= DomainRenamer("dramsync")
124 dramcore
= gramCore(phy
=self
.ddrphy
,
125 geom_settings
=ddrmodule
.geom_settings
,
126 timing_settings
=ddrmodule
.timing_settings
,
128 self
.dramcore
= drs(dramcore
)
129 self
._decoder
.add(self
.dramcore
.bus
, addr
=dramcore_addr
)
131 # map the DRAM onto Wishbone
132 self
.drambone
= drs(gramWishbone(self
.dramcore
))
133 self
._decoder
.add(self
.drambone
.bus
, addr
=ddr_addr
)
135 self
.memory_map
= self
._decoder
.bus
.memory_map
137 self
.clk_freq
= clk_freq
139 def elaborate(self
, platform
):
143 # add the peripherals and clock-reset-generator
144 if platform
is not None:
145 m
.submodules
.sysclk
= self
.crg
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
179 # add blinky lights so we know FPGA is alive
180 if platform
is not None:
181 m
.submodules
.blinky
= Blinky()
183 # connect the arbiter (of wishbone masters)
184 # to the decoder (addressing wishbone slaves)
185 comb
+= self
._arbiter
.bus
.connect(self
._decoder
.bus
)
187 if hasattr(self
, "cpu"):
188 # wire up the CPU interrupts
189 comb
+= self
.cpu
.irq
.eq(self
.intc
.ip
)
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
)
200 self
.uart
.add_verilog_source(fname
, platform
)
205 # puzzlingly the only IO ports needed are peripheral pins,
206 # and at the moment that's just UART tx/rx.
208 ports
+= [self
.uart
.tx_o
, self
.uart
.rx_i
]
211 if __name__
== "__main__":
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
216 if len(sys
.argv
) >= 2:
218 platform_kls
= {'versa_ecp5': VersaECP5Platform
,
219 'ulx3s': ULX3S_85F_Platform
,
220 'arty_a7': ArtyA7_100Platform
,
223 toolchain
= {'arty_a7': "yosys_nextpnr",
224 'versa_ecp5': 'Trellis',
228 if platform_kls
is not None:
229 platform
= platform_kls(toolchain
=toolchain
)
233 # select a firmware file
236 if len(sys
.argv
) >= 3:
237 firmware
= sys
.argv
[2]
238 fw_addr
= 0x0000_0000
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)
249 uart_pins
= Record([('tx', 1), ('rx', 1)], name
="uart_0")
252 soc
= DDR3SoC(ddrphy_addr
=0xff000000, # DRAM firmware init base
253 dramcore_addr
=0x80000000,
260 if platform
is not None:
261 # build and upload it
262 platform
.build(soc
, do_program
=True)
264 # for now, generate verilog
265 vl
= verilog
.convert(soc
, ports
=soc
.ports())
266 with
open("ls2.v", "w") as f
: