Skip to content

Commit

Permalink
Fix I2C read stobe timing and add more elaborate tests for reads.
Browse files Browse the repository at this point in the history
  • Loading branch information
whitequark committed Apr 29, 2018
1 parent a59b11a commit af95c91
Showing 1 changed file with 101 additions and 76 deletions.
177 changes: 101 additions & 76 deletions gateware/i2c.py
Expand Up @@ -6,6 +6,11 @@


class I2CBus(Module):
"""
I2C bus.
Decodes bus conditions (start, stop, sample and setup) and provides synchronization.
"""
def __init__(self, scl, sda):
self.scl_i = Signal()
self.scl_o = Signal(reset=1)
Expand Down Expand Up @@ -33,12 +38,10 @@ def __init__(self, scl, sda):
self.start.eq(self.scl_i & sda_r & ~self.sda_i),
self.stop.eq(self.scl_i & ~sda_r & self.sda_i),
]

self.sync += [
scl_r.eq(self.scl_i),
sda_r.eq(self.sda_i),
]

self.specials += [
MultiReg(scl.i, self.scl_i, reset=1),
MultiReg(sda.i, self.sda_i, reset=1),
Expand All @@ -64,7 +67,7 @@ class I2CSlave(Module):
:attr data_i:
Data octet received from the master. Valid when ``write`` is high.
:attr read:
Read strobe. Active for one cycle immediately after latching ``data_o``.
Read strobe. Active for one cycle immediately before latching ``data_o``.
:attr data_o:
Data octet sent to the master. Latched immedately after receiving a read command. Can be changed once ``read`` has gone high.
:attr ack_o:
Expand Down Expand Up @@ -132,7 +135,6 @@ def __init__(self, scl, sda):
If(bus.stop,
NextState("IDLE")
).Elif(bus.start,
NextValue(bitno, 0),
NextState("START")
).Elif(bus.setup,
If(~shreg_i[0],
Expand All @@ -148,62 +150,62 @@ def __init__(self, scl, sda):
)
)
)
self.comb += self.read.eq(self.fsm.after_entering("READ-SHIFT"))
self.fsm.act("READ-SHIFT",
self.fsm.act("WRITE-SHIFT",
If(bus.stop,
NextState("IDLE")
).Elif(bus.start,
NextState("START")
).Elif(bus.sample,
NextValue(shreg_i, (shreg_i << 1) | bus.sda_i),
).Elif(bus.setup,
NextValue(bitno, bitno + 1),
NextValue(bus.sda_o, shreg_o[7]),
NextValue(shreg_o, shreg_o << 1),
).Elif(bus.sample,
If(bitno == 0,
NextValue(bus.sda_o, 1),
NextState("READ-ACK")
If(bitno == 7,
NextValue(self.data_i, shreg_i),
NextState("WRITE-ACK")
)
)
)
self.fsm.act("READ-ACK",
self.comb += self.write.eq(self.fsm.after_entering("WRITE-ACK"))
self.fsm.act("WRITE-ACK",
If(bus.stop,
NextState("IDLE")
).Elif(bus.start,
NextState("START")
).Elif(bus.sample,
If(~bus.sda_i,
NextState("READ-SHIFT")
).Else(
NextState("IDLE")
)
).Elif(~bus.scl_i & self.ack_o,
NextValue(bus.sda_o, 0)
).Elif(bus.setup,
NextValue(bus.sda_o, 1),
NextState("WRITE-SHIFT")
)
)
self.fsm.act("WRITE-SHIFT",
self.comb += self.read.eq(self.fsm.before_entering("READ-SHIFT"))
self.fsm.act("READ-SHIFT",
If(bus.stop,
NextState("IDLE")
).Elif(bus.start,
NextState("START")
).Elif(bus.sample,
NextValue(shreg_i, (shreg_i << 1) | bus.sda_i),
).Elif(bus.setup,
NextValue(bitno, bitno + 1),
If(bitno == 7,
NextValue(self.data_i, shreg_i),
NextState("WRITE-ACK")
NextValue(bus.sda_o, shreg_o[7]),
NextValue(shreg_o, shreg_o << 1),
).Elif(bus.sample,
If(bitno == 0,
NextValue(bus.sda_o, 1),
NextState("READ-ACK")
)
)
)
self.comb += self.write.eq(self.fsm.after_entering("WRITE-ACK"))
self.fsm.act("WRITE-ACK",
self.fsm.act("READ-ACK",
If(bus.stop,
NextState("IDLE")
).Elif(bus.start,
NextState("START")
).Elif(~bus.scl_i & self.ack_o,
NextValue(bus.sda_o, 0)
).Elif(bus.setup,
NextValue(bus.sda_o, 1),
NextState("WRITE-SHIFT")
).Elif(bus.sample,
If(~bus.sda_i,
NextState("READ-SHIFT")
).Else(
NextState("IDLE")
)
)
)

Expand Down Expand Up @@ -290,14 +292,12 @@ def write_octet(self, octet):
for bit in range(8)[::-1]:
yield from self.write_bit((octet >> bit) & 1)

def read_bit(self, last_half = True):
assert (yield self.scl_i) == 1
def read_bit(self):
yield self.scl_o.eq(0)
yield from self.half_period()
yield self.scl_o.eq(1)
bit = (yield self.sda_i)
if last_half:
yield from self.half_period()
yield from self.half_period()
return bit

def read_octet(self):
Expand Down Expand Up @@ -343,8 +343,11 @@ def test_addr_nak(self, tb):
def test_addr_r_ack(self, tb):
yield from tb.start()
yield from tb.write_octet(0b01010001)
self.assertEqual((yield from tb.read_bit(last_half=False)), 0)
yield tb.scl_o.eq(0)
yield from tb.half_period()
self.assertEqual((yield from tb.dut_state()), "ADDR-ACK")
self.assertEqual((yield tb.sda_i), 0)
yield tb.scl_o.eq(1)
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.start))))
yield
self.assertEqual((yield from tb.dut_state()), "READ-SHIFT")
Expand All @@ -360,11 +363,14 @@ def test_addr_w_ack(self, tb):
yield
self.assertEqual((yield from tb.dut_state()), "WRITE-SHIFT")

@simulation_test
def test_write_shift(self, tb):
def start_addr(self, tb, read):
yield from tb.start()
yield from tb.write_octet(0b01010000)
yield from tb.write_octet(0b01010000 | read)
self.assertEqual((yield from tb.read_bit()), 0)

@simulation_test
def test_write_shift(self, tb):
yield from self.start_addr(tb, read=False)
yield from tb.write_octet(0b10100101)
yield tb.scl_o.eq(0)
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.write))))
Expand All @@ -373,22 +379,50 @@ def test_write_shift(self, tb):
self.assertEqual((yield from tb.dut_state()), "WRITE-ACK")

