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
23 class Tercel(Elaboratable
):
24 """Tercel SPI controller from Raptor Engineering, nmigen wrapper.
25 remember to call Tercel.add_verilog_source
28 def __init__(self
, bus
=None, cfg_bus
=None, features
=None, name
=None,
29 data_width
=32, spi_region_addr_width
=28, pins
=None,
31 lattice_ecp5_usrmclk
=False,
32 adr_offset
=0): # address offset (bytes)
34 # convention: give the name in the format "name_number"
35 self
.idx
= int(name
.split("_")[-1])
40 self
.data_width
= data_width
41 self
.dsize
= log2_int(self
.data_width
//self
.granularity
)
42 self
.adr_offset
= adr_offset
43 self
.lattice_ecp5_usrmclk
= lattice_ecp5_usrmclk
45 # TODO, sort this out.
46 assert clk_freq
is not None
47 clk_freq
= round(clk_freq
)
48 self
.clk_freq
= Const(clk_freq
, clk_freq
.bit_length())
50 # set up the wishbone busses
52 features
= frozenset()
54 bus
= Interface(addr_width
=spi_region_addr_width
,
55 data_width
=data_width
,
58 name
=name
+"_wb_%d_0" % self
.idx
)
60 cfg_bus
= Interface(addr_width
=6,
61 data_width
=data_width
,
64 name
=name
+"_wb_%d_1" % self
.idx
)
66 assert len(self
.bus
.dat_r
) == data_width
, \
67 "bus width must be %d" % data_width
68 self
.cfg_bus
= cfg_bus
69 assert len(self
.cfg_bus
.dat_r
) == data_width
, \
70 "bus width must be %d" % data_width
72 mmap
= MemoryMap(addr_width
=spi_region_addr_width
+self
.dsize
,
73 data_width
=self
.granularity
)
74 cfg_mmap
= MemoryMap(addr_width
=6+self
.dsize
,
75 data_width
=self
.granularity
)
77 self
.bus
.memory_map
= mmap
78 self
.cfg_bus
.memory_map
= cfg_mmap
81 self
.dq_out
= Signal(4) # Data
82 self
.dq_direction
= Signal(4)
83 self
.dq_in
= Signal(4)
84 self
.cs_n_out
= Signal() # Slave select
85 self
.spi_clk
= Signal() # Clock
86 self
.dbg_port
= Signal(8) # debug info
92 def add_verilog_source(cls
, verilog_src_dir
, platform
):
93 # add each of the verilog sources, needed for when doing platform.build
94 for fname
in ['wishbone_spi_master.v', 'phy.v']:
95 # prepend the src directory to each filename, add its contents
96 fullname
= os
.path
.join(verilog_src_dir
, fname
)
97 with
open(fullname
) as f
:
98 platform
.add_file(fullname
, f
)
100 def elaborate(self
, platform
):
103 pins
, bus
, cfg_bus
= self
.pins
, self
.bus
, self
.cfg_bus
105 # Calculate SPI flash address
106 spi_bus_adr
= Signal(30)
107 # wb address is in words, offset is in bytes
108 comb
+= spi_bus_adr
.eq(bus
.adr
- (self
.adr_offset
>> 2))
110 # create definition of external verilog Tercel code here, so that
111 # nmigen understands I/O directions (defined by i_ and o_ prefixes)
112 idx
, bus
= self
.idx
, self
.bus
113 tercel
= Instance("tercel_core",
115 i_sys_clk_freq
= self
.clk_freq
,
117 # Clock/reset (use DomainRenamer if needed)
118 i_peripheral_clock
=ClockSignal(),
119 i_peripheral_reset
=ResetSignal(),
121 # SPI region Wishbone bus signals
122 i_wishbone_adr
=spi_bus_adr
,
123 i_wishbone_dat_w
=bus
.dat_w
,
124 i_wishbone_sel
=bus
.sel
,
125 o_wishbone_dat_r
=bus
.dat_r
,
126 i_wishbone_we
=bus
.we
,
127 i_wishbone_stb
=bus
.stb
,
128 i_wishbone_cyc
=bus
.cyc
,
129 o_wishbone_ack
=bus
.ack
,
131 # Configuration region Wishbone bus signals
132 i_cfg_wishbone_adr
=cfg_bus
.adr
,
133 i_cfg_wishbone_dat_w
=cfg_bus
.dat_w
,
134 i_cfg_wishbone_sel
=cfg_bus
.sel
,
135 o_cfg_wishbone_dat_r
=cfg_bus
.dat_r
,
136 i_cfg_wishbone_we
=cfg_bus
.we
,
137 i_cfg_wishbone_stb
=cfg_bus
.stb
,
138 i_cfg_wishbone_cyc
=cfg_bus
.cyc
,
139 o_cfg_wishbone_ack
=cfg_bus
.ack
,
142 o_spi_d_out
=self
.dq_out
,
143 o_spi_d_direction
=self
.dq_direction
,
144 i_spi_d_in
=self
.dq_in
,
145 o_spi_ss_n
=self
.cs_n_out
,
146 o_spi_clock
=self
.spi_clk
,
149 o_debug_port
=self
.dbg_port
152 m
.submodules
['tercel_%d' % self
.idx
] = tercel
155 comb
+= pins
.dq
.o
.eq(self
.dq_out
)
156 comb
+= pins
.dq
.oe
.eq(self
.dq_direction
)
157 comb
+= pins
.dq
.oe
.eq(self
.dq_direction
)
158 comb
+= pins
.dq
.o_clk
.eq(ClockSignal())
159 comb
+= self
.dq_in
.eq(pins
.dq
.i
)
160 comb
+= pins
.dq
.i_clk
.eq(ClockSignal())
161 # XXX invert handled by SPIFlashResource
162 comb
+= pins
.cs
.eq(~self
.cs_n_out
)
163 # ECP5 needs special handling for the SPI clock, sigh.
164 if self
.lattice_ecp5_usrmclk
:
165 m
.submodules
+= Instance("USRMCLK",
166 i_USRMCLKI
= self
.spi_clk
,
170 comb
+= pins
.clk
.eq(self
.spi_clk
)
175 return [self
.bus
.cyc
, self
.bus
.stb
, self
.bus
.ack
,
176 self
.bus
.dat_r
, self
.bus
.dat_w
, self
.bus
.adr
,
177 self
.bus
.we
, self
.bus
.sel
,
178 self
.cfg_bus
.cyc
, self
.cfg_bus
.stb
,
180 self
.cfg_bus
.dat_r
, self
.cfg_bus
.dat_w
,
182 self
.cfg_bus
.we
, self
.cfg_bus
.sel
,
183 self
.dq_out
, self
.dq_direction
, self
.dq_in
,
184 self
.cs_n_out
, self
.spi_clk
188 def create_ilang(dut
, ports
, test_name
):
189 vl
= rtlil
.convert(dut
, name
=test_name
, ports
=ports
)
190 with
open("%s.il" % test_name
, "w") as f
:
193 def create_verilog(dut
, ports
, test_name
):
194 vl
= verilog
.convert(dut
, name
=test_name
, ports
=ports
)
195 with
open("%s.v" % test_name
, "w") as f
:
199 if __name__
== "__main__":
200 tercel
= Tercel(name
="spi_0", data_width
=32, clk_freq
=100e6
)
201 create_ilang(tercel
, tercel
.ports(), "spi_0")