From 5cf952244aac59e5eec4f789e8b4261967c7055a Mon Sep 17 00:00:00 2001 From: Nathan Ringo Date: Wed, 23 Oct 2024 18:08:47 -0500 Subject: like 1/3 of an i2c ctrler --- fpga/src/I2C.bs | 234 ++++++++++++++++++++++++++++++++++------------------- fpga/src/TopSim.bs | 31 ++++--- 2 files changed, 164 insertions(+), 101 deletions(-) diff --git a/fpga/src/I2C.bs b/fpga/src/I2C.bs index 4b66af5..4a302dd 100644 --- a/fpga/src/I2C.bs +++ b/fpga/src/I2C.bs @@ -5,19 +5,21 @@ 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. + { -- | Whether a read or write is not in progress, so a new command can be + -- performed. Writing this bit to move it from high to low begins a + -- command. + notBusy :: Bool + ; -- | Whether the bus is idle. Usually, software will want to use the + -- `notBusy` bit instead to determine when to send new commands; the + -- `busIdle` bit is only set to `True` after the STOP condition (i.e., + -- after the transaction ends). 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. + ; -- | The last ACK bit we received during a write, or the next ACK bit we'll + -- send during a read. 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 @@ -25,7 +27,7 @@ struct I2CStatus = -- the internal state of the I²C controller. arbitrationLost :: Bool ; -- | Not-yet-allocated bits. - rsvd :: Bit 2 + rsvd :: Bit 3 } deriving (Bits) @@ -46,18 +48,145 @@ interface I2C = -- | 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 +data I2CBusState + = -- | (We believe) the bus is idle. + Idle + | -- | We've driven SDA low as part of the START condition. + StartSDA + | -- | We're sending an address, and about to write the numbered bit to SDA. + -- SCL should be low. + SendAddrBitSDA (Bit 3) + | -- | We're sending an address, and about to raise SCL. + SendAddrBitRaiseSCL (Bit 3) (Bit 1) + | -- | We're waiting for the device to read our address bit. SCL should be + -- high. + SendAddrBitWait (Bit 3) (Bit 1) + | -- | We're sending an address, and about to lower SCL. + SendAddrBitLowerSCL (Bit 3) (Bit 1) + | -- | We're lowering SDA as part of getting the ACK bit after sending an + -- address. + GetAddrAckSDA + | -- | We're raising SCL to start the clock cycle in which we'll get the ACK + -- bit after sending an address. + GetAddrAckRaiseSCL + | -- | We're reading SDA, which should be the ACK bit after sending an + -- address. + GetAddrAckReadSDA + | -- | We're about to either lower SCL or keep it low as part of reading a + -- byte from a device. + ReadLowerSCL (Bit 3) (Bit 1) + | -- | We're about to raise SCL as part of reading a byte from a device. + ReadRaiseSCL (Bit 3) + | -- | We're about to read SDA while keeping SCL high as part of reading a + -- byte from a device. + ReadReadSDA (Bit 3) + | -- | We're about to lower SCL as part of writing an ACK bit after reading a + -- byte from a device. + ReadAckLowerSCL + | -- | We're about to write SDA while keeping SCL low as part of writing an + -- ACK bit in response to reading a byte from a device. + ReadAckWriteSDA + | -- | We're about to raise SCL or keep it raised as part of writing an ACK + -- bit after reading a byte from a device. The first bit is the bit to + -- hold, the second is the number of cycles to hold it for. + ReadAckRaiseSCL (Bit 1) (Bit 1) + | -- | TODO + TODO + deriving (Bits, FShow) -- | 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 + Reg (Bit 8) -> Reg I2CStatus -> Module () +mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg = module + state :: Reg I2CBusState <- mkReg Idle rules - "output txSCL": when True ==> txSCL := n - "flip": when True ==> n := (n + 1)[0:0] + "debug": when True ==> $display (fshow state) + "remain Idle": when Idle <- state, statusReg.notBusy ==> do + state := Idle + txSCL := 1 + txSDA := 1 + "triggered from Idle": when Idle <- state, not statusReg.notBusy ==> + if rxSDA == 0 then do + statusReg := statusReg { arbitrationLost = True } + state := Idle + txSCL := 1 + txSDA := 1 + else do + state := StartSDA + txSCL := 1 + txSDA := 0 + "StartSDA": when StartSDA <- state ==> do + state := SendAddrBitSDA 7 + txSCL := 0 + txSDA := 0 + "SendAddrBitSDA": when SendAddrBitSDA n <- state ==> do + let bit = addrReg[n:n] + state := SendAddrBitRaiseSCL n bit + txSCL := 0 + txSDA := bit + "SendAddrBitRaiseSCL": when SendAddrBitRaiseSCL n bit <- state ==> do + state := SendAddrBitWait n bit + txSCL := 1 + txSDA := bit + "SendAddrBitWait": when SendAddrBitWait n bit <- state ==> do + state := SendAddrBitLowerSCL n bit + txSCL := 1 + txSDA := bit + "SendAddrBitLowerSCL": when SendAddrBitLowerSCL n bit <- state ==> do + state := if n == 0 + then GetAddrAckSDA + else SendAddrBitSDA (n - 1) + txSCL := 0 + txSDA := bit + "GetAddrAckSDA": when GetAddrAckSDA <- state ==> do + state := GetAddrAckRaiseSCL + txSCL := 0 + txSDA := 0 + "GetAddrAckRaiseSCL": when GetAddrAckRaiseSCL <- state ==> do + state := GetAddrAckReadSDA + txSCL := 1 + txSDA := 1 + "GetAddrAckReadSDA": when GetAddrAckReadSDA <- state ==> do + statusReg := statusReg { addrAck = unpack rxSDA } + state := if rxSDA == 1 -- NACK + then TODO + else if addrReg[0:0] == 0 -- R/!W + then TODO -- !W + else ReadLowerSCL 7 1 + txSCL := 1 + txSDA := 1 + "ReadLowerSCL": when ReadLowerSCL n time <- state ==> do + state := if time == 1 + then ReadLowerSCL n (time - 1) + else ReadRaiseSCL n + txSCL := 0 + txSDA := 1 + "ReadRaiseSCL": when ReadRaiseSCL n <- state ==> do + state := ReadReadSDA n + txSCL := 1 + txSDA := 1 + "ReadReadSDA": when ReadReadSDA n <- state ==> do + dataReg := dataReg [6:0] ++ rxSDA + state := if n == 0 + then ReadAckLowerSCL + else ReadLowerSCL (n - 1) 1 + txSCL := 1 + txSDA := 1 + "ReadAckLowerSCL": when ReadAckLowerSCL <- state ==> do + state := ReadAckWriteSDA + txSCL := 0 + txSDA := 1 + "ReadAckWriteSDA": when ReadAckWriteSDA <- state ==> do + let bit = pack statusReg.dataAck + state := ReadAckRaiseSCL bit 1 + txSCL := 0 + txSDA := bit + "ReadAckRaiseSCL": when ReadAckRaiseSCL bit time <- state ==> do + state := if time == 1 + then ReadAckRaiseSCL bit (time - 1) + else TODO + txSCL := 1 + txSDA := bit return () -- | Returns an I²C interface that runs on the current clock. This is not what @@ -69,15 +198,14 @@ mkI2C rxSCL rxSDA = module addrReg <- mkReg 0 dataReg <- mkReg 0 statusReg <- mkReg (I2CStatus - { busy = False + { notBusy = False ; busIdle = False ; addrAck = False ; dataAck = False ; arbitrationLost = False ; rsvd = 0 }) - trigger <- mkPulseWire - mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg trigger + mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg interface I2C txSCL = txSCL @@ -85,72 +213,8 @@ mkI2C rxSCL rxSDA = module 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 - 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. diff --git a/fpga/src/TopSim.bs b/fpga/src/TopSim.bs index 322ffb2..d4a5812 100644 --- a/fpga/src/TopSim.bs +++ b/fpga/src/TopSim.bs @@ -20,27 +20,26 @@ mkTopSim = module hyperbus_rwds_in hyperbus_dq_in i2c_scl_in i2c_sda_in -} - i2c_scl <- mkReg 0 - i2c_sda <- mkReg 0 - -- i2c <- mkDividedI2C (12_000_000 / 200_000) 1 1 - -- i2c <- mkDividedI2C 2 1 1 - i2c <- mkI2C 1 1 + txSCL <- mkReg 0 + txSDA <- mkReg 0 + rxSCL <- mkReg 1 + rxSDA <- mkReg 1 + i2c <- mkI2C rxSCL rxSDA rules - when True ==> i2c_scl := i2c.txSCL - when True ==> i2c_sda := i2c.txSDA + when True ==> txSCL := i2c.txSCL + when True ==> txSDA := i2c.txSDA timer :: Reg (Bit 16) <- mkReg 0 rules - {- - "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) - -} + "t0000": when (timer == 0x0000) ==> do + i2c.addrReg := 0x01 + i2c.dataReg := 0x00 + "t0001": when (timer == 0x0001) ==> do + i2c.statusReg := i2c.statusReg { notBusy = True } + "t0022": when (timer == 0x0022) ==> rxSDA := 0 + "t0025": when (timer == 0x0025) ==> rxSDA := 1 "advance timer": when True ==> timer := timer + 1 - "finish": when (timer == 0x1fff) ==> $finish + "finish": when (timer == 0x00ff) ==> $finish {- "log received values": when True ==> do result <- i2c.recv.get -- cgit v1.2.3