Type system
There are two independent type systems within Neo. The meta type system is static and inextensible, and is used for the interpreter runtime. Types such as Nat (natural number) or a function delcaration are meta types. The logic type system applies to the hardware circuit being generated, and includes types such as Bits or a user-defined struct.
Meta Types
"Meta types" refer to the types used by the interpreter runtime. In a software programming language, this would be the only type system that exists. The available meta types are: Nat
, LogicType
, LogicVal
, FunctionReference
, ModuleReference
, ModuleInstance
, and Clock
.
Nat
A Nat
, short for natural number, is an interger greater than 0. This invariant makes Nat
particularly useful for specifying widths of wires.
Logic Type
LogicType
is the meta type for a value that is a logic type. See the logic types section for an explanation of why logic types are treated as values.
Logic Val
A LogicVal
is similar to a logic
or wire
in Verilog. It has no value in the interpreter as it only represents a wire to be generated. Instead of just having a width, LogicVal
s are more strongly typed via an associated LogicType
. While this distills down to a simple width in generated hardware, having strong types during development leads to more concise and error-free code.
Function Reference
Neo treats functions as first-class, so you can pass them to other functions, return them from functions, and so on. A function's type signature include the argument types and the return type.
Module Reference
Module references are an extension of function references. Their special treatment is mostly superficial, intented to ensure instantiations clearly defined and named for readability of both Neo and generated SystemVerilog code.
Module Instance
Upon instantiating a module, the name becomes a variable with a ModuleInstance
value. Unlike Verilog, IO connections are not specified in the instantiation statement. Instead, the returned ModuleInstance
value provides accessors for IO.
Clock
TODO (still under development)
Logic Types
While software programming languages only have one type system, hardware design languages need a second type system that applies to the generated hardware. Logic types can be used to parameterize modules and are parameterizable themselves. This is implemented by treating logic types as values within the interpreter, hence the meta type LogicType
.
A simple example of this types-as-values system is the language-provided Bits
type, which represents a bitstring of some width (think of it as a simple wire in Verilog). While we think of Bits
as a type, it's actually just a function. It takes a width as an argument, and returns a logic type. So, Bits(32)
is an expression that will resolve to an instance of a LogicType
with the specified width. This also applies to user-defined parameterized types. If you were to declare a parameterized struct, that would actually become a function declaration that returns a LogicType
, just like Bits
.
This concept of types as values is a little unintuitive at first, but results in great flexibility without the complexities of a standard generic type system. In other languages, a type with generic parameters is not that different from a function call. It's just designed to execute at compile time and to be restrictive enough that the langauge can reason about it statically for error checking. Since Neo is an interpreted language, there is no seperate compilation phase; types are evaluated at the same time as values. And thanks to the symbolic engine, any function can be reasoned about and validated by the language, ensuring no surprise errors from even the most complex parameterized types. Implementing types as values enables unrestricted parameterization without the overhead of learning a complex type system.