aboutsummaryrefslogtreecommitdiff
path: root/fpga
diff options
context:
space:
mode:
Diffstat (limited to 'fpga')
-rw-r--r--fpga/src/I2C.bs234
-rw-r--r--fpga/src/TopSim.bs31
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