From 324a404ab29968801c7e898329abdb52f54143a5 Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 01:23:57 -0500 Subject: [PATCH 01/10] use morty crate to pickle multiple files --- Cargo.lock | 469 ++++++++++++++++++++++++++++++++++- calyx-backend/Cargo.toml | 2 + calyx-backend/src/verilog.rs | 250 +++++++++++++++++-- 3 files changed, 696 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3037b460b4..c714f5f76c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.80" @@ -232,6 +281,18 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitvec" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -276,6 +337,18 @@ version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +[[package]] +name = "bytecount" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f861d9ce359f56dbcb6e0c2a1cb84e52ad732cadb57b806adeb3c7668caccbd8" + +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "bytemuck" version = "1.14.3" @@ -324,6 +397,7 @@ dependencies = [ "itertools 0.11.0", "linked-hash-map", "log", + "morty", "petgraph", "quick-xml", "serde", @@ -332,6 +406,7 @@ dependencies = [ "serde_with 3.6.1", "smallvec", "string-interner", + "tempfile", "vast", ] @@ -602,6 +677,46 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "clipboard-win" version = "4.5.0" @@ -613,6 +728,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.52.0", +] + [[package]] name = "component_cells" version = "0.7.1" @@ -697,7 +828,7 @@ checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", "itertools 0.10.5", @@ -1188,6 +1319,12 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures" version = "0.3.30" @@ -1284,6 +1421,15 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.12" @@ -1347,6 +1493,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1487,6 +1639,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -1526,6 +1684,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.153" @@ -1653,6 +1824,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "morty" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6f9bcf39547c7974641e3967c95abc0ae4399b165eb83625244af48d7b22" +dependencies = [ + "anyhow", + "chrono", + "clap 4.5.23", + "colored", + "log", + "petgraph", + "pulldown-cmark", + "rayon", + "serde", + "serde_json", + "simple_logger", + "sv-parser", + "term", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1683,6 +1875,30 @@ dependencies = [ "version_check 0.1.5", ] +[[package]] +name = "nom" +version = "5.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" +dependencies = [ + "lexical-core", + "memchr", + "version_check 0.9.4", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check 0.9.4", +] + [[package]] name = "nom" version = "7.1.3" @@ -1693,6 +1909,116 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom-greedyerror" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133e5024c0b65c4235e3200a3b6e30f3875475f1e452525e1a421b7f2a997c52" +dependencies = [ + "nom 5.1.3", + "nom 6.1.2", + "nom_locate 1.0.0", + "nom_locate 2.1.0", + "nom_locate 3.0.2", +] + +[[package]] +name = "nom-packrat" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c5a5a7eae83c3c9d53bdfd94e8bb1d700c6bb78f00d25af71263fc07cf477b" +dependencies = [ + "nom-packrat-macros", + "nom_locate 1.0.0", + "nom_locate 3.0.2", +] + +[[package]] +name = "nom-packrat-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fccdfb4771d14a08918cd7b7352de2797ade66a2df9920cee13793e943c3d09" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nom-recursive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0de2967d4f9065b08596dcfa9be631abc4997951b9e0a93e2279b052370bacc" +dependencies = [ + "nom-recursive-macros", + "nom_locate 1.0.0", + "nom_locate 3.0.2", +] + +[[package]] +name = "nom-recursive-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07744fc6b7423baf7198f9e1200305f27eafe7395289fa7462b63dacd4eac78d" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nom-tracable" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128b58b88f084359e18858edde832830041e0a561d23bb214e656e00972de316" +dependencies = [ + "nom 6.1.2", + "nom-tracable-macros", + "nom_locate 1.0.0", + "nom_locate 3.0.2", +] + +[[package]] +name = "nom-tracable-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8416fc5553b00d217b0381929fbce7368935d609afdee46c844e09f962b379e6" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nom_locate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f932834fd8e391fc7710e2ba17e8f9f8645d846b55aa63207e17e110a1e1ce35" +dependencies = [ + "bytecount 0.3.2", + "memchr", + "nom 5.1.3", +] + +[[package]] +name = "nom_locate" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67484adf5711f94f2f28b653bf231bff8e438be33bf5b0f35935a0db4f618a2" +dependencies = [ + "bytecount 0.6.8", + "memchr", + "nom 5.1.3", +] + +[[package]] +name = "nom_locate" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4689294073dda8a54e484212171efdcb6b12b1908fd70c3dc3eec15b8833b06d" +dependencies = [ + "bytecount 0.6.8", + "memchr", + "nom 6.1.2", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1796,6 +2122,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.32.2" @@ -2057,6 +2392,18 @@ dependencies = [ "unarray", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.4.2", + "getopts", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2082,6 +2429,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "radix_trie" version = "0.2.1" @@ -2543,6 +2896,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +[[package]] +name = "simple_logger" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.48.0", +] + [[package]] name = "slab" version = "0.4.9" @@ -2625,6 +2990,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[package]] +name = "str-concat" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3468939e48401c4fe3cdf5e5cef50951c2808ed549d1467fde249f1fcb602634" + [[package]] name = "string-interner" version = "0.14.0" @@ -2660,13 +3031,87 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", "syn 2.0.90", ] +[[package]] +name = "sv-parser" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6567e4ff8c8f26fbd561e8797f2bf658462b68346d481d9a53f9390873688d" +dependencies = [ + "nom 6.1.2", + "nom-greedyerror", + "sv-parser-error", + "sv-parser-parser", + "sv-parser-pp", + "sv-parser-syntaxtree", +] + +[[package]] +name = "sv-parser-error" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ab9840f6af470b46a27ec709c5bdba054d0fe57d408ba550d99b6de027b143" +dependencies = [ + "thiserror 1.0.64", +] + +[[package]] +name = "sv-parser-macros" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60599265d86a4647a32e97f5c9ae758c1ac82402a6e0d123347384997148ba6a" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sv-parser-parser" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4628479b05874500c1227228f481e04e9aaf858bc43eecd87fbbf4b6999b7269" +dependencies = [ + "nom 6.1.2", + "nom-greedyerror", + "nom-packrat", + "nom-recursive", + "nom-tracable", + "nom_locate 3.0.2", + "str-concat", + "sv-parser-macros", + "sv-parser-syntaxtree", +] + +[[package]] +name = "sv-parser-pp" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db489604d13d9f630173a477357a0010d310597df4728a35bc54409af72ebb3c" +dependencies = [ + "nom 6.1.2", + "nom-greedyerror", + "sv-parser-error", + "sv-parser-parser", + "sv-parser-syntaxtree", +] + +[[package]] +name = "sv-parser-syntaxtree" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49debd66a99e1783badfa9780bb96b3a96b92d94ebd58ca4b82f3a32ca498a3" +dependencies = [ + "regex", + "sv-parser-macros", + "walkdir", +] + [[package]] name = "symbol_table" version = "0.2.0" @@ -2722,6 +3167,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempdir" version = "0.3.7" @@ -2837,7 +3288,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -3140,6 +3593,12 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -3491,6 +3950,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/calyx-backend/Cargo.toml b/calyx-backend/Cargo.toml index 9ccd175e33..5254a62fe7 100644 --- a/calyx-backend/Cargo.toml +++ b/calyx-backend/Cargo.toml @@ -29,6 +29,8 @@ calyx-ir.workspace = true csv = { version = "1.1", optional = true } vast = "0.3.1" +morty = "0.9.0" +tempfile = "3.3" [dependencies.quick-xml] version = "0.30" diff --git a/calyx-backend/src/verilog.rs b/calyx-backend/src/verilog.rs index 0f0371af53..a46d38f62f 100644 --- a/calyx-backend/src/verilog.rs +++ b/calyx-backend/src/verilog.rs @@ -8,9 +8,12 @@ use calyx_ir::{self as ir, Control, FlatGuard, Group, Guard, GuardRef, RRC}; use calyx_utils::{CalyxResult, Error, OutputFile}; use ir::Nothing; use itertools::Itertools; +use morty::{FileBundle, LibraryBundle}; use std::io; -use std::{collections::HashMap, rc::Rc}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::{collections::HashMap, collections::HashSet, path::PathBuf, rc::Rc}; use std::{fs::File, time::Instant}; +use tempfile::NamedTempFile; use vast::v17::ast as v; /// Implements a simple Verilog backend. The backend only accepts Calyx programs with no control @@ -90,6 +93,145 @@ fn validate_control(ctrl: &ir::Control) -> CalyxResult<()> { } } +/// Each external library has its own specific handler to build all information that Morty needs to do pickling. We can implement each command line argument (see https://github.com/pulp-platform/morty/blob/master/src/main.rs for available arguments) as a trait's method. +trait LibraryHandlerTrait { + /// Add search path(s) for SystemVerilog includes to build a LibraryBundle + fn add_incs(&self) -> CalyxResult>; + /// Directory(s) to search for SystemVerilog modules + fn add_library_dirs(&self) -> CalyxResult>; + /// Define preprocesor macro(s) + fn add_defs(&self) -> CalyxResult>>; + /// Add search path(s) for SystemVerilog includes to build a FileBundle + fn add_stdin_incdirs(&self) -> CalyxResult>; + /// Add export include directories + fn add_export_incdirs(&self) -> CalyxResult>>; + /// Create a map from module name to file path + fn map_module_names_to_file_paths( + &self, + ) -> CalyxResult> { + // a hashmap from 'module name' to 'path' for all libraries. + let mut library_files = HashMap::new(); + // a list of paths for all library files + let mut library_paths: Vec = Vec::new(); + + // we first accumulate all library files from the 'library_dir' and 'library_file' options into + // a vector of paths, and then construct the library hashmap. + let library_dirs: Vec = self.add_library_dirs()?; + for dir in library_dirs { + let entries = std::fs::read_dir(&dir).map_err(|e| { + Error::invalid_file(format!( + "Error accessing library directory `{:?}`: {}", + dir, e + )) + })?; + + for entry in entries { + let entry = entry.map_err(|e| { + Error::invalid_file(format!( + "Error reading entry in directory `{:?}`: {}", + dir, e + )) + })?; + library_paths.push(entry.path()); + } + } + + for p in &library_paths { + // Must have the library extension (.v or .sv). + if morty::has_libext(p) { + if let Some(m) = morty::lib_module(p) { + library_files.insert(m, p.to_owned()); + } + } + } + + Ok(library_files) + } +} + +/// Check if any special library is needed +fn check_library_needed(ctx: &ir::Context) -> bool { + ctx.lib + .extern_paths() + .iter() + .any(|path| path.to_string_lossy().contains("float")) +} + +/// Collect all included files specified by the Calyx source file +fn collect_included_files(ctx: &ir::Context) -> Vec { + ctx.lib + .extern_paths() + .into_iter() + .map(|pb| pb.to_string_lossy().into_owned()) + .collect() +} + +/// Build the library bundle for all libraries needed for pickling +fn build_library_bundle( + ctx: &ir::Context, + calyx_emitted_file: &NamedTempFile, + handlers: &Vec>, +) -> CalyxResult { + let mut included_files = collect_included_files(ctx); + included_files + .push(calyx_emitted_file.path().to_string_lossy().to_string()); // `calyx_emitted_file` is used as part of the source files + let mut include_dirs = Vec::new(); + let mut defines = HashMap::new(); + let mut files = HashMap::new(); + for handler in handlers { + include_dirs.extend(handler.add_incs()?); + defines.extend(handler.add_defs()?); + files.extend(handler.map_module_names_to_file_paths()?); + } + let main_module = String::from(ctx.entrypoint.id.as_str()); + files.insert(main_module, calyx_emitted_file.path().to_path_buf()); + + let include_dirs: Vec = include_dirs + .into_iter() + .map(|path| path.to_string_lossy().into_owned()) + .collect(); + Ok(LibraryBundle { + include_dirs, + defines, + files, + }) +} + +/// Build a list of `FileBundle`s needed for building the syntax tree +fn derive_file_list( + ctx: &ir::Context, + file: &NamedTempFile, + handlers: &Vec>, +) -> CalyxResult> { + let mut file_bundles = Vec::new(); + for handler in handlers { + let stdin_incdirs = handler.add_stdin_incdirs()?; + let include_dirs: Vec = stdin_incdirs + .iter() + .map(|path| path.to_string_lossy().into_owned()) + .collect(); + let export_incdirs = handler.add_export_incdirs()?; + let defines = handler.add_defs()?; + let mut files = handler.map_module_names_to_file_paths()?; + + let main_module = String::from(ctx.entrypoint.id.as_str()); + files.insert(main_module, file.path().to_path_buf()); + + let mut included_files = collect_included_files(ctx); + for lib_file in files.values() { + included_files.push(String::from(lib_file.to_str().unwrap())); + } + + file_bundles.push(FileBundle { + include_dirs, + export_incdirs, + defines, + files: included_files, + }); + } + Ok(file_bundles) +} + impl Backend for VerilogBackend { fn name(&self) -> &'static str { "verilog" @@ -103,35 +245,42 @@ impl Backend for VerilogBackend { Ok(()) } - /// Generate a "fat" library by copy-pasting all of the extern files. - /// A possible alternative in the future is to use SystemVerilog `include` - /// statement. + /// If no special libraries are needed, generate a "fat" library by copy-pasting all of the extern files. fn link_externs( ctx: &ir::Context, file: &mut OutputFile, ) -> CalyxResult<()> { - let fw = &mut file.get_write(); - for extern_path in &ctx.lib.extern_paths() { - // The extern file is guaranteed to exist by the frontend. - let mut ext = File::open(extern_path).unwrap(); - io::copy(&mut ext, fw).map_err(|err| { - let std::io::Error { .. } = err; - Error::write_error(format!( - "File not found: {}", - file.as_path_string() - )) - })?; - // Add a newline after appending a library file - writeln!(fw)?; - } - for (prim, _) in ctx.lib.prim_inlines() { - emit_prim_inline(prim, fw)?; + // If we need special libraries (like HardFloat), run Morty to pickle the files in the `emit` stage. We postpone linking extern special libraries because Morty needs all emitted information to do pickle. We could soley use Morty, but currently it eliminates the body inside `ifndef-endif`. Also discussed in here: https://github.com/pulp-platform/morty/issues/49 + if !check_library_needed(ctx) { + let fw = &mut file.get_write(); + for extern_path in &ctx.lib.extern_paths() { + let mut ext = File::open(extern_path).unwrap(); + io::copy(&mut ext, fw).map_err(|err| { + let std::io::Error { .. } = err; + Error::write_error(format!( + "File not found: {}", + file.as_path_string() + )) + })?; + writeln!(fw)?; + } } + Ok(()) } fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> { - let out = &mut file.get_write(); + // Create a temporary file as an intermediate storage to emit inline primtives and components to. This temporary file will be used as one of the source SystemVerilog file for Morty to do pickle. + let temp_file = tempfile::NamedTempFile::new().map_err(|_| { + Error::write_error("Failed to create a temporary file".to_string()) + })?; + let mut temp_writer = temp_file.as_file(); + + // Write inline primitives + for (prim, _) in ctx.lib.prim_inlines() { + emit_prim_inline(prim, &mut temp_writer)?; + } + let comps = ctx.components.iter().try_for_each(|comp| { // Time the generation of the component. let time = Instant::now(); @@ -140,7 +289,7 @@ impl Backend for VerilogBackend { ctx.bc.synthesis_mode, ctx.bc.enable_verification, ctx.bc.flat_assign, - out, + &mut temp_writer, ); log::info!("Generated `{}` in {:?}", comp.name, time.elapsed()); out @@ -151,7 +300,62 @@ impl Backend for VerilogBackend { "File not found: {}", file.as_path_string() )) - }) + })?; + + if check_library_needed(ctx) { + let handlers: Vec> = Vec::new(); + // Special libraries (like HardFloat) are needed, run Morty to pickle the files + let library_bundle = + build_library_bundle(ctx, &temp_file, &handlers)?; + + let file_list = derive_file_list(ctx, &temp_file, &handlers)?; + let syntax_trees = morty::build_syntax_tree( + &file_list, false, // By default, don't strip comments + false, // By default, don't ignore unparseable + true, // By default, propagate defines from first files to the following files + false, // By default, don't force sequential + ) + .unwrap(); + let top_module = ctx.entrypoint.to_string(); + let _pickled = morty::do_pickle( + None::<&String>, // By default, don't need to prepend a name to all global names + None::<&String>, // By default, don't need to append a name to all global names + HashSet::new(), // By default, don't specify module, interface, package that should not be renamed; instead, let morty to dictate the behavior + HashSet::new(), // By default, don't specify module, interface, package that shouldn't be included in the pickled file list; instead, we let morty to dictate the behavior + library_bundle, + syntax_trees, + Box::new(temp_file.reopen()?) as Box, + Some(&top_module), + true, // By default, keep defines to prevent removal of `define` statements + true, // By default, propagate defines from first files to the following files + false, // By default, keep the time units + ) + .map_err(|err| { + Error::write_error(format!("{}", err.to_string())) + })?; + } + // Rewind to the start of the temporary file so that we can read the content + temp_writer.seek(SeekFrom::Start(0)).map_err(|_| { + Error::write_error( + "Failed to rewind the temporary file".to_string(), + ) + })?; + // Read from the temporary file and write to the user-specified output `file` + let mut temp_content = String::new(); + temp_writer.read_to_string(&mut temp_content).map_err(|_| { + Error::write_error("Failed to read from temporary file".to_string()) + })?; + + let mut final_writer = file.get_write(); + final_writer + .write_all(temp_content.as_bytes()) + .map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("Write failed: {}", err), + ) + })?; + Ok(()) } } From 7ae26b087e82193634ceae8c1ea236c09e02f902 Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 01:31:25 -0500 Subject: [PATCH 02/10] add hardfloat library handler --- calyx-backend/src/verilog.rs | 76 +++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/calyx-backend/src/verilog.rs b/calyx-backend/src/verilog.rs index a46d38f62f..023755a6cf 100644 --- a/calyx-backend/src/verilog.rs +++ b/calyx-backend/src/verilog.rs @@ -9,6 +9,7 @@ use calyx_utils::{CalyxResult, Error, OutputFile}; use ir::Nothing; use itertools::Itertools; use morty::{FileBundle, LibraryBundle}; +use std::env; use std::io; use std::io::{Read, Seek, SeekFrom, Write}; use std::{collections::HashMap, collections::HashSet, path::PathBuf, rc::Rc}; @@ -149,6 +150,78 @@ trait LibraryHandlerTrait { } } +struct HardFloatHandler; +impl LibraryHandlerTrait for HardFloatHandler { + fn add_incs(&self) -> CalyxResult> { + let current_dir = env::current_dir() + .map_err(|e| Error::invalid_file(e.to_string()))?; + + let source_path = + current_dir.join("primitives/float/HardFloat-1/source/"); + let riscv_path = source_path.join("RISCV/"); + + let mut inc_paths = Vec::new(); + + if source_path.exists() + && std::fs::metadata(&source_path) + .map(|m| m.is_dir()) + .unwrap_or(false) + { + inc_paths.push(source_path); + } else { + return Err(Error::invalid_file( + "Invalid path for HardFloat source directory", + )); + } + + if riscv_path.exists() + && std::fs::metadata(&riscv_path) + .map(|m| m.is_dir()) + .unwrap_or(false) + { + inc_paths.push(riscv_path); + } else { + return Err(Error::invalid_file( + "Invalid path for HardFloat RISC-V directory", + )); + } + + Ok(inc_paths) + } + fn add_library_dirs(&self) -> CalyxResult> { + let current_dir = env::current_dir() + .map_err(|e| Error::invalid_file(e.to_string()))?; + + let source_path = + current_dir.join("primitives/float/HardFloat-1/source/"); + + let mut inc_paths = Vec::new(); + + if source_path.exists() + && std::fs::metadata(&source_path) + .map(|m| m.is_dir()) + .unwrap_or(false) + { + inc_paths.push(source_path); + } else { + return Err(Error::invalid_file( + "Invalid path for HardFloat source directory", + )); + } + + Ok(inc_paths) + } + fn add_defs(&self) -> CalyxResult>> { + Ok(HashMap::new()) + } + fn add_stdin_incdirs(&self) -> CalyxResult> { + self.add_incs() + } + fn add_export_incdirs(&self) -> CalyxResult>> { + Ok(HashMap::new()) + } +} + /// Check if any special library is needed fn check_library_needed(ctx: &ir::Context) -> bool { ctx.lib @@ -303,7 +376,8 @@ impl Backend for VerilogBackend { })?; if check_library_needed(ctx) { - let handlers: Vec> = Vec::new(); + let handlers: Vec> = + vec![Box::new(HardFloatHandler)]; // Special libraries (like HardFloat) are needed, run Morty to pickle the files let library_bundle = build_library_bundle(ctx, &temp_file, &handlers)?; From 1d6ff18ff0fea70641279911d309d30e6dee6d68 Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 01:36:31 -0500 Subject: [PATCH 03/10] remove useless use of format --- calyx-backend/src/verilog.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/calyx-backend/src/verilog.rs b/calyx-backend/src/verilog.rs index 023755a6cf..aa25bad1df 100644 --- a/calyx-backend/src/verilog.rs +++ b/calyx-backend/src/verilog.rs @@ -404,9 +404,7 @@ impl Backend for VerilogBackend { true, // By default, propagate defines from first files to the following files false, // By default, keep the time units ) - .map_err(|err| { - Error::write_error(format!("{}", err.to_string())) - })?; + .map_err(|err| Error::write_error(format!("{}", err)))?; } // Rewind to the start of the temporary file so that we can read the content temp_writer.seek(SeekFrom::Start(0)).map_err(|_| { From d2fec1cd3b1f3e36d6e1060cc1beb07f664aea6b Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 01:44:12 -0500 Subject: [PATCH 04/10] using a script to get HardFloat Verilog source code --- primitives/float/get_hardfloat.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 primitives/float/get_hardfloat.sh diff --git a/primitives/float/get_hardfloat.sh b/primitives/float/get_hardfloat.sh new file mode 100755 index 0000000000..39312c5f63 --- /dev/null +++ b/primitives/float/get_hardfloat.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +HARDFLOAT_DIR="." + +HARDFLOAT_URL="http://www.jhauser.us/arithmetic/HardFloat-1.zip" + +ZIP_FILE="HardFloat-1.zip" + +rm -rf "${HARDFLOAT_DIR}/HardFloat-1" + +mkdir -p "${HARDFLOAT_DIR}" + +cd "${HARDFLOAT_DIR}" + +curl -LO "${HARDFLOAT_URL}" + +if [ -f "$ZIP_FILE" ]; then + unzip "$ZIP_FILE" && rm "$ZIP_FILE" + echo "HardFloat library fetched and extracted to ${HARDFLOAT_DIR}" +else + echo "Failed to download HardFloat library from ${HARDFLOAT_URL}" +fi From 0d5ee030ec5a1e5864d4d523888459cd6a185d3c Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 02:16:52 -0500 Subject: [PATCH 05/10] add hardfloat add, m ul, compare, and their corresponding tests --- primitives/float/addFN.futil | 20 ++++ primitives/float/addFN.sv | 97 +++++++++++++++++ primitives/float/compareFN.futil | 21 ++++ primitives/float/compareFN.sv | 100 ++++++++++++++++++ primitives/float/mulFN.futil | 19 ++++ primitives/float/mulFN.sv | 95 +++++++++++++++++ runt.toml | 17 +++ tests/correctness/hardfloat/addFN.expect | 14 +++ tests/correctness/hardfloat/addFN.futil | 40 +++++++ tests/correctness/hardfloat/addFN.futil.data | 26 +++++ tests/correctness/hardfloat/compareFN.expect | 11 ++ tests/correctness/hardfloat/compareFN.futil | 65 ++++++++++++ .../hardfloat/compareFN.futil.data | 18 ++++ tests/correctness/hardfloat/mulFN.expect | 14 +++ tests/correctness/hardfloat/mulFN.futil | 39 +++++++ tests/correctness/hardfloat/mulFN.futil.data | 26 +++++ 16 files changed, 622 insertions(+) create mode 100644 primitives/float/addFN.futil create mode 100644 primitives/float/addFN.sv create mode 100644 primitives/float/compareFN.futil create mode 100644 primitives/float/compareFN.sv create mode 100644 primitives/float/mulFN.futil create mode 100644 primitives/float/mulFN.sv create mode 100644 tests/correctness/hardfloat/addFN.expect create mode 100644 tests/correctness/hardfloat/addFN.futil create mode 100644 tests/correctness/hardfloat/addFN.futil.data create mode 100644 tests/correctness/hardfloat/compareFN.expect create mode 100644 tests/correctness/hardfloat/compareFN.futil create mode 100644 tests/correctness/hardfloat/compareFN.futil.data create mode 100644 tests/correctness/hardfloat/mulFN.expect create mode 100644 tests/correctness/hardfloat/mulFN.futil create mode 100644 tests/correctness/hardfloat/mulFN.futil.data diff --git a/primitives/float/addFN.futil b/primitives/float/addFN.futil new file mode 100644 index 0000000000..fa8cd53372 --- /dev/null +++ b/primitives/float/addFN.futil @@ -0,0 +1,20 @@ +extern "addFN.sv" { + primitive std_addFN[ + expWidth, + sigWidth, + numWidth + ]( + @clk clk: 1, + @reset reset: 1, + @go go: 1, + control: 1, + @write_together(1) subOp: 1, + @write_together(1) left: numWidth, + @write_together(1) right: numWidth, + roundingMode: 3 + ) -> ( + out: numWidth, + exceptionFlags: 5, + @done done: 1 + ); +} diff --git a/primitives/float/addFN.sv b/primitives/float/addFN.sv new file mode 100644 index 0000000000..4db06f925b --- /dev/null +++ b/primitives/float/addFN.sv @@ -0,0 +1,97 @@ +`ifndef __ADDFN_V__ +`define __ADDFN_V__ + +`include "HardFloat_consts.vi" + +module std_addFN #(parameter expWidth = 8, parameter sigWidth = 24, parameter numWidth = 32) ( + input clk, + input reset, + input go, + input [(`floatControlWidth - 1):0] control, + input subOp, + input [(expWidth + sigWidth - 1):0] left, + input [(expWidth + sigWidth - 1):0] right, + input [2:0] roundingMode, + output logic [(expWidth + sigWidth - 1):0] out, + output logic [4:0] exceptionFlags, + output done +); + + // Intermediate signals for recoded formats + wire [(expWidth + sigWidth):0] l_recoded, r_recoded; + + // Convert 'a' and 'b' from standard to recoded format + fNToRecFN #(expWidth, sigWidth) convert_l( + .in(left), + .out(l_recoded) + ); + + fNToRecFN #(expWidth, sigWidth) convert_r( + .in(right), + .out(r_recoded) + ); + + // Intermediate signals after the adder + wire [(expWidth + sigWidth):0] res_recoded; + wire [4:0] except_flag; + + // Compute recoded numbers + addRecFN #(expWidth, sigWidth) adder( + .control(control), + .subOp(subOp), + .a(l_recoded), + .b(r_recoded), + .roundingMode(roundingMode), + .out(res_recoded), + .exceptionFlags(except_flag) + ); + + wire [(expWidth + sigWidth - 1):0] res_std; + + // Convert the result back to standard format + recFNToFN #(expWidth, sigWidth) convert_res( + .in(res_recoded), + .out(res_std) + ); + + logic done_buf[1:0]; + + assign done = done_buf[1]; + + // If the done buffer is completely empty and go is high then execution + // just started. + logic start; + assign start = go; + + // Start sending the done signal. + always_ff @(posedge clk) begin + if (start) + done_buf[0] <= 1; + else + done_buf[0] <= 0; + end + + // Push the done signal through the pipeline. + always_ff @(posedge clk) begin + if (go) begin + done_buf[1] <= done_buf[0]; + end else begin + done_buf[1] <= 0; + end + end + + // Compute the output and save it into out + always_ff @(posedge clk) begin + if (reset) begin + out <= 0; + end else if (go) begin + out <= res_std; + end else begin + out <= out; + end + end + +endmodule + + +`endif /* __ADDFN_V__ */ diff --git a/primitives/float/compareFN.futil b/primitives/float/compareFN.futil new file mode 100644 index 0000000000..c37217e1fa --- /dev/null +++ b/primitives/float/compareFN.futil @@ -0,0 +1,21 @@ +extern "compareFN.sv" { + primitive std_compareFN[ + expWidth, + sigWidth, + numWidth + ]( + @clk clk: 1, + @reset reset: 1, + @go go: 1, + @write_together(1) left: numWidth, + @write_together(1) right: numWidth, + signaling: 1 + ) -> ( + lt: 1, + eq: 1, + gt: 1, + unordered: 1, + exceptionFlags: 5, + @done done: 1 + ); +} diff --git a/primitives/float/compareFN.sv b/primitives/float/compareFN.sv new file mode 100644 index 0000000000..5d8b1ccd2c --- /dev/null +++ b/primitives/float/compareFN.sv @@ -0,0 +1,100 @@ +`ifndef __COMPAREFN_V__ +`define __COMPAREFN_V__ + +module std_compareFN #(parameter expWidth = 8, parameter sigWidth = 24, parameter numWidth = 32) ( + input clk, + input reset, + input go, + input [(expWidth + sigWidth - 1):0] left, + input [(expWidth + sigWidth - 1):0] right, + input signaling, + output logic lt, + output logic eq, + output logic gt, + output logic unordered, + output logic [4:0] exceptionFlags, + output done +); + + // Intermediate signals for recoded formats + wire [(expWidth + sigWidth):0] l_recoded, r_recoded; + + // Convert 'left' and 'right' from standard to recoded format + fNToRecFN #(expWidth, sigWidth) convert_l( + .in(left), + .out(l_recoded) + ); + + fNToRecFN #(expWidth, sigWidth) convert_r( + .in(right), + .out(r_recoded) + ); + + // Intermediate signals for comparison outputs + wire comp_lt, comp_eq, comp_gt, comp_unordered; + wire [4:0] comp_exceptionFlags; + + // Compare recoded numbers + compareRecFN #(expWidth, sigWidth) comparator( + .a(l_recoded), + .b(r_recoded), + .signaling(signaling), + .lt(comp_lt), + .eq(comp_eq), + .gt(comp_gt), + .unordered(comp_unordered), + .exceptionFlags(comp_exceptionFlags) + ); + + logic done_buf[1:0]; + + assign done = done_buf[1]; + + // If the done buffer is empty and go is high, execution just started. + logic start; + assign start = go; + + // Start sending the done signal. + always_ff @(posedge clk) begin + if (start) + done_buf[0] <= 1; + else + done_buf[0] <= 0; + end + + // Push the done signal through the pipeline. + always_ff @(posedge clk) begin + if (go) begin + done_buf[1] <= done_buf[0]; + end else begin + done_buf[1] <= 0; + end + end + + // Capture the comparison results + always_ff @(posedge clk) begin + if (reset) begin + lt <= 0; + eq <= 0; + gt <= 0; + unordered <= 0; + exceptionFlags <= 0; + end else if (go) begin + lt <= comp_lt; + eq <= comp_eq; + gt <= comp_gt; + unordered <= comp_unordered; + exceptionFlags <= comp_exceptionFlags; + end else begin + lt <= lt; + eq <= eq; + gt <= gt; + unordered <= unordered; + exceptionFlags <= exceptionFlags; + end + end + +endmodule + + +`endif /* __COMPAREFN_V__ */ diff --git a/primitives/float/mulFN.futil b/primitives/float/mulFN.futil new file mode 100644 index 0000000000..ec23f81a1e --- /dev/null +++ b/primitives/float/mulFN.futil @@ -0,0 +1,19 @@ +extern "mulFN.sv" { + primitive std_mulFN[ + expWidth, + sigWidth, + numWidth + ]( + @clk clk: 1, + @reset reset: 1, + @go go: 1, + control: 1, + @write_together(1) left: numWidth, + @write_together(1) right: numWidth, + roundingMode: 3 + ) -> ( + out: numWidth, + exceptionFlags: 5, + @done done: 1 + ); +} diff --git a/primitives/float/mulFN.sv b/primitives/float/mulFN.sv new file mode 100644 index 0000000000..1f2ece51bd --- /dev/null +++ b/primitives/float/mulFN.sv @@ -0,0 +1,95 @@ +`ifndef __MULFN_V__ +`define __MULFN_V__ + +`include "HardFloat_consts.vi" + +module std_mulFN #(parameter expWidth = 8, parameter sigWidth = 24, parameter numWidth = 32) ( + input clk, + input reset, + input go, + input [(`floatControlWidth - 1):0] control, + input [(expWidth + sigWidth - 1):0] left, + input [(expWidth + sigWidth - 1):0] right, + input [2:0] roundingMode, + output logic [(expWidth + sigWidth - 1):0] out, + output logic [4:0] exceptionFlags, + output done +); + + // Intermediate signals for recoded formats + wire [(expWidth + sigWidth):0] l_recoded, r_recoded; + + // Convert 'a' and 'b' from standard to recoded format + fNToRecFN #(expWidth, sigWidth) convert_l( + .in(left), + .out(l_recoded) + ); + + fNToRecFN #(expWidth, sigWidth) convert_r( + .in(right), + .out(r_recoded) + ); + + // Intermediate signals after the multiplier + wire [(expWidth + sigWidth):0] res_recoded; + wire [4:0] except_flag; + + // Compute recoded numbers + mulRecFN #(expWidth, sigWidth) multiplier( + .control(control), + .a(l_recoded), + .b(r_recoded), + .roundingMode(roundingMode), + .out(res_recoded), + .exceptionFlags(except_flag) + ); + + wire [(expWidth + sigWidth - 1):0] res_std; + + // Convert the result back to standard format + recFNToFN #(expWidth, sigWidth) convert_res( + .in(res_recoded), + .out(res_std) + ); + + logic done_buf[1:0]; + + assign done = done_buf[1]; + + // If the done buffer is completely empty and go is high then execution + // just started. + logic start; + assign start = go; + + // Start sending the done signal. + always_ff @(posedge clk) begin + if (start) + done_buf[0] <= 1; + else + done_buf[0] <= 0; + end + + // Push the done signal through the pipeline. + always_ff @(posedge clk) begin + if (go) begin + done_buf[1] <= done_buf[0]; + end else begin + done_buf[1] <= 0; + end + end + + // Compute the output and save it into out + always_ff @(posedge clk) begin + if (reset) begin + out <= 0; + end else if (go) begin + out <= res_std; + end else begin + out <= out; + end + end + +endmodule + + +`endif /* __MULFN_V__ */ diff --git a/runt.toml b/runt.toml index 4b439deb60..73c8eea93f 100644 --- a/runt.toml +++ b/runt.toml @@ -557,6 +557,23 @@ fud exec -s calyx.exec './target/debug/calyx' \ grep '%Error' | sed 's/%Error: [^:]*:[^:]*:/%Error:/' """ +## Tests HardFloat library +[[tests]] +name = "correctness HardFloat floating point" +paths = ["tests/correctness/hardfloat/*.futil"] +cmd = """ +cd primitives/float && ./get_hardfloat.sh > /dev/null 2>&1 && cd ../../ && +fud exec --from calyx --to jq \ + --through verilog \ + --through dat \ + -s verilog.data {}.data \ + -s calyx.exec './target/debug/calyx' \ + -s verilog.cycle_limit 500 \ + {} -q +""" +expect_dir = "tests/correctness/hardfloat/" +timeout = 20 + ##### Examples ##### [[tests]] diff --git a/tests/correctness/hardfloat/addFN.expect b/tests/correctness/hardfloat/addFN.expect new file mode 100644 index 0000000000..6067a5d8bd --- /dev/null +++ b/tests/correctness/hardfloat/addFN.expect @@ -0,0 +1,14 @@ +{ + "cycles": 2, + "memories": { + "mem_read_a": [ + -4.2 + ], + "mem_read_b": [ + 1.5 + ], + "mem_write": [ + -2.6999998 + ] + } +} diff --git a/tests/correctness/hardfloat/addFN.futil b/tests/correctness/hardfloat/addFN.futil new file mode 100644 index 0000000000..b54aa7a1f9 --- /dev/null +++ b/tests/correctness/hardfloat/addFN.futil @@ -0,0 +1,40 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/float/addFN.futil"; + +component main(@go go: 1) -> (@done done: 1) { + cells { + @external mem_read_a = comb_mem_d1(32, 1, 1); + @external mem_read_b = comb_mem_d1(32, 1, 1); + @external mem_write = comb_mem_d1(32, 1, 1); + addFN0 = std_addFN(8, 24, 32); + } + + wires { + group add_std_format { + mem_read_a.addr0 = 1'b0; + addFN0.left = mem_read_a.read_data; + + mem_read_b.addr0 = 1'b0; + addFN0.right = mem_read_b.read_data; + + addFN0.go = 1'b1; + addFN0.subOp = 1'b0; + + addFN0.control = 1'b0; + addFN0.roundingMode = 3'b0; + + mem_write.addr0 = 1'b0; + mem_write.write_data = addFN0.out; + mem_write.write_en = 1'b1; + + add_std_format[done] = (mem_write.done & addFN0.done) ? 1'd1; + } + } + + control { + seq { + add_std_format; + } + } +} diff --git a/tests/correctness/hardfloat/addFN.futil.data b/tests/correctness/hardfloat/addFN.futil.data new file mode 100644 index 0000000000..ec508a423f --- /dev/null +++ b/tests/correctness/hardfloat/addFN.futil.data @@ -0,0 +1,26 @@ +{ + "mem_read_a": { + "data": [-4.2], + "format": { + "numeric_type": "ieee754_float", + "is_signed": true, + "width": 32 + } + }, + "mem_read_b": { + "data": [1.5], + "format": { + "numeric_type": "ieee754_float", + "is_signed": true, + "width": 32 + } + }, + "mem_write": { + "data": [0], + "format": { + "numeric_type": "ieee754_float", + "is_signed": true, + "width": 32 + } + } +} diff --git a/tests/correctness/hardfloat/compareFN.expect b/tests/correctness/hardfloat/compareFN.expect new file mode 100644 index 0000000000..5d8f3c5b24 --- /dev/null +++ b/tests/correctness/hardfloat/compareFN.expect @@ -0,0 +1,11 @@ +{ + "cycles": 7, + "memories": { + "mem_write_eq": [ + 0 + ], + "mem_write_unordered": [ + 1 + ] + } +} diff --git a/tests/correctness/hardfloat/compareFN.futil b/tests/correctness/hardfloat/compareFN.futil new file mode 100644 index 0000000000..9f263dd860 --- /dev/null +++ b/tests/correctness/hardfloat/compareFN.futil @@ -0,0 +1,65 @@ +import "primitives/float.futil"; +import "primitives/core.futil"; +import "primitives/compile.futil"; +import "primitives/float/compareFN.futil"; +import "primitives/memories/comb.futil"; +component main(@go go: 1) -> (@done done: 1) { + cells { + cst_0 = std_float_const(0, 32, 2.01); + in0 = std_float_const(0, 32, 1.99); + std_and_1 = std_and(1); + std_and_0 = std_and(1); + unordered_port_0_reg = std_reg(1); + compare_port_0_reg = std_reg(1); + cmpf_0_reg = std_reg(1); + std_compareFN_0 = std_compareFN(8, 24, 32); + @external mem_write_eq = comb_mem_d1(1, 1, 1); + @external mem_write_unordered = comb_mem_d1(1, 1, 1); + std_not_0 = std_not(1); + } + wires { + group bb0_0 { + std_compareFN_0.left = cst_0.out; + std_compareFN_0.right = in0.out; + std_compareFN_0.signaling = 1'd0; + + std_not_0.in = std_compareFN_0.unordered; + + compare_port_0_reg.write_en = std_compareFN_0.done; + compare_port_0_reg.in = std_compareFN_0.eq; + + unordered_port_0_reg.write_en = std_compareFN_0.done; + unordered_port_0_reg.in = std_not_0.out; + + std_and_0.left = compare_port_0_reg.out; + std_and_0.right = unordered_port_0_reg.out; + std_and_1.left = compare_port_0_reg.done; + std_and_1.right = unordered_port_0_reg.done; + + cmpf_0_reg.in = std_and_0.out; + cmpf_0_reg.write_en = std_and_1.out; + + std_compareFN_0.go = !std_compareFN_0.done ? 1'd1; + bb0_0[done] = cmpf_0_reg.done; + } + group ret_assign_0 { + mem_write_eq.addr0 = 1'b0; + mem_write_eq.write_data = cmpf_0_reg.out; + mem_write_eq.write_en = 1'b1; + + mem_write_unordered.addr0 = 1'b0; + mem_write_unordered.write_data = unordered_port_0_reg.out; + mem_write_unordered.write_en = 1'b1; + + ret_assign_0[done] = (mem_write_eq.done) ? 1'd1; + } + } + control { + seq { + seq { + bb0_0; + ret_assign_0; + } + } + } +} diff --git a/tests/correctness/hardfloat/compareFN.futil.data b/tests/correctness/hardfloat/compareFN.futil.data new file mode 100644 index 0000000000..7ee7ef3f82 --- /dev/null +++ b/tests/correctness/hardfloat/compareFN.futil.data @@ -0,0 +1,18 @@ +{ + "mem_write_eq": { + "data": [0], + "format": { + "numeric_type": "bitnum", + "is_signed": true, + "width": 1 + } + }, + "mem_write_unordered": { + "data": [0], + "format": { + "numeric_type": "bitnum", + "is_signed": true, + "width": 1 + } + } +} diff --git a/tests/correctness/hardfloat/mulFN.expect b/tests/correctness/hardfloat/mulFN.expect new file mode 100644 index 0000000000..8b93967501 --- /dev/null +++ b/tests/correctness/hardfloat/mulFN.expect @@ -0,0 +1,14 @@ +{ + "cycles": 2, + "memories": { + "mem_read_a": [ + 2.7 + ], + "mem_read_b": [ + -2.0 + ], + "mem_write": [ + -5.4 + ] + } +} diff --git a/tests/correctness/hardfloat/mulFN.futil b/tests/correctness/hardfloat/mulFN.futil new file mode 100644 index 0000000000..3790db63ce --- /dev/null +++ b/tests/correctness/hardfloat/mulFN.futil @@ -0,0 +1,39 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/float/mulFN.futil"; + +component main(@go go: 1) -> (@done done: 1) { + cells { + @external mem_read_a = comb_mem_d1(32, 1, 1); + @external mem_read_b = comb_mem_d1(32, 1, 1); + @external mem_write = comb_mem_d1(32, 1, 1); + mulFN0 = std_mulFN(8, 24, 32); + } + + wires { + group mul_std_format { + mem_read_a.addr0 = 1'b0; + mulFN0.left = mem_read_a.read_data; + + mem_read_b.addr0 = 1'b0; + mulFN0.right = mem_read_b.read_data; + + mulFN0.go = 1'b1; + + mulFN0.control = 1'b0; + mulFN0.roundingMode = 3'b0; + + mem_write.addr0 = 1'b0; + mem_write.write_data = mulFN0.out; + mem_write.write_en = 1'b1; + + mul_std_format[done] = (mem_write.done & mulFN0.done) ? 1'd1; + } + } + + control { + seq { + mul_std_format; + } + } +} diff --git a/tests/correctness/hardfloat/mulFN.futil.data b/tests/correctness/hardfloat/mulFN.futil.data new file mode 100644 index 0000000000..fb56a82746 --- /dev/null +++ b/tests/correctness/hardfloat/mulFN.futil.data @@ -0,0 +1,26 @@ +{ + "mem_read_a": { + "data": [2.7], + "format": { + "numeric_type": "ieee754_float", + "is_signed": true, + "width": 32 + } + }, + "mem_read_b": { + "data": [-2.0], + "format": { + "numeric_type": "ieee754_float", + "is_signed": true, + "width": 32 + } + }, + "mem_write": { + "data": [0], + "format": { + "numeric_type": "ieee754_float", + "is_signed": true, + "width": 32 + } + } +} From 0775b6ccd0682b4b5db12f1789970d70986adb9d Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 06:55:09 -0500 Subject: [PATCH 06/10] increase test hardfloat timeout; remove hardfloat dir after each test case; change . to PWD --- primitives/float/get_hardfloat.sh | 2 +- runt.toml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/primitives/float/get_hardfloat.sh b/primitives/float/get_hardfloat.sh index 39312c5f63..4742bf8ce2 100755 --- a/primitives/float/get_hardfloat.sh +++ b/primitives/float/get_hardfloat.sh @@ -1,6 +1,6 @@ #!/bin/bash -HARDFLOAT_DIR="." +HARDFLOAT_DIR="$PWD" HARDFLOAT_URL="http://www.jhauser.us/arithmetic/HardFloat-1.zip" diff --git a/runt.toml b/runt.toml index 73c8eea93f..013d4576ea 100644 --- a/runt.toml +++ b/runt.toml @@ -569,10 +569,11 @@ fud exec --from calyx --to jq \ -s verilog.data {}.data \ -s calyx.exec './target/debug/calyx' \ -s verilog.cycle_limit 500 \ - {} -q + {} -q && +rm -rf primitives/float/HardFloat-1/ """ expect_dir = "tests/correctness/hardfloat/" -timeout = 20 +timeout = 30 ##### Examples ##### From f2fa79829cb9b4ba7b8398375084d61b4db2dbfe Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 09:31:41 -0500 Subject: [PATCH 07/10] change runt and get_hardfloat to try to pass the CI --- primitives/float/get_hardfloat.sh | 9 +++------ runt.toml | 7 ++++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/primitives/float/get_hardfloat.sh b/primitives/float/get_hardfloat.sh index 4742bf8ce2..4e2d14dd5b 100755 --- a/primitives/float/get_hardfloat.sh +++ b/primitives/float/get_hardfloat.sh @@ -1,22 +1,19 @@ #!/bin/bash -HARDFLOAT_DIR="$PWD" +HARDFLOAT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" HARDFLOAT_URL="http://www.jhauser.us/arithmetic/HardFloat-1.zip" ZIP_FILE="HardFloat-1.zip" -rm -rf "${HARDFLOAT_DIR}/HardFloat-1" - -mkdir -p "${HARDFLOAT_DIR}" - cd "${HARDFLOAT_DIR}" curl -LO "${HARDFLOAT_URL}" if [ -f "$ZIP_FILE" ]; then - unzip "$ZIP_FILE" && rm "$ZIP_FILE" + unzip -o "$ZIP_FILE" echo "HardFloat library fetched and extracted to ${HARDFLOAT_DIR}" else echo "Failed to download HardFloat library from ${HARDFLOAT_URL}" + exit 1 fi diff --git a/runt.toml b/runt.toml index 013d4576ea..78b5e54627 100644 --- a/runt.toml +++ b/runt.toml @@ -562,15 +562,16 @@ fud exec -s calyx.exec './target/debug/calyx' \ name = "correctness HardFloat floating point" paths = ["tests/correctness/hardfloat/*.futil"] cmd = """ -cd primitives/float && ./get_hardfloat.sh > /dev/null 2>&1 && cd ../../ && +if [ ! -d "primitives/float/HardFloat-1" ]; then + bash primitives/float/get_hardfloat.sh > /dev/null 2>&1 +fi && fud exec --from calyx --to jq \ --through verilog \ --through dat \ -s verilog.data {}.data \ -s calyx.exec './target/debug/calyx' \ -s verilog.cycle_limit 500 \ - {} -q && -rm -rf primitives/float/HardFloat-1/ + {} -q """ expect_dir = "tests/correctness/hardfloat/" timeout = 30 From ac23208ba5d2758c2ce383ded0428eee0c06c5ef Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Sun, 5 Jan 2025 09:49:03 -0500 Subject: [PATCH 08/10] change -2.0 to -2 to pass the CI --- tests/correctness/hardfloat/mulFN.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/correctness/hardfloat/mulFN.expect b/tests/correctness/hardfloat/mulFN.expect index 8b93967501..0eb31c5639 100644 --- a/tests/correctness/hardfloat/mulFN.expect +++ b/tests/correctness/hardfloat/mulFN.expect @@ -5,7 +5,7 @@ 2.7 ], "mem_read_b": [ - -2.0 + -2 ], "mem_write": [ -5.4 From dd7e3fe47ece6cc85a420837878b58afc8fe30a5 Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Mon, 6 Jan 2025 22:32:59 -0500 Subject: [PATCH 09/10] remove unnecessary type after None --- calyx-backend/src/verilog.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calyx-backend/src/verilog.rs b/calyx-backend/src/verilog.rs index aa25bad1df..21832b7bc9 100644 --- a/calyx-backend/src/verilog.rs +++ b/calyx-backend/src/verilog.rs @@ -392,8 +392,8 @@ impl Backend for VerilogBackend { .unwrap(); let top_module = ctx.entrypoint.to_string(); let _pickled = morty::do_pickle( - None::<&String>, // By default, don't need to prepend a name to all global names - None::<&String>, // By default, don't need to append a name to all global names + None, // By default, don't need to prepend a name to all global names + None, // By default, don't need to append a name to all global names HashSet::new(), // By default, don't specify module, interface, package that should not be renamed; instead, let morty to dictate the behavior HashSet::new(), // By default, don't specify module, interface, package that shouldn't be included in the pickled file list; instead, we let morty to dictate the behavior library_bundle, From 8eb4fd7cf4e25e1465cb0d3aa56fd48a9e48af3d Mon Sep 17 00:00:00 2001 From: Jiahan Xie Date: Wed, 8 Jan 2025 10:00:10 +0800 Subject: [PATCH 10/10] add more comments reg. includes and why we need a temporary file --- calyx-backend/src/verilog.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/calyx-backend/src/verilog.rs b/calyx-backend/src/verilog.rs index 21832b7bc9..7598c81694 100644 --- a/calyx-backend/src/verilog.rs +++ b/calyx-backend/src/verilog.rs @@ -156,8 +156,10 @@ impl LibraryHandlerTrait for HardFloatHandler { let current_dir = env::current_dir() .map_err(|e| Error::invalid_file(e.to_string()))?; + // To include `HardFloat_consts.vi` file let source_path = current_dir.join("primitives/float/HardFloat-1/source/"); + // Randomly pick the RISCV directory as the specialization subdirectory to include `HardFloat_specialize.vi` let riscv_path = source_path.join("RISCV/"); let mut inc_paths = Vec::new(); @@ -343,7 +345,7 @@ impl Backend for VerilogBackend { } fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> { - // Create a temporary file as an intermediate storage to emit inline primtives and components to. This temporary file will be used as one of the source SystemVerilog file for Morty to do pickle. + // Create a temporary file as an intermediate storage to emit inline primtives and components to. This temporary file will be used as one of the source SystemVerilog file for Morty to do pickle. It is necessary because the user-specified output `file` might be `stdout`, which cannot be part of the source files for Morty to build the syntax tree. let temp_file = tempfile::NamedTempFile::new().map_err(|_| { Error::write_error("Failed to create a temporary file".to_string()) })?;