@simulation_test
def test_write_stop(self, tb):
def test_read_shift(self, tb):
yield from tb.start()
yield from tb.write_octet(0b01010000)
self.assertEqual((yield from tb.read_bit()), 0)
yield from tb.write_octet(0b01010001)
yield tb.scl_o.eq(0)
yield from tb.half_period()
self.assertEqual((yield tb.sda_i), 0)
# this sequence ensures combinatorial feedback works
yield tb.scl_o.eq(1)
yield
yield
yield tb.dut.data_o.eq(0b10100101)
yield
self.assertEqual((yield tb.dut.read), 1)
yield tb.dut.data_o.eq(0)
yield
self.assertEqual((yield tb.dut.read), 0)
yield from tb.half_period()
yield tb.scl_o.eq(1)
self.assertEqual((yield from tb.read_octet()), 0b10100101)
yield
self.assertEqual((yield from tb.dut_state()), "READ-ACK")

@simulation_test
def test_write_stop(self, tb):
yield from self.start_addr(tb, read=False)
yield from tb.write_bit(0)
yield from tb.write_bit(1)
yield from tb.stop()
self.assertEqual((yield from tb.dut_state()), "IDLE")

@simulation_test
def test_read_stop(self, tb):
yield tb.dut.data_o.eq(0b11111111)
yield from self.start_addr(tb, read=True)
yield from tb.read_bit()
yield from tb.read_bit()
yield from tb.stop()
self.assertEqual((yield from tb.dut_state()), "IDLE")

