aboutsummaryrefslogtreecommitdiff
path: root/fpga/src/I2C.bs
blob: 1e7ba275a00d569301571611e13f6f0dfd88d119 (plain)
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
-- | An I²C controller with support for 7-bit addresses, with a 16-element FIFO
-- buffer.
package I2C where

import Clocks
import GetPut

-- | An I²C controller.
interface I2C =
  -- | The TX side of the SCL pin. High corresponds to high-impedance.
  txSCL :: Bit 1 {-# always_ready #-}
  -- | The TX side of the SDA pin. High corresponds to high-impedance.
  txSDA :: Bit 1 {-# always_ready #-}
  -- | Reads a result from the I²C controller's receive buffer.
  recv :: Get I2CResult
  -- | Writes a command to the I²C controller's command buffer.
  send :: Put I2CCommand

data I2CCommand
  = -- | A read from the given address. If the read succeeds with the byte `b`,
    -- `Just b` is written to the receive buffer. If the read fails, `Nothing`
    -- is written to the receive buffer.
    Read (Bit 7)
  | -- | A write to the given address. If the write succeeds, `Just 0` is
    -- written to the receive buffer. If the write fails, `Nothing` is written
    -- to the receive buffer.
    Write (Bit 7) (Bit 8)
  deriving (Bits, FShow)

getAddrAndRW :: I2CCommand -> Bit 8
getAddrAndRW (Read addr) = addr ++ 0b0
getAddrAndRW (Write addr _) = addr ++ 0b1

data I2CResult
  = -- | A byte was successfully read.
    ReadOK (Bit 8)
  | -- | Arbitration was lost.
    ArbitrationLost
  | -- | A NACK bit was received.
    NACK
  deriving (Bits, FShow)

data State
  = -- | The initial state.
    Idle
  | -- | We're about to lower the clock as part of writing the address bit with
    -- the given index. Also used as part of the START sequence.
    LowerSCLBeforeAddrAndRWBit (Bit 3) I2CCommand
  | -- | We're about to write the address bit with the given index.
    WriteAddrAndRWBit (Bit 3) I2CCommand
  | -- | We're about to raise the clock as part of writing the address.
    RaiseSCLAfterAddrAndRWBit (Bit 3) I2CCommand
  | -- | We're about to wait for the target to read the address bit.
    WaitAfterAddrAndRWBit (Bit 3) I2CCommand
  | -- | TODO
    TODO
  deriving (Bits, FShow)

-- | Runs an I²C interface on the current clock.
mkI2C' :: Bit 1 -> Bit 1 -> Reg (Bit 1) -> Reg (Bit 1) ->
  SyncFIFOIfc I2CCommand -> SyncFIFOIfc I2CResult -> Module I2C
mkI2C' rxSCL rxSDA txSCL txSDA sendFIFO recvFIFO = module
  state :: Reg State <- mkReg Idle

  rules
    when True ==> do
      $display "state: " (fshow state)
    when Idle <- state
      rules
        when not sendFIFO.notEmpty ==> do
          $display "Staying idle"
          -- Keep idling.
          txSCL := 1
          txSDA := 1
        when sendFIFO.notEmpty ==> do
          let cmd = sendFIFO.first
          sendFIFO.deq
          $display "Got a command: " (fshow cmd)
          -- See if we can get the bus.
          case rxSDA of
            0 -> do
              -- We lost arbitration -- someone else is driving the bus low.
              recvFIFO.enq ArbitrationLost
            1 -> do
              -- Drive SDA low and wait.
              txSDA := 0
              state := LowerSCLBeforeAddrAndRWBit 7 cmd
    when LowerSCLBeforeAddrAndRWBit i cmd <- state ==> do
      -- Drive SCL low.
      txSCL := 0
      -- Prepare to start writing the address.
      state := WriteAddrAndRWBit i cmd
    when WriteAddrAndRWBit i cmd <- state ==> do
      -- Keep SCL low while writing the data bit.
      txSDA := (getAddrAndRW cmd)[i:i]
      -- Next, we'll raise the clock for a cycle.
      state := RaiseSCLAfterAddrAndRWBit i cmd
    when RaiseSCLAfterAddrAndRWBit i cmd <- state ==> do
      -- Keep SDA the same while raising SCL.
      txSCL := 1
      state := WaitAfterAddrAndRWBit i cmd
    when WaitAfterAddrAndRWBit i cmd <- state ==> do
      if i == 0 then
        state := TODO
      else
        state := LowerSCLBeforeAddrAndRWBit (i - 1) cmd
    when TODO <- state ==> do
      state := TODO

  interface I2C
    txSCL = txSCL
    txSDA = txSDA
    recv = toGet recvFIFO
    send = toPut sendFIFO

-- | Returns an I²C interface with FIFOs that runs on a divided clock. Note
-- that the clock rate should be twice the bus speed -- to run the bus at
-- 100kbit/s (normal mode), the clock should run at 200kHz.
mkDividedI2C :: Integer -> Bit 1 -> Bit 1 -> Module I2C
mkDividedI2C divisor rxSCL rxSDA = module
  clock <- mkClockDivider divisor
  reset <- mkAsyncResetFromCR divisor clock.slowClock

  rxSCLSync :: SyncBitIfc (Bit 1) <- mkSyncBitFromCC clock.slowClock
  rxSDASync :: SyncBitIfc (Bit 1) <- mkSyncBitFromCC clock.slowClock
  txSCLSync :: Reg (Bit 1) <- mkSyncRegToCC 1 clock.slowClock reset
  txSDASync :: Reg (Bit 1) <- mkSyncRegToCC 1 clock.slowClock reset
  recvFIFO :: SyncFIFOIfc I2CResult <- mkSyncFIFOToCC 16 clock.slowClock reset
  sendFIFO :: SyncFIFOIfc I2CCommand <- mkSyncFIFOFromCC 16 clock.slowClock
  rules
    when True ==> do
      rxSCLSync.send rxSCL
      rxSDASync.send rxSDA

  changeSpecialWires (Just clock.slowClock) (Just reset) Nothing $ module
    mkI2C' rxSCLSync.read rxSDASync.read txSCLSync txSDASync sendFIFO recvFIFO

  interface I2C
    txSCL = txSCLSync
    txSDA = txSDASync
    recv = toGet recvFIFO
    send = toPut sendFIFO

-- vim: set ft=haskell :