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
91 def add_verilog_source(cls
, verilog_src_dir
, platform
):
92 # add each of the verilog sources, needed for when doing platform.build
93 for fname
in ['wishbone_spi_master.v', 'phy.v']:
94 # prepend the src directory to each filename, add its contents
95 fullname
= os
.path
.join(verilog_src_dir
, fname
)
96 with
open(fullname
) as f
:
97 platform
.add_file(fullname
, f
)
99 def elaborate(self
, platform
):
102 pins
, bus
, cfg_bus
= self
.pins
, self
.bus
, self
.cfg_bus
104 # Calculate SPI flash address
105 spi_bus_adr
= Signal(30)
106 # wb address is in words, offset is in bytes
107 comb
+= spi_bus_adr
.eq(bus
.adr
- (self
.adr_offset
>> 2))
109 # create definition of external verilog Tercel code here, so that
110 # nmigen understands I/O directions (defined by i_ and o_ prefixes)
111 idx
, bus
= self
.idx
, self
.bus
112 tercel
= Instance("tercel_core",
114 i_sys_clk_freq
= self
.clk_freq
,
116 # Clock/reset (use DomainRenamer if needed)
117 i_peripheral_clock
=ClockSignal(),
118 i_peripheral_reset
=ResetSignal(),
120 # SPI region Wishbone bus signals
121 i_wishbone_adr_i
=spi_bus_adr
,
122 i_wishbone_dat_i
=bus
.dat_w
,
123 i_wishbone_sel_i
=bus
.sel
,
124 o_wishbone_dat_o
=bus
.dat_r
,
125 i_wishbone_we_i
=bus
.we
,
126 i_wishbone_stb_i
=bus
.stb
,
127 i_wishbone_cyc_i
=bus
.cyc
,
128 o_wishbone_ack_o
=bus
.ack
,
130 # Configuration region Wishbone bus signals
131 i_cfg_wishbone_adr_i
=cfg_bus
.adr
,
132 i_cfg_wishbone_dat_i
=cfg_bus
.dat_w
,
133 i_cfg_wishbone_sel_i
=cfg_bus
.sel
,
134 o_cfg_wishbone_dat_o
=cfg_bus
.dat_r
,
135 i_cfg_wishbone_we_i
=cfg_bus
.we
,
136 i_cfg_wishbone_stb_i
=cfg_bus
.stb
,
137 i_cfg_wishbone_cyc_i
=cfg_bus
.cyc
,
138 o_cfg_wishbone_ack_o
=cfg_bus
.ack
,
141 o_spi_d_out
=self
.dq_out
,
142 o_spi_d_direction
=self
.dq_direction
,
143 i_spi_d_in
=self
.dq_in
,
144 o_spi_ss_n
=self
.cs_n_out
,
145 o_spi_clock
=self
.spi_clk
148 m
.submodules
['tercel_%d' % self
.idx
] = tercel
151 comb
+= pins
.dq
.o
.eq(self
.dq_out
)
152 comb
+= pins
.dq
.oe
.eq(self
.dq_direction
)
153 comb
+= pins
.dq
.oe
.eq(self
.dq_direction
)
154 comb
+= pins
.dq
.o_clk
.eq(ClockSignal())
155 comb
+= self
.dq_in
.eq(pins
.dq
.i
)
156 comb
+= pins
.dq
.i_clk
.eq(ClockSignal())
157 # XXX invert handled by SPIFlashResource
158 comb
+= pins
.cs
.eq(~self
.cs_n_out
)
159 # ECP5 needs special handling for the SPI clock, sigh.
160 if self
.lattice_ecp5_usrmclk
:
161 self
.specials
+= Instance("USRMCLK",
162 i_USRMCLKI
= self
.spi_clk
,
166 comb
+= pins
.clk
.eq(self
.spi_clk
)
171 def create_ilang(dut
, ports
, test_name
):
172 vl
= rtlil
.convert(dut
, name
=test_name
, ports
=ports
)
173 with
open("%s.il" % test_name
, "w") as f
:
176 def create_verilog(dut
, ports
, test_name
):
177 vl
= verilog
.convert(dut
, name
=test_name
, ports
=ports
)
178 with
open("%s.v" % test_name
, "w") as f
:
182 if __name__
== "__main__":
183 tercel
= Tercel(name
="spi_0", data_width
=32, clk_freq
=100e6
)
184 create_ilang(tercel
, [tercel
.bus
.cyc
, tercel
.bus
.stb
, tercel
.bus
.ack
,
185 tercel
.bus
.dat_r
, tercel
.bus
.dat_w
, tercel
.bus
.adr
,
186 tercel
.bus
.we
, tercel
.bus
.sel
,
187 tercel
.cfg_bus
.cyc
, tercel
.cfg_bus
.stb
,
189 tercel
.cfg_bus
.dat_r
, tercel
.cfg_bus
.dat_w
,
191 tercel
.cfg_bus
.we
, tercel
.cfg_bus
.sel
,
192 tercel
.dq_out
, tercel
.dq_direction
, tercel
.dq_in
,
193 tercel
.cs_n_out
, tercel
.spi_clk