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.
9 # this is a wrapper around the opencores verilog tercel module
11 from nmigen
import (Elaboratable
, Cat
, Module
, Signal
, ClockSignal
, Instance
,
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
24 class Tercel(Elaboratable
):
25 """Tercel SPI controller from Raptor Engineering, nmigen wrapper.
26 remember to call Tercel.add_verilog_source
29 def __init__(self
, bus
=None, cfg_bus
=None, features
=None, name
=None,
30 data_width
=32, spi_region_addr_width
=28, pins
=None,
32 lattice_ecp5_usrmclk
=False,
33 adr_offset
=0): # address offset (bytes)
35 # convention: give the name in the format "name_number"
36 self
.idx
= int(name
.split("_")[-1])
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
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())
51 # set up the wishbone busses
53 #features = frozenset({'err'}) # sigh
54 features
= frozenset()
56 bus
= Interface(addr_width
=spi_region_addr_width
,
57 data_width
=data_width
,
60 name
=name
+"_wb_%d_0" % self
.idx
)
62 cfg_bus
= Interface(addr_width
=6,
63 data_width
=data_width
,
66 name
=name
+"_wb_%d_1" % self
.idx
)
68 assert len(self
.bus
.dat_r
) == data_width
, \
69 "bus width must be %d" % data_width
70 self
.cfg_bus
= cfg_bus
71 assert len(self
.cfg_bus
.dat_r
) == data_width
, \
72 "bus width must be %d" % data_width
74 mmap
= MemoryMap(addr_width
=spi_region_addr_width
+self
.dsize
,
75 data_width
=self
.granularity
)
76 cfg_mmap
= MemoryMap(addr_width
=6+self
.dsize
,
77 data_width
=self
.granularity
)
79 self
.bus
.memory_map
= mmap
80 self
.cfg_bus
.memory_map
= cfg_mmap
83 self
.dq_out
= Signal(4) # Data
84 self
.dq_direction
= Signal(4)
85 self
.dq_in
= Signal(4)
86 self
.cs_n_out
= Signal() # Slave select
87 self
.spi_clk
= Signal() # Clock
88 self
.dbg_port
= Signal(8) # debug info
94 def add_verilog_source(cls
, verilog_src_dir
, platform
):
95 # add each of the verilog sources, needed for when doing platform.build
96 for fname
in ['wishbone_spi_master.v', 'phy.v']:
97 # prepend the src directory to each filename, add its contents
98 fullname
= os
.path
.join(verilog_src_dir
, fname
)
99 with
open(fullname
) as f
:
100 platform
.add_file(fullname
, f
)
102 def elaborate(self
, platform
):
105 pins
, bus
, cfg_bus
= self
.pins
, self
.bus
, self
.cfg_bus
107 # Calculate SPI flash address
108 spi_bus_adr
= Signal(30)
109 # wb address is in words, offset is in bytes
110 comb
+= spi_bus_adr
.eq(bus
.adr
- (self
.adr_offset
>> 2))
112 # urrr.... byte-reverse the config bus and data bus read/write
113 cdat_w
= Signal
.like(cfg_bus
.dat_w
)
114 cdat_r
= Signal
.like(cfg_bus
.dat_r
)
115 dat_w
= Signal
.like(bus
.dat_w
)
116 dat_r
= Signal
.like(bus
.dat_r
)
117 comb
+= cdat_w
.eq(byte_reverse(m
, "rv_cdat_w", cfg_bus
.dat_w
, 4))
118 comb
+= cfg_bus
.dat_r
.eq(byte_reverse(m
, "rv_cdat_r", cdat_r
, 4))
119 comb
+= dat_w
.eq(byte_reverse(m
, "rv_dat_w", bus
.dat_w
, 4))
120 comb
+= bus
.dat_r
.eq(byte_reverse(m
, "rv_dat_r", dat_r
, 4))
122 # create definition of external verilog Tercel code here, so that
123 # nmigen understands I/O directions (defined by i_ and o_ prefixes)
124 idx
, bus
= self
.idx
, self
.bus
125 tercel
= Instance("tercel_core",
127 i_sys_clk_freq
= self
.clk_freq
,
129 # Clock/reset (use DomainRenamer if needed)
130 i_peripheral_clock
=ClockSignal(),
131 i_peripheral_reset
=ResetSignal(),
133 # SPI region Wishbone bus signals
134 i_wishbone_adr
=spi_bus_adr
,
135 i_wishbone_dat_w
=dat_w
,
136 i_wishbone_sel
=bus
.sel
,
137 o_wishbone_dat_r
=dat_r
,
138 i_wishbone_we
=bus
.we
,
139 i_wishbone_stb
=bus
.stb
,
140 i_wishbone_cyc
=bus
.cyc
,
141 o_wishbone_ack
=bus
.ack
,
142 #o_wishbone_err=bus.err,
144 # Configuration region Wishbone bus signals
145 i_cfg_wishbone_adr
=cfg_bus
.adr
,
146 i_cfg_wishbone_dat_w
=cdat_w
,
147 i_cfg_wishbone_sel
=cfg_bus
.sel
,
148 o_cfg_wishbone_dat_r
=cdat_r
,
149 i_cfg_wishbone_we
=cfg_bus
.we
,
150 i_cfg_wishbone_stb
=cfg_bus
.stb
,
151 i_cfg_wishbone_cyc
=cfg_bus
.cyc
,
152 o_cfg_wishbone_ack
=cfg_bus
.ack
,
153 #o_cfg_wishbone_err=cfg_bus.err,
156 o_spi_d_out
=self
.dq_out
,
157 o_spi_d_direction
=self
.dq_direction
,
158 i_spi_d_in
=self
.dq_in
,
159 o_spi_ss_n
=self
.cs_n_out
,
160 o_spi_clock
=self
.spi_clk
,
163 o_debug_port
=self
.dbg_port
166 m
.submodules
['tercel_%d' % self
.idx
] = tercel
169 comb
+= pins
.dq
.o
.eq(self
.dq_out
)
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_n
.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
,
183 comb
+= pins
.clk
.eq(self
.spi_clk
)
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
,
193 self
.cfg_bus
.dat_r
, self
.cfg_bus
.dat_w
,
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
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
:
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
:
212 if __name__
== "__main__":
213 tercel
= Tercel(name
="spi_0", data_width
=32, clk_freq
=100e6
)
214 create_ilang(tercel
, tercel
.ports(), "spi_0")