-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: staging
Are you sure you want to change the base?
Conversation
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. |
a5a2e56
to
703daa7
Compare
@Losiek You have to commit with your QBayLogic email for the CLA to pass |
5aa49b2
to
f0f6cef
Compare
f0f6cef
to
ab34214
Compare
There was a problem hiding this 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 #-} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
-- | 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 |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
-- | 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) | ||
] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
fromMaybeBytes :: Maybe (Bytes bytes) -> Bytes bytes | ||
fromMaybeBytes x = case x of | ||
Just a -> a | ||
Nothing -> 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See fromMaybe
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.