Skip to content

Chisel backend #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions src/generator/chisel/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
//! Implementations of Chisel traits for common representation.

use std::collections::HashMap;

use crate::generator::chisel::{Analyze, ChiselError, ChiselResult, Declare, Identify};
use crate::generator::common::{Component, Library, Mode, Port, Type};

impl Identify for Mode {
fn identify(&self) -> ChiselResult {
match self {
Mode::In => Ok("Input".to_string()),
Mode::Out => Ok("Output".to_string()),
_ => Err(ChiselError::NotSynthesizable),
}
}
}

impl Declare for Type {
fn declare(&self) -> ChiselResult {
match self {
Type::Bit => Ok("Bool()".to_string()),
Type::BitVec { width } => {
let actual_width = if *width == 0 { 1 } else { *width };
Ok(format!("UInt({}.W)", actual_width))
}
Type::Record(rec) => {
let mut result = format!("class {} extends Bundle {{ \n", rec.identifier);
//Handle nested bundles (new operator)
for field in &rec.fields {
match &field.typ {
Type::Record(_rec) => result.push_str(
format!(" val {} = new {};\n", field.name, field.typ.identify()?)
.as_str(),
),
_ => result.push_str(
format!(" val {} = {};\n", field.name, field.typ.identify()?).as_str(),
),
}
}
result.push_str("}");
Ok(result)
}
Type::Array(arr) => Ok(format!("Vec ({}, {})", arr.size - 1, arr.typ.declare()?)),
}
}
}

impl Identify for Type {
fn identify(&self) -> ChiselResult {
// Records and arrays use type definitions.
// Any other types are used directly.
match self {
Type::Record(rec) => Ok(rec.identifier.clone()),
Type::Array(arr) => Ok(arr.identifier.clone()),
_ => self.declare(),
}
}
}

impl Analyze for Type {
fn list_record_types(&self) -> Vec<Type> {
match self {
// Only record can have nested records.
Type::Record(rec) => {
let mut result: Vec<Type> = vec![];
result.push(self.clone());
for f in rec.fields.iter() {
let children = f.typ.list_record_types();
result.extend(children.into_iter());
}
result
}
_ => vec![],
}
}
}

impl Declare for Port {
fn declare(&self) -> ChiselResult {
Ok(format!(
"val {} = {}({})",
self.identifier,
self.mode.identify()?,
//Handle custom bundle
match &self.typ {
Type::Record(_rec) => "new ".to_string(),
_ => "".to_string(),
} + &self.typ.identify()?
))
}
}

impl Identify for Port {
fn identify(&self) -> ChiselResult {
Ok(self.identifier.to_string())
}
}

impl Analyze for Port {
fn list_record_types(&self) -> Vec<Type> {
self.typ.list_record_types()
}
}

impl Declare for Component {
fn declare(&self) -> ChiselResult {
let mut result = String::new();
result.push_str(format!("class {} extends Module {{\n", self.identifier).as_str());
if !self.ports.is_empty() {
let mut ports = self.ports.iter().peekable();
result.push_str(" val io = IO(new Bundle(\n");
while let Some(p) = ports.next() {
result.push_str(" ");
result.push_str(p.declare()?.to_string().as_str());

if ports.peek().is_some() {
result.push_str(";\n");
} else {
result.push_str("\n");
}
}
result.push_str(" ))\n")
}
result.push_str("\n\n// User code\n\n");
result.push_str("}");
Ok(result)
}
}

impl Analyze for Component {
fn list_record_types(&self) -> Vec<Type> {
let mut result: Vec<Type> = vec![];
for p in &self.ports {
let children = p.list_record_types();
result.extend(children.into_iter());
}
result
}
}

impl Declare for Library {
fn declare(&self) -> ChiselResult {
// Whatever generated the common representation is responsible to not to use the same
// identifiers for different types.
// Use a set to remember which type identifiers we've already used, so we don't declare
// them twice.
let mut type_ids = HashMap::<String, Type>::new();
let mut result = String::new();
result.push_str(format!("package {};\n\nimport chisel3._ \n\n", self.identifier).as_str());
for c in &self.components {
let comp_records = c.list_record_types();
for r in comp_records.iter() {
match type_ids.get(&r.identify()?) {
None => {
type_ids.insert(r.identify()?, r.clone());
result.push_str(format!("{}\n\n", r.declare()?).as_str());
}
Some(already_defined_type) => {
if r != already_defined_type {
return Err(ChiselError::TypeNameConflict);
}
}
}
}
result.push_str(format!("{}\n\n", c.declare()?).as_str());
}
Ok(result)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::generator::common::test::*;

#[test]
fn test_mode_decl() {
let m0 = Mode::In;
let m1 = Mode::Out;
println!("{}", m0.identify().unwrap());
println!("{}", m1.identify().unwrap());
}

#[test]
fn test_type_decl() {
let t0 = Type::Bit;
let t1 = Type::BitVec { width: 8 };
let t2 = test_rec();
let t3 = test_rec_nested();
println!("{}", t0.declare().unwrap());
println!("{}", t1.declare().unwrap());
println!("{}", t2.declare().unwrap());
println!("{}", t3.declare().unwrap());
}

#[test]
fn test_port_decl() {
let p = Port::new("test", Mode::In, Type::BitVec { width: 10 });
println!("{}", p.declare().unwrap());
}

#[test]
fn test_comp_decl() {
let c = test_comp();
println!("{}", c.declare().unwrap());
}

#[test]
fn test_package_decl() {
let p = Library {
identifier: "test".to_string(),
components: vec![test_comp()],
};
println!("{}", p.declare().unwrap());
}
}
89 changes: 85 additions & 4 deletions src/generator/chisel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

use std::{error::Error, path::Path};

use crate::generator::{common::Project, GenerateProject};
use crate::generator::{common::*, GenerateProject};

mod impls;

/// Chisel back-end errors.
#[derive(Debug, Clone, PartialEq)]
pub enum ChiselError {}
pub enum ChiselError {
NotSynthesizable,
TypeNameConflict,
}

impl std::fmt::Display for ChiselError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Expand All @@ -28,6 +33,24 @@ pub struct ChiselConfig {
gen_suffix: Option<String>,
}

/// Generate trait for VHDL declarations.
pub trait Declare {
/// Generate a VHDL declaration from self.
fn declare(&self) -> ChiselResult;
}

/// Generate trait for VHDL identifiers.
pub trait Identify {
/// Generate a VHDL identifier from self.
fn identify(&self) -> ChiselResult;
}

/// Analyze trait for VHDL objects.
pub trait Analyze {
/// List all record types used.
fn list_record_types(&self) -> Vec<Type>;
}

impl Default for ChiselConfig {
fn default() -> Self {
ChiselConfig {
Expand All @@ -36,7 +59,7 @@ impl Default for ChiselConfig {
}
}

/// A configurable VHDL back-end entry point.
/// A configurable Chisel back-end entry point.
#[derive(Default)]
#[allow(dead_code)]
pub struct ChiselBackEnd {
Expand All @@ -47,6 +70,64 @@ pub struct ChiselBackEnd {
#[allow(unused_variables)]
impl GenerateProject for ChiselBackEnd {
fn generate(&self, project: &Project, path: &Path) -> Result<(), Box<dyn Error>> {
unimplemented!();
// Create the project directory.
let mut dir = path.to_path_buf();
dir.push(project.identifier.clone());
std::fs::create_dir_all(dir.as_path())?;

for lib in project.libraries.iter() {
let mut pkg = dir.clone();
pkg.push(lib.identifier.clone());
std::fs::create_dir_all(pkg.as_path())?;
pkg.push(lib.identifier.clone());
//pkg.push(format!("{}_pkg", lib.identifier));
pkg.set_extension(match self.config.gen_suffix.clone() {
None => "scala".to_string(),
Some(suffix) => format!("{}.scala", suffix),
});
std::fs::write(pkg.as_path(), lib.declare()?)?;
}
Ok(())
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::generator::common::test::*;
use std::{error, fs};

#[test]
fn test_type_conflict() {
let t0 = Type::record("a", vec![Field::new("x", Type::Bit)]);
let t1 = Type::record("a", vec![Field::new("y", Type::Bit)]);
let c = Component {
identifier: "test".to_string(),
parameters: vec![],
ports: vec![Port::new("q", Mode::In, t0), Port::new("r", Mode::Out, t1)],
};
let p = Library {
identifier: "lib".to_string(),
components: vec![c],
};
let result = p.declare();
assert_eq!(result.unwrap_err(), ChiselError::TypeNameConflict);
}

#[test]
fn test_backend() -> Result<(), Box<dyn error::Error>> {
let v = ChiselBackEnd::default();

let tmpdir = tempfile::tempdir()?;
let path = tmpdir.path().join("__test");

assert!(v.generate(&test_proj(), &path).is_ok());

// Check if files were correclty generated.
assert!(fs::metadata(&path).is_ok());
assert!(fs::metadata(&path.join("proj")).is_ok());
assert!(fs::metadata(&path.join("proj/lib/lib.gen.scala")).is_ok());

Ok(())
}
}