Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Wishbone wrapper for the True Dual Port block ram, issue #472 #535

Open
wants to merge 2 commits into
base: staging
Choose a base branch
from

Conversation

Losiek
Copy link

@Losiek Losiek commented May 30, 2024

This PR contains a thin wishbone wrapper around Xilinx true dual-port block RAM from clash-cores.

True dual-port memory block RAM provides two separate access points to the same memory array, allowing for simultaneous and independent read/write operations, which enhances data throughput and flexibility in various high-performance applications. More on the TDPBRAM: PG058

The Wishbone protocol is a simple and flexible bus interconnect standard for connecting various components within a System-on-Chip (SoC). It supports standard signaling, master-slave communication, and efficient data transfer. More information about the protocol can be found here: Wishbone B4

This wrapper only supports classic Wishbone operations without pipelining and burst transfers.

Copy link

google-cla bot commented May 30, 2024

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@Losiek Losiek force-pushed the wishbone-tdpbram-wrapper branch 3 times, most recently from a5a2e56 to 703daa7 Compare May 30, 2024 10:40
@martijnbastiaan
Copy link
Contributor

@Losiek You have to commit with your QBayLogic email for the CLA to pass

@Losiek Losiek force-pushed the wishbone-tdpbram-wrapper branch 4 times, most recently from 5aa49b2 to f0f6cef Compare June 6, 2024 10:22
Copy link
Contributor

@lmbollen lmbollen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of review. ping me if you want to discuss the points :)

@@ -9,10 +9,12 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I htink you shouldn't need this, do you get an error when removing this pragma

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was developing this wrapper, I encountered type issues. Adding this pragma helped. However, after rerunning the tests without it, it seems that it is no longer needed.

Comment on lines 203 to 208
addrBitToIndex :: forall dom n . (KnownDomain dom, KnownNat n) =>
Signal dom (WishboneM2S n 4 (Bytes 4)) -> Signal dom (Index (2 ^ n))
addrBitToIndex wbM2S = bv2i . addr <$> wbM2S
writeDataWb :: forall dom . (KnownDomain dom) =>
Signal dom (WishboneM2S nAddrs 4 (Bytes 4)) -> Signal dom (BitVector 32)
writeDataWb wbM2S = writeData <$> wbM2S
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the mental overhead of new functions and their types are justified here.
I think you can just write bv2i . addr <$> aM2S and bv2i . addr <$> bM2S instead.
Same goes for writeData <$> aM2S and writeData <$> bM2S.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the beginning, that was helpful for me. Now I see that it's a little bit noisy, so I will change it.

Comment on lines 209 to 214
byteEna :: forall dom . (KnownDomain dom) =>
Signal dom (WishboneM2S nAddrs 4 (Bytes 4)) -> Signal dom (BitVector 4)
byteEna wbM2S = (\wb -> if writeEnable wb then busSelect wb else 0 :: BitVector 4) <$> wbM2S
enableWb :: forall dom . (KnownDomain dom) =>
Signal dom (WishboneM2S nAddrs 4 (Bytes 4)) -> Enable dom
enableWb wbM2S = toEnable $ (\wb -> busCycle wb && strobe wb) <$> wbM2S
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be able to omit the types here without them becoming ambiguous.
Types are nice when transformations are ambiguous (typechecker complains) or when they are not immediately obvious from the implementation.

In this case I feel like they are pretty obvious because we know byte enables are BitVector n (size will get derived from wbM2S).

We can also immediately tell from the implementation that we transform a wbM2S to an Enable dom.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I will remove types.

Comment on lines 895 to 913
-- | Compare transaction with undefined value in the Read data field
compareTransWithUndef :: (KnownNat addrW, KnownNat selWidth, KnownNat n) =>
(Transaction addrW selWidth (BitVector n)) -> (Transaction addrW selWidth (BitVector n)) -> Bool
compareTransWithUndef transA transB =
case (transA, transB) of
((WriteSuccess mA _), (WriteSuccess mB _)) ->
checkField "addr" addr mA mB &&
checkField "buSelect" busSelect mA mB &&
checkField "writeData" writeData mA mB
((ReadSuccess mA sA), (ReadSuccess mB sB)) ->
checkField "addr" addr mA mB &&
checkField "busSelect" busSelect mA mB &&
checkBitVectorField readData sA sB
((Error _), (Error _)) -> True
((Retry _), (Retry _)) -> True
((Stall _), (Stall _)) -> True
((Illegal _ _), (Illegal _ _)) -> True
((Ignored _), (Ignored _)) -> True
(_, _) -> False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are comparing for equality, but that's not obvious from the name of the function. Perhaps equalTransactions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm comparing for equality (with a caveat in L907):

        checkBitVectorField readData sA sB