@simulation_test
def test_write_ack(self, tb):
yield from tb.start()
yield from tb.write_octet(0b01010000)
self.assertEqual((yield from tb.read_bit()), 0)
yield from self.start_addr(tb, read=False)
yield from tb.write_octet(0b10100101)
# this convoluted sequence ensures combinatorial feedback works
# this sequence ensures combinatorial feedback works
yield tb.scl_o.eq(0)
yield
yield
Expand All @@ -405,17 +439,13 @@ def test_write_ack(self, tb):

@simulation_test
def test_write_nak(self, tb):
yield from tb.start()
yield from tb.write_octet(0b01010000)
self.assertEqual((yield from tb.read_bit()), 0)
yield from self.start_addr(tb, read=False)
yield from tb.write_octet(0b10100101)
self.assertEqual((yield from tb.read_bit()), 1)

@simulation_test
def test_write_ack_stop(self, tb):
yield from tb.start()
yield from tb.write_octet(0b01010000)
self.assertEqual((yield from tb.read_bit()), 0)
yield from self.start_addr(tb, read=False)
yield from tb.write_octet(0b10100101)
self.assertEqual((yield from tb.read_bit()), 1)
yield tb.scl_o.eq(0)
Expand All @@ -429,39 +459,34 @@ def test_write_ack_stop(self, tb):
self.assertEqual((yield from tb.dut_state()), "IDLE")

@simulation_test
def test_read_nack_stop(self, tb):
def test_read_ack(self, tb):
yield tb.dut.data_o.eq(0b10100101)
yield from tb.start()
yield from tb.write_octet(0b01010001)
self.assertEqual((yield from tb.read_bit(last_half=False)), 0) # ACK
self.assertEqual((yield from tb.dut_state()), "ADDR-ACK")
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.start))))
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.read))))
yield from self.start_addr(tb, read=True)
self.assertEqual((yield from tb.read_octet()), 0b10100101)
yield tb.scl_o.eq(0)
yield from tb.half_period()
self.assertEqual((yield from tb.dut_state()), "READ-ACK")
yield tb.scl_o.eq(1)
yield from tb.half_period()
self.assertEqual((yield from tb.dut_state()), "IDLE")
yield from tb.write_bit(0)
self.assertEqual((yield from tb.dut_state()), "READ-SHIFT")

@simulation_test
def test_read_multi(self, tb):
def test_read_nak(self, tb):
yield tb.dut.data_o.eq(0b10100101)
yield from tb.start()
yield from tb.write_octet(0b01010001)
self.assertEqual((yield from tb.read_bit(last_half=False)), 0) # ACK
self.assertEqual((yield from tb.dut_state()), "ADDR-ACK")
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.start))))
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.read))))
yield from self.start_addr(tb, read=True)
self.assertEqual((yield from tb.read_octet()), 0b10100101)
yield from tb.write_bit(0)
self.assertEqual((yield from tb.dut_state()), "READ-SHIFT")

@simulation_test
def test_read_nak_stop(self, tb):
yield tb.dut.data_o.eq(0b10100101)
yield from self.start_addr(tb, read=True)
self.assertEqual((yield from tb.read_octet()), 0b10100101)
yield tb.scl_o.eq(0)
yield tb.sda_o.eq(0)
yield from tb.half_period()
self.assertEqual((yield from tb.dut_state()), "READ-ACK")
yield tb.scl_o.eq(1)
yield from tb.half_period()
self.assertEqual((yield from tb.dut_state()), "READ-SHIFT")
self.assertTrue((yield from tb.wait_for(lambda: (yield tb.dut.stop))))
yield
self.assertEqual((yield from tb.dut_state()), "IDLE")


if __name__ == "__main__":
from migen.fhdl import verilog
Expand Down

0 comments on commit af95c91

Please sign in to comment.