c50c53744f9d162acb6f5635db4a76093963647f
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. remember to call
25 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):
33 # convention: give the name in the format "name_number"
34 self
.idx
= int(name
.split("_")[-1])
39 self
.data_width
= data_width
40 self
.dsize
= log2_int(self
.data_width
//self
.granularity
)
42 # set up the wishbone busses
44 features
= frozenset()
46 bus
= Interface(addr_width
=spi_region_addr_width
,
47 data_width
=data_width
,
50 name
=name
+"_wb_%d_0" % self
.idx
)
52 cfg_bus
= Interface(addr_width
=6,
53 data_width
=data_width
,
56 name
=name
+"_wb_%d_1" % self
.idx
)
58 assert len(self
.bus
.dat_r
) == data_width
, \
59 "bus width must be %d" % data_width
60 self
.cfg_bus
= cfg_bus
61 assert len(self
.cfg_bus
.dat_r
) == data_width
, \
62 "bus width must be %d" % data_width
64 mmap
= MemoryMap(addr_width
=spi_region_addr_width
+self
.dsize
,
65 data_width
=self
.granularity
)
66 cfg_mmap
= MemoryMap(addr_width
=6+self
.dsize
,
67 data_width
=self
.granularity
)
69 self
.bus
.memory_map
= mmap
70 self
.cfg_bus
.memory_map
= cfg_mmap
73 self
.dq_out
= Signal(4) # Data
74 self
.dq_direction
= Signal(4)
75 self
.dq_in
= Signal(4)
76 self
.cs_n_out
= Signal() # Slave select
77 self
.spi_clk
= Signal() # Clock
83 def add_verilog_source(cls
, verilog_src_dir
, platform
):
84 # add each of the verilog sources, needed for when doing platform.build
85 for fname
in ['wishbone_spi_master.v', 'phy.v']:
86 # prepend the src directory to each filename, add its contents
87 fullname
= os
.path
.join(verilog_src_dir
, fname
)
88 with
open(fullname
) as f
:
89 platform
.add_file(fullname
, f
)
91 def elaborate(self
, platform
):
95 # Calculate SPI flash address
96 spi_bus_adr
= Signal(30)
97 self
.comb
+= spi_bus_adr
.eq(bus
.adr
- (adr_offset
>> 2)) # wb address is in words, offset is in bytes
99 # create definition of external verilog Tercel code here, so that
100 # nmigen understands I/O directions (defined by i_ and o_ prefixes)
101 idx
, bus
= self
.idx
, self
.bus
102 tercel
= Instance("tercel_core",
104 i_sys_clk_freq
= clk_freq
,
106 # Clock/reset (use DomainRenamer if needed)
107 i_peripheral_clock
=ClockSignal(),
108 i_peripheral_reset
=ResetSignal(),
110 # SPI region Wishbone bus signals
111 i_wishbone_adr_i
=spi_bus_adr
,
112 i_wishbone_dat_i
=bus
.dat_w
,
113 i_wishbone_sel_i
=bus
.sel
,
114 o_wishbone_dat_o
=bus
.dat_r
,
115 i_wishbone_we_i
=bus
.we
,
116 i_wishbone_stb_i
=bus
.stb
,
117 i_wishbone_cyc_i
=bus
.cyc
,
118 o_wishbone_ack_o
=bus
.ack
,
120 # Configuration region Wishbone bus signals
121 i_cfg_wishbone_adr_i
=cfg_bus
.adr
,
122 i_cfg_wishbone_dat_i
=cfg_bus
.dat_w
,
123 i_cfg_wishbone_sel_i
=cfg_bus
.sel
,
124 o_cfg_wishbone_dat_o
=cfg_bus
.dat_r
,
125 i_cfg_wishbone_we_i
=cfg_bus
.we
,
126 i_cfg_wishbone_stb_i
=cfg_bus
.stb
,
127 i_cfg_wishbone_cyc_i
=cfg_bus
.cyc
,
128 o_cfg_wishbone_ack_o
=cfg_bus
.ack
,
131 o_spi_d_out
=self
.dq_out
,
132 o_spi_d_direction
=self
.dq_direction
,
133 i_spi_d_in
=self
.dq_in
,
134 o_spi_ss_n
=self
.cs_n_out
,
135 o_spi_clock
=self
.spi_clk
138 m
.submodules
['tercel_%d' % self
.idx
] = uart
140 if self
.pins
is not None:
141 comb
+= self
.pins
.dq
.o
.eq(self
.dq_out
)
142 comb
+= self
.pins
.dq
.oe
.eq(self
.dq_direction
)
143 comb
+= self
.pins
.dq
.oe
.eq(self
.dq_direction
)
144 comb
+= self
.pins
.dq
.o_clk
.eq(ClockSignal())
145 comb
+= self
.dq_in
.eq(self
.pins
.dq
.i
)
146 comb
+= self
.pins
.dq
.i_clk
.eq(ClockSignal())
147 comb
+= self
.pins
.cs_n
.eq(self
.cs_n_out
)
148 if lattice_ecp5_usrmclk
:
149 self
.specials
+= Instance("USRMCLK",
150 i_USRMCLKI
= self
.spi_clk
,
154 self
.comb
+= pads
.clk
.eq(self
.spi_clk
)
159 def create_ilang(dut
, ports
, test_name
):
160 vl
= rtlil
.convert(dut
, name
=test_name
, ports
=ports
)
161 with
open("%s.il" % test_name
, "w") as f
:
164 def create_verilog(dut
, ports
, test_name
):
165 vl
= verilog
.convert(dut
, name
=test_name
, ports
=ports
)
166 with
open("%s.v" % test_name
, "w") as f
:
170 if __name__
== "__main__":
171 tercel
= Tercel(name
="spi_0", data_width
=32)
172 create_ilang(tercel
, [tercel
.bus
.cyc
, tercel
.bus
.stb
, tercel
.bus
.ack
,
173 tercel
.bus
.dat_r
, tercel
.bus
.dat_w
, tercel
.bus
.adr
,
174 tercel
.bus
.we
, tercel
.bus
.sel
,
175 tercel
.cfg_bus
.cyc
, tercel
.cfg_bus
.stb
, tercel
.cfg_bus
.ack
,
176 tercel
.cfg_bus
.dat_r
, tercel
.cfg_bus
.dat_w
, tercel
.cfg_bus
.adr
,
177 tercel
.cfg_bus
.we
, tercel
.cfg_bus
.sel
,
178 tercel
.dq_out
, tercel
.dq_direction
, tercel
.dq_in
,
179 tercel
.cs_n_out
, tercel
.spi_clk