diff --git a/src/check.rs b/src/check.rs
index 0608df1b611d79c9acd6f8945559519845721d82..6539e3e0c0712f4c0a37e192501a8f7df1bdad71 100644
--- a/src/check.rs
+++ b/src/check.rs
@@ -5,7 +5,7 @@ use std::collections::HashMap;
 use syn::{Ident, Path};
 
 use error::*;
-use {util, Resources, Statics, Crcs};
+use {util, Resources, Statics, Crcs, Ips, Ops};
 
 /// `$($Ident: { .. },)*`
 pub type Tasks = HashMap<Ident, Task>;
@@ -194,9 +194,41 @@ fn tasks(tasks: Option<::Tasks>) -> Result<Tasks> {
 pub struct Crc {
     /// `crcs: Crcs`
     pub crcs: Option<Crcs>,
+    /// `crcs: Crcs`
+    pub ips: ::Ips,
+    /// `crcs: Crcs`
+    pub ops: ::Ops,
 }
 
 /// Checks the syntax of the parsed `app!` macro
 pub fn crc(crc: ::Crc) -> Result<Crc> {
-    Ok(Crc { crcs: crc.crcs })
+    Ok(Crc {
+        crcs: crc.crcs,
+        ips: ::check::ips(crc.ips).chain_err(|| "checking `ips`")?,
+        ops: ::check::ops(crc.ops).chain_err(|| "checking `ops`")?,
+    })
+
+}
+
+///
+pub fn ips(ips: Option<::Ips>) -> Result<::Ips> {
+    Ok(if let Some(ips) = ips {
+        ensure!(
+            !ips.is_empty(),
+            "empty `ips` field. Each Crc must have at least one input port"
+        );
+        ips
+    } else {
+        bail!("missing input ports ")
+    })
+}
+
+///
+pub fn ops(ops: Option<::Ops>) -> Result<::Ops> {
+    Ok(if let Some(ops) = ops {
+        ops
+    } else {
+        bail!("missing input ports ")
+        //Ops::new()
+    })
 }
diff --git a/src/lib.rs b/src/lib.rs
index 5de85110b2030c301d06eb0fd410c26dd55c40b7..495c6b3135b28405c37fe10e30c7235544507997 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -117,13 +117,18 @@ pub enum IpTo {
 /// `$($Ident: $Ty > $Path,)*`
 pub type Ips = HashMap<Ident, (Ty, Path)>;
 
+/// `$($Ident: $Ty < $Path,)*`
+pub type Ops = HashMap<Ident, (Ty, Path)>;
+
 /// `crc! { .. }`
 #[derive(Debug)]
 pub struct Crc {
     /// Inner Crcs (can be None)
     pub crcs: Option<Crcs>,
-    /// Input ports (should not be None)
+    /// Input ports (should not be None, given we have interrupts at top level)
     pub ips: Option<Ips>,
+    /// Output ports (should not be None, given we treat peripherals as Crcs)
+    pub ops: Option<Ops>,
 }
 
 impl Crc {
diff --git a/src/parse.rs b/src/parse.rs
index 6e88ea2c0549dc229b43981fce0c33e5474362b5..1dab965c6b6e9688cdddc88a69789dc341734b21 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -7,7 +7,8 @@ use syn::{self, DelimToken, Ident, IntTy, Lit, Path, Token, BinOpToken,
 
 use error::*;
 
-use {Crc, Crcs, Ips, App, Idle, Init, Resources, Static, Statics, Task, Tasks};
+use {Crc, Crcs, Ips, Ops, App, Idle, Init, Resources, Static, Statics, Task,
+     Tasks};
 
 /// Parses the contents of `app! { $App }`
 pub fn app(input: &str) -> Result<App> {
@@ -445,6 +446,7 @@ pub fn crc(input: &str) -> Result<Crc> {
 
     let mut crcs = None;
     let mut ips = None;
+    let mut ops = None;
 
     fields(&tts, |key, tts| {
         match key.as_ref() {
@@ -456,7 +458,13 @@ pub fn crc(input: &str) -> Result<Crc> {
             "ips" => {
                 ensure!(ips.is_none(), "duplicated `ips` field");
 
-                ips = Some(::parse::ip(tts).chain_err(|| "parsing `ips`")?);
+                ips = Some(::parse::ips(tts).chain_err(|| "parsing `ips`")?);
+            }
+
+            "ops" => {
+                ensure!(ops.is_none(), "duplicated `ops` field");
+
+                ops = Some(::parse::ops(tts).chain_err(|| "parsing `ops`")?);
             }
 
             _ => bail!("unknown field: `{}`", key),
@@ -466,7 +474,7 @@ pub fn crc(input: &str) -> Result<Crc> {
         Ok(())
     })?;
 
-    Ok(Crc { crcs, ips })
+    Ok(Crc { crcs, ips, ops })
 }
 
 
@@ -505,7 +513,7 @@ fn crcs(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Crcs> {
 }
 
 /// Parses `$($Ident: $Ty > $Path,)*`
-fn ip(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ips> {
+fn ips(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ips> {
     ::parse::delimited(tts, DelimToken::Brace, |tts| {
         let mut ips = HashMap::new();
         let mut tts = tts.iter().peekable();
@@ -528,9 +536,9 @@ fn ip(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ips> {
                 bail!("expected Colon, found {:?}", tt);
             }
 
-            let ty = ::parse::ty(&mut tts).chain_err(|| {
-                format!("parsing `type` for {:?}", ident)
-            })?;
+            let ty =
+                ::parse::ty(&TokenTree::Token(Token::Gt), &mut tts)
+                    .chain_err(|| format!("parsing `type` for {:?}", ident))?;
 
             let path = ::parse::path(&mut tts).chain_err(|| "parsing `path`")?;
             tts.next();
@@ -542,19 +550,56 @@ fn ip(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ips> {
     })
 }
 
+/// Parses `$($Ident: $Ty < $Path,)*`
+fn ops(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ops> {
+    ::parse::delimited(tts, DelimToken::Brace, |tts| {
+        let mut ops = HashMap::new();
+        let mut tts = tts.iter().peekable();
+        while let Some(tt) = tts.next() {
+            let ident = if let &TokenTree::Token(Token::Ident(ref id)) = tt {
+                id
+            } else {
+                bail!("expected Ident, found {:?}", tt);
+            };
+
+            ensure!(
+                !ops.contains_key(ident),
+                "op {} listed more than once",
+                ident
+            );
+
+            let tt = tts.next();
+            if let Some(&TokenTree::Token(Token::Colon)) = tt {
+            } else {
+                bail!("expected Colon, found {:?}", tt);
+            }
+
+            let ty =
+                ::parse::ty(&TokenTree::Token(Token::Lt), &mut tts)
+                    .chain_err(|| format!("parsing `type` for {:?}", ident))?;
+
+            let path = ::parse::path(&mut tts).chain_err(|| "parsing `path`")?;
+            tts.next();
+
+            ops.insert(ident.clone(), (ty, path));
+        }
+        Ok(ops)
+    })
+}
+
+
 /// Parses `$Ty `
-fn ty(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ty> {
+fn ty(token: &TokenTree, tts: &mut Peekable<Iter<TokenTree>>) -> Result<Ty> {
     let mut fragments = vec![];
     loop {
         if let Some(tt) = tts.next() {
-            // if tt == &TokenTree::Token(Token::BinOp(BinOpToken::Minus)) {
-            if tt == &TokenTree::Token(Token::Gt) {
+            if tt == token {
                 break;
             } else {
                 fragments.push(tt);
             }
         } else {
-            bail!("expected `>`, found end of macro");
+            bail!("expected `{:?}`, found end of macro", token);
         }
     }