From ee38cf7feceef308fd0fcbca89f1e7f76714bd62 Mon Sep 17 00:00:00 2001 From: Nathan Ringo Date: Wed, 23 Oct 2024 10:30:11 -0500 Subject: =?UTF-8?q?Start=20of=20an=20I=C2=B2C=20rewrite...=20again...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fpga/src/I2C.bs | 137 ++++++++++++++++++++++++++++++++++------------------- fpga/src/TopSim.bs | 27 ++++++----- 2 files changed, 104 insertions(+), 60 deletions(-) (limited to 'fpga/src') 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 : diff --git a/fpga/src/TopSim.bs b/fpga/src/TopSim.bs index add8b54..322ffb2 100644 --- a/fpga/src/TopSim.bs +++ b/fpga/src/TopSim.bs @@ -1,7 +1,7 @@ -- | The top-level module for simulation. package TopSim where -import Numini +-- import Numini import GetPut import I2C @@ -23,23 +23,28 @@ mkTopSim = module i2c_scl <- mkReg 0 i2c_sda <- mkReg 0 -- i2c <- mkDividedI2C (12_000_000 / 200_000) 1 1 - i2c <- mkDividedI2C 2 1 1 + -- i2c <- mkDividedI2C 2 1 1 + i2c <- mkI2C 1 1 rules when True ==> i2c_scl := i2c.txSCL when True ==> i2c_sda := i2c.txSDA timer :: Reg (Bit 16) <- mkReg 0 rules - when (timer == 0) ==> i2c.send.put (Write 0x20 0x00) - when (timer == 1) ==> i2c.send.put (Write 0x20 0x00) - when (timer == 2) ==> i2c.send.put (Write 0x20 0x00) - when (timer == 3) ==> i2c.send.put (Write 0x20 0x12) - when (timer == 4) ==> i2c.send.put (Write 0x20 0xaa) - when (timer == 5) ==> i2c.send.put (Write 0x20 0x55) - when True ==> timer := timer + 1 - when (timer == 0x1fff) ==> $finish - when True ==> do + {- + "t0": when (timer == 0) ==> i2c.send.put (Write 0x20 0x00) + "t1": when (timer == 1) ==> i2c.send.put (Write 0x20 0x00) + "t2": when (timer == 2) ==> i2c.send.put (Write 0x20 0x00) + "t3": when (timer == 3) ==> i2c.send.put (Write 0x20 0x12) + "t4": when (timer == 4) ==> i2c.send.put (Write 0x20 0xaa) + "t5": when (timer == 5) ==> i2c.send.put (Write 0x20 0x55) + -} + "advance timer": when True ==> timer := timer + 1 + "finish": when (timer == 0x1fff) ==> $finish + {- + "log received values": when True ==> do result <- i2c.recv.get $display "recv: " (fshow result) + -} -- vim: set ft=haskell : -- cgit v1.2.3