array-forth-0.2.1.4: src/Language/ArrayForth/Channel.hs
{-# LANGUAGE MonadComprehensions #-}
{-# LANGUAGE RecordWildCards #-}
-- | Defines the basic operations for reading and writing through ports.
--
-- Each core has four ports connecting it to its neighbors. The cores
-- around the edges have ports connected to IO devices. A "Channel" is
-- just a type containing the four ports that you can write to or read
-- from.
module Language.ArrayForth.Channel where
import Control.Applicative ((<|>))
import Data.Bits (testBit)
import Data.Monoid (Monoid (..))
import Language.ArrayForth.Opcode (F18Word)
-- | A channel representing the four communication directions a core
-- may use. In practice, these will either be hooked up to other cores
-- or to IO. Nothing represents no message; if there is a word,
-- execution will block.
data Channel = Channel { right, down, left, up :: Maybe F18Word } deriving (Show, Eq)
-- | The four possible port directions.
data Port = R | D | L | U deriving (Show, Eq, Bounded, Enum)
-- The monoid instance is based around *replacement*.
instance Monoid Channel where
mempty = emptyChannel
c₁ `mappend` c₂ = Channel { right = right c₁ <|> right c₂
, down = down c₁ <|> down c₂
, left = left c₁ <|> left c₂
, up = up c₁ <|> up c₂ }
-- | An empty channel has no reads or writes and doesn't block execution.
emptyChannel :: Channel
emptyChannel = Channel Nothing Nothing Nothing Nothing
-- | Write to the ports specified by the given memory address. This
-- will clear all the channels not being written to (by setting them
-- to Nothing).
--
-- The ports to use are specified by bits 5–8 of the address. These
-- bits correspond respectively to up, left, down and right. Bits 5
-- and 7 are inverted—0 turns the channel *on*.
writePort :: F18Word -- ^ The address to write to. Only bits 5–8 are considered.
-> F18Word -- ^ The word to write to the channel.
-> Channel -- ^ The resulting channel, with any unused ports empty.
writePort ports word = Channel { right = [ word | testBit ports 8 ]
, down = [ word | not $ testBit ports 7 ]
, left = [ word | testBit ports 6 ]
, up = [ word | not $ testBit ports 5 ] }
-- | Read the inputs from the ports specified by the given
-- address. The address is handled the same way as in
-- @'writePort'@. Returns @Nothing@ if blocked on the read.
--
-- If more than one of the read ports has data, this currently just
-- chooses the first one based on the right, down, left, up order. I
-- don't know if this is the correct behavior—perhaps I should just
-- xor them together or something?
readPort :: F18Word -> Channel -> Maybe F18Word
readPort ports Channel {..} = [ word | testBit ports 8, word <- right ]
<|> [ word | not $ testBit ports 7, word <- down ]
<|> [ word | testBit ports 6, word <- left ]
<|> [ word | not $ testBit ports 5, word <- up ]