I will change the name to equalTransactionsWithUndef.

-- | Behavioral test for 'wbStorageTDP', it checks whether the behavior of
-- 'wbStorageTDP' matches the 'wbStorageTDPBehaviorModel'.
wbStorageTDPBehavior :: Property
wbStorageTDPBehavior = verifiedTermination . withTests 1000 . property $ do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment mentioning the purpose of verifiedTermination? Als you should not need withTests 1000 since that's the default value iirc

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a leftover from some experiments. I will remove it.

Comment on lines 1051 to 1059
-- | Check equiality of two BitVectors containing the undefined values ('.')
-- Example:
-- x: "000111..."
-- y: "01.01.01."
-- result: False
eqBitVec :: (KnownNat n) => BitVector n -> BitVector n -> Bool
eqBitVec x@(BV.BV xMask _) y =
case (x `xor` y) of
BV.BV m v -> xMask == m && v == 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +969 to +985
genWishboneTransfer ::
(KnownNat addressWidth, KnownNat (BitSize a)) =>
Int -> -- ^ size
Gen a ->
Gen (WishboneMasterRequest addressWidth a, Int)
genWishboneTransfer size genA =
let
validAddr = (4 *) . fromIntegral <$> Gen.enum 0 (size - 1)
-- Make wbOps that won't be repeated
mkRead address bs = (Read address bs, 0)
mkWrite address bs a = (Write address bs a, 0)
in
-- Generate valid operations. The boolean represents the validity of the operation.
Gen.choice
[ (mkRead <$> validAddr <*> genDefinedBitVector)
, (mkWrite <$> validAddr <*> genDefinedBitVector <*> genA)
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see I've been copying this around too.. Shame on me!

Can we make a generic, re-usable function for this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this function occurs multiple times, with a slightly changed form. I will make a generic during refactoring.

Comment on lines 987 to 1005
checkAddressEqual :: (KnownNat addressWidth, KnownNat (BitSize a)) =>
WishboneMasterRequest addressWidth a ->
WishboneMasterRequest addressWidth a ->
Bool
checkAddressEqual req1 req2 = extractAddress req1 == extractAddress req2
where
extractAddress :: WishboneMasterRequest addressWidth a -> BitVector addressWidth
extractAddress (Read addr _) = addr
extractAddress (Write addr _ _) = addr

getAddressWbRequest :: (KnownNat addressWidth, KnownNat (BitSize a)) =>
WishboneMasterRequest addressWidth a -> Unsigned addressWidth
getAddressWbRequest (Read addr _) = unpack addr
getAddressWbRequest (Write addr _ _) = unpack addr

getByteEnableWbRequest :: (KnownNat addressWidth, KnownNat (BitSize a)) =>
WishboneMasterRequest addressWidth a -> Unsigned (BitSize a `DivRU` 8)
getByteEnableWbRequest (Read _ be) = unpack be
getByteEnableWbRequest (Write _ be _) = unpack be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need these, we could perhaps make some utility functions where WishboneMasterRequest is defined

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, WishboneMasterRequest is defined in clash-protocols.

wbStorageTDPBehaviorModel initWbOpsA initWbOpsB = case (cancelMulDiv @bytes @8) of
Dict -> snd $ L.mapAccumL g emptyMemoryMap (L.zipWith (\wbA wbB -> (wbA, wbB)) initWbOpsA initWbOpsB)
where
emptyMemoryMap = Map.empty :: MemoryModel (BitVector addrW) (Bytes bytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just using Map is fine here.

It does not seem to contribute to the clarity of the API, nor does it make your code more concise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

Comment on lines 1022 to 1025
fromMaybeBytes :: Maybe (Bytes bytes) -> Bytes bytes
fromMaybeBytes x = case x of
Just a -> a
Nothing -> 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See fromMaybe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants