aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fpga/Makefile2
-rw-r--r--fpga/src/I2C.bs110
-rw-r--r--fpga/src/TopSim.bs29
3 files changed, 106 insertions, 35 deletions
diff --git a/fpga/Makefile b/fpga/Makefile
index 8219ab3..ac618b0 100644
--- a/fpga/Makefile
+++ b/fpga/Makefile
@@ -16,7 +16,7 @@ tmp/%.asc tmp/%-report.json: tmp/%.json src/icebreaker.pcf
--asc $@ --report tmp/$*-report.json \
--pcf src/icebreaker.pcf --json $<
tmp/mkTop.json: tmp/mkTop.rewritten.v $(addprefix $(BSC)/lib/Verilog/,$(BSC_SOURCES))
- yosys -ql tmp/mkTop.yslog -p 'synth_ice40 -top mkTop -json $@' $^
+ yosys -ql tmp/mkTop.yslog -p 'synth_ice40 -device u -top mkTop -json $@' $^
tmp/mkTop.rewritten.v: tmp/mkTop.v
cp $< $@
perl $(BSC_SRC)/util/scripts/basicinout.pl $@
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
diff --git a/fpga/src/TopSim.bs b/fpga/src/TopSim.bs
index bf5d59c..add8b54 100644
--- a/fpga/src/TopSim.bs
+++ b/fpga/src/TopSim.bs
@@ -3,20 +3,43 @@ package TopSim where
import Numini
+import GetPut
+import I2C
+
mkTopSim :: Module Empty
mkTopSim = module
+ {-
ch559_uart_rx <- mkWire
inkplate_uart_rx <- mkWire
usb_uart_rx <- mkWire
hyperbus_rwds_in <- mkWire
hyperbus_dq_in <- mkWire
+ i2c_scl_in <- mkWire
i2c_sda_in <- mkWire
numini <- mkNumini ch559_uart_rx inkplate_uart_rx usb_uart_rx
- hyperbus_rwds_in hyperbus_dq_in i2c_sda_in
+ 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
+ rules
+ when True ==> i2c_scl := i2c.txSCL
+ when True ==> i2c_sda := i2c.txSDA
- timer :: Reg (Bit 8) <- mkReg 0
+ 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 == 0xff) ==> $finish
+ when (timer == 0x1fff) ==> $finish
+ when True ==> do
+ result <- i2c.recv.get
+ $display "recv: " (fshow result)
-- vim: set ft=haskell :