-
Notifications
You must be signed in to change notification settings - Fork 48
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 CORDIC Support #132
Add CORDIC Support #132
Conversation
Anyone example in perticular? The goal is that they should all work AFAIK... |
BTW, thanks a lot for your contribution! :) |
It must be something on my end, as CI of course is successfully building the examples... I'm running this command: cargo build --examples --features stm32g431,log-rtt,defmt And it's emitting large linker errors, I don't know why!
Of course! |
I think #133 might be the cause of the errors you are seeing. The comparator related examples were likely broken for non G474 devices. |
Does this work? cargo build --example blinky --features stm32g431,log-rtt,defmt |
Ahah! You're right! Nice, I'll add an example now. Thanks! |
If you merge #133 I'll pull it from upstream and CI should pass :) |
I have never used this peripheral so I do not know what the typical use case looks like.. But say you wanted to first run one function and then switch to another function. // (some sort of pseudo rustish code)
fn do_math_stuff(cordic: &mut Cordic<...>) {
cordic.do_sin_calc();
cordic.do_sqrt_calc(); // <--- This would require to (atleast temporary) change type which is not allowed
cordic.change_back_to_sin_mode();
} I assume this would be not so simple to do as is. Again, not sure how this is typically used and if it at all is something we care about? |
Since I felt it imperative for the peripheral state to be encoded in the type system, this can be done with the type system: type CordicForSin = Cordic<_, _, Sin, _>;
type CordicForSqrt = Cordic<_, _, Sqrt, _>;
fn do_math_stuff(mut cordic: CordicForSin) -> CordicForSin { // cordic must be moved now
cordic.start(_);
let _ = cordic.result();
let mut cordic: CordicForSqrt = cordic.freeze();
cordic.start(_);
let _ = cordic.result();
cordic.freeze()
} If something wants to change the resource then
I did consider two options for enabling highly dynamic usage:
Note: Unfortunately Rust doesn't have a way to specify My priority was maximizing safety and performance, but certainly either of these two options can be easily adopted later. |
I don't know what |
Cargo.toml
Outdated
|
||
[[example]] | ||
name = "cordic" | ||
required-features = ["defmt"] |
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.
should not be needed after #134.
required-features = ["defmt"] |
(Apparently the macro was only used and tested without formatting args.)
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 command fails now:
cargo build --examples --features stm32g431,log-rtt
Looks like the log-rtt
feature no longer enables the defmt
feature?
Func: func::State<Arg, Res>, | ||
Prec: prec::State, | ||
{ | ||
rb: CORDIC, |
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.
Just a thought, what if rb
was impl BorrowMut
? Then the user could (optionally)
type CordicForSin = Cordic<_, _, Sin, _>;
type CordicForSqrt = Cordic<_, _, Sqrt, _>;
fn do_math_stuff(rb: &mut CORDIC) {
{
let mut cordic = rb.freeze_not_consuming(); // Find better name :)
cordic.start(_);
let _ = cordic.result();
} // <--- rb no longer borrowed by cordic, it is free to be reused
let mut cordic: CordicForSqrt = rb.freeze();
cordic.start(_);
let _ = cordic.result();
}
again, just a thought. Depending how the cordic is typically used, maybe not worth the extra generic arg.
and if this would be useful, I suppose it could be done as a future extension.
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.
So, you nerdsniped me and I've been mulling this over since yesterday.
I just refactored the whole thing to be much stronger in terms of type relationships and enforcements.
As a result, now, adding this dynamic behavior is easy!
/// Traits and structures for dynamic function operation.
pub mod dynamic {
use super::{prec, signature, types, Cordic, State};
/// Any function can be invoked with this type-state.
pub struct Any;
/// A Cordic in dynamic mode.
pub trait Mode<Arg, Res>
where
Arg: types::arg::State,
Res: types::res::State,
{
/// Run a function with provided arguments and get the result.
///
/// *Note: This employs the polling strategy.
/// For less overhead, use static operations.*
fn run<Func>(&mut self, args: Func::Arguments) -> Func::Results
where
Func: State<Arg, Res>;
}
impl<Arg, Res, Prec> Mode<Arg, Res> for Cordic<Arg, Res, Any, Prec>
where
Arg: types::arg::State,
Res: types::res::State,
Prec: prec::State,
{
fn run<Func>(&mut self, args: Func::Arguments) -> Func::Results
where
Func: State<Arg, Res>,
{
use signature::Property as _;
self.apply_config::<Arg, Res, Func, Prec>();
args.write(&self.rb.wdata);
self.when_ready(|cordic| Func::Results::read(&cordic.rb.rdata))
}
}
}
So your example would look like this:
fn do_math_stuff(cordic: &mut Cordic<_, _, Any, _>) {
let sin = cordic.run::<Sin>(_);
let sqrt = cordic.run::<Sqrt<N0>>(_);
}
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.
Wow :)
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.
Thank you very much! This is awesome!
Is it ok if I merge now, or is there anything else you want to add? |
Thanks, I'm pretty sure we're good 😅. I did start thinking about some minor organizational changes but they should be inconsequential. Maybe let me sit on it for a day, if that's ok with you. |
I decluttered the type signature from the Something I realized that is really annoying me right now is that an enforcement strategy I came up with to further strengthen the system invariances seems to be impossible to implement! Currently one could comment out a line of fn apply_config<NewArg, NewRes, NewFunc, NewPrec>(&mut self)
where
NewArg: types::arg::State,
NewRes: types::res::State,
NewFunc: func::Feature<NewArg, NewRes>,
NewPrec: prec::State,
{
self.rb.csr.write(|w| {
use func::reg_count::arg::State as _;
use func::reg_count::res::State as _;
use func::scale::State as _;
NewArg::set(w.argsize());
NewRes::set(w.ressize());
NewPrec::set(w.precision());
NewFunc::set(w.func());
// NewFunc::NArgs::set(w.nargs()); whoopsies!
NewFunc::NRes::set(w.nres());
NewFunc::Scale::set(w.scale());
w
});
} Nothing about the signature requires all states to be set. So I decided to make fn apply_config<NewArg, NewRes, NewFunc, NewPrec>(&mut self) -> (NewArf, NewRes, ..)
where
NewArg: types::arg::State,
NewRes: types::res::State,
NewFunc: func::Feature<NewArg, NewRes>,
NewPrec: prec::State,
{
let result;
self.rb.csr.write(|w| {
use func::reg_count::arg::State as _;
use func::reg_count::res::State as _;
use func::scale::State as _;
result = (
NewArg::set(w.argsize()),
NewRes::set(w.ressize()),
NewPrec::set(w.precision()),
NewFunc::set(w.func()),
NewFunc::NArgs::set(w.nargs()),
NewFunc::NRes::set(w.nres()),
NewFunc::Scale::set(w.scale()),
);
w
});
result
} But rust does not accept the deferred initialization of Other than that I think everything is fine. I can continue to torment myself outside of this PR :) |
Sorry for late answer
I guess rust does not know that Still, requires unsafe so not ideal... But I believe it would still enforce type safety, right? fn apply_config<NewArg, NewRes, NewFunc, NewPrec>(&mut self) -> (NewArf, NewRes, ..)
where
NewArg: types::arg::State,
NewRes: types::res::State,
NewFunc: func::Feature<NewArg, NewRes>,
NewPrec: prec::State,
{
let mut result = let mut x = MaybeUninit::uninit();
self.rb.csr.write(|w| {
use func::reg_count::arg::State as _;
use func::reg_count::res::State as _;
use func::scale::State as _;
result.write(( // <--- Initialize result
NewArg::set(w.argsize()),
NewRes::set(w.ressize()),
NewPrec::set(w.precision()),
NewFunc::set(w.func()),
NewFunc::NArgs::set(w.nargs()),
NewFunc::NRes::set(w.nres()),
NewFunc::Scale::set(w.scale()),
));
w
});
// SAFETY: We trust `self.rb.csr.write` to have called the closure which initialized result
unsafe { result.assume_init() };
} |
Actually no, I think it would work! It's a little less ergonomic but yes that is a way to do it. I actually ended up getting this working by forking svd2rust and changing The result was perfection :) pub fn freeze<NewArg, NewRes, NewPrec, NewOp>(self) -> Cordic<NewArg, NewRes, NewPrec, NewOp>
where
NewArg: types::arg::State,
NewRes: types::res::State,
NewPrec: prec::State,
NewOp: op::sealed::Feature,
NewOp::NArgs<NewArg>: reg_count::arg::State,
NewOp::NRes<NewRes>: reg_count::res::State,
NewOp::Scale: scale::State,
NewOp::Func: func::State,
{
use func::State as _;
use reg_count::arg::State as _;
use reg_count::res::State as _;
use scale::State as _;
let config = self.rb.csr().modify(|_, w| Config {
arg: NewArg::set(w.argsize()),
res: NewRes::set(w.ressize()),
nargs: NewOp::NArgs::set(w.nargs()),
nres: NewOp::NRes::set(w.nres()),
scale: NewOp::Scale::set(w.scale()),
prec: NewPrec::set(w.precision()),
func: NewOp::Func::set(w.func()),
});
Cordic {
rb: self.rb,
config,
}
} |
That is really nice! What should we do in the mean time? :) |
It works as is, there is some kind of bug somewhere because the resulting binary is smaller than doing the register writes manually. My finalized type-enforced version has no such bug. I am no longer working on the version in this PR, so it could be merged for now, and eventually when the PAC and svd2rust are updated, I can re-PR the final version. |
Not sure I understand, but do you know if the |
I set up some tests where I configure the CORDIC with the PAC manually, and then do the same with the abstraction, and compare the resulting binaries. The version in this PR resulted in a different binary, so clearly it is not doing the exact same thing. With the version in my fork, this does not happen. I imagine there is a very subtle error somewhere that is not being caught by type-enforcement because that system is not in place.
I believe it could, I'm not sure, but I don't particularly want to pursue that avenue myself as I don't like the hacky and incomplete nature of it, sorry. |
Thanks for the read! :)
That's fair, no worries :) I will merge this shortly. Again, thanks a lot! |
Haha, of course! |
Create a new HAL component for using the CORDIC co-processor.
The interface permits configuration of all permutations of argument/result size and count, function, scale, and precision.
Configurations are represented by type-states and as such are compile-time validated.
Todo
I haven't added an interface for DMA (yet).
Example
As far as I can tell, examples are broken, so I only included a minimal example in the module-level docs. Here is that example: