870b3dd0695a7277b4f7033900aeab9f46fda81f
[soc.git] / src / soc / bus / tercel.py
1 #!/usr/bin/env python3
2 #
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.
8 #
9 # this is a wrapper around the opencores verilog tercel module
10
11 from nmigen import (Elaboratable, Cat, Module, Signal, ClockSignal, Instance,
12 ResetSignal)
13
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 import os
19
20 __all__ = ["Tercel"]
21
22
23 class Tercel(Elaboratable):
24 """Tercel SPI controller from Raptor Engineering, nmigen wrapper.
25 remember to call Tercel.add_verilog_source
26 """
27
28 def __init__(self, bus=None, cfg_bus=None, features=None, name=None,
29 data_width=32, spi_region_addr_width=28, pins=None,
30 clk_freq=None,
31 lattice_ecp5_usrmclk=False):
32 if name is not None:
33 # convention: give the name in the format "name_number"
34 self.idx = int(name.split("_")[-1])
35 else:
36 self.idx = 0
37 name = "spi_0"
38 self.granularity = 8
39 self.data_width = data_width
40 self.dsize = log2_int(self.data_width//self.granularity)
41
42 # set up the wishbone busses
43 if features is None:
44 features = frozenset()
45 if bus is None:
46 bus = Interface(addr_width=spi_region_addr_width,
47 data_width=data_width,
48 features=features,
49 granularity=8,
50 name=name+"_wb_%d_0" % self.idx)
51 if cfg_bus is None:
52 cfg_bus = Interface(addr_width=6,
53 data_width=data_width,
54 features=features,
55 granularity=8,
56 name=name+"_wb_%d_1" % self.idx)
57 self.bus = bus
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
63
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)
68
69 self.bus.memory_map = mmap
70 self.cfg_bus.memory_map = cfg_mmap
71
72 # QSPI signals
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
78
79 # pins resource
80 self.pins = pins
81
82 @classmethod
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)
90
91 def elaborate(self, platform):
92 m = Module()
93 comb = m.d.comb
94
95 # Calculate SPI flash address
96 spi_bus_adr = Signal(30)
97 # wb address is in words, offset is in bytes
98 comb += spi_bus_adr.eq(bus.adr - (adr_offset >> 2))
99
100 # create definition of external verilog Tercel code here, so that
101 # nmigen understands I/O directions (defined by i_ and o_ prefixes)
102 idx, bus = self.idx, self.bus
103 tercel = Instance("tercel_core",
104 # System parameters
105 i_sys_clk_freq = clk_freq,
106
107 # Clock/reset (use DomainRenamer if needed)
108 i_peripheral_clock=ClockSignal(),
109 i_peripheral_reset=ResetSignal(),
110
111 # SPI region Wishbone bus signals
112 i_wishbone_adr_i=spi_bus_adr,
113 i_wishbone_dat_i=bus.dat_w,
114 i_wishbone_sel_i=bus.sel,
115 o_wishbone_dat_o=bus.dat_r,
116 i_wishbone_we_i=bus.we,
117 i_wishbone_stb_i=bus.stb,
118 i_wishbone_cyc_i=bus.cyc,
119 o_wishbone_ack_o=bus.ack,
120
121 # Configuration region Wishbone bus signals
122 i_cfg_wishbone_adr_i=cfg_bus.adr,
123 i_cfg_wishbone_dat_i=cfg_bus.dat_w,
124 i_cfg_wishbone_sel_i=cfg_bus.sel,
125 o_cfg_wishbone_dat_o=cfg_bus.dat_r,
126 i_cfg_wishbone_we_i=cfg_bus.we,
127 i_cfg_wishbone_stb_i=cfg_bus.stb,
128 i_cfg_wishbone_cyc_i=cfg_bus.cyc,
129 o_cfg_wishbone_ack_o=cfg_bus.ack,
130
131 # QSPI signals
132 o_spi_d_out=self.dq_out,
133 o_spi_d_direction=self.dq_direction,
134 i_spi_d_in=self.dq_in,
135 o_spi_ss_n=self.cs_n_out,
136 o_spi_clock=self.spi_clk
137 );
138
139 m.submodules['tercel_%d' % self.idx] = uart
140
141 if self.pins is not None:
142 comb += self.pins.dq.o.eq(self.dq_out)
143 comb += self.pins.dq.oe.eq(self.dq_direction)
144 comb += self.pins.dq.oe.eq(self.dq_direction)
145 comb += self.pins.dq.o_clk.eq(ClockSignal())
146 comb += self.dq_in.eq(self.pins.dq.i)
147 comb += self.pins.dq.i_clk.eq(ClockSignal())
148 comb += self.pins.cs_n.eq(self.cs_n_out)
149 # ECP5 needs special handling for the SPI clock, sigh.
150 if lattice_ecp5_usrmclk:
151 self.specials += Instance("USRMCLK",
152 i_USRMCLKI = self.spi_clk,
153 i_USRMCLKTS = 0
154 )
155 else:
156 comb += pads.clk.eq(self.spi_clk)
157
158 return m
159
160
161 def create_ilang(dut, ports, test_name):
162 vl = rtlil.convert(dut, name=test_name, ports=ports)
163 with open("%s.il" % test_name, "w") as f:
164 f.write(vl)
165
166 def create_verilog(dut, ports, test_name):
167 vl = verilog.convert(dut, name=test_name, ports=ports)
168 with open("%s.v" % test_name, "w") as f:
169 f.write(vl)
170
171
172 if __name__ == "__main__":
173 tercel = Tercel(name="spi_0", data_width=32)
174 create_ilang(tercel, [tercel.bus.cyc, tercel.bus.stb, tercel.bus.ack,
175 tercel.bus.dat_r, tercel.bus.dat_w, tercel.bus.adr,
176 tercel.bus.we, tercel.bus.sel,
177 tercel.cfg_bus.cyc, tercel.cfg_bus.stb,
178 tercel.cfg_bus.ack,
179 tercel.cfg_bus.dat_r, tercel.cfg_bus.dat_w,
180 tercel.cfg_bus.adr,
181 tercel.cfg_bus.we, tercel.cfg_bus.sel,
182 tercel.dq_out, tercel.dq_direction, tercel.dq_in,
183 tercel.cs_n_out, tercel.spi_clk
184 ], "spi_0")
185