From 76ce6c75b4bd723aabfd0fd6f4d310d3ed90e7fd Mon Sep 17 00:00:00 2001 From: Nathan Ringo Date: Wed, 23 Oct 2024 08:24:53 -0500 Subject: checkpoint; something is broken with timing --- fpga/src/I2C.bs | 110 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 31 deletions(-) (limited to 'fpga/src/I2C.bs') diff --git a/fpga/src/I2C.bs b/fpga/src/I2C.bs index a8553a8..1e7ba27 100644 --- a/fpga/src/I2C.bs +++ b/fpga/src/I2C.bs @@ -8,11 +8,11 @@ import GetPut -- | An I²C controller. interface I2C = -- | The TX side of the SCL pin. High corresponds to high-impedance. - txSCL :: Bit 1 + txSCL :: Bit 1 {-# always_ready #-} -- | The TX side of the SDA pin. High corresponds to high-impedance. - txSDA :: Bit 1 - -- | Reads a byte from the I²C controller's receive buffer. - recv :: Get (Maybe (Bit 8)) + 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 @@ -25,34 +25,93 @@ data I2CCommand -- written to the receive buffer. If the write fails, `Nothing` is written -- to the receive buffer. Write (Bit 7) (Bit 8) - deriving (Bits) + 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) + deriving (Bits, FShow) --- | Returns an I²C interface that does _not_ have FIFOs. -mkI2C' :: Bit 1 -> Bit 1 -> Module I2C -mkI2C' rxSCL rxSDA = module - recvFIFO :: Wire (Maybe (Bit 8)) <- mkWire - sendFIFO :: Wire I2CCommand <- mkWire +-- | 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 - txSCL :: Wire (Bit 1) <- mkWire - txSDA :: Wire (Bit 1) <- mkWire rules - when Idle <- state ==> do - txSCL._write 1 - txSDA._write 1 + 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._read - send = toPut sendFIFO._write + 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 @@ -66,7 +125,7 @@ mkDividedI2C divisor rxSCL rxSDA = module 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 (Maybe (Bit 8)) <- mkSyncFIFOToCC 16 clock.slowClock reset + recvFIFO :: SyncFIFOIfc I2CResult <- mkSyncFIFOToCC 16 clock.slowClock reset sendFIFO :: SyncFIFOIfc I2CCommand <- mkSyncFIFOFromCC 16 clock.slowClock rules when True ==> do @@ -74,18 +133,7 @@ mkDividedI2C divisor rxSCL rxSDA = module rxSDASync.send rxSDA changeSpecialWires (Just clock.slowClock) (Just reset) Nothing $ module - i2c <- mkI2C' rxSCLSync.read rxSDASync.read - rules - when True ==> do - txSCLSync._write i2c.txSCL - txSDASync._write i2c.txSDA - when True ==> do - byte <- i2c.recv.get - recvFIFO.enq byte - when True ==> do - i2c.send.put sendFIFO.first - sendFIFO.deq - return () + mkI2C' rxSCLSync.read rxSDASync.read txSCLSync txSDASync sendFIFO recvFIFO interface I2C txSCL = txSCLSync -- cgit v1.2.3