diff options
author | Nathan Ringo <nathan@remexre.com> | 2024-10-20 17:49:23 -0500 |
---|---|---|
committer | Nathan Ringo <nathan@remexre.com> | 2024-10-20 17:49:23 -0500 |
commit | 5c89c77fcf7493778732c12e2b141b5e9fa1f4a3 (patch) | |
tree | 943e0a339d28f8ef3a7aa7aaf8ad09f94ba63186 | |
parent | ddf01d51c3429c25a57077d93d3309ce0e5d2262 (diff) |
Maybe repaired UART, I2C, and HyperBus (but now getting a weird error)...
-rw-r--r-- | fpga/Makefile | 2 | ||||
-rw-r--r-- | fpga/src/HyperBus.bs | 7 | ||||
-rw-r--r-- | fpga/src/I2C.bs | 93 | ||||
-rw-r--r-- | fpga/src/Numini.bs | 40 | ||||
-rw-r--r-- | fpga/src/Top.bs | 53 | ||||
-rw-r--r-- | fpga/src/Uart.bs | 117 |
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 : |