diff options
-rw-r--r-- | fpga/Makefile | 2 | ||||
-rw-r--r-- | fpga/src/I2C.bs | 110 | ||||
-rw-r--r-- | fpga/src/TopSim.bs | 29 |
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 : |