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
=0xc000000) # 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
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
112 if ddr_pins
is not None:
113 self
.ddrphy
= DomainRenamer("dramsync")(ECP5DDRPHY(ddr_pins
,
115 self
._decoder
.add(self
.ddrphy
.bus
, addr
=ddrphy_addr
)
117 ddrmodule
= MT41K256M16(clk_freq
, "1:2") # match DDR3 ASIC P/N
119 drs
= DomainRenamer("dramsync")
120 dramcore
= gramCore(phy
=self
.ddrphy
,
121 geom_settings
=ddrmodule
.geom_settings
,
122 timing_settings
=ddrmodule
.timing_settings
,
124 self
.dramcore
= drs(dramcore
)
125 self
._decoder
.add(self
.dramcore
.bus
, addr
=dramcore_addr
)
127 # map the DRAM onto Wishbone
128 self
.drambone
= drs(gramWishbone(self
.dramcore
))
129 self
._decoder
.add(self
.drambone
.bus
, addr
=ddr_addr
)
131 self
.memory_map
= self
._decoder
.bus
.memory_map
133 self
.clk_freq
= clk_freq
135 def elaborate(self
, platform
):
139 # add the peripherals and clock-reset-generator
140 if platform
is not None:
141 m
.submodules
.sysclk
= self
.crg
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
160 # add blinky lights so we know FPGA is alive
161 if platform
is not None:
162 m
.submodules
.blinky
= Blinky()
164 # connect the arbiter (of wishbone masters)
165 # to the decoder (addressing wishbone slaves)
166 comb
+= self
._arbiter
.bus
.connect(self
._decoder
.bus
)
168 if hasattr(self
, "cpu"):
169 # wire up the CPU interrupts
170 comb
+= self
.cpu
.irq
.eq(self
.intc
.ip
)
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
)
181 self
.uart
.add_verilog_source(fname
, platform
)
186 # puzzlingly the only IO ports needed are peripheral pins,
187 # and at the moment that's just UART tx/rx.
189 ports
+= [self
.uart
.tx_o
, self
.uart
.rx_i
]
192 if __name__
== "__main__":
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
197 if len(sys
.argv
) >= 2:
199 platform_kls
= {'versa_ecp5': VersaECP5Platform
,
200 'ulx3s': ULX3S_85F_Platform
,
201 'arty_a7': ArtyA7_100Platform
,
204 toolchain
= {'arty_a7': "yosys_nextpnr",
205 'versa_ecp5': 'Trellis',
209 if platform_kls
is not None:
210 platform
= platform_kls(toolchain
=toolchain
)
214 # select a firmware file
217 if len(sys
.argv
) >= 3:
218 firmware
= sys
.argv
[2]
219 fw_addr
= 0x0000_0000
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)
230 uart_pins
= Record([('tx', 1), ('rx', 1)], name
="uart_0")
233 soc
= DDR3SoC(ddrphy_addr
=0xff000000, # DRAM firmware init base
234 dramcore_addr
=0x80000000,
241 if platform
is not None:
242 # build and upload it
243 platform
.build(soc
, do_program
=True)
245 # for now, generate verilog
246 vl
= verilog
.convert(soc
, ports
=soc
.ports())
247 with
open("ls2.v", "w") as f
: