aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Ringo <nathan@remexre.com>2024-10-20 17:49:23 -0500
committerNathan Ringo <nathan@remexre.com>2024-10-20 17:49:23 -0500
commit5c89c77fcf7493778732c12e2b141b5e9fa1f4a3 (patch)
tree943e0a339d28f8ef3a7aa7aaf8ad09f94ba63186
parentddf01d51c3429c25a57077d93d3309ce0e5d2262 (diff)
Maybe repaired UART, I2C, and HyperBus (but now getting a weird error)...
-rw-r--r--fpga/Makefile2
-rw-r--r--fpga/src/HyperBus.bs7
-rw-r--r--fpga/src/I2C.bs93
-rw-r--r--fpga/src/Numini.bs40
-rw-r--r--fpga/src/Top.bs53
-rw-r--r--fpga/src/Uart.bs117
6 files changed, 209 insertions, 103 deletions
diff --git a/fpga/Makefile b/fpga/Makefile
index d71968c..8219ab3 100644
--- a/fpga/Makefile
+++ b/fpga/Makefile
@@ -1,6 +1,6 @@
BSC_COMP_FLAGS = -bdir tmp -p src:+ -simdir tmp -vdir tmp
BSC_LINK_FLAGS = -bdir tmp -simdir tmp -vdir tmp
-BSC_SOURCES = BRAM2.v FIFO1.v FIFO10.v RevertReg.v SizedFIFO.v TriState.v
+BSC_SOURCES = BRAM2.v ClockDiv.v FIFO1.v FIFO10.v RevertReg.v SizedFIFO.v SyncBit.v SyncFIFO.v SyncHandshake.v SyncRegister.v SyncResetA.v TriState.v
all: tmp/mkTop.bin
flash: tmp/mkTop.bin
diff --git a/fpga/src/HyperBus.bs b/fpga/src/HyperBus.bs
index 2b75b6e..b841a64 100644
--- a/fpga/src/HyperBus.bs
+++ b/fpga/src/HyperBus.bs
@@ -13,7 +13,7 @@ interface HyperBusOut =
rwds_out :: Maybe (Bit 1)
dq_out :: Maybe (Bit 8)
-mkHyperBus :: Wire (Bit 1) -> Wire (Bit 8) -> Module HyperBusOut
+mkHyperBus :: Bit 1 -> Bit 8 -> Module HyperBusOut
mkHyperBus rwds_in dq_in = module
clockPin :: Reg (Bit 1) <- mkReg 0
rules
@@ -31,4 +31,9 @@ mkHyperBus rwds_in dq_in = module
rwds_out = Nothing
dq_out = Nothing
+mkDividedHyperBus :: Integer -> Bit 1 -> Bit 8 -> Module HyperBusOut
+mkDividedHyperBus _divisor rwds_in dq_in = module
+ -- TODO
+ mkHyperBus rwds_in dq_in
+
-- vim: set ft=haskell :
diff --git a/fpga/src/I2C.bs b/fpga/src/I2C.bs
index f3cd760..a8553a8 100644
--- a/fpga/src/I2C.bs
+++ b/fpga/src/I2C.bs
@@ -1,3 +1,96 @@
+-- | An I²C controller with support for 7-bit addresses, with a 16-element FIFO
+-- buffer.
package I2C where
+import Clocks
+import GetPut
+
+-- | An I²C controller.
+interface I2C =
+ -- | The TX side of the SCL pin. High corresponds to high-impedance.
+ txSCL :: Bit 1
+ -- | 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))
+ -- | Writes a command to the I²C controller's command buffer.
+ send :: Put I2CCommand
+
+data I2CCommand
+ = -- | A read from the given address. If the read succeeds with the byte `b`,
+ -- `Just b` is written to the receive buffer. If the read fails, `Nothing`
+ -- is written to the receive buffer.
+ Read (Bit 7)
+ | -- | A write to the given address. If the write succeeds, `Just 0` is
+ -- written to the receive buffer. If the write fails, `Nothing` is written
+ -- to the receive buffer.
+ Write (Bit 7) (Bit 8)
+ deriving (Bits)
+
+data State
+ = -- | The initial state.
+ Idle
+ | -- | TODO
+ TODO
+ deriving (Bits)
+
+-- | 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
+ 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
+
+ interface I2C
+ txSCL = txSCL
+ txSDA = txSDA
+ recv = toGet recvFIFO._read
+ send = toPut sendFIFO._write
+
+-- | 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.
+mkDividedI2C :: Integer -> Bit 1 -> Bit 1 -> Module I2C
+mkDividedI2C divisor rxSCL rxSDA = module
+ clock <- mkClockDivider divisor
+ reset <- mkAsyncResetFromCR divisor clock.slowClock
+
+ 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 (Maybe (Bit 8)) <- mkSyncFIFOToCC 16 clock.slowClock reset
+ sendFIFO :: SyncFIFOIfc I2CCommand <- mkSyncFIFOFromCC 16 clock.slowClock
+ rules
+ when True ==> do
+ rxSCLSync.send rxSCL
+ 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 ()
+
+ interface I2C
+ txSCL = txSCLSync
+ txSDA = txSDASync
+ recv = toGet recvFIFO
+ send = toPut sendFIFO
+
-- vim: set ft=haskell :
diff --git a/fpga/src/Numini.bs b/fpga/src/Numini.bs
index 39990fc..cb26d5e 100644
--- a/fpga/src/Numini.bs
+++ b/fpga/src/Numini.bs
@@ -5,6 +5,7 @@ package Numini where
import Clocks
import Connectable
import HyperBus
+import I2C
import Uart
-- | The output pins.
@@ -30,39 +31,22 @@ interface NuminiOut =
hyperbus_rwds_out :: Maybe (Bit 1)
hyperbus_dq_out :: Maybe (Bit 8)
- i2c_scl :: Bit 1
- i2c_sda_out :: Maybe (Bit 1)
+ i2c_scl_out :: Bit 1
+ i2c_sda_out :: Bit 1
clockFreqHz :: Integer
clockFreqHz = 12_000_000
mkNumini :: Wire (Bit 1) -> Wire (Bit 1) -> Wire (Bit 1) -> Wire (Bit 1) ->
- Wire (Bit 8) -> Wire (Bit 1) -> Module NuminiOut
+ Wire (Bit 8) -> Wire (Bit 1) -> Wire (Bit 1) -> Module NuminiOut
mkNumini ch559_uart_rx inkplate_uart_rx usb_uart_rx hyperbus_rwds_in
- hyperbus_dq_in i2c_sda_in = module
- {-
- -- Make derived clocks and resets.
- clk9600 <- mkClockDivider (clockFreqHz / 9600)
- clk2M <- mkClockDivider (clockFreqHz / 2_000_000)
- clk3M <- mkClockDivider (clockFreqHz / 3_000_000)
- reset9600 <- mkReset (clockFreqHz / 9600) True clk9600.slowClock
- reset2M <- mkReset (clockFreqHz / 2_000_000) True clk2M.slowClock
- reset3M <- mkReset (clockFreqHz / 3_000_000) True clk3M.slowClock
-
+ hyperbus_dq_in i2c_scl_in i2c_sda_in = module
-- Make the peripherals.
- ch559_uart <- changeSpecialWires (Just clk9600.slowClock)
- (Just reset9600.new_rst) Nothing (mkUart ch559_uart_rx)
- inkplate_uart <- changeSpecialWires (Just clk2M.slowClock)
- (Just reset2M.new_rst) Nothing (mkUart inkplate_uart_rx)
- usb_uart <- changeSpecialWires (Just clk9600.slowClock)
- (Just reset9600.new_rst) Nothing (mkUart usb_uart_rx)
- hyperbus <- changeSpecialWires (Just clk3M.slowClock)
- (Just reset3M.new_rst) Nothing (mkHyperBus hyperbus_rwds_in hyperbus_dq_in)
- -}
- ch559_uart <- (mkUart ch559_uart_rx)
- inkplate_uart <- (mkUart inkplate_uart_rx)
- usb_uart <- (mkUart usb_uart_rx)
- hyperbus <- (mkHyperBus hyperbus_rwds_in hyperbus_dq_in)
+ ch559_uart <- mkDividedUart (clockFreqHz / 9_600) ch559_uart_rx
+ 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
mkConnection usb_uart.send usb_uart.recv
@@ -88,7 +72,7 @@ mkNumini ch559_uart_rx inkplate_uart_rx usb_uart_rx hyperbus_rwds_in
hyperbus_rwds_out = hyperbus.rwds_out
hyperbus_dq_out = hyperbus.dq_out
- i2c_scl = 1
- i2c_sda_out = Nothing
+ i2c_scl_out = i2c.txSCL
+ i2c_sda_out = i2c.txSDA
-- vim: set ft=haskell :
diff --git a/fpga/src/Top.bs b/fpga/src/Top.bs
index 67449bd..65baf8a 100644
--- a/fpga/src/Top.bs
+++ b/fpga/src/Top.bs
@@ -24,25 +24,25 @@ interface Top =
hyperbus_cs3_n :: Bit 1 {-# always_ready, result = P1A7 #-}
hyperbus_cs1_n :: Bit 1 {-# always_ready, result = P1A8 #-}
hyperbus_reset_n :: Bit 1 {-# always_ready, result = P1A9 #-}
- hyperbus_rwds :: Inout (Bit 1) {-# prefix = "P1A10" #-}
+ hyperbus_rwds :: Inout (Bit 1) {-# prefix = P1A10 #-}
-- HyperBus 2 (PMOD 1B)
- hyperbus_dq0 :: Inout (Bit 1) {-# prefix = "P1B1" #-}
- hyperbus_dq1 :: Inout (Bit 1) {-# prefix = "P1B2" #-}
- hyperbus_dq2 :: Inout (Bit 1) {-# prefix = "P1B3" #-}
- hyperbus_dq3 :: Inout (Bit 1) {-# prefix = "P1B4" #-}
- hyperbus_dq7 :: Inout (Bit 1) {-# prefix = "P1B7" #-}
- hyperbus_dq6 :: Inout (Bit 1) {-# prefix = "P1B8" #-}
- hyperbus_dq5 :: Inout (Bit 1) {-# prefix = "P1B9" #-}
- hyperbus_dq4 :: Inout (Bit 1) {-# prefix = "P1B10" #-}
+ hyperbus_dq0 :: Inout (Bit 1) {-# prefix = P1B1 #-}
+ hyperbus_dq1 :: Inout (Bit 1) {-# prefix = P1B2 #-}
+ hyperbus_dq2 :: Inout (Bit 1) {-# prefix = P1B3 #-}
+ hyperbus_dq3 :: Inout (Bit 1) {-# prefix = P1B4 #-}
+ hyperbus_dq7 :: Inout (Bit 1) {-# prefix = P1B7 #-}
+ hyperbus_dq6 :: Inout (Bit 1) {-# prefix = P1B8 #-}
+ hyperbus_dq5 :: Inout (Bit 1) {-# prefix = P1B9 #-}
+ hyperbus_dq4 :: Inout (Bit 1) {-# prefix = P1B10 #-}
-- Serial buses (PMOD 2)
ch559_uart_rx :: Bit 1 -> Action {-# always_enabled, always_ready, prefix = "", arg_names = [P2_1] #-}
ch559_uart_tx :: Bit 1 {-# always_ready, result = P2_2 #-}
inkplate_uart_rx :: Bit 1 -> Action {-# always_enabled, always_ready, prefix = "", arg_names = [P2_3] #-}
inkplate_uart_tx :: Bit 1 {-# always_ready, result = P2_4 #-}
- i2c_scl :: Bit 1 {-# always_ready, result = P2_7 #-}
- i2c_sda :: Inout (Bit 1) {-# prefix = "P2_8" #-}
- todo_btn :: Bit 1 -> Action {-# always_enabled, always_ready, prefix = "", arg_names = [P2_9] #-}
- todo_led :: Bit 1 {-# always_ready, result = P2_10 #-}
+ todo_btn :: Bit 1 -> Action {-# always_enabled, always_ready, prefix = "", arg_names = [P2_7] #-}
+ todo_led :: Bit 1 {-# always_ready, result = P2_8 #-}
+ i2c_scl :: Inout (Bit 1) {-# prefix = P2_9 #-}
+ i2c_sda :: Inout (Bit 1) {-# prefix = P2_10 #-}
clockFreqHz :: Integer
clockFreqHz = 12_000_000
@@ -68,18 +68,21 @@ mkTop = module
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 inout.
+ -- 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_out <- mkReg 0
- i2c_sda <- mkTriState i2c_sda_enable i2c_sda_out
+ 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_sda_in
+ hyperbus_rwds_in hyperbus_dq_in i2c_scl_in i2c_sda_in
-- Wire up the tristates.
rules
@@ -110,14 +113,12 @@ mkTop = module
++ hyperbus_dq2._read
++ hyperbus_dq1._read
++ hyperbus_dq0._read
+ "update_i2c_scl_out": when True ==> do
+ i2c_scl_enable := unpack numini.i2c_scl_out
+ "update_i2c_scl_in": when True ==> do
+ i2c_scl_in := i2c_scl._read
"update_i2c_sda_out": when True ==> do
- case numini.i2c_sda_out of
- Just bits -> do
- i2c_sda_enable := True
- i2c_sda_out := bits
- Nothing -> do
- i2c_sda_enable := False
- i2c_sda_out := 0
+ i2c_sda_enable := unpack numini.i2c_sda_out
"update_i2c_sda_in": when True ==> do
i2c_sda_in := i2c_sda._read
@@ -155,10 +156,10 @@ mkTop = module
ch559_uart_tx = numini.ch559_uart_tx
inkplate_uart_rx bit = inkplate_uart_rx := bit
inkplate_uart_tx = numini.inkplate_uart_tx
- i2c_scl = numini.i2c_scl
- i2c_sda = i2c_sda.io
todo_btn _ = noAction
todo_led = 0
+ i2c_scl = i2c_scl.io
+ i2c_sda = i2c_sda.io
{-# verilog mkTop #-}
{-# properties mkTop = { RSTN = BTN_N } #-}
diff --git a/fpga/src/Uart.bs b/fpga/src/Uart.bs
index 82f8e06..954f96e 100644
--- a/fpga/src/Uart.bs
+++ b/fpga/src/Uart.bs
@@ -1,56 +1,39 @@
package Uart where
-import FIFOF
+import Clocks
import GetPut
-- | The state of the TX side of the UART.
data TxState
- = -- | The UART is not currently sending anything. May transition to
- -- 'Start b' when ready to send 'b'.
+ = -- | The UART is not currently sending anything. Transitions to 'Data b 0'
+ -- after sending the start bit once.
Idle
- | -- | The UART is about to send the start bit. 'Start b' transitions to
- -- 'Data b 7' by sending the start bit.
- Start (Bit 8)
| -- | The UART is about to send a data bit. 'Data b n' transitions to
- -- 'Data (b >> 1) (n - 1)' by sending a data bit. 'Data b 0' transitions to
- -- 'Idle' by sending the last data bit. Being in the 'Idle' state for a
- -- clock transmits the stop bit.
+ -- 'Data b (n + 1)' by sending a data bit. 'Data b 7' transitions to 'Idle'
+ -- by sending the last data bit. Being in the 'Idle' state for a clock
+ -- transmits the stop bit.
Data (Bit 8) (Bit 3)
deriving (Bits)
--- | The TX side of the UART.
-interface TxUart =
- -- | The TX pin.
- pin :: Bit 1
- -- | Writes a byte to the UART's transmit buffer.
- send :: Put (Bit 8)
-
-mkTxUart :: Integer -> Module TxUart
-mkTxUart bufferSize = module
- fifo :: FIFOF (Bit 8) <- mkSizedFIFOF bufferSize
+-- | Runs the TX side of the UART, receiving bytes from the 'Get' and writing
+-- output bits to the 'Put'.
+mkTxUart :: RWire (Bit 8) -> Wire (Bit 1) -> Module ()
+mkTxUart fifo tx = module
state :: Reg TxState <- mkReg Idle
- pin :: Reg (Bit 1) <- mkReg 1
-
rules
- "uart_tx_idle": when Idle <- state, not fifo.notEmpty ==> do
- pin := 1
- "uart_tx_idle_to_start": when Idle <- state, fifo.notEmpty ==> do
- pin := 1
- b <- (toGet fifo).get
- state := Start b
- "uart_tx_start": when Start b <- state ==> do
- pin := 0
- state := Data b 7
+ "uart_tx_idle": when Idle <- state ==> do
+ case fifo.wget of
+ Just b -> do
+ state := Data b 7
+ tx._write 0
+ Nothing ->
+ tx._write 1
"uart_tx_data": when Data b n <- state ==> do
- pin := b[0:0]
- if n == 0 then
+ tx._write b[n:n]
+ if n == 7 then
state := Idle
else
- state := Data (b >> 1) (n - 1)
-
- interface TxUart
- pin = pin
- send = toPut fifo
+ state := Data b (n + 1)
-- | The state of the RX side of the UART.
data RxState
@@ -68,11 +51,17 @@ data RxState
Stop (Bit 8)
deriving (Bits, FShow)
+-- | Runs the RX side of the UART, receiving input bits from the 'Get' and
+-- writing bytes to the 'Put'.
+mkRxUart :: Wire (Bit 8) -> Bit 1 -> Module ()
+mkRxUart fifo rxBit = module
+ rules
+
+{-
mkRxUart :: Wire (Bit 1) -> Integer -> Module (Get (Bit 8))
mkRxUart rx bufferSize = module
fifo :: FIFOF (Bit 8) <- mkGSizedFIFOF True False bufferSize
state :: Reg RxState <- mkReg Idle
-
rules
"uart_rx_idle_to_start": when Idle <- state, rx == 0 ==> do
state := Data 0 0
@@ -83,26 +72,60 @@ mkRxUart rx bufferSize = module
"uart_rx_stop_to_idle": when Stop bits <- state, rx == 1 ==> do
fifo.enq bits
state := Idle
-
return (toGet fifo)
+-}
-- | An 8n1 UART.
interface Uart =
-- | The TX pin.
tx :: Bit 1
-
-- | Reads a byte from the UART's receive buffer.
recv :: Get (Bit 8)
-- | Writes a byte to the UART's transmit buffer.
send :: Put (Bit 8)
-mkUart :: Wire (Bit 1) -> Module Uart
-mkUart rx = module
- recv <- mkRxUart rx 8
- uart_tx <- mkTxUart 8
+-- | Returns a UART that does _not_ have FIFOs.
+mkUart' :: Bit 1 -> Module Uart
+mkUart' rx = module
+ recvFIFO :: Wire (Bit 8) <- mkWire
+ sendFIFO :: RWire (Bit 8) <- mkRWireSBR
+ tx :: Wire (Bit 1) <- mkWire
+
+ mkRxUart recvFIFO rx
+ mkTxUart sendFIFO tx
+
+ interface Uart
+ tx = tx._read
+ recv = toGet recvFIFO
+ send = toPut sendFIFO
+
+mkDividedUart :: Integer -> Bit 1 -> Module Uart
+mkDividedUart divisor rx = module
+ clock <- mkClockDivider divisor
+ reset <- mkAsyncResetFromCR divisor clock.slowClock
+
+ rxSync :: SyncBitIfc (Bit 1) <- mkSyncBitFromCC clock.slowClock
+ txSync :: Reg (Bit 1) <- mkSyncRegToCC 1 clock.slowClock reset
+ recvFIFO :: SyncFIFOIfc (Bit 8) <- mkSyncFIFOToCC 16 clock.slowClock reset
+ sendFIFO :: SyncFIFOIfc (Bit 8) <- mkSyncFIFOFromCC 16 clock.slowClock
+ rules
+ when True ==> rxSync.send rx
+
+ changeSpecialWires (Just clock.slowClock) (Just reset) Nothing $ module
+ uart <- mkUart' rxSync.read
+ rules
+ when True ==> txSync._write uart.tx
+ when True ==> do
+ byte <- uart.recv.get
+ recvFIFO.enq byte
+ when True ==> do
+ uart.send.put sendFIFO.first
+ sendFIFO.deq
+ return ()
+
interface Uart
- tx = uart_tx.pin
- recv = recv
- send = uart_tx.send
+ tx = txSync._read
+ recv = toGet recvFIFO
+ send = toPut sendFIFO
-- vim: set ft=haskell :