Per-operation CORDIC config system with ZCD archive integration
This commit is contained in:
parent
97653580e5
commit
5eed41557f
|
|
@ -10,3 +10,6 @@ categories = ["mathematics", "no-std"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cord-trig = { path = "../cord-trig" }
|
cord-trig = { path = "../cord-trig" }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
toml = "0.8"
|
||||||
|
regex = "1"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use cord_trig::{TrigGraph, TrigOp};
|
use cord_trig::{TrigGraph, TrigOp};
|
||||||
|
use crate::config::{CordicConfig, CordicOp};
|
||||||
use crate::ops::*;
|
use crate::ops::*;
|
||||||
|
|
||||||
/// A compiled CORDIC program ready for binary serialization or execution.
|
/// A compiled CORDIC program ready for binary serialization or execution.
|
||||||
|
|
@ -6,11 +8,29 @@ use crate::ops::*;
|
||||||
/// Each instruction produces its result into a slot matching its index.
|
/// Each instruction produces its result into a slot matching its index.
|
||||||
/// The instruction list parallels the TrigGraph node list — compilation
|
/// The instruction list parallels the TrigGraph node list — compilation
|
||||||
/// is a direct 1:1 mapping with constants folded to fixed-point.
|
/// is a direct 1:1 mapping with constants folded to fixed-point.
|
||||||
|
///
|
||||||
|
/// Supports per-operation iteration counts via `tables`: each unique
|
||||||
|
/// iteration count gets its own atan table and gain constant. Instructions
|
||||||
|
/// index into `tables` via `instr_table_idx`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CORDICProgram {
|
pub struct CORDICProgram {
|
||||||
pub word_bits: u8,
|
pub word_bits: u8,
|
||||||
pub instructions: Vec<CORDICInstr>,
|
pub instructions: Vec<CORDICInstr>,
|
||||||
pub output: u32,
|
pub output: u32,
|
||||||
|
/// One entry per unique iteration count.
|
||||||
|
pub tables: Vec<CordicTable>,
|
||||||
|
/// Maps instruction index → table index. Only meaningful for CORDIC ops;
|
||||||
|
/// non-CORDIC instructions have 0 here (ignored at eval time).
|
||||||
|
pub instr_table_idx: Vec<u8>,
|
||||||
|
/// Legacy fields for backward compat with single-table programs.
|
||||||
|
pub atan_table: Vec<i64>,
|
||||||
|
pub gain: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Precomputed atan table + gain for a specific iteration count.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CordicTable {
|
||||||
|
pub iterations: u8,
|
||||||
pub atan_table: Vec<i64>,
|
pub atan_table: Vec<i64>,
|
||||||
pub gain: i64,
|
pub gain: i64,
|
||||||
}
|
}
|
||||||
|
|
@ -18,11 +38,12 @@ pub struct CORDICProgram {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CompileConfig {
|
pub struct CompileConfig {
|
||||||
pub word_bits: u8,
|
pub word_bits: u8,
|
||||||
|
pub cordic: Option<CordicConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CompileConfig {
|
impl Default for CompileConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { word_bits: 32 }
|
Self { word_bits: 32, cordic: None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,15 +51,80 @@ impl CORDICProgram {
|
||||||
/// Compile a TrigGraph into a CORDIC program.
|
/// Compile a TrigGraph into a CORDIC program.
|
||||||
///
|
///
|
||||||
/// Each TrigOp node becomes one CORDICInstr. Constants are converted
|
/// Each TrigOp node becomes one CORDICInstr. Constants are converted
|
||||||
/// from f64 to fixed-point at compile time. Everything else is a
|
/// from f64 to fixed-point at compile time. Per-operation iteration
|
||||||
/// direct structural mapping.
|
/// counts are resolved from the CordicConfig if present, otherwise
|
||||||
|
/// all ops use word_bits as their iteration count.
|
||||||
pub fn compile(graph: &TrigGraph, config: &CompileConfig) -> Self {
|
pub fn compile(graph: &TrigGraph, config: &CompileConfig) -> Self {
|
||||||
let frac_bits = config.word_bits - 1;
|
let frac_bits = config.word_bits - 1;
|
||||||
let to_fixed = |val: f64| -> i64 {
|
let to_fixed = |val: f64| -> i64 {
|
||||||
(val * (1i64 << frac_bits) as f64).round() as i64
|
(val * (1i64 << frac_bits) as f64).round() as i64
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cordic_cfg = config.cordic.as_ref().map(|c| c.clone())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let mut c = CordicConfig::default();
|
||||||
|
c.word_bits = config.word_bits;
|
||||||
|
c.default_f = config.word_bits;
|
||||||
|
c
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build tables for each unique iteration count
|
||||||
|
let unique_counts = cordic_cfg.unique_iteration_counts();
|
||||||
|
let mut count_to_table_idx: BTreeMap<u8, u8> = BTreeMap::new();
|
||||||
|
let mut tables = Vec::new();
|
||||||
|
for &iters in &unique_counts {
|
||||||
|
count_to_table_idx.insert(iters, tables.len() as u8);
|
||||||
|
tables.push(CordicTable {
|
||||||
|
iterations: iters,
|
||||||
|
atan_table: atan_table(iters),
|
||||||
|
gain: cordic_gain(iters, frac_bits),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_table_idx = count_to_table_idx
|
||||||
|
.get(&cordic_cfg.default_f)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// Ensure default has a table entry
|
||||||
|
let idx = tables.len() as u8;
|
||||||
|
tables.push(CordicTable {
|
||||||
|
iterations: cordic_cfg.default_f,
|
||||||
|
atan_table: atan_table(cordic_cfg.default_f),
|
||||||
|
gain: cordic_gain(cordic_cfg.default_f, frac_bits),
|
||||||
|
});
|
||||||
|
idx
|
||||||
|
});
|
||||||
|
|
||||||
|
let resolve_table = |cop: CordicOp| -> u8 {
|
||||||
|
let iters = cordic_cfg.iterations_for(cop);
|
||||||
|
count_to_table_idx.get(&iters).copied().unwrap_or(default_table_idx)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instr_table_idx = Vec::with_capacity(graph.nodes.len());
|
||||||
|
|
||||||
let instructions: Vec<CORDICInstr> = graph.nodes.iter().map(|op| {
|
let instructions: Vec<CORDICInstr> = graph.nodes.iter().map(|op| {
|
||||||
|
let tidx = match op {
|
||||||
|
TrigOp::Sin(_) => resolve_table(CordicOp::Sin),
|
||||||
|
TrigOp::Cos(_) => resolve_table(CordicOp::Cos),
|
||||||
|
TrigOp::Tan(_) => resolve_table(CordicOp::Tan),
|
||||||
|
TrigOp::Asin(_) => resolve_table(CordicOp::Asin),
|
||||||
|
TrigOp::Acos(_) => resolve_table(CordicOp::Acos),
|
||||||
|
TrigOp::Atan(_) => resolve_table(CordicOp::Atan),
|
||||||
|
TrigOp::Sinh(_) => resolve_table(CordicOp::Sinh),
|
||||||
|
TrigOp::Cosh(_) => resolve_table(CordicOp::Cosh),
|
||||||
|
TrigOp::Tanh(_) => resolve_table(CordicOp::Tanh),
|
||||||
|
TrigOp::Asinh(_) => resolve_table(CordicOp::Asinh),
|
||||||
|
TrigOp::Acosh(_) => resolve_table(CordicOp::Acosh),
|
||||||
|
TrigOp::Atanh(_) => resolve_table(CordicOp::Atanh),
|
||||||
|
TrigOp::Sqrt(_) => resolve_table(CordicOp::Sqrt),
|
||||||
|
TrigOp::Exp(_) => resolve_table(CordicOp::Exp),
|
||||||
|
TrigOp::Ln(_) => resolve_table(CordicOp::Ln),
|
||||||
|
TrigOp::Hypot(_, _) => resolve_table(CordicOp::Hypot),
|
||||||
|
TrigOp::Atan2(_, _) => resolve_table(CordicOp::Atan2),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
instr_table_idx.push(tidx);
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
TrigOp::InputX => CORDICInstr::InputX,
|
TrigOp::InputX => CORDICInstr::InputX,
|
||||||
TrigOp::InputY => CORDICInstr::InputY,
|
TrigOp::InputY => CORDICInstr::InputY,
|
||||||
|
|
@ -77,12 +163,21 @@ impl CORDICProgram {
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
|
// Legacy single-table fields use the first table (or default)
|
||||||
|
let primary = tables.first().cloned().unwrap_or_else(|| CordicTable {
|
||||||
|
iterations: config.word_bits,
|
||||||
|
atan_table: atan_table(config.word_bits),
|
||||||
|
gain: cordic_gain(config.word_bits, frac_bits),
|
||||||
|
});
|
||||||
|
|
||||||
CORDICProgram {
|
CORDICProgram {
|
||||||
word_bits: config.word_bits,
|
word_bits: config.word_bits,
|
||||||
instructions,
|
instructions,
|
||||||
output: graph.output,
|
output: graph.output,
|
||||||
atan_table: atan_table(config.word_bits),
|
tables,
|
||||||
gain: cordic_gain(config.word_bits, frac_bits),
|
instr_table_idx,
|
||||||
|
atan_table: primary.atan_table,
|
||||||
|
gain: primary.gain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,6 +248,15 @@ impl CORDICProgram {
|
||||||
pos += consumed;
|
pos += consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(CORDICProgram { word_bits, instructions, output, atan_table, gain })
|
let tables = vec![CordicTable {
|
||||||
|
iterations: word_bits,
|
||||||
|
atan_table: atan_table.clone(),
|
||||||
|
gain,
|
||||||
|
}];
|
||||||
|
let instr_table_idx = vec![0u8; instructions.len()];
|
||||||
|
|
||||||
|
Some(CORDICProgram {
|
||||||
|
word_bits, instructions, output, tables, instr_table_idx, atan_table, gain,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use cord_trig::{TrigGraph, TrigOp};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Canonical name for each CORDIC-consuming operation.
|
||||||
|
/// Non-CORDIC ops (Add, Sub, etc.) don't appear here.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum CordicOp {
|
||||||
|
Sin,
|
||||||
|
Cos,
|
||||||
|
Tan,
|
||||||
|
Asin,
|
||||||
|
Acos,
|
||||||
|
Atan,
|
||||||
|
Sinh,
|
||||||
|
Cosh,
|
||||||
|
Tanh,
|
||||||
|
Asinh,
|
||||||
|
Acosh,
|
||||||
|
Atanh,
|
||||||
|
Sqrt,
|
||||||
|
Exp,
|
||||||
|
Ln,
|
||||||
|
Hypot,
|
||||||
|
Atan2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CordicOp {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Sin => "sin",
|
||||||
|
Self::Cos => "cos",
|
||||||
|
Self::Tan => "tan",
|
||||||
|
Self::Asin => "asin",
|
||||||
|
Self::Acos => "acos",
|
||||||
|
Self::Atan => "atan",
|
||||||
|
Self::Sinh => "sinh",
|
||||||
|
Self::Cosh => "cosh",
|
||||||
|
Self::Tanh => "tanh",
|
||||||
|
Self::Asinh => "asinh",
|
||||||
|
Self::Acosh => "acosh",
|
||||||
|
Self::Atanh => "atanh",
|
||||||
|
Self::Sqrt => "sqrt",
|
||||||
|
Self::Exp => "exp",
|
||||||
|
Self::Ln => "ln",
|
||||||
|
Self::Hypot => "hypot",
|
||||||
|
Self::Atan2 => "atan2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all() -> &'static [CordicOp] {
|
||||||
|
&[
|
||||||
|
Self::Sin, Self::Cos, Self::Tan,
|
||||||
|
Self::Asin, Self::Acos, Self::Atan,
|
||||||
|
Self::Sinh, Self::Cosh, Self::Tanh,
|
||||||
|
Self::Asinh, Self::Acosh, Self::Atanh,
|
||||||
|
Self::Sqrt, Self::Exp, Self::Ln,
|
||||||
|
Self::Hypot, Self::Atan2,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_name(s: &str) -> Option<Self> {
|
||||||
|
Self::all().iter().find(|op| op.name() == s).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On-disk TOML representation.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CordicConfigFile {
|
||||||
|
#[serde(default)]
|
||||||
|
pub defaults: Defaults,
|
||||||
|
#[serde(default, rename = "override")]
|
||||||
|
pub overrides: Vec<Override>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Defaults {
|
||||||
|
#[serde(default = "default_word_bits")]
|
||||||
|
pub word_bits: u8,
|
||||||
|
#[serde(default = "default_f")]
|
||||||
|
pub f: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Defaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
word_bits: default_word_bits(),
|
||||||
|
f: default_f(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_word_bits() -> u8 { 32 }
|
||||||
|
fn default_f() -> u8 { 32 }
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Override {
|
||||||
|
/// Regex pattern matching operation names (e.g. "sin|cos", "sinh|cosh|tanh")
|
||||||
|
pub op: String,
|
||||||
|
/// Iteration count override
|
||||||
|
pub f: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolved configuration: each operation has a concrete iteration count.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CordicConfig {
|
||||||
|
pub word_bits: u8,
|
||||||
|
pub default_f: u8,
|
||||||
|
/// Per-operation iteration counts. Only contains ops actually used.
|
||||||
|
pub ops: BTreeMap<CordicOp, u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CordicConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
word_bits: 32,
|
||||||
|
default_f: 32,
|
||||||
|
ops: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CordicConfig {
|
||||||
|
/// Scan a TrigGraph and build a config with default f for every CORDIC op found.
|
||||||
|
pub fn scan(graph: &TrigGraph) -> Self {
|
||||||
|
let mut config = Self::default();
|
||||||
|
for op in &graph.nodes {
|
||||||
|
if let Some(cop) = trig_op_to_cordic(op) {
|
||||||
|
config.ops.entry(cop).or_insert(config.default_f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scan a TrigGraph and build config from a file representation.
|
||||||
|
/// Overrides are applied in order; last match wins.
|
||||||
|
pub fn from_file(graph: &TrigGraph, file: &CordicConfigFile) -> Self {
|
||||||
|
let mut config = Self {
|
||||||
|
word_bits: file.defaults.word_bits,
|
||||||
|
default_f: file.defaults.f,
|
||||||
|
ops: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate with defaults for all ops found in graph
|
||||||
|
for op in &graph.nodes {
|
||||||
|
if let Some(cop) = trig_op_to_cordic(op) {
|
||||||
|
config.ops.entry(cop).or_insert(config.default_f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply overrides in order
|
||||||
|
for ov in &file.overrides {
|
||||||
|
if let Ok(re) = regex::Regex::new(&ov.op) {
|
||||||
|
for (&cop, f_val) in config.ops.iter_mut() {
|
||||||
|
if re.is_match(cop.name()) {
|
||||||
|
*f_val = ov.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from TOML string, resolving against the given graph.
|
||||||
|
pub fn from_toml(graph: &TrigGraph, toml_str: &str) -> Result<Self, toml::de::Error> {
|
||||||
|
let file: CordicConfigFile = toml::from_str(toml_str)?;
|
||||||
|
Ok(Self::from_file(graph, &file))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize the current config to a TOML file representation.
|
||||||
|
pub fn to_file(&self) -> CordicConfigFile {
|
||||||
|
// Group ops by f value to produce compact overrides
|
||||||
|
let mut by_f: BTreeMap<u8, Vec<CordicOp>> = BTreeMap::new();
|
||||||
|
for (&op, &f) in &self.ops {
|
||||||
|
if f != self.default_f {
|
||||||
|
by_f.entry(f).or_default().push(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let overrides = by_f.into_iter().map(|(f, ops)| {
|
||||||
|
let pattern = ops.iter()
|
||||||
|
.map(|op| op.name())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("|");
|
||||||
|
Override { op: pattern, f }
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
CordicConfigFile {
|
||||||
|
defaults: Defaults {
|
||||||
|
word_bits: self.word_bits,
|
||||||
|
f: self.default_f,
|
||||||
|
},
|
||||||
|
overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize to TOML string.
|
||||||
|
pub fn to_toml(&self) -> String {
|
||||||
|
let file = self.to_file();
|
||||||
|
toml::to_string_pretty(&file).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get iteration count for a specific operation.
|
||||||
|
pub fn iterations_for(&self, op: CordicOp) -> u8 {
|
||||||
|
self.ops.get(&op).copied().unwrap_or(self.default_f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the set of unique iteration counts used, for precomputing tables.
|
||||||
|
pub fn unique_iteration_counts(&self) -> Vec<u8> {
|
||||||
|
let mut counts: Vec<u8> = self.ops.values().copied().collect();
|
||||||
|
counts.sort();
|
||||||
|
counts.dedup();
|
||||||
|
if counts.is_empty() {
|
||||||
|
counts.push(self.default_f);
|
||||||
|
}
|
||||||
|
counts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trig_op_to_cordic(op: &TrigOp) -> Option<CordicOp> {
|
||||||
|
match op {
|
||||||
|
TrigOp::Sin(_) => Some(CordicOp::Sin),
|
||||||
|
TrigOp::Cos(_) => Some(CordicOp::Cos),
|
||||||
|
TrigOp::Tan(_) => Some(CordicOp::Tan),
|
||||||
|
TrigOp::Asin(_) => Some(CordicOp::Asin),
|
||||||
|
TrigOp::Acos(_) => Some(CordicOp::Acos),
|
||||||
|
TrigOp::Atan(_) => Some(CordicOp::Atan),
|
||||||
|
TrigOp::Sinh(_) => Some(CordicOp::Sinh),
|
||||||
|
TrigOp::Cosh(_) => Some(CordicOp::Cosh),
|
||||||
|
TrigOp::Tanh(_) => Some(CordicOp::Tanh),
|
||||||
|
TrigOp::Asinh(_) => Some(CordicOp::Asinh),
|
||||||
|
TrigOp::Acosh(_) => Some(CordicOp::Acosh),
|
||||||
|
TrigOp::Atanh(_) => Some(CordicOp::Atanh),
|
||||||
|
TrigOp::Sqrt(_) => Some(CordicOp::Sqrt),
|
||||||
|
TrigOp::Exp(_) => Some(CordicOp::Exp),
|
||||||
|
TrigOp::Ln(_) => Some(CordicOp::Ln),
|
||||||
|
TrigOp::Hypot(_, _) => Some(CordicOp::Hypot),
|
||||||
|
TrigOp::Atan2(_, _) => Some(CordicOp::Atan2),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use cord_trig::ir::TrigOp;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scan_discovers_ops() {
|
||||||
|
let mut g = TrigGraph::new();
|
||||||
|
let angle = g.push(TrigOp::Const(1.0));
|
||||||
|
let s = g.push(TrigOp::Sin(angle));
|
||||||
|
let c = g.push(TrigOp::Cos(angle));
|
||||||
|
let _ = g.push(TrigOp::Add(s, c));
|
||||||
|
g.set_output(3);
|
||||||
|
|
||||||
|
let config = CordicConfig::scan(&g);
|
||||||
|
assert!(config.ops.contains_key(&CordicOp::Sin));
|
||||||
|
assert!(config.ops.contains_key(&CordicOp::Cos));
|
||||||
|
assert!(!config.ops.contains_key(&CordicOp::Tan));
|
||||||
|
assert_eq!(config.ops.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn override_applies() {
|
||||||
|
let mut g = TrigGraph::new();
|
||||||
|
let a = g.push(TrigOp::Const(1.0));
|
||||||
|
let _ = g.push(TrigOp::Sin(a));
|
||||||
|
let _ = g.push(TrigOp::Cos(a));
|
||||||
|
let _ = g.push(TrigOp::Sinh(a));
|
||||||
|
g.set_output(1);
|
||||||
|
|
||||||
|
let file = CordicConfigFile {
|
||||||
|
defaults: Defaults { word_bits: 32, f: 32 },
|
||||||
|
overrides: vec![
|
||||||
|
Override { op: "sin|cos".into(), f: 24 },
|
||||||
|
Override { op: "sinh".into(), f: 16 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = CordicConfig::from_file(&g, &file);
|
||||||
|
assert_eq!(config.iterations_for(CordicOp::Sin), 24);
|
||||||
|
assert_eq!(config.iterations_for(CordicOp::Cos), 24);
|
||||||
|
assert_eq!(config.iterations_for(CordicOp::Sinh), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn toml_roundtrip() {
|
||||||
|
let mut g = TrigGraph::new();
|
||||||
|
let a = g.push(TrigOp::Const(1.0));
|
||||||
|
let _ = g.push(TrigOp::Sin(a));
|
||||||
|
let _ = g.push(TrigOp::Cos(a));
|
||||||
|
g.set_output(1);
|
||||||
|
|
||||||
|
let mut config = CordicConfig::scan(&g);
|
||||||
|
config.ops.insert(CordicOp::Sin, 24);
|
||||||
|
|
||||||
|
let toml_str = config.to_toml();
|
||||||
|
let config2 = CordicConfig::from_toml(&g, &toml_str).unwrap();
|
||||||
|
assert_eq!(config2.iterations_for(CordicOp::Sin), 24);
|
||||||
|
assert_eq!(config2.iterations_for(CordicOp::Cos), 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use cord_trig::{TrigGraph, TrigOp};
|
use cord_trig::{TrigGraph, TrigOp};
|
||||||
|
use crate::compiler::{CORDICProgram, CordicTable};
|
||||||
|
use crate::ops::{atan_table, cordic_gain};
|
||||||
|
|
||||||
/// CORDIC evaluator: evaluates a TrigGraph using only integer
|
/// CORDIC evaluator: evaluates a TrigGraph using only integer
|
||||||
/// shifts, adds, and comparisons. No floating point trig.
|
/// shifts, adds, and comparisons. No floating point trig.
|
||||||
|
|
@ -6,33 +8,32 @@ use cord_trig::{TrigGraph, TrigOp};
|
||||||
/// Proof that the entire pipeline compiles down to
|
/// Proof that the entire pipeline compiles down to
|
||||||
/// binary arithmetic — shift, add, compare, repeat.
|
/// binary arithmetic — shift, add, compare, repeat.
|
||||||
pub struct CORDICEvaluator {
|
pub struct CORDICEvaluator {
|
||||||
word_bits: u8,
|
|
||||||
frac_bits: u8,
|
frac_bits: u8,
|
||||||
atan_table: Vec<i64>,
|
instr_table_idx: Vec<u8>,
|
||||||
gain: i64,
|
tables: Vec<CordicTable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CORDICEvaluator {
|
impl CORDICEvaluator {
|
||||||
pub fn new(word_bits: u8) -> Self {
|
pub fn new(word_bits: u8) -> Self {
|
||||||
let frac_bits = word_bits - 1;
|
let frac_bits = word_bits - 1;
|
||||||
let iterations = word_bits;
|
CORDICEvaluator {
|
||||||
|
frac_bits,
|
||||||
// Precompute atan(2^-i) as fixed-point
|
instr_table_idx: Vec::new(),
|
||||||
let atan_table: Vec<i64> = (0..iterations)
|
tables: vec![CordicTable {
|
||||||
.map(|i| {
|
iterations: word_bits,
|
||||||
let angle = (2.0f64).powi(-(i as i32)).atan();
|
atan_table: atan_table(word_bits),
|
||||||
(angle * (1i64 << frac_bits) as f64).round() as i64
|
gain: cordic_gain(word_bits, frac_bits),
|
||||||
})
|
}],
|
||||||
.collect();
|
|
||||||
|
|
||||||
// CORDIC gain K = product of 1/sqrt(1 + 2^{-2i})
|
|
||||||
let mut k = 1.0f64;
|
|
||||||
for i in 0..iterations {
|
|
||||||
k *= 1.0 / (1.0 + (2.0f64).powi(-2 * i as i32)).sqrt();
|
|
||||||
}
|
}
|
||||||
let gain = (k * (1i64 << frac_bits) as f64).round() as i64;
|
}
|
||||||
|
|
||||||
CORDICEvaluator { word_bits, frac_bits, atan_table, gain }
|
/// Create an evaluator from a compiled program, inheriting per-op tables.
|
||||||
|
pub fn from_program(program: &CORDICProgram) -> Self {
|
||||||
|
CORDICEvaluator {
|
||||||
|
frac_bits: program.word_bits - 1,
|
||||||
|
instr_table_idx: program.instr_table_idx.clone(),
|
||||||
|
tables: program.tables.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert f64 to fixed-point.
|
/// Convert f64 to fixed-point.
|
||||||
|
|
@ -72,71 +73,57 @@ impl CORDICEvaluator {
|
||||||
(((a as i128) << self.frac_bits) / b as i128) as i64
|
(((a as i128) << self.frac_bits) / b as i128) as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CORDIC rotation mode: given angle z, compute (cos(z), sin(z)).
|
/// Resolve the table for a given instruction index.
|
||||||
/// Input z is fixed-point radians.
|
fn table_for(&self, instr_idx: usize) -> &CordicTable {
|
||||||
/// Returns (x, y) = (cos(z), sin(z)) in fixed-point.
|
if self.instr_table_idx.is_empty() || self.tables.is_empty() {
|
||||||
///
|
// Fallback: use legacy single table via a static-lifetime trick
|
||||||
/// Algorithm:
|
// won't work — build a table reference from self fields instead
|
||||||
/// Start with x = K (gain), y = 0, z = angle
|
return &self.tables[0];
|
||||||
/// For each iteration i:
|
}
|
||||||
/// if z >= 0: rotate positive (d = +1)
|
let tidx = self.instr_table_idx.get(instr_idx).copied().unwrap_or(0) as usize;
|
||||||
/// else: rotate negative (d = -1)
|
&self.tables[tidx.min(self.tables.len() - 1)]
|
||||||
/// x_new = x - d * (y >> i)
|
}
|
||||||
/// y_new = y + d * (x >> i)
|
|
||||||
/// z_new = z - d * atan(2^-i)
|
/// CORDIC rotation mode using a specific table.
|
||||||
fn cordic_rotation(&self, angle: i64) -> (i64, i64) {
|
fn cordic_rotation_with(&self, angle: i64, table: &CordicTable) -> (i64, i64) {
|
||||||
let mut x = self.gain;
|
let mut x = table.gain;
|
||||||
let mut y: i64 = 0;
|
let mut y: i64 = 0;
|
||||||
let mut z = angle;
|
let mut z = angle;
|
||||||
|
|
||||||
for i in 0..self.word_bits as usize {
|
for i in 0..table.iterations as usize {
|
||||||
let d = if z >= 0 { 1i64 } else { -1 };
|
let d = if z >= 0 { 1i64 } else { -1 };
|
||||||
let x_new = x - d * (y >> i);
|
let x_new = x - d * (y >> i);
|
||||||
let y_new = y + d * (x >> i);
|
let y_new = y + d * (x >> i);
|
||||||
z -= d * self.atan_table[i];
|
z -= d * table.atan_table[i];
|
||||||
x = x_new;
|
x = x_new;
|
||||||
y = y_new;
|
y = y_new;
|
||||||
}
|
}
|
||||||
|
|
||||||
(x, y) // (cos, sin)
|
(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CORDIC vectoring mode: given (x, y), compute magnitude and angle.
|
/// CORDIC vectoring mode using a specific table.
|
||||||
/// Returns (magnitude, angle) in fixed-point.
|
fn cordic_vectoring_with(&self, x_in: i64, y_in: i64, table: &CordicTable) -> (i64, i64) {
|
||||||
///
|
|
||||||
/// Algorithm:
|
|
||||||
/// Start with x, y, z = 0
|
|
||||||
/// For each iteration i:
|
|
||||||
/// if y < 0: rotate positive (d = +1)
|
|
||||||
/// else: rotate negative (d = -1)
|
|
||||||
/// x_new = x - d * (y >> i)
|
|
||||||
/// y_new = y + d * (x >> i)
|
|
||||||
/// z_new = z - d * atan(2^-i)
|
|
||||||
/// Result: x ≈ sqrt(x₀² + y₀²) / K, z ≈ atan2(y₀, x₀)
|
|
||||||
fn cordic_vectoring(&self, x_in: i64, y_in: i64) -> (i64, i64) {
|
|
||||||
let mut x = x_in;
|
let mut x = x_in;
|
||||||
let mut y = y_in;
|
let mut y = y_in;
|
||||||
let mut z: i64 = 0;
|
let mut z: i64 = 0;
|
||||||
|
|
||||||
// Handle negative x by reflecting into right half-plane
|
|
||||||
let negate_x = x < 0;
|
let negate_x = x < 0;
|
||||||
if negate_x {
|
if negate_x {
|
||||||
x = -x;
|
x = -x;
|
||||||
y = -y;
|
y = -y;
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..self.word_bits as usize {
|
for i in 0..table.iterations as usize {
|
||||||
let d = if y < 0 { 1i64 } else { -1 };
|
let d = if y < 0 { 1i64 } else { -1 };
|
||||||
let x_new = x - d * (y >> i);
|
let x_new = x - d * (y >> i);
|
||||||
let y_new = y + d * (x >> i);
|
let y_new = y + d * (x >> i);
|
||||||
z -= d * self.atan_table[i];
|
z -= d * table.atan_table[i];
|
||||||
x = x_new;
|
x = x_new;
|
||||||
y = y_new;
|
y = y_new;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vectoring output: x_final = (1/K) * sqrt(x0^2 + y0^2).
|
let magnitude = self.fixed_mul(x, table.gain);
|
||||||
// self.gain stores K (~0.6073). Multiply to recover true magnitude.
|
|
||||||
let magnitude = self.fixed_mul(x, self.gain);
|
|
||||||
|
|
||||||
if negate_x {
|
if negate_x {
|
||||||
let pi = self.to_fixed(std::f64::consts::PI);
|
let pi = self.to_fixed(std::f64::consts::PI);
|
||||||
|
|
@ -147,10 +134,12 @@ impl CORDICEvaluator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Evaluate the entire trig graph using only CORDIC operations.
|
/// Evaluate the entire trig graph using only CORDIC operations.
|
||||||
/// Returns the output as f64 (converted from fixed-point at the end).
|
/// Returns the output as f64 (converted from fixed-point at the end).
|
||||||
pub fn evaluate(&self, graph: &TrigGraph, x: f64, y: f64, z: f64) -> f64 {
|
pub fn evaluate(&self, graph: &TrigGraph, x: f64, y: f64, z: f64) -> f64 {
|
||||||
let mut vals = vec![0i64; graph.nodes.len()];
|
let mut vals = vec![0i64; graph.nodes.len()];
|
||||||
|
let use_per_instr = !self.instr_table_idx.is_empty() && self.tables.len() > 1;
|
||||||
|
|
||||||
for (i, op) in graph.nodes.iter().enumerate() {
|
for (i, op) in graph.nodes.iter().enumerate() {
|
||||||
vals[i] = match op {
|
vals[i] = match op {
|
||||||
|
|
@ -167,40 +156,44 @@ impl CORDICEvaluator {
|
||||||
TrigOp::Abs(a) => vals[*a as usize].abs(),
|
TrigOp::Abs(a) => vals[*a as usize].abs(),
|
||||||
|
|
||||||
TrigOp::Sin(a) => {
|
TrigOp::Sin(a) => {
|
||||||
let (_, sin) = self.cordic_rotation(vals[*a as usize]);
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
|
let (_, sin) = self.cordic_rotation_with(vals[*a as usize], t);
|
||||||
sin
|
sin
|
||||||
}
|
}
|
||||||
TrigOp::Cos(a) => {
|
TrigOp::Cos(a) => {
|
||||||
let (cos, _) = self.cordic_rotation(vals[*a as usize]);
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
|
let (cos, _) = self.cordic_rotation_with(vals[*a as usize], t);
|
||||||
cos
|
cos
|
||||||
}
|
}
|
||||||
TrigOp::Tan(a) => {
|
TrigOp::Tan(a) => {
|
||||||
let (cos, sin) = self.cordic_rotation(vals[*a as usize]);
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
|
let (cos, sin) = self.cordic_rotation_with(vals[*a as usize], t);
|
||||||
self.fixed_div(sin, cos)
|
self.fixed_div(sin, cos)
|
||||||
}
|
}
|
||||||
TrigOp::Asin(a) => {
|
TrigOp::Asin(a) => {
|
||||||
// asin(x) = atan2(x, sqrt(1-x²))
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
let x = vals[*a as usize];
|
let xv = vals[*a as usize];
|
||||||
let one = self.to_fixed(1.0);
|
let one = self.to_fixed(1.0);
|
||||||
let x2 = self.fixed_mul(x, x);
|
let x2 = self.fixed_mul(xv, xv);
|
||||||
let rem = one - x2;
|
let rem = one - x2;
|
||||||
let sqrt_rem = self.fixed_sqrt(rem);
|
let sqrt_rem = self.fixed_sqrt(rem);
|
||||||
let (_, angle) = self.cordic_vectoring(sqrt_rem, x);
|
let (_, angle) = self.cordic_vectoring_with(sqrt_rem, xv, t);
|
||||||
angle
|
angle
|
||||||
}
|
}
|
||||||
TrigOp::Acos(a) => {
|
TrigOp::Acos(a) => {
|
||||||
// acos(x) = atan2(sqrt(1-x²), x)
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
let x = vals[*a as usize];
|
let xv = vals[*a as usize];
|
||||||
let one = self.to_fixed(1.0);
|
let one = self.to_fixed(1.0);
|
||||||
let x2 = self.fixed_mul(x, x);
|
let x2 = self.fixed_mul(xv, xv);
|
||||||
let rem = one - x2;
|
let rem = one - x2;
|
||||||
let sqrt_rem = self.fixed_sqrt(rem);
|
let sqrt_rem = self.fixed_sqrt(rem);
|
||||||
let (_, angle) = self.cordic_vectoring(x, sqrt_rem);
|
let (_, angle) = self.cordic_vectoring_with(xv, sqrt_rem, t);
|
||||||
angle
|
angle
|
||||||
}
|
}
|
||||||
TrigOp::Atan(a) => {
|
TrigOp::Atan(a) => {
|
||||||
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
let one = self.to_fixed(1.0);
|
let one = self.to_fixed(1.0);
|
||||||
let (_, angle) = self.cordic_vectoring(one, vals[*a as usize]);
|
let (_, angle) = self.cordic_vectoring_with(one, vals[*a as usize], t);
|
||||||
angle
|
angle
|
||||||
}
|
}
|
||||||
TrigOp::Sinh(a) => self.to_fixed(self.to_float(vals[*a as usize]).sinh()),
|
TrigOp::Sinh(a) => self.to_fixed(self.to_float(vals[*a as usize]).sinh()),
|
||||||
|
|
@ -214,11 +207,13 @@ impl CORDICEvaluator {
|
||||||
TrigOp::Ln(a) => self.to_fixed(self.to_float(vals[*a as usize]).ln()),
|
TrigOp::Ln(a) => self.to_fixed(self.to_float(vals[*a as usize]).ln()),
|
||||||
|
|
||||||
TrigOp::Hypot(a, b) => {
|
TrigOp::Hypot(a, b) => {
|
||||||
let (mag, _) = self.cordic_vectoring(vals[*a as usize], vals[*b as usize]);
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
|
let (mag, _) = self.cordic_vectoring_with(vals[*a as usize], vals[*b as usize], t);
|
||||||
mag
|
mag
|
||||||
}
|
}
|
||||||
TrigOp::Atan2(a, b) => {
|
TrigOp::Atan2(a, b) => {
|
||||||
let (_, angle) = self.cordic_vectoring(vals[*b as usize], vals[*a as usize]);
|
let t = if use_per_instr { self.table_for(i) } else { &self.tables[0] };
|
||||||
|
let (_, angle) = self.cordic_vectoring_with(vals[*b as usize], vals[*a as usize], t);
|
||||||
angle
|
angle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,4 +285,47 @@ mod tests {
|
||||||
assert!((cordic_val - float_val).abs() < 0.1,
|
assert!((cordic_val - float_val).abs() < 0.1,
|
||||||
"sphere inside: CORDIC={cordic_val}, f64={float_val}");
|
"sphere inside: CORDIC={cordic_val}, f64={float_val}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_per_op_config_roundtrip() {
|
||||||
|
use crate::config::{CordicConfig, CordicOp, CordicConfigFile, Defaults, Override};
|
||||||
|
use crate::compiler::CompileConfig;
|
||||||
|
|
||||||
|
let mut g = TrigGraph::new();
|
||||||
|
let angle = g.push(TrigOp::Const(std::f64::consts::FRAC_PI_4));
|
||||||
|
let s = g.push(TrigOp::Sin(angle));
|
||||||
|
let a = g.push(TrigOp::Const(3.0));
|
||||||
|
let b = g.push(TrigOp::Const(4.0));
|
||||||
|
let h = g.push(TrigOp::Hypot(a, b));
|
||||||
|
let _ = g.push(TrigOp::Add(s, h));
|
||||||
|
g.set_output(5);
|
||||||
|
|
||||||
|
// Config: sin at 16 iterations, hypot at 24
|
||||||
|
let file = CordicConfigFile {
|
||||||
|
defaults: Defaults { word_bits: 32, f: 32 },
|
||||||
|
overrides: vec![
|
||||||
|
Override { op: "sin".into(), f: 16 },
|
||||||
|
Override { op: "hypot".into(), f: 24 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let cfg = CordicConfig::from_file(&g, &file);
|
||||||
|
assert_eq!(cfg.iterations_for(CordicOp::Sin), 16);
|
||||||
|
assert_eq!(cfg.iterations_for(CordicOp::Hypot), 24);
|
||||||
|
|
||||||
|
// Compile with config
|
||||||
|
let compile_cfg = CompileConfig {
|
||||||
|
word_bits: 32,
|
||||||
|
cordic: Some(cfg),
|
||||||
|
};
|
||||||
|
let program = crate::CORDICProgram::compile(&g, &compile_cfg);
|
||||||
|
assert!(program.tables.len() >= 2);
|
||||||
|
|
||||||
|
// Evaluate from program
|
||||||
|
let eval = CORDICEvaluator::from_program(&program);
|
||||||
|
let result = eval.evaluate(&g, 0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
// Should still produce a reasonable result (sin(pi/4) + hypot(3,4) ≈ 0.707 + 5 = 5.707)
|
||||||
|
assert!((result - 5.707).abs() < 0.5,
|
||||||
|
"per-op config: result={result}, expected ~5.707");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
//! reference is typically zero at the precision boundary.
|
//! reference is typically zero at the precision boundary.
|
||||||
|
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
|
pub mod config;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
|
||||||
pub use compiler::CORDICProgram;
|
pub use compiler::CORDICProgram;
|
||||||
|
pub use config::CordicConfig;
|
||||||
pub use eval::CORDICEvaluator;
|
pub use eval::CORDICEvaluator;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ pub struct Layers {
|
||||||
pub trig: bool,
|
pub trig: bool,
|
||||||
pub shader: bool,
|
pub shader: bool,
|
||||||
pub cordic: bool,
|
pub cordic: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub config: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Manifest {
|
impl Default for Manifest {
|
||||||
|
|
@ -46,6 +48,7 @@ impl Default for Manifest {
|
||||||
trig: false,
|
trig: false,
|
||||||
shader: false,
|
shader: false,
|
||||||
cordic: false,
|
cordic: false,
|
||||||
|
config: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,4 +66,16 @@ impl<R: Read + Seek> ZcdReader<R> {
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read CORDIC configuration (TOML).
|
||||||
|
pub fn read_config(&mut self) -> Result<Option<String>> {
|
||||||
|
match self.archive.by_name("config/cordic.toml") {
|
||||||
|
Ok(mut file) => {
|
||||||
|
let mut buf = String::new();
|
||||||
|
file.read_to_string(&mut buf)?;
|
||||||
|
Ok(Some(buf))
|
||||||
|
}
|
||||||
|
Err(_) => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,15 @@ impl<W: Write + Seek> ZcdWriter<W> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write CORDIC configuration (TOML).
|
||||||
|
pub fn write_config(&mut self, config_toml: &str) -> Result<()> {
|
||||||
|
let options = SimpleFileOptions::default();
|
||||||
|
self.zip.start_file("config/cordic.toml", options)?;
|
||||||
|
self.zip.write_all(config_toml.as_bytes())?;
|
||||||
|
self.manifest.layers.config = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn finish(mut self) -> Result<W> {
|
pub fn finish(mut self) -> Result<W> {
|
||||||
let manifest_json = serde_json::to_string_pretty(&self.manifest)?;
|
let manifest_json = serde_json::to_string_pretty(&self.manifest)?;
|
||||||
let options = SimpleFileOptions::default();
|
let options = SimpleFileOptions::default();
|
||||||
|
|
|
||||||
15
src/main.rs
15
src/main.rs
|
|
@ -168,9 +168,15 @@ fn cmd_build(input: &std::path::Path, output: Option<PathBuf>, word_bits: u8) ->
|
||||||
let (source, graph) = load_trig_graph(input)?;
|
let (source, graph) = load_trig_graph(input)?;
|
||||||
let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
|
let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
|
||||||
|
|
||||||
let config = cord_cordic::compiler::CompileConfig { word_bits };
|
// Auto-generate CORDIC config by scanning the graph
|
||||||
|
let cordic_cfg = cord_cordic::CordicConfig::scan(&graph);
|
||||||
|
let config = cord_cordic::compiler::CompileConfig {
|
||||||
|
word_bits,
|
||||||
|
cordic: Some(cordic_cfg.clone()),
|
||||||
|
};
|
||||||
let cordic = cord_cordic::CORDICProgram::compile(&graph, &config);
|
let cordic = cord_cordic::CORDICProgram::compile(&graph, &config);
|
||||||
let cordic_bytes = cordic.to_bytes();
|
let cordic_bytes = cordic.to_bytes();
|
||||||
|
let config_toml = cordic_cfg.to_toml();
|
||||||
|
|
||||||
let file = std::fs::File::create(&out_path)
|
let file = std::fs::File::create(&out_path)
|
||||||
.with_context(|| format!("creating {}", out_path.display()))?;
|
.with_context(|| format!("creating {}", out_path.display()))?;
|
||||||
|
|
@ -187,6 +193,7 @@ fn cmd_build(input: &std::path::Path, output: Option<PathBuf>, word_bits: u8) ->
|
||||||
}
|
}
|
||||||
writer.write_shader(&wgsl)?;
|
writer.write_shader(&wgsl)?;
|
||||||
writer.write_cordic(&cordic_bytes, word_bits)?;
|
writer.write_cordic(&cordic_bytes, word_bits)?;
|
||||||
|
writer.write_config(&config_toml)?;
|
||||||
writer.finish()?;
|
writer.finish()?;
|
||||||
|
|
||||||
println!("wrote {}", out_path.display());
|
println!("wrote {}", out_path.display());
|
||||||
|
|
@ -236,7 +243,11 @@ fn cmd_decompile(
|
||||||
let graph = cord_sdf::sdf_to_trig(&result.sdf);
|
let graph = cord_sdf::sdf_to_trig(&result.sdf);
|
||||||
let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
|
let wgsl = cord_shader::generate_wgsl_from_trig(&graph);
|
||||||
|
|
||||||
let cordic_config = cord_cordic::compiler::CompileConfig { word_bits };
|
let cordic_cfg = cord_cordic::CordicConfig::scan(&graph);
|
||||||
|
let cordic_config = cord_cordic::compiler::CompileConfig {
|
||||||
|
word_bits,
|
||||||
|
cordic: Some(cordic_cfg),
|
||||||
|
};
|
||||||
let cordic = cord_cordic::CORDICProgram::compile(&graph, &cordic_config);
|
let cordic = cord_cordic::CORDICProgram::compile(&graph, &cordic_config);
|
||||||
let cordic_bytes = cordic.to_bytes();
|
let cordic_bytes = cordic.to_bytes();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue