-
Notifications
You must be signed in to change notification settings - Fork 1
/
i2c_slave.vhd
283 lines (245 loc) · 11.4 KB
/
i2c_slave.vhd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
----------------------------------------------------------------------------------
-- Copyright (c) 2015, Przemyslaw Wegrzyn <[email protected]>
-- This file is distributed under the Modified BSD License.
--
-- This module implements a simple I2C bus slave interface.
----------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity i2c_slave is
generic (
-- address on the I2C bus
address: std_logic_vector(6 downto 0)
);
port (
-- should be ~10 times the I2C bitrate or more, all activity is performed
-- on the rising edge od this clock signal
clk : in std_logic;
-- I2C bidirectional pins (should be connected directly to FPGA pins,
-- allowing fot the synthesis tool to infer proper 3-state buffers)
scl : inout std_logic;
sda : inout std_logic;
-- user interface below. Note rd/wr naming is from the master perspective,
-- so wr_ is for master->slave writes, and rd_ is for slave->master reads.
-- The wr_data_valid goes high each time a new byte is received (available
-- on wr_data). It is held high until receiving side acknowledges by putting
-- wr_data_ack high for one clock cycle.
wr_data : out std_logic_vector (7 downto 0);
wr_data_valid : out std_logic;
wr_data_ack : in std_logic;
-- The rd_data_req goes high whenever there's a byte about to be transmitted
-- to the master. It stays high until user puts the data on rd_data and sets
-- rd_data_valid high for one clock cycle.
rd_data : in std_logic_vector (7 downto 0);
rd_data_req : out std_logic;
rd_data_valid : in std_logic
);
end i2c_slave;
architecture behavioral of i2c_slave is
signal scl_in : std_logic;
signal scl_pull : std_logic := '0';
signal sda_in : std_logic;
signal sda_pull : std_logic := '0';
-- deglitcher shift registers
signal scl_sreg : std_logic_vector(2 downto 0) := (others => '1');
signal sda_sreg : std_logic_vector(2 downto 0) := (others => '1');
-- reclocked and deglitched SCL/SDA inputs
signal scl_in_clean : std_logic := '1';
signal sda_in_clean : std_logic := '1';
-- previous states
signal scl_in_prev : std_logic;
signal sda_in_prev : std_logic;
-- helper signals - start/stop/edge conditions
signal start_condition : boolean;
signal stop_condition : boolean;
signal rising_clk_edge : boolean;
signal falling_clk_edge : boolean;
-- FSM states
type fsm_state_t is (s_idle, s_addr, s_addr_ack,
s_read_ws, s_read, s_read_ack,
s_write, s_write_ws, s_write_ack);
signal fsm_state : fsm_state_t := s_idle;
-- input shift register
signal rx_sreg : std_logic_vector(7 downto 0);
-- TODO: convert to SREG once we have FSM fully working
-- count of rx/tx bits
signal bit_counter : integer;
-- TODO: check if it is better to latch SDA on raising or falling SCL edge
begin
-- concurrent statements for the bidirectional pins
scl_in <= scl;
scl <= '0' when scl_pull = '1' else 'Z';
sda_in <= sda;
sda <= '0' when sda_pull = '1' else 'Z';
-- deglitching / reclocking (because I2C inputs are not aligned to CLK)
i2c_deglitch: process(clk) is
begin
if rising_edge(clk) then
-- shift SCL/SDA into MSB of the shift registers
scl_sreg <= to_X01(scl_in) & scl_sreg(scl_sreg'high downto 1);
sda_sreg <= to_X01(sda_in) & sda_sreg(sda_sreg'high downto 1);
if scl_sreg = (scl_sreg'range => '1') then
scl_in_clean <= '1';
elsif scl_sreg = (scl_sreg'range => '0') then
scl_in_clean <= '0';
end if;
if sda_sreg = (sda_sreg'range => '1') then
sda_in_clean <= '1';
elsif sda_sreg = (sda_sreg'range => '0') then
sda_in_clean <= '0';
end if;
scl_in_prev <= scl_in_clean;
sda_in_prev <= sda_in_clean;
end if;
end process;
-- start/stop conditions
start_condition <= scl_in_prev = '1' and scl_in_clean = '1' and
sda_in_prev = '1' and sda_in_clean = '0';
stop_condition <= scl_in_prev = '1' and scl_in_clean = '1' and
sda_in_prev = '0' and sda_in_clean = '1';
rising_clk_edge <= scl_in_prev = '0' and scl_in_clean = '1';
falling_clk_edge <= scl_in_prev = '1' and scl_in_clean = '0';
-- main I2C slave FSM
i2c_fsm: process(clk) is
begin
if rising_edge(clk) then
case fsm_state is
when s_idle =>
-- detect start condition
if start_condition then
rx_sreg <= (others => '0');
bit_counter <= 8;
fsm_state <= s_addr;
end if;
when s_addr =>
if stop_condition then
-- stop condition during the address phase - go back to idle
fsm_state <= s_idle;
elsif start_condition then
-- start condition means sync error, treat it as a (re)start
-- of a new transaction
rx_sreg <= (others => '0');
bit_counter <= 8;
fsm_state <= s_addr;
elsif rising_clk_edge then
-- shift in next bit on each rising SCL edge
rx_sreg <= rx_sreg(6 downto 0) & sda_in_clean;
bit_counter <= bit_counter - 1;
elsif falling_clk_edge then
-- note: it's a signal, so we "see" previous state
-- if all 8 bits are clocked in, is it addressed to us?
if bit_counter = 0 then
if rx_sreg(7 downto 1) = address then
fsm_state <= s_addr_ack;
else
fsm_state <= s_idle;
end if;
end if;
end if;
when s_addr_ack =>
-- note: sda_pull is set high in this state by concurrent statement
-- we only wait for the clock pulse
if falling_clk_edge then
if rx_sreg(0) = '1' then
fsm_state <= s_read_ws;
scl_pull <= '1';
rd_data_req <= '1';
else
fsm_state <= s_write;
bit_counter <= 8;
end if;
rx_sreg <= (0 => '1', others => '0');
end if;
-- read states
when s_read_ws =>
-- in this state we pull SCL down and wait for the user to provide
-- a byte to send, then we go to s_read. Note: because we pull SCL
-- down, start/stop conditions can't occur.
if rd_data_valid = '1' then
-- latch the data
rd_data_req <= '0';
rx_sreg <= rd_data;
fsm_state <= s_read;
scl_pull <= '0';
bit_counter <= 8;
end if;
when s_read =>
-- there's a byte to send to master,
if stop_condition then
fsm_state <= s_idle;
elsif start_condition then
-- start condition means sync error, treat it as a (re)start
-- of a new transaction
rx_sreg <= (others => '0');
bit_counter <= 8;
fsm_state <= s_addr;
elsif falling_clk_edge then
-- was it the last bit?
if bit_counter = 0 then
-- yes, go wait for master's ACK
fsm_state <= s_read_ack;
else
-- nope, continue
bit_counter <= bit_counter - 1;
rx_sreg <= rx_sreg(6 downto 0) & '0';
end if;
end if;
when s_read_ack =>
-- all bits shifted out, here we wait for falling edge to
-- check if master ACKs the byte
if stop_condition then
fsm_state <= s_idle;
elsif start_condition then
-- start condition means sync error, treat it as a (re)start
-- of a new transaction
rx_sreg <= (others => '0');
bit_counter <= 8;
fsm_state <= s_addr;
elsif falling_clk_edge then
if sda_in_clean = '1' then
-- byte acked, fetch the next one
fsm_state <= s_read_ws;
scl_pull <= '1';
rd_data_req <= '1';
else
-- shortcut - go idle before the stop condition
fsm_state <= s_idle;
end if;
end if;
-- write states
when s_write =>
-- TODO: star/stop conditions
if falling_clk_edge then
-- last bit ?
if bit_counter = 0 then
-- yes, push it out
fsm_state <= s_write_ws;
scl_pull <= '1';
wr_data_valid <= '1';
else
-- nope, continue
bit_counter <= bit_counter - 1;
rx_sreg <= rx_sreg(6 downto 0) & sda_in_clean;
end if;
end if;
when s_write_ws =>
-- waiting for user to pick the byte received
if wr_data_ack = '1' then
scl_pull <= '0';
wr_data_valid <= '0';
fsm_state <= s_write_ack;
end if;
when s_write_ack =>
-- this simple implementation always ACKs writes (SDA is always high here)
if falling_clk_edge then
-- once ACK'ed, wait for next byte (or stop condition)
fsm_state <= s_write;
end if;
end case;
end if;
end process;
-- SDA output is mux'ed based on fsm_state
sda_pull <= '1' when fsm_state = s_addr_ack
else not rx_sreg(7) when fsm_state = s_read
else '0';
end behavioral;