aboutsummaryrefslogtreecommitdiff
path: root/fpga/src/I2C.bs
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/src/I2C.bs')
-rw-r--r--fpga/src/I2C.bs137
1 files changed, 88 insertions, 49 deletions
diff --git a/fpga/src/I2C.bs b/fpga/src/I2C.bs
index 1e7ba27..4b66af5 100644
--- a/fpga/src/I2C.bs
+++ b/fpga/src/I2C.bs
@@ -1,65 +1,102 @@
--- | An I²C controller with support for 7-bit addresses, with a 16-element FIFO
--- buffer.
+-- | A simple I²C controller.
package I2C where
import Clocks
import GetPut
+struct I2CStatus =
+ { -- | Whether a read or write is in progress. Writing to this bit does not
+ -- affect the internal state of the I²C controller.
+ busy :: Bool
+ ; -- | Whether the bus is idle. Usually, software will want to use the `busy`
+ -- bit instead to determine when to send new commands; the `busIdle` bit is
+ -- only set to `True` after the STOP condition. Writing to this bit does
+ -- not affect the internal state of the I²C controller.
+ busIdle :: Bool
+ ; -- | The last ACK bit we received from sending an address. Writing to this
+ -- bit does not affect the internal state of the I²C controller.
+ addrAck :: Bool
+ ; -- | The last ACK bit we received from sending an address, or the next ACK
+ -- bit we'll send.
+ dataAck :: Bool
+ ; -- | A flag set if we detect during a START condition that we've lost
+ -- arbitration. If this occurs, no read or write will occur, and the read
+ -- or write should be retried later. Writing to this bit does not affect
+ -- the internal state of the I²C controller.
+ arbitrationLost :: Bool
+ ; -- | Not-yet-allocated bits.
+ rsvd :: Bit 2
+ }
+ deriving (Bits)
+
-- | An I²C controller.
interface I2C =
-- | The TX side of the SCL pin. High corresponds to high-impedance.
- txSCL :: Bit 1 {-# always_ready #-}
+ txSCL :: Bit 1
-- | 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)
+ txSDA :: Bit 1
+
+ -- | The address register, which contains the address in the high 7 bits and
+ -- the R/!W bit in the low bit.
+ addrReg :: Reg (Bit 8)
+ -- | The data register, which can be read into by a read command or written
+ -- from by a write command, depending on which the address register's low bit
+ -- says to do.
+ dataReg :: Reg (Bit 8)
+ -- | The status register.
+ statusReg :: Reg I2CStatus
+
+ -- | Attempts to trigger a read or write. If one is already in progress (as
+ -- indicated by the `busy` status bit being True), does nothing.
+ trigger :: Action
-- | Runs an I²C interface on the current clock.
+mkI2C' :: Bit 1 -> Bit 1 -> Wire (Bit 1) -> Wire (Bit 1) -> Reg (Bit 8) ->
+ Reg (Bit 8) -> Reg I2CStatus -> PulseWire -> Module ()
+mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg trigger = module
+ n <- mkReg 0
+ rules
+ "output txSCL": when True ==> txSCL := n
+ "flip": when True ==> n := (n + 1)[0:0]
+ return ()
+
+-- | Returns an I²C interface that runs on the current clock. This is not what
+-- you want, most likely.
+mkI2C :: Bit 1 -> Bit 1 -> Module I2C
+mkI2C rxSCL rxSDA = module
+ txSCL <- mkWire
+ txSDA <- mkWire
+ addrReg <- mkReg 0
+ dataReg <- mkReg 0
+ statusReg <- mkReg (I2CStatus
+ { busy = False
+ ; busIdle = False
+ ; addrAck = False
+ ; dataAck = False
+ ; arbitrationLost = False
+ ; rsvd = 0
+ })
+ trigger <- mkPulseWire
+ mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg trigger
+
+ interface I2C
+ txSCL = txSCL
+ txSDA = txSDA
+ addrReg = addrReg
+ dataReg = dataReg
+ statusReg = statusReg
+ trigger = trigger.send
+
+{-
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 (Bit 1) <- mkReg 0
+ rules
+ "output txSCL": when True ==> txSCL := state
+ "flip state": when True ==> state := 1 - state
+
+ {-
state :: Reg State <- mkReg Idle
rules
@@ -106,6 +143,7 @@ mkI2C' rxSCL rxSDA txSCL txSDA sendFIFO recvFIFO = module
state := LowerSCLBeforeAddrAndRWBit (i - 1) cmd
when TODO <- state ==> do
state := TODO
+ -}
interface I2C
txSCL = txSCL
@@ -128,7 +166,7 @@ mkDividedI2C divisor rxSCL rxSDA = module
recvFIFO :: SyncFIFOIfc I2CResult <- mkSyncFIFOToCC 16 clock.slowClock reset
sendFIFO :: SyncFIFOIfc I2CCommand <- mkSyncFIFOFromCC 16 clock.slowClock
rules
- when True ==> do
+ "copy rxSCL and rxSDA": when True ==> do
rxSCLSync.send rxSCL
rxSDASync.send rxSDA
@@ -140,5 +178,6 @@ mkDividedI2C divisor rxSCL rxSDA = module
txSDA = txSDASync
recv = toGet recvFIFO
send = toPut sendFIFO
+-}
-- vim: set ft=haskell :