aboutsummaryrefslogtreecommitdiff
path: root/fpga
diff options
context:
space:
mode:
authorNathan Ringo <nathan@remexre.com>2024-11-02 00:08:29 -0500
committerNathan Ringo <nathan@remexre.com>2024-11-02 00:08:29 -0500
commit2a506c1476c55d9ecf990bd4878169278038f6ff (patch)
tree2d618474d89d0b90fe0166e30ba96268e94a2d1e /fpga
parentd3bcde4eedd2dd3dcf5cbc4302821f38ff553498 (diff)
...
Diffstat (limited to 'fpga')
-rw-r--r--fpga/src/I2C.bs105
-rw-r--r--fpga/src/Numini.bs2
-rw-r--r--fpga/src/Top.bs155
-rw-r--r--fpga/src/TopSim.bs75
4 files changed, 282 insertions, 55 deletions
diff --git a/fpga/src/I2C.bs b/fpga/src/I2C.bs
index 2a0614f..e092853 100644
--- a/fpga/src/I2C.bs
+++ b/fpga/src/I2C.bs
@@ -45,15 +45,24 @@ interface I2C =
-- | The TX side of the SDA pin. High corresponds to high-impedance.
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
+ -- | Reads from the address register, which contains the address in the high
+ -- 7 bits and the R/!W bit in the low bit.
+ addrRegGet :: Bit 8
+ -- | Writes to the address register, which contains the address in the high
+ -- 7 bits and the R/!W bit in the low bit.
+ addrRegPut :: Bit 8 -> Action
+ -- | Reads from 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.
+ dataRegGet :: Bit 8
+ -- | Writes to 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.
+ dataRegPut :: Bit 8 -> Action
+ -- | Reads from the status register.
+ statusRegGet :: I2CStatus
+ -- | Writes to the status register.
+ statusRegPut :: I2CStatus -> Action
data I2CBusState
= -- | (We believe) the bus is idle.
@@ -94,6 +103,8 @@ data I2CBusState
| -- | TODO
WriteAckReadSDA
| -- | TODO
+ WriteAckLowerSCL2
+ | -- | TODO
TODO
| -- | We're about to either lower SCL or keep it low as part of reading a
-- byte from a device.
@@ -144,14 +155,14 @@ mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg = module
txSDA := 1
else do
state := StartSDA
+ savedAddr := addrReg
+ savedData := if savedAddr[0:0] == 1 -- R/!W
+ then 0 -- R
+ else dataReg -- !W
txSCL := 1
txSDA := 0
"StartSDA": when StartSDA <- state ==> do
state := SendAddrBitSDA 7
- savedAddr := addrReg
- savedData := if savedAddr[0:0] == 1 -- R/!W
- then 0 -- R
- else dataReg -- !W
txSCL := 0
txSDA := 0
"SendAddrBitSDA": when SendAddrBitSDA n <- state ==> do
@@ -223,9 +234,20 @@ mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg = module
statusReg := statusReg { ready = True; dataAckBit = unpack rxSDA }
state := if rxSDA == 1 -- NACK
then StopLowerSCL
- else TODO
+ else WriteAckLowerSCL2
txSCL := 1
txSDA := 1
+ "WriteAckLowerSCL2": when WriteAckLowerSCL2 <- state ==> do
+ if not statusReg.ready && addrReg == savedAddr then do
+ state := WriteWriteSDA 7
+ savedAddr := addrReg
+ savedData := if savedAddr[0:0] == 1 -- R/!W
+ then 0 -- R
+ else dataReg -- !W
+ else do
+ state := StopLowerSDA
+ txSCL := 0
+ txSDA := 1
"ReadLowerSCL": when ReadLowerSCL n time <- state ==> do
state := if time == 1
then ReadLowerSCL n (time - 1)
@@ -302,14 +324,17 @@ mkI2C rxSCL rxSDA = module
interface I2C
txSCL = txSCL
txSDA = txSDA
- addrReg = addrReg
- dataReg = dataReg
- statusReg = statusReg
+ addrRegGet = addrReg
+ addrRegPut value = addrReg := value
+ dataRegGet = dataReg
+ dataRegPut value = dataReg := value
+ statusRegGet = statusReg
+ statusRegPut value = statusReg := value
{-
--- | 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.
+-- | Returns an I²C interface that runs on a clock derived from the current
+-- clock divided by `divisor`. This should run at four times the rate of the
+-- I²C bus's clock rate.
mkDividedI2C :: Integer -> Bit 1 -> Bit 1 -> Module I2C
mkDividedI2C divisor rxSCL rxSDA = module
clock <- mkClockDivider divisor
@@ -317,23 +342,37 @@ mkDividedI2C divisor rxSCL rxSDA = module
rxSCLSync :: SyncBitIfc (Bit 1) <- mkSyncBitFromCC clock.slowClock
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 I2CResult <- mkSyncFIFOToCC 16 clock.slowClock reset
- sendFIFO :: SyncFIFOIfc I2CCommand <- mkSyncFIFOFromCC 16 clock.slowClock
- rules
- "copy rxSCL and rxSDA": when True ==> do
- rxSCLSync.send rxSCL
- rxSDASync.send rxSDA
+ txSCLSync :: SyncPulseIfc <- mkSyncPulseToCC clock.slowClock reset
+ txSDASync :: SyncPulseIfc <- mkSyncPulseToCC clock.slowClock reset
+ addrReg <- mkReg 0
+ dataReg <- mkReg 0
+ statusReg <- mkReg (I2CStatus
+ { ready = True
+ ; busIdle = True
+ ; addrAckBit = False
+ ; dataAckBit = False
+ ; arbitrationLost = False
+ ; rsvd = 0
+ })
+
+ txSCL <- mkWire
+ txSDA <- mkWire
changeSpecialWires (Just clock.slowClock) (Just reset) Nothing $ module
- mkI2C' rxSCLSync.read rxSDASync.read txSCLSync txSDASync sendFIFO recvFIFO
+ mkI2C' rxSCL rxSDA txSCL txSDA addrReg dataReg statusReg
+ rules
+ when unpack txSCL ==> txSCLSync.send
+ when unpack txSDA ==> txSDASync.send
interface I2C
- txSCL = txSCLSync
- txSDA = txSDASync
- recv = toGet recvFIFO
- send = toPut sendFIFO
+ txSCL = pack txSCLSync.pulse
+ txSDA = pack txSDASync.pulse
+ addrRegGet = 0
+ addrRegPut value = undefined
+ dataRegGet = 0
+ dataRegPut value = undefined
+ statusRegGet = undefined
+ statusRegPut value = undefined
-}
-- vim: set ft=haskell :
diff --git a/fpga/src/Numini.bs b/fpga/src/Numini.bs
index cb26d5e..b5e0223 100644
--- a/fpga/src/Numini.bs
+++ b/fpga/src/Numini.bs
@@ -46,7 +46,7 @@ mkNumini ch559_uart_rx inkplate_uart_rx usb_uart_rx hyperbus_rwds_in
inkplate_uart <- mkDividedUart (clockFreqHz / 2_000_000) inkplate_uart_rx
usb_uart <- mkDividedUart (clockFreqHz / 9_600) usb_uart_rx
hyperbus <- mkDividedHyperBus (clockFreqHz / 3_000_000) hyperbus_rwds_in hyperbus_dq_in
- i2c <- mkDividedI2C (clockFreqHz / 200_000) i2c_scl_in i2c_sda_in
+ i2c <- mkDividedI2C (clockFreqHz / 400_000) i2c_scl_in i2c_sda_in
mkConnection usb_uart.send usb_uart.recv
diff --git a/fpga/src/Top.bs b/fpga/src/Top.bs
index 65baf8a..df5098f 100644
--- a/fpga/src/Top.bs
+++ b/fpga/src/Top.bs
@@ -1,7 +1,8 @@
-- | The top-level module, for the iCEBreaker.
package Top where
-import Numini
+import I2C
+-- import Numini
import TriState
-- | The interface to the iCEBreaker.
@@ -81,6 +82,157 @@ mkTop = module
i2c_scl_in :: Wire (Bit 1) <- mkWire
i2c_sda_in :: Wire (Bit 1) <- mkWire
+ -- i2c <- mkDividedI2C (clockFreqHz / 400_000) i2c_scl_in i2c_sda_in
+ i2c <- mkI2C i2c_scl_in i2c_sda_in
+
+ -- Provide commands.
+ state :: Reg (Bit 4) <- mkReg 0
+ rules
+ when state == 0, i2c.statusRegGet.ready ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x00
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 1
+ when state == 1 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x00
+ state := 2
+ when state == 2, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 3
+ when state == 3 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x00
+ state := 4
+ when state == 4, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 5
+ when state == 5 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x12
+ state := 6
+ when state == 6, i2c.statusRegGet.busIdle ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 7
+ when state == 7 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0xaa
+ state := 8
+ when state == 8, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 9
+ when state == 9 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0xaa
+ state := 10
+ when state == 10, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 11
+
+ -- Wire up the tristates.
+ rules
+ "update_hyperbus_rwds_out": when True ==> do
+ hyperbus_rwds_enable := False
+ hyperbus_rwds_out := 0
+ "update_hyperbus_rwds_in": when True ==> do
+ hyperbus_rwds_in := hyperbus_rwds._read
+ "update_hyperbus_dq_out": when True ==> do
+ hyperbus_dq_enable := False
+ hyperbus_dq_out := 0
+ "update_hyperbus_dq_in": when True ==> do
+ hyperbus_dq_in := hyperbus_dq7._read
+ ++ hyperbus_dq6._read
+ ++ hyperbus_dq5._read
+ ++ hyperbus_dq4._read
+ ++ hyperbus_dq3._read
+ ++ hyperbus_dq2._read
+ ++ hyperbus_dq1._read
+ ++ hyperbus_dq0._read
+ "update_i2c_scl_out": when True ==> do
+ i2c_scl_enable := unpack i2c.txSCL
+ "update_i2c_scl_in": when True ==> do
+ i2c_scl_in := i2c_scl._read
+ "update_i2c_sda_out": when True ==> do
+ i2c_sda_enable := unpack i2c.txSDA
+ "update_i2c_sda_in": when True ==> do
+ i2c_sda_in := i2c_sda._read
+
+ interface Top
+ -- RS232
+ usb_uart_rx bit = usb_uart_rx := bit
+ usb_uart_tx = 0
+ -- Onboard LEDs
+ led_r_n = 1
+ led_g_n = 1
+ -- RGB LED driver
+ rgb_r_n = 1
+ rgb_g_n = 1
+ rgb_b_n = 1
+ -- HyperBus 1 (PMOD 1A)
+ hyperbus_cs2_n = 1
+ hyperbus_cs0_n = 1
+ hyperbus_ck = 0
+ hyperbus_ck_n = 1
+ hyperbus_cs3_n = 1
+ hyperbus_cs1_n = 1
+ hyperbus_reset_n = 1
+ hyperbus_rwds = hyperbus_rwds.io
+ -- HyperBus 2 (PMOD 1B)
+ hyperbus_dq0 = hyperbus_dq0.io
+ hyperbus_dq1 = hyperbus_dq1.io
+ hyperbus_dq2 = hyperbus_dq2.io
+ hyperbus_dq3 = hyperbus_dq3.io
+ hyperbus_dq4 = hyperbus_dq4.io
+ hyperbus_dq5 = hyperbus_dq5.io
+ hyperbus_dq6 = hyperbus_dq6.io
+ hyperbus_dq7 = hyperbus_dq7.io
+ -- LEDs and buttons (PMOD 2)
+ ch559_uart_rx bit = ch559_uart_rx := bit
+ ch559_uart_tx = 0
+ inkplate_uart_rx bit = inkplate_uart_rx := bit
+ inkplate_uart_tx = 0
+ todo_btn _ = noAction
+ todo_led = 0
+ i2c_scl = i2c_scl.io
+ i2c_sda = i2c_sda.io
+{-# verilog mkTop #-}
+{-# properties mkTop = { RSTN = BTN_N } #-}
+
+{-
+mkTop :: Module Top
+mkTop = module
+ ch559_uart_rx <- mkWire
+ inkplate_uart_rx <- mkWire
+ usb_uart_rx <- mkWire
+
+ -- Make tristates for the HyperBus inouts.
+ hyperbus_rwds_enable :: Reg Bool <- mkReg False
+ hyperbus_rwds_out :: Reg (Bit 1) <- mkReg 0
+ hyperbus_dq_enable :: Reg Bool <- mkReg False
+ hyperbus_dq_out :: Reg (Bit 8) <- mkReg 0
+ hyperbus_rwds <- mkTriState hyperbus_rwds_enable hyperbus_rwds_out
+ hyperbus_dq0 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[0:0]
+ hyperbus_dq1 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[1:1]
+ hyperbus_dq2 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[2:2]
+ hyperbus_dq3 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[3:3]
+ hyperbus_dq4 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[4:4]
+ hyperbus_dq5 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[5:5]
+ hyperbus_dq6 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[6:6]
+ hyperbus_dq7 <- mkTriState hyperbus_dq_enable hyperbus_dq_out[7:7]
+
+ -- Make a tristate for the I2C inouts. Note that we never drive the line
+ -- high -- I2C uses a high-impedance state instead of high.
+ i2c_scl_enable <- mkReg False
+ i2c_scl <- mkTriState i2c_scl_enable 0
+ i2c_sda_enable <- mkReg False
+ i2c_sda <- mkTriState i2c_sda_enable 0
+
+ -- Make wires for all the inout inputs.
+ hyperbus_rwds_in :: Wire (Bit 1) <- mkWire
+ hyperbus_dq_in :: Wire (Bit 8) <- mkWire
+ i2c_scl_in :: Wire (Bit 1) <- mkWire
+ i2c_sda_in :: Wire (Bit 1) <- mkWire
+
numini <- mkNumini ch559_uart_rx inkplate_uart_rx usb_uart_rx
hyperbus_rwds_in hyperbus_dq_in i2c_scl_in i2c_sda_in
@@ -162,5 +314,6 @@ mkTop = module
i2c_sda = i2c_sda.io
{-# verilog mkTop #-}
{-# properties mkTop = { RSTN = BTN_N } #-}
+-}
-- vim: set ft=haskell :
diff --git a/fpga/src/TopSim.bs b/fpga/src/TopSim.bs
index e4e65c9..2a58a83 100644
--- a/fpga/src/TopSim.bs
+++ b/fpga/src/TopSim.bs
@@ -29,28 +29,63 @@ mkTopSim = module
when True ==> txSCL := i2c.txSCL
when True ==> txSDA := i2c.txSDA
- timer :: Reg (Bit 16) <- mkReg 0
+ -- Provide commands.
+ state :: Reg (Bit 4) <- mkReg 0
rules
- "t0000": when (timer == 0x0000) ==> do
- i2c.addrReg := 0x20 ++ (0 :: Bit 1)
- i2c.dataReg := 0x12
- "t0001": when (timer == 0x0001) ==> do
- i2c.statusReg := i2c.statusReg { ready = False; dataAckBit = True }
- "t0002": when (timer == 0x0002) ==> do
- i2c.addrReg := 0x20 ++ (0 :: Bit 1)
- i2c.dataReg := 0xaa
- "t0022": when (timer == 0x0023) ==> rxSDA := 0
- "t0025": when (timer == 0x0027) ==> rxSDA := 1
- -- "t0047": when (timer == 0x0047) ==> rxSDA := 0
- -- "t0051": when (timer == 0x004b) ==> rxSDA := 1
- -- "t004b": when (timer == 0x004b) ==> do
- -- i2c.statusReg := i2c.statusReg { ready = False }
- "advance timer": when True ==> timer := timer + 1
- "finish": when (timer == 0x00ff) ==> $finish
+ when state == 0, i2c.statusRegGet.ready ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x01
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 1
+ when state == 1 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x02
+ state := 2
+ when state == 2, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 3
{-
- "log received values": when True ==> do
- result <- i2c.recv.get
- $display "recv: " (fshow result)
+ when state == 3 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x03
+ state := 4
+ when state == 4, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 5
+ when state == 5 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0x12
+ state := 6
+ when state == 6, i2c.statusRegGet.busIdle ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 7
+ when state == 7 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0xaa
+ state := 8
+ when state == 8, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 9
+ when state == 9 ==> do
+ i2c.addrRegPut (0x20 ++ (0 :: Bit 1))
+ i2c.dataRegPut 0xaa
+ state := 10
+ when state == 10, i2c.statusRegGet.ready ==> do
+ i2c.statusRegPut (i2c.statusRegGet { ready = False })
+ state := 11
-}
+ timer :: Reg (Bit 16) <- mkReg 0
+ rules
+ "t0022": when (timer == 0x0022) ==> rxSDA := 0
+ "t0026": when (timer == 0x0026) ==> rxSDA := 1
+ "t0046": when (timer == 0x0046) ==> rxSDA := 0
+ "t004a": when (timer == 0x004a) ==> rxSDA := 1
+ "t006a": when (timer == 0x006a) ==> rxSDA := 0
+ "t006e": when (timer == 0x006e) ==> rxSDA := 1
+ "t008e": when (timer == 0x008e) ==> rxSDA := 0
+ "t0092": when (timer == 0x0092) ==> rxSDA := 1
+ "advance timer": when True ==> timer := timer + 1
+ "finish": when (timer == 0x01ff) ==> $finish
+
-- vim: set ft=haskell :