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

Solve the constant crisis by adding memory map description to circuits #581

Draft
wants to merge 5 commits into
base: staging
Choose a base branch
from

Conversation

hydrolarus
Copy link
Contributor

This implements #481

There's a few noteworthy parts to this PR:

  • the description of memory maps and their validation (Protocols.MemoryMap/.Check)
  • the BitPackC type class
  • the ToFieldType type class
  • a generator for Rust wrappers based on the memory map firmware-support/memmap-generate

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.

Thanks for all the hard work :) Here's a first short round of review. Could you first add some documentation to the functions you export and to help me better interpret the intentions of the code?

@@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2022 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

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

We switched to year of creation copyrights :)

@@ -0,0 +1,228 @@
-- SPDX-FileCopyrightText: 2022-2024 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

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

We switched to year of creation copyrights :)

cabal.project Outdated
Comment on lines 33 to 30
-- flags: -rusty-callisto

Copy link
Contributor

Choose a reason for hiding this comment

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

Was this added by accident?
For development purposes you can create a cabal.project.local

-- be possible to generate an equivalent data structure in Rust or C.
--

data BackwardAnnotated (annotation :: Type) (a :: Type)
Copy link
Contributor

Choose a reason for hiding this comment

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

BackAnnotated?
BwdAnnotated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a bit of a mouthful, yeah. We could also go with BwdAnn as the shortest option 😅 Probably somewhere in between is good?

Copy link
Contributor

Choose a reason for hiding this comment

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

"Back annotation" has a specific meaning in the hardware world: https://www.quora.com/What-is-meant-by-back-annotated-netlist-in-asic-design-flow. Just a heads up. I think BwdAnn is best (though I'm generally a bit meh on abbreviations like this).

Comment on lines 151 to 152
, fieldSize :: Size
-- ^ Size of the register in bytes
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we not derive this from the FieldType?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In theory, yes, but it would involve a bunch of duplicated work. This field is meant to be filled by the BitPackC, I initially added a sizeOf :: FieldType -> Size function but it was pretty complex as it has to encode the same rules as BitPackC does. If we're fine with this duplication then I could add a function like that.

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Google LLC
# SPDX-FileCopyrightText: 2024 Google LLC
Copy link
Contributor

Choose a reason for hiding this comment

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

no need for this anymore :)

Comment on lines +317 to +318
counter: addr.add(2).cast::<u64>(),
frequency: addr.add(4).cast::<u64>(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this related to alignment requirements?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes exactly, probably should have gone in a separate commit, it's been a bit messy 🥴

Comment on lines +196 to +567
"end_line": 435,
"file": "src/Protocols/MemoryMap.hs",
"module": "Protocols.MemoryMap",
"package": "clash-protocols-memmap-0.1-inplace",
"start_col": 15,
"start_line": 435
}
}
}
},
{
"relative_address": 2684354560,
"size": 536870911,
"tree": {
"device_instance": {
"absolute_address": 2684354560,
"device_name": "Timer",
"instance_name": "Timer",
"src_location": {
"end_col": 24,
"end_line": 435,
"file": "src/Protocols/MemoryMap.hs",
"module": "Protocols.MemoryMap",
"package": "clash-protocols-memmap-0.1-inplace",
"start_col": 15,
"start_line": 435
}
}
}
},
{
"relative_address": 3221225472,
"size": 536870911,
"tree": {
"device_instance": {
"absolute_address": 3221225472,
"device_name": "UART",
"instance_name": "UART",
"src_location": {
"end_col": 60,
"end_line": 45,
"file": "src/Bittide/Instances/MemoryMapLogic.hs",
"module": "Bittide.Instances.MemoryMapLogic",
"package": "bittide-instances-0.1-inplace",
"start_col": 50,
"start_line": 45
}
}
}
},
{
"relative_address": 3758096384,
"size": 536870911,
"tree": {
"device_instance": {
"absolute_address": 3758096384,
"device_name": "StatusReg",
"instance_name": "StatusReg",
"src_location": {
"end_col": 24,
"end_line": 435,
"file": "src/Protocols/MemoryMap.hs",
"module": "Protocols.MemoryMap",
"package": "clash-protocols-memmap-0.1-inplace",
"start_col": 15,
"start_line": 435
}
}
}
}
],
"src_location": {
"end_col": 48,
"end_line": 233,
"file": "src/Bittide/ProcessingElement.hs",
"module": "Bittide.ProcessingElement",
"package": "bittide-0.1-inplace",
"start_col": 23,
"start_line": 233
}
}
}
}

"#;

#[test]
fn register_access() {
let src = "\"read_only\"";
let x: RegisterAccess = serde_json::from_str(src).unwrap();
assert_eq!(x, RegisterAccess::ReadOnly);
}

#[test]
fn test_deserialise_memmap() {
let memmap: MemoryMapDesc = serde_json::from_str(TEST_SRC).unwrap();

dbg!(&memmap);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we use an actual generated description from a clash design instead of hardcoding this?

Comment on lines +31 to +32
UART: mut uart,
StatusReg: status,
Copy link
Contributor

Choose a reason for hiding this comment

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

Am I correct in understanding that DeviceInstances is used to auto generated components at compile time (e.g. UART based on the json description, and we automatically get generate the API (e.g. data based on the field and set_data to write?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes exactly, DeviceInstances contains all the different accessible components specified in the memory map! Every device type gets a Rust wrapper and every register/field of a device gets the appropriate getter and setter.

Copy link
Contributor

@martijnbastiaan martijnbastiaan left a comment

Choose a reason for hiding this comment

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

I think this looks good in general! Let's replace all error "shouldn't happen" with breadcrumbs. The more we introduce the more likely the unexpected is expected to happen.

Comment on lines +59 to +70
-- ╭────────┬───────┬───────┬────────────────────────────────────╮
-- │ bin │ hex │ bus │ description │
-- ├────────┼───────┼───────┼────────────────────────────────────┤
-- │ 0b000. │ 0x0 │ │ │
-- │ 0b001. │ 0x2 │ │ │
-- │ 0b010. │ 0x4 │ 1 │ Data memory │
-- │ 0b011. │ 0x6 │ │ │
-- │ 0b100. │ 0x8 │ 0 │ Instruction memory │
-- │ 0b101. │ 0xA │ 2 │ Time │
-- │ 0b110. │ 0xC │ 3 │ UART │
-- │ 0b111. │ 0xE │ 4 │ Test status register │
-- ╰────────┴───────┴───────┴────────────────────────────────────╯
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 we can delete this now :)

DeviceDefinition
{ deviceName = Name{name = "StatusReg", description = ""}
, registers = [(statusRegName, defLoc, statusRegDesc)]
, defLocation = defLoc
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to hide this away in a smart constructor deviceDefinition?

Comment on lines +35 to +43
{- | Typeclass that can be implemented (or derived) to enable C-FFI safe
representation of Haskell types.

This can be derived automatically as long as 'Generic' is implemented for the
type.
Currently there is a high likelihood that more complex types need an unusual
amount of constraints on the instance. Ideally this won't be necessary when
@ghc-typelits-extra@ gets extended to automatically discharge those
constraints.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a few words on why this is more difficult than just BitPack? I.e., you need alignment --- what does that mean.

Comment on lines +170 to +183
( BitPackC a
, BitPackC b
, BitPackC c
, 1 <= Max (AlignmentC a) (AlignmentC b)
, 1 <= Max (AlignmentC b) (AlignmentC c)
, 1 <= Max (AlignmentC a) (Max (AlignmentC b) (AlignmentC c))
, Mod (ByteSizeC a) (AlignmentC b) <= AlignmentC b
, Mod (ByteSizeC a) (AlignmentC b) <= ByteSizeC a
, Mod (ByteSizeC b) (AlignmentC c) <= AlignmentC c
, Mod (ByteSizeC b) (AlignmentC c) <= ByteSizeC b
, Mod (ByteSizeC a) (Max (AlignmentC b) (AlignmentC c))
<= Max (AlignmentC b) (AlignmentC c)
, Mod (ByteSizeC a) (Max (AlignmentC b) (AlignmentC c))
<= ByteSizeC a
Copy link
Contributor

Choose a reason for hiding this comment

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

L U V I T

type Size = Integer

{- | A protocol agnostic wrapper for memory mapped protocols. It provides a way
for memory mapped subordinates to propagate their memory map to the top level,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for memory mapped subordinates to propagate their memory map to the top level,
for memory mapped devices to propagate their memory map to the top level,

You call them devices elsewhere right?

Comment on lines +50 to +51
validateAbsAddresses ::
ComponentPath ->
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
validateAbsAddresses ::
ComponentPath ->
validateAbsAddresses ::
HasCallStack =>
ComponentPath ->

Given that you're using error here.

}
| interconnectSize > availableSize
]
componentErrors = flip map comps2 $ \(path', (addr', size', comp)) -> walk path' (start + addr', start + addr' + size') comp
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
componentErrors = flip map comps2 $ \(path', (addr', size', comp)) -> walk path' (start + addr', start + addr' + size') comp
componentErrors = flip map comps2 $ \(path', (addr', size', comp)) ->
walk path' (start + addr', start + addr' + size') comp

toFieldType :: FieldType

args :: Vec (Generics a) FieldType
args = repeat undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
args = repeat undefined
args = repeat (error "something something")

(Generic (WithVars a), GToFieldType (Rep (WithVars a))) => FieldType
toFieldType = case (gToFieldType @(Rep (WithVars a)), toList (args @a)) of
(ft, []) -> TypeReference ft []
(TypeReference _ft _args', _args'') -> error "shouldn't happen!??"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(TypeReference _ft _args', _args'') -> error "shouldn't happen!??"
(TypeReference _ft _args', _args'') -> error "toFieldType: TypeReference: unexpected constructor"

// TODO handle things like CamelCase/snake_case conversion?
let s = match n.as_ref() {
"(,)" => "Pair",
"(,,)" => "Thruple",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"(,,)" => "Thruple",
"(,,)" => "Thruple",

Haha, haven't seen that one before! Triple?

@martijnbastiaan martijnbastiaan added the 3 Review/merge third label Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 Review/merge third
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants