diff --git a/src/main.rs b/src/main.rs index df91ef7..1aa9a6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,144 +1,24 @@ mod literals; +mod parser; +mod rtlil; -use nom::{ - self, - branch::alt, - bytes::complete::tag, - character::complete::{alpha1, alphanumeric1, char, multispace0}, - combinator::{map, opt, recognize}, - error::ParseError, - error::{context, convert_error, VerboseError}, - multi::{many0, separated_list1}, - sequence::{delimited, pair, preceded, terminated, tuple}, -}; - -use crate::literals::{decimal, hexadecimal}; - -const REG_EXAMPLE: &'static str = "reg [0:15] msg = 16'hAAAA;"; +use nom::error::{convert_error, VerboseError}; // custom IResult type for verboseerror pub type IResult> = nom::IResult; -fn ws0<'a, F: 'a, O, E: ParseError<&'a str>>( - inner: F, -) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> -where - F: FnMut(&'a str) -> IResult<&'a str, O, E>, -{ - delimited(multispace0, inner, multispace0) -} -fn identifier(input: &str) -> IResult<&str, &str> { - recognize(pair( - alt((alpha1, tag("_"))), - many0(alt((alphanumeric1, tag("_")))), - ))(input) -} - -fn widthspec(input: &str) -> IResult<&str, u64> { - delimited(char('['), ws0(decimal), char(']'))(input) -} - -fn intliteral(input: &str) -> IResult<&str, (u64, u64)> { - tuple((terminated(decimal, char('\'')), alt((decimal, hexadecimal))))(input) -} - -#[derive(Debug)] -struct NetDecl { - name: String, - width: Option, - value: Option<(u64, u64)>, -} - -#[derive(Debug)] -enum PortDirection { - Input, - Output, -} - -#[derive(Debug)] -struct PortDecl { - direction: PortDirection, - net: NetDecl, -} - -#[derive(Debug)] -struct Module { - name: String, - ports: Vec, -} - -fn declaration(i: &str) -> IResult<&str, NetDecl> { - let (i, (_, width, ident, value)) = tuple(( - ws0(alt((tag("reg"), tag("wire")))), - opt(ws0(widthspec)), - identifier, - opt(preceded(ws0(char('=')), intliteral)), - ))(i)?; - Ok(( - i, - NetDecl { - name: ident.into(), - width, - value, - }, - )) -} - -fn port_decl(i: &str) -> IResult<&str, PortDecl> { - map( - tuple(( - alt(( - map(tag("input"), |_| PortDirection::Input), - map(tag("output"), |_| PortDirection::Output), - )), - declaration, - )), - |(direction, net)| PortDecl { direction, net }, - )(i) -} - -fn ports_list(input: &str) -> IResult<&str, Vec> { - context( - "port declaration", - separated_list1(ws0(char(',')), ws0(port_decl)), - )(input) -} - -fn module(input: &str) -> IResult<&str, Module> { - context( - "module", - map( - tuple(( - tag("module"), - ws0(identifier), - ws0(delimited(char('('), ws0(ports_list), char(')'))), - )), - |(_, name, ports)| Module { - name: name.into(), - ports, - }, - ), - )(input) -} - fn main() { let input = include_str!("../identity.fut"); - let parsed = module(input); + let parsed = parser::module(input); match parsed { Err(nom::Err::Error(err) | nom::Err::Failure(err)) => { print!("{}", convert_error(input, err)) } Err(_) => (), - Ok(res) => println!("{:#?}", res), - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_decl() { - declaration("input reg abcd").unwrap(); + Ok(res) => { + println!("{:#?}", res); + let lowered = crate::rtlil::lower_module(res.1); + println!("{}", lowered); + } } } diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..b735613 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,232 @@ +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{alpha1, alphanumeric1, char, multispace0, multispace1}, + combinator::{map, opt, recognize}, + error::ParseError, + error::{context, convert_error, VerboseError}, + multi::{many0, many1, separated_list0}, + sequence::{delimited, pair, preceded, separated_pair, terminated, tuple}, +}; + +use crate::literals::{decimal, hexadecimal}; +use crate::IResult; + +fn ws0<'a, F: 'a, O, E: ParseError<&'a str>>( + inner: F, +) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +where + F: FnMut(&'a str) -> IResult<&'a str, O, E>, +{ + delimited(multispace0, inner, multispace0) +} +fn identifier(input: &str) -> IResult<&str, &str> { + recognize(pair( + alt((alpha1, tag("_"))), + many0(alt((alphanumeric1, tag("_")))), + ))(input) +} + +fn widthspec(input: &str) -> IResult<&str, u64> { + delimited(char('['), ws0(decimal), char(']'))(input) +} + +fn intliteral(input: &str) -> IResult<&str, (u64, u64)> { + tuple((terminated(decimal, char('\'')), alt((decimal, hexadecimal))))(input) +} + +#[derive(Debug)] +pub struct NetDecl { + pub name: String, + pub width: Option, + pub value: Option<(u64, u64)>, +} + +#[derive(Debug)] +pub enum PortDirection { + Input, + Output, +} + +#[derive(Debug)] +pub struct PortDecl { + pub direction: PortDirection, + pub net: NetDecl, +} + +#[derive(Debug)] +pub struct Module { + pub name: String, + pub ports: Vec, + pub statements: Vec, +} + +#[derive(Debug)] +pub enum Statement { + Assign(Assign), +} + +#[derive(Debug)] +pub struct Assign { + pub lhs: String, + pub expr: Expression, +} + +#[derive(Debug)] +pub enum Operation { + And { a: String, b: Expression }, + Or { a: String, b: Expression }, +} + +#[derive(Debug)] +pub struct Call { + name: String, + params: Vec, +} + +#[derive(Debug)] +pub enum Expression { + Ident(String), + Call(Box), + Operation(Box), +} + +fn declaration(i: &str) -> IResult<&str, NetDecl> { + map( + tuple(( + ws0(alt((tag("reg"), tag("wire")))), + opt(ws0(widthspec)), + identifier, + opt(preceded(ws0(char('=')), intliteral)), + )), + |(_, width, ident, value)| NetDecl { + name: ident.into(), + width, + value, + }, + )(i) +} + +fn port_decl(i: &str) -> IResult<&str, PortDecl> { + map( + tuple(( + alt(( + map(tag("input"), |_| PortDirection::Input), + map(tag("output"), |_| PortDirection::Output), + )), + declaration, + )), + |(direction, net)| PortDecl { direction, net }, + )(i) +} + +fn ports_list(input: &str) -> IResult<&str, Vec> { + separated_list0(ws0(char(',')), ws0(port_decl))(input) +} + +fn operation(input: &str) -> IResult<&str, Operation> { + // temporarily given up on before I learn the shunting yard algorithm + alt(( + map( + separated_pair(ws0(identifier), char('&'), ws0(expression)), + |(a, b)| Operation::And { a: a.into(), b }, + ), + map( + separated_pair(ws0(identifier), char('|'), ws0(expression)), + |(a, b)| Operation::Or { a: a.into(), b }, + ), + ))(input) +} + +fn call_item(input: &str) -> IResult<&str, Call> { + map( + tuple(( + ws0(identifier), + delimited(char('('), ws0(separated_list0(char(','), expression)), char(')')) + )), + |(name, params)| Call { name: name.into(), params } + )(input) +} + +fn expression(input: &str) -> IResult<&str, Expression> { + alt(( + map(ws0(operation), |op| Expression::Operation(Box::new(op))), + map(ws0(call_item), |call| Expression::Call(Box::new(call))), + map(ws0(identifier), |ident| Expression::Ident(ident.into())), + ))(input) +} + +fn assign_statement(input: &str) -> IResult<&str, Assign> { + context( + "assignment", + delimited( + ws0(terminated(tag("assign"), multispace1)), + map( + separated_pair(ws0(identifier), char('='), ws0(expression)), + |(lhs, expr)| Assign { + lhs: lhs.into(), + expr: expr.into(), + }, + ), + ws0(char(';')), + ), + )(input) +} + +pub fn module(input: &str) -> IResult<&str, Module> { + context( + "module", + map( + tuple(( + tag("module"), + ws0(identifier), + ws0(delimited(char('('), ws0(ports_list), char(')'))), + ws0(delimited( + char('{'), + many1(ws0(assign_statement)), + char('}'), + )), + )), + |(_, name, ports, statements)| Module { + name: name.into(), + ports, + statements, + }, + ), + )(input) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_decl() { + declaration("reg abcd").unwrap(); + } + + #[test] + fn test_operation() { + operation(" a | b ").unwrap(); + operation(" a & b ").unwrap(); + } + + #[test] + fn test_expression() { + expression(" a ").unwrap(); + expression(" a | b ").unwrap(); + expression(" a | b | c ").unwrap(); + } + + #[test] + fn test_assignment() { + assign_statement(" assign a = b ; ").unwrap(); + assign_statement(" assign a = b | c ; ").unwrap(); + } + + #[test] + fn test_call() { + call_item("thing ( )").unwrap(); + call_item("thing ( a , b , c )").unwrap(); + } +} diff --git a/src/rtlil.rs b/src/rtlil.rs new file mode 100644 index 0000000..6a46a22 --- /dev/null +++ b/src/rtlil.rs @@ -0,0 +1,126 @@ +use crate::parser; +use std::collections::HashMap; + +#[derive(Debug, Default)] +pub struct ILWriter { + data: String, + indent: usize, +} + +// this would be much nicer if indent gave you a new writer +// which would indent things +impl ILWriter { + fn new() -> Self { + Default::default() + } + + fn write_line(&mut self, line: &str) { + self.data += &"\t".repeat(self.indent); + self.data += line; + self.data += "\n"; + } + + fn indent(&mut self) { + self.indent += 1 + } + + fn dedent(&mut self) { + self.indent = self + .indent + .checked_sub(1) + .expect("tried to dedent negative amount") + } + + fn finish(self) -> String { + self.data + } +} + +// the proper way +pub enum WireOption { + Input(i32), + Output(i32), +} + +pub struct Wire { + id: String, + options: Vec<()>, +} + +struct Module {} + +struct Cell { + id: String, + celltype: String, + parameters: Vec<(String, String)>, + connections: Vec<(String, String)>, +} + +impl Cell { + fn new(id: &str, celltype: &str) -> Self { + Cell { + id: id.into(), + celltype: celltype.into(), + parameters: Vec::new(), + connections: Vec::new(), + } + } + + fn add_param(&mut self, name: &str, value: &str) { + self.parameters.push((name.into(), value.into())) + } + + fn add_connection(&mut self, from: &str, to: &str) { + self.connections.push((from.into(), to.into())) + } + + fn write_rtlil(&self, writer: &mut ILWriter) { + writer.write_line(&format!("cell {} {}", self.celltype, self.id)); + writer.indent(); + for param in &self.parameters { + writer.write_line(&format!("parameter {} {}", param.0, param.1)) + } + for conn in &self.connections { + writer.write_line(&format!("connect {} {}", conn.0, conn.1)) + } + writer.dedent(); + writer.write_line("end"); + } +} + +fn adder_cell(id: &str, a: &str, b: &str, y: &str) -> Cell { + let mut cell = Cell::new(id, "$and"); + cell.add_param("\\A_SIGNED", "0"); + cell.add_param("\\A_WIDTH", "1"); + cell.add_param("\\B_SIGNED", "0"); + cell.add_param("\\B_WIDTH", "1"); + cell.add_param("\\Y_WIDTH", "1"); + cell.add_connection("\\A", a); + cell.add_connection("\\B", b); + cell.add_connection("\\Y", y); + cell +} + +// the hacky way + +pub fn lower_module(module: parser::Module) -> String { + let mut writer = ILWriter::new(); + writer.write_line("autoidx 1"); + writer.write_line(&format!("module \\{}", module.name)); + writer.indent(); + for (num, port) in module.ports.iter().enumerate() { + writer.write_line(&format!( + "wire {} {} \\{}", + "output", + (num + 1), + port.net.name + )) + } + for stmt in module.statements { + // writer.write_line(&format!("connect \\{} \\{}", stmt.lhs, stmt.expr)); + } + adder_cell("\\my_and", "\\a", "\\b", "\\y").write_rtlil(&mut writer); + writer.dedent(); + writer.write_line("end"); + writer.finish() +}