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()
55 bus
= Interface(addr_width
=spi_region_addr_width
,
56 data_width
=data_width
,
59 name
=name
+"_wb_%d_0" % self
.idx
)
61 cfg_bus
= Interface(addr_width
=6,
62 data_width
=data_width
,
65 name
=name
+"_wb_%d_1" % self
.idx
)
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
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
)
78 self
.bus
.memory_map
= mmap
79 self
.cfg_bus
.memory_map
= cfg_mmap
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
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
)
101 def elaborate(self
, platform
):
104 pins
, bus
, cfg_bus
= self
.pins
, self
.bus
, self
.cfg_bus
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))
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))
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",
126 i_sys_clk_freq
= self
.clk_freq
,
128 # Clock/reset (use DomainRenamer if needed)
129 i_peripheral_clock
=ClockSignal(),
130 i_peripheral_reset
=ResetSignal(),
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
,
142 # Configuration region Wishbone bus signals
143 i_cfg_wishbone_adr
=cfg_bus
.adr
,
144 i_cfg_wishbone_dat_w
=cdat_w
,
145 i_cfg_wishbone_sel
=cfg_bus
.sel
,
146 o_cfg_wishbone_dat_r
=cdat_r
,
147 i_cfg_wishbone_we
=cfg_bus
.we
,
148 i_cfg_wishbone_stb
=cfg_bus
.stb
,
149 i_cfg_wishbone_cyc
=cfg_bus
.cyc
,
150 o_cfg_wishbone_ack
=cfg_bus
.ack
,
153 o_spi_d_out
=self
.dq_out
,
154 o_spi_d_direction
=self
.dq_direction
,
155 i_spi_d_in
=self
.dq_in
,
156 o_spi_ss_n
=self
.cs_n_out
,
157 o_spi_clock
=self
.spi_clk
,
160 o_debug_port
=self
.dbg_port
163 m
.submodules
['tercel_%d' % self
.idx
] = tercel
166 comb
+= pins
.dq
.o
.eq(self
.dq_out
)
167 comb
+= pins
.dq
.oe
.eq(self
.dq_direction
)
168 comb
+= pins
.dq
.oe
.eq(self
.dq_direction
)
169 comb
+= pins
.dq
.o_clk
.eq(ClockSignal())
170 comb
+= self
.dq_in
.eq(pins
.dq
.i
)
171 comb
+= pins
.dq
.i_clk
.eq(ClockSignal())
172 # XXX invert handled by SPIFlashResource
173 comb
+= pins
.cs
.eq(~self
.cs_n_out
)
174 # ECP5 needs special handling for the SPI clock, sigh.
175 if self
.lattice_ecp5_usrmclk
:
176 m
.submodules
+= Instance("USRMCLK",
177 i_USRMCLKI
= self
.spi_clk
,
181 comb
+= pins
.clk
.eq(self
.spi_clk
)
186 return [self
.bus
.cyc
, self
.bus
.stb
, self
.bus
.ack
,
187 self
.bus
.dat_r
, self
.bus
.dat_w
, self
.bus
.adr
,
188 self
.bus
.we
, self
.bus
.sel
,
189 self
.cfg_bus
.cyc
, self
.cfg_bus
.stb
,
191 self
.cfg_bus
.dat_r
, self
.cfg_bus
.dat_w
,
193 self
.cfg_bus
.we
, self
.cfg_bus
.sel
,
194 self
.dq_out
, self
.dq_direction
, self
.dq_in
,
195 self
.cs_n_out
, self
.spi_clk
199 def create_ilang(dut
, ports
, test_name
):
200 vl
= rtlil
.convert(dut
, name
=test_name
, ports
=ports
)
201 with
open("%s.il" % test_name
, "w") as f
:
204 def create_verilog(dut
, ports
, test_name
):
205 vl
= verilog
.convert(dut
, name
=test_name
, ports
=ports
)
206 with
open("%s.v" % test_name
, "w") as f
:
210 if __name__
== "__main__":
211 tercel
= Tercel(name
="spi_0", data_width
=32, clk_freq
=100e6
)
212 create_ilang(tercel
, tercel
.ports(), "spi_0")