102218236a5faa2830ff8e83395798fc741b7aa2
[soc.git] / src / soc / bus / tercel.py
1 #!/usr/bin/env python3
2 #
3 # SPDX-License-Identifier: LGPLv3+
4 # Copyright (C) 2020-2022 Raptor Engineering LLC <support@raptorengineering.com>
5 # Copyright (C) 2022 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
6 # Sponsored by NLnet and NGI POINTER under EU Grants 871528 and 957073
7 # Part of the Libre-SOC Project.
8 #
9 # this is a wrapper around the opencores verilog tercel module
10
11 from nmigen import (Elaboratable, Cat, Module, Signal, ClockSignal, Instance,
12 ResetSignal, Const)
13
14 from nmigen_soc.wishbone.bus import Interface
15 from nmigen_soc.memory import MemoryMap
16 from nmigen.utils import log2_int
17 from nmigen.cli import rtlil, verilog
18 from nmutil.byterev import byte_reverse
19 import os
20
21 __all__ = ["Tercel"]
22
23
24 class Tercel(Elaboratable):
25 """Tercel SPI controller from Raptor Engineering, nmigen wrapper.
26 remember to call Tercel.add_verilog_source
27 """
28
29 def __init__(self, bus=None, cfg_bus=None, features=None, name=None,
30 data_width=32, spi_region_addr_width=28, pins=None,
31 clk_freq=None,
32 lattice_ecp5_usrmclk=False,
33 adr_offset=0): # address offset (bytes)
34 if name is not None:
35 # convention: give the name in the format "name_number"
36 self.idx = int(name.split("_")[-1])
37 else:
38 self.idx = 0
39 name = "spi_0"
40 self.granularity = 8
41 self.data_width = data_width
42 self.dsize = log2_int(self.data_width//self.granularity)
43 self.adr_offset = adr_offset
44 self.lattice_ecp5_usrmclk = lattice_ecp5_usrmclk
45
46 # TODO, sort this out.
47 assert clk_freq is not None
48 clk_freq = round(clk_freq)
49 self.clk_freq = Const(clk_freq, 32) #clk_freq.bit_length())
50
51 # set up the wishbone busses
52 if features is None:
53 features = frozenset({'err'})
54 if bus is None:
55 bus = Interface(addr_width=spi_region_addr_width,
56 data_width=data_width,
57 features=features,
58 granularity=8,
59 name=name+"_wb_%d_0" % self.idx)
60 if cfg_bus is None:
61 cfg_bus = Interface(addr_width=6,
62 data_width=data_width,
63 features=features,
64 granularity=8,
65 name=name+"_wb_%d_1" % self.idx)
66 self.bus = bus
67 assert len(self.bus.dat_r) == data_width, \
68 "bus width must be %d" % data_width
69 self.cfg_bus = cfg_bus
70 assert len(self.cfg_bus.dat_r) == data_width, \
71 "bus width must be %d" % data_width
72
73 mmap = MemoryMap(addr_width=spi_region_addr_width+self.dsize,
74 data_width=self.granularity)
75 cfg_mmap = MemoryMap(addr_width=6+self.dsize,
76 data_width=self.granularity)
77
78 self.bus.memory_map = mmap
79 self.cfg_bus.memory_map = cfg_mmap
80
81 # QSPI signals
82 self.dq_out = Signal(4) # Data
83 self.dq_direction = Signal(4)
84 self.dq_in = Signal(4)
85 self.cs_n_out = Signal() # Slave select
86 self.spi_clk = Signal() # Clock
87 self.dbg_port = Signal(8) # debug info
88
89 # pins resource
90 self.pins = pins
91
92 @classmethod
93 def add_verilog_source(cls, verilog_src_dir, platform):
94 # add each of the verilog sources, needed for when doing platform.build
95 for fname in ['wishbone_spi_master.v', 'phy.v']:
96 # prepend the src directory to each filename, add its contents
97 fullname = os.path.join(verilog_src_dir, fname)
98 with open(fullname) as f:
99 platform.add_file(fullname, f)
100
101 def elaborate(self, platform):
102 m = Module()
103 comb = m.d.comb
104 pins, bus, cfg_bus = self.pins, self.bus, self.cfg_bus
105
106 # Calculate SPI flash address
107 spi_bus_adr = Signal(30)
108 # wb address is in words, offset is in bytes
109 comb += spi_bus_adr.eq(bus.adr - (self.adr_offset >> 2))
110
111 # urrr.... byte-reverse the config bus and data bus read/write
112 cdat_w = Signal.like(cfg_bus.dat_w)
113 cdat_r = Signal.like(cfg_bus.dat_r)
114 dat_w = Signal.like(bus.dat_w)
115 dat_r = Signal.like(bus.dat_r)
116 comb += cdat_w.eq(byte_reverse(m, "rv_cdat_w", cfg_bus.dat_w, 4))
117 comb += cfg_bus.dat_r.eq(byte_reverse(m, "rv_cdat_r", cdat_r, 4))
118 comb += dat_w.eq(byte_reverse(m, "rv_dat_w", bus.dat_w, 4))
119 comb += bus.dat_r.eq(byte_reverse(m, "rv_dat_r", dat_r, 4))
120
121 # create definition of external verilog Tercel code here, so that
122 # nmigen understands I/O directions (defined by i_ and o_ prefixes)
123 idx, bus = self.idx, self.bus
124 tercel = Instance("tercel_core",
125 # System parameters
126 i_sys_clk_freq = self.clk_freq,
127
128 # Clock/reset (use DomainRenamer if needed)
129 i_peripheral_clock=ClockSignal(),
130 i_peripheral_reset=ResetSignal(),
131
132 # SPI region Wishbone bus signals
133 i_wishbone_adr=spi_bus_adr,
134 i_wishbone_dat_w=dat_w,
135 i_wishbone_sel=bus.sel,
136 o_wishbone_dat_r=dat_r,
137 i_wishbone_we=bus.we,
138 i_wishbone_stb=bus.stb,
139 i_wishbone_cyc=bus.cyc,
140 o_wishbone_ack=bus.ack,
141 o_wishbone_err=bus.err,
142
143 # Configuration region Wishbone bus signals
144 i_cfg_wishbone_adr=cfg_bus.adr,
145 i_cfg_wishbone_dat_w=cdat_w,
146 i_cfg_wishbone_sel=cfg_bus.sel,
147 o_cfg_wishbone_dat_r=cdat_r,
148 i_cfg_wishbone_we=cfg_bus.we,
149 i_cfg_wishbone_stb=cfg_bus.stb,
150 i_cfg_wishbone_cyc=cfg_bus.cyc,
151 o_cfg_wishbone_ack=cfg_bus.ack,
152 o_cfg_wishbone_err=cfg_bus.err,
153
154 # QSPI signals
155 o_spi_d_out=self.dq_out,
156 o_spi_d_direction=self.dq_direction,
157 i_spi_d_in=self.dq_in,
158 o_spi_ss_n=self.cs_n_out,
159 o_spi_clock=self.spi_clk,
160
161 # debug port
162 o_debug_port=self.dbg_port
163 );
164
165 m.submodules['tercel_%d' % self.idx] = tercel
166
167 if pins is not None:
168 comb += pins.dq.o.eq(self.dq_out)
169 comb += pins.dq.oe.eq(self.dq_direction)
170 comb += pins.dq.oe.eq(self.dq_direction)
171 comb += pins.dq.o_clk.eq(ClockSignal())
172 comb += self.dq_in.eq(pins.dq.i)
173 comb += pins.dq.i_clk.eq(ClockSignal())
174 # XXX invert handled by SPIFlashResource
175 comb += pins.cs.eq(~self.cs_n_out)
176 # ECP5 needs special handling for the SPI clock, sigh.
177 if self.lattice_ecp5_usrmclk:
178 m.submodules += Instance("USRMCLK",
179 i_USRMCLKI = self.spi_clk,
180 i_USRMCLKTS = 0
181 )
182 else:
183 comb += pins.clk.eq(self.spi_clk)
184
185 return m
186
187 def ports(self):
188 return [self.bus.cyc, self.bus.stb, self.bus.ack,
189 self.bus.dat_r, self.bus.dat_w, self.bus.adr,
190 self.bus.we, self.bus.sel,
191 self.cfg_bus.cyc, self.cfg_bus.stb,
192 self.cfg_bus.ack,
193 self.cfg_bus.dat_r, self.cfg_bus.dat_w,
194 self.cfg_bus.adr,
195 self.cfg_bus.we, self.cfg_bus.sel,
196 self.dq_out, self.dq_direction, self.dq_in,
197 self.cs_n_out, self.spi_clk
198 ]
199
200
201 def create_ilang(dut, ports, test_name):
202 vl = rtlil.convert(dut, name=test_name, ports=ports)
203 with open("%s.il" % test_name, "w") as f:
204 f.write(vl)
205
206 def create_verilog(dut, ports, test_name):
207 vl = verilog.convert(dut, name=test_name, ports=ports)
208 with open("%s.v" % test_name, "w") as f:
209 f.write(vl)
210
211
212 if __name__ == "__main__":
213 tercel = Tercel(name="spi_0", data_width=32, clk_freq=100e6)
214 create_ilang(tercel, tercel.ports(), "spi_0")
215