Skip to content

Commit

Permalink
Floating-point Constants (#2316)
Browse files Browse the repository at this point in the history
Subsumes #2195.
  • Loading branch information
rachitnigam authored Oct 26, 2024
1 parent 8d242da commit b6ef6bc
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 9 deletions.
55 changes: 54 additions & 1 deletion calyx-frontend/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::ast::{
};
use super::Attributes;
use crate::{Attribute, Direction, PortDef, Primitive, Width};
use calyx_utils::{self, CalyxResult, Id, PosString};
use calyx_utils::{self, float, CalyxResult, Id, PosString};
use calyx_utils::{FileIdx, GPosIdx, GlobalPositionTable};
use pest::pratt_parser::{Assoc, Op, PrattParser};
use pest_consume::{match_nodes, Error, Parser};
Expand Down Expand Up @@ -295,6 +295,13 @@ impl CalyxParser {
.map_err(|_| input.error("Expected binary number"))
}

// Floats are parsed as strings and converted within the float_const rule.
// This is so that we can check and see if the number can be represented
// exactly with the given bitwidth.
fn float(input: Node) -> ParseResult<String> {
Ok(input.as_str().to_string())
}

fn num_lit(input: Node) -> ParseResult<BitNum> {
let span = Self::get_span(&input);
let num = match_nodes!(
Expand Down Expand Up @@ -541,10 +548,56 @@ impl CalyxParser {
}

// ================ Cells =====================
fn float_const(input: Node) -> ParseResult<ast::Cell> {
let span = Self::get_span(&input);
Ok(match_nodes!(
input.clone().into_children();
[
at_attributes(attrs),
identifier(id),
bitwidth(rep),
bitwidth(width),
float(val)
] => {
let v = match float::parse(rep, width, val) {
Ok(v) => v,
Err(e) => return Err(input.error(format!("{e:?}")))
};
ast::Cell::from(
id,
Id::from("std_float_const"),
vec![rep, width, v],
attrs.add_span(span),
false
)
},
[
at_attributes(attrs),
reference(_),
identifier(id),
bitwidth(rep),
bitwidth(width),
float(val)
] => {
let v = match float::parse(rep, width, val) {
Ok(v) => v,
Err(e) => return Err(input.error(format!("{e:?}")))
};
ast::Cell::from(
id,
Id::from("std_float_const"),
vec![rep, width, v],
attrs.add_span(span),
true
)},
))
}

fn cell_without_semi(input: Node) -> ParseResult<ast::Cell> {
let span = Self::get_span(&input);
Ok(match_nodes!(
input.into_children();
[float_const(fl)] => fl,
[at_attributes(attrs), reference(_), identifier(id), identifier(prim), args(args)] =>
ast::Cell::from(id, prim, args, attrs.add_span(span),true),
[at_attributes(attrs), identifier(id), identifier(prim), args(args)] =>
Expand Down
14 changes: 13 additions & 1 deletion calyx-frontend/src/syntax.pest
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ decimal = @{ ASCII_HEX_DIGIT+ }
octal = @{ ASCII_HEX_DIGIT+ }
hex = @{ ASCII_HEX_DIGIT+ }

// Floating-point numbers are only supported within the `std_float_const` primitive.
float = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }

// `$` creates a compound rule which ignores whitespace while allowing for
// inner rules (`@` makes inner rules silent).
// See: https://pest.rs/book/print.html#atomic
Expand Down Expand Up @@ -139,8 +142,17 @@ args = {
"(" ~ (bitwidth ~ ("," ~ bitwidth)*)? ~ ")"
}

float_const = {
at_attributes ~ reference? ~ identifier ~ "=" ~ "std_float_const" ~ "(" ~
bitwidth ~ "," ~ // REP
bitwidth ~ "," ~ // WIDTH
float ~ // VALUE
")"
}

cell_without_semi = {
at_attributes ~ reference? ~ identifier ~ "=" ~ identifier ~ args
float_const |
(at_attributes ~ reference? ~ identifier ~ "=" ~ identifier ~ args)
}

cell = {
Expand Down
38 changes: 38 additions & 0 deletions calyx-ir/src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! to the Component.
use crate::{self as ir, RRC};
use calyx_frontend::PrimitiveInfo;
use calyx_utils::float;
use itertools::Itertools;
use std::io;
use std::path::Path;
Expand Down Expand Up @@ -274,6 +275,39 @@ impl Printer {
write!(f, "}}")
}

pub fn write_float_const<F: io::Write>(
cell: &ir::Cell,
indent_level: usize,
f: &mut F,
) -> io::Result<()> {
let ir::CellType::Primitive {
name: prim,
param_binding,
..
} = &cell.prototype
else {
unreachable!("Expected std_float_const cell")
};

let (rep, width, val) =
(param_binding[0].1, param_binding[1].1, param_binding[2].1);

let fl = match float::emit(val, width) {
Ok(fl) => fl,
Err(e) => {
panic!("Error emitting float constant: {e:?}")
}
};

write!(f, "{}", " ".repeat(indent_level))?;
write!(f, "{}", Self::format_at_attributes(&cell.attributes))?;
if cell.is_reference() {
write!(f, "ref ")?;
}
writeln!(f, "{} = {prim}({rep}, {width}, {fl});", cell.name().id)?;
Ok(())
}

/// Format and write a cell.
pub fn write_cell<F: io::Write>(
cell: &ir::Cell,
Expand All @@ -286,6 +320,10 @@ impl Printer {
param_binding,
..
} => {
if name == "std_float_const" {
return Self::write_float_const(cell, indent_level, f);
}

write!(f, "{}", " ".repeat(indent_level))?;
write!(f, "{}", Self::format_at_attributes(&cell.attributes))?;
if cell.is_reference() {
Expand Down
53 changes: 53 additions & 0 deletions calyx-utils/src/float.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! Implement parsing and generation for floating point constants used by std_float_const.
use crate::{CalyxResult, Error};

pub fn parse(rep: u64, width: u64, fl: String) -> CalyxResult<u64> {
if rep != 0 {
return Err(Error::misc(format!(
"Unknown representation: {rep}. Support representations: 0 (IEEE754)"
)));
}

let bits: u64 = match width {
32 => {
let fl = fl.parse::<f32>().map_err(|e| {
Error::misc(format!(
"Expected valid floating point number: {e}"
))
})?;
fl.to_bits() as u64
}
64 => {
let fl = fl.parse::<f64>().map_err(|e| {
Error::misc(format!(
"Expected valid floating point number: {e}"
))
})?;
fl.to_bits()
}
r => {
return Err(Error::misc(format!(
"Unsupported floating point width: {r}. Supported values: 32, 64"
)))
}
};

Ok(bits)
}

pub fn emit(bits: u64, width: u64) -> CalyxResult<String> {
match width {
32 => {
let fl = f32::from_bits(bits as u32);
Ok(format!("{}", fl))
}
64 => {
let fl = f64::from_bits(bits);
Ok(format!("{}", fl))
}
r => Err(Error::misc(format!(
"Unsupported floating point width: {r}. Supported values: 32, 64"
))),
}
}
2 changes: 2 additions & 0 deletions calyx-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ mod weight_graph;
mod math;
pub(crate) mod measure_time;

pub mod float;

pub use errors::{CalyxResult, Error, MultiError};
pub use id::{GSym, GetName, Id};
pub use math::bits_needed_for;
Expand Down
20 changes: 20 additions & 0 deletions docs/libraries/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ such as registers and basic bitwise operations.
- [Numerical Operators](#numerical-operators)
- [Logical Operators](#logical-operators)
- [Comparison Operators](#comparison-operators)
- [Floating Point](#floating-point)
- [Memories](#memories)

---
Expand Down Expand Up @@ -315,6 +316,25 @@ Less than or equal. This component is combinational.

---

## Floating Point

### `std_float_const`

A floating-point constant with a particular representation and bitwidth.
Floating-point values are specially parsed by the frontend and turned into the equivalent bit pattern (as dictated by the representation).
Similarly, the backend supports specialized printing for constants based on the representation

**Parameters:**

- `REP`: The representation to use. `0` corresponds to [IEEE-754 floating point][ieee754] numbers. No other representation is supported at this point.
- `WIDTH`: Bitwidth to use. Supported values: `32`, `64`.
- `VAL`: The floating-point value. Frontend converts this into a `u64` internally.


[ieee754]: https://en.wikipedia.org/wiki/IEEE_754

---

## Memories

Calyx features two flavors of memories: combinational and sequential.
Expand Down
19 changes: 19 additions & 0 deletions primitives/float.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** A floating-point constant with a specific representation.
* NOTE: Floating point constants have special parsing and generation rules in
* the frontend and the backend. If you make changes here, you will likely
* need to change calyx_utils::float as well.
*
* PARAMETERS
* - REP: The representation of the floating-point number.
* 0: IEEE 754
* _: Reserved for future use.
* - WIDTH=32, 64 are supported. Other values result in an error.
*
* PARSING
* The value is converted into the bitpattern of the floating-point representation.
* If the number cannot be represented exactly with the given bitwidth, we will
* emit a warning.
*/
comb primitive std_float_const<"share"=1>[REP, WIDTH, VALUE]() -> (out: WIDTH) {
assign out = VALUE;
}
15 changes: 8 additions & 7 deletions runt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ paths = [
"tests/errors/*.futil",
"tests/errors/papercut/*.futil",
"tests/errors/parser/*.futil",
"tests/errors/float/*.futil",
]
cmd = """
./target/debug/calyx {} -p well-formed -p papercut -p synthesis-papercut -l . -m file
Expand Down Expand Up @@ -109,12 +110,12 @@ cmd = "python3 calyx-py/calyx/gen_exp.py {}"
[[tests]]
name = "[frontend] queues correctness"
paths = [
"frontends/queues/tests/*.py",
"frontends/queues/tests/round_robin/*.py",
"frontends/queues/tests/strict/*.py",
"frontends/queues/tests/binheap/*.py",
"frontends/queues/tests/binheap/round_robin/*.py",
"frontends/queues/tests/binheap/strict/*.py"
"frontends/queues/tests/*.py",
"frontends/queues/tests/round_robin/*.py",
"frontends/queues/tests/strict/*.py",
"frontends/queues/tests/binheap/*.py",
"frontends/queues/tests/binheap/round_robin/*.py",
"frontends/queues/tests/binheap/strict/*.py",
]
cmd = """
name=$(basename {} .py) &&
Expand Down Expand Up @@ -697,4 +698,4 @@ name = "profiler"
paths = ["tests/profiler/*.futil"]
cmd = """
bash tools/profiler/get-profile-counts-info.sh {} {}.data STDOUT -d
"""
"""
7 changes: 7 additions & 0 deletions tests/errors/float/invalid-rep.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---CODE---
1
---STDERR---
Error: tests/errors/float/invalid-rep.futil
3 | f = std_float_const(1, 32, 0.5);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Parse error
Unknown representation: 1. Support representations: 0 (IEEE754)
7 changes: 7 additions & 0 deletions tests/errors/float/invalid-rep.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
component main() -> () {
cells {
f = std_float_const(1, 32, 0.5);
}
wires {}
control {}
}
7 changes: 7 additions & 0 deletions tests/errors/float/invalid-width.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---CODE---
1
---STDERR---
Error: tests/errors/float/invalid-width.futil
3 | f = std_float_const(0, 16, 0.5);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Parse error
Unsupported floating point width: 16. Supported values: 32, 64
7 changes: 7 additions & 0 deletions tests/errors/float/invalid-width.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
component main() -> () {
cells {
f = std_float_const(0, 16, 0.5);
}
wires {}
control {}
}
20 changes: 20 additions & 0 deletions tests/parsing/float.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import "primitives/float.futil";
import "primitives/core.futil";
component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) {
cells {
f = std_float_const(0, 32, 0.5);
add = std_add(32);
r = std_reg(32);
}
wires {
static<1> group add_one {
add.left = f.out;
add.right = 32'd1;
r.in = add.out;
r.write_en = 1'd1;
}
}
control {
add_one;
}
}

0 comments on commit b6ef6bc

Please sign in to comment.