我正在尝试在仿真器和CPU的CLaSH实现之间共享尽可能多的代码。作为此过程的一部分,我正在按照以下方式写指令获取和解码:
fetchInstr :: (Monad m) => m Word8 -> m Instr
使用monad在模拟器中运行很简单,monad的状态为程序计数器并直接访问内存。对于硬件版本,我制作了一个固定大小的缓冲区(因为指令字节的长度是有界的),并且在每个周期中,如果缓冲区中没有足够的数据,则将获取操作短路。
data Failure
= Underrun
| Overrun
deriving Show
data Buffer n dat = Buffer
{ bufferContents :: Vec n dat
, bufferNext :: Index (1 + n)
}
deriving (Show, Generic, Undefined)
instance (KnownNat n, Default dat) => Default (Buffer n dat) where
def = Buffer (pure def) 0
remember :: (KnownNat n) => Buffer n dat -> dat -> Buffer n dat
remember Buffer{..} x = Buffer
{ bufferContents = replace bufferNext x bufferContents
, bufferNext = bufferNext + 1
}
newtype FetchM n dat m a = FetchM{ unFetchM :: ReaderT (Buffer n dat) (StateT (Index (1 + n)) (ExceptT Failure m)) a }
deriving newtype (Functor, Applicative, Monad)
runFetchM :: (Monad m, KnownNat n) => Buffer n dat -> FetchM n dat m a -> m (Either Failure a)
runFetchM buf act = runExceptT $ evalStateT (runReaderT (unFetchM act) buf) 0
fetch :: (Monad m, KnownNat n) => FetchM n dat m dat
fetch = do
Buffer{..} <- FetchM ask
idx <- FetchM get
when (idx == maxBound) overrun
when (idx >= bufferNext) underrun
FetchM $ modify (+ 1)
return $ bufferContents !! idx
where
overrun = FetchM . lift . lift . throwE $ Overrun
underrun = FetchM . lift . lift . throwE $ Underrun
这个想法是通过在获取指令期间以CPU的状态存储Buffer n dat
并在内存不足的情况下remember
从内存中输入值来使用此方法:
case cpuState of
Fetching buf -> do
buf' <- remember buf <$> do
modify $ \s -> s{ pc = succ pc }
return cpuInMem
instr_ <- runFetchM buf' $ fetchInstr fetch
instr <- case instr_ of
Left Underrun -> goto (Fetching buf') >> abort
Left Overrun -> errorX "Overrun"
Right instr -> return instr
goto $ Fetching def
exec instr
这在CLaSH模拟器中工作正常。
问题是,如果我开始以这种方式使用它,则CLaSH需要更大的内联限制才能合成它。例如,在CHIP-8实现中,this commit开始使用上述FetchM
。在进行此更改之前,只有100的内联深度足以通过CLaSH合成器。更改之后,300不够,而1000会使CLaSH搅动直到内存耗尽。
FetchM
有什么邪恶之处,以至于内线人士对此感到窒息?
答案 0 :(得分:2)
事实证明,真正的罪魁祸首不是FetchM
,而是我代码的其他部分,它们需要内联很多功能(在我的主要CPU
monad中,每个monadic绑定一个!),以及FetchM
刚刚增加了绑定数。
真正的问题是我的CPU
monad was, among other things, a Writer (Endo CPUOut)
和所有这些CPUOut -> CPUOut
函数必须完全内联,因为CLaSH不能将函数表示为信号。
所有这些都在the related CLaSH bug ticket中进行了详细说明。