Giter Club home page Giter Club logo

hoodlum's Introduction

hoodlum (alpha)

Hoodlum is a nice-looking hardware description language that compiles to Verilog. It wants to add stronger type guarantees and high-level concepts like enums (and structs/typedefs), but also make FPGA design easier and more fun to get involved with. To get started:

cargo install hoodlum

Add this to a file called blinky.hdl:

entity Main {
    in clk: bit,
    out LED1: bit,
}

impl Main {
    def mut index: uint{..6000000};
    on clk.posedge {
        if index == 6000000 - 1 {
            index <= 0;
            LED1 <= !LED1;
        } else {
            index <= index + 1;
        }
    }
}

And run:

hoodlum blinky.hdl -o output.v

The tutorial and examples target the $20 iCEstick evaluation board and the IceStorm open source compiler toolchain. See "examples/blinky" for the complete source code.

Blinky example

NOTE: I'm learning Verilog and VHDL as I go along. Feel free to suggest ideas and best practices in the issues section!

Examples

  • examples/blinky — Blink an LED on and off with a counter. Example for the iCEstick evaluation board.
  • examples/sequence — Shows an LED display pattern compiled via a sequence generator.
  • examples/ntsc — Generates NTSC video and displays a static image. Requires an external circuit.
  • examples/ethernet — Drives the enc424j600 Ethernet PMOD board to send UDP packets. Requires an enc424j600 PMOD board.

Each example has a Makefile allowing you to run:

make && make flash

If you are getting errors on macOS about the device not being found, try running this first to unload the native FTDI driver:

sudo kextunload -b com.apple.driver.AppleUSBFTDI

Atom Highlighting Plugin

You can highlight .hdl scripts in Atom. Run the following to install the Atom Plugin:

cd language-hoodlum
apm link --local .

Hoodlum Tutorial

A Hoodlum script is composed of entity definitions in entity blocks, and logic definitions are in impl blocks. The topmost entity is the Main block.

entity Main {
    in clk: bit,
    out LED1: bit,
}

These top level entries reference pin definitions in your .pcf file; see "examples/blinky/icestick.pcf" for one for the iCEstick evaluation board.

Each of these entity members declares:

  • A direction: in indicates a value will be read by the entity, and out indicates a value will be written by the entity.
  • A name. This can be referenced by name in the impl block.
  • A type. Here, we declare both values to be one bit wide, i.e. a logic value of either 0 or 1 (or x, in more complicated logic).

Inside of a corresponding impl block, we can reference these values:

impl Main {
    def mut toggle: bit;

    def toggle_next: bit = !toggle;

    on clk.negedge {
        toggle <= toggle_next;
    }

    LED1 = toggle;
}

Let's break this down. First, we define a variable "toggle" which is one bit wide. We declare this as def mut, meaning the variable is mutable inside of a on block.

Next, we define a variable "toggle_next" which is not mutable. This means we have a stateless definition that requires no state (latches or flip-flops). We can declare its value inline with the definition or as a standalone declaration. Its value is always equal to the inverse of the "toggle" variable.

The on block has a sensitivity to either the positive or negative falling edge of a signal value. Inside of an on block we can make assignments to mutable variables using the nonblocking <= or blocking := operators. Here, every time we see a negative clock edge, we reset the "toggle" latch to be the value of "toggle_next", i.e. the inverse of itself.

(Nonblocking definitions are deferred until the end of the on block; this simplifies stateful logic to all be set on the same clock impulse.)

Last, we declare a value for our output variable. LED1 is set to always be equal to the value of the mutable "toggle" value.

At 12Mhz, you won't be able to see the LED toggling at this speed! See "examples/blinky/blinky.hdl" for how to use a counter to make it visible.

Types

Each definition has a type, declared by the format def [mut] name: <type>.

  • bit declares a value that can hold 0, 1, or x.
  • bit[n] declares a port that is n bits wide. You can reference individual bits using a slice operator (e.g. myvariable[1]) and set it at once with a numeric literal (e.g. myvariable = 0b101101, etc.)
  • uint{..n} and int{..n} declares integers (unsigned and signed) whose max value is n (to the nearest power of two). This is shorthand for calculating the maximum bit length for a given integer.

Inside an entity, you can also define sub-entities:

entity Toggle {
    in value: bit,
    out opposite: bit,
}

impl Toggle { ... }

entity Main { ... }

impl Main {
    def clk_prime: bit;
    def toggle = Toggle {
        value: clk,
        opposite: clk_prime,
    }
}

Main declares a sub-entity Toggle with an input value given as "clk" and a output value as "clk_prime". Note that you have to declare output variables alongside the sub-entity to use them.


Contributing

Open up any issues, discussion, ideas, or bugs in the issue tracker.

Language Design Goals

Goals:

  1. Define a hardware description language that's elegant, borrowing syntax from Rust syntax and other modern (familiar) languages.
  2. Emit compatible Verilog-2001 (and VHDL) code.
  3. Create abstractions to simplify generation of state machines, sequential code, and reset states.
  4. Hackable so you can add your own constructs.
  5. Statically detect errors before they reach synthesis stage.
  6. In the future, add simulation capabilities.

Non-goals:

  1. Don't compile Rust into HDL. Rust's stdlib fits an entirely different computing model. The abstraction mismatch makes code that's hard to debug.
  2. Don't support compiling all features of Verilog-2001 or VHDL, just a functional subset.

License

MIT or Apache-2.0.

hoodlum's People

Contributors

tcr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hoodlum's Issues

Help with design low-level HDL language

FPGA world suffers a lot from fragmentation - some tools produce Verilog, some VHDL, some - only subsets of them, creating low-level LLVM-like alternative will help everyone, so HDL implementations will opt only for generating this low-level HDL and routing/synthesizers accept it. LLVM or WebAssembly - you can see how many languages and targets are supported now by both. With more open source tools for FPGA this is more feasible now than ever.

See f4pga/ideas#19

Keywords "def" and "def mut"

This issue is for bikeshedding keywords.

"def" is introduced as an analog for "wire" in Verilog, albeit more strictly typed (no latches should be inferred when using this). The term "def" for definition is used instead of "let" because we don't lexically scope the variable definition; instead it is referencable by the entire entity.

"def mut" is analogous to "reg" in Verilog, and is meant to draw a similarity to Rust's "mut" declaration. This means that a latch will be generated (if necessary) for the variable, and it can only be assigned using nonblocking "<=" or blocking ":=" operators inside of an on <expr> { ... } block.

"def" for defining a static declaration isn't my favorite; "static" would be nice, but is fairly verbose. "mut" I am more comfortable with, though it means something different in Rust and HDL, since the guarantees by either feel similar.

How "reset" should work

Initial reset values are convenient but visibly separate from where the values are actually used. I prototyped a reset { } block that would assign initial values from variable declaration, but this mostly seems to make it difficult to track where variables get assigned / easy to miss these initialization blocks. The alternative, having an explicit reset block, is easy to do with just if { ... } statements.

Undeclared `_FSM` reg breaks `sequence`

With the sequence.hdl example, the _FSM variable in the generated Verilog is never declared, so it defaults to a 1-bit wire. This results in it toggling between two states instead of going through the whole sequence. I added a reg [2:0] _FSM = 0; to the generated Verilog and it behaved as expected.

Entity definitions

Borrowing another idea from VHDL (and Rust), it'd be nice to separate the entity definition from its logic/"architecture".

entity SpiMaster {
  in rst: bit,
  in clk: bit,
  in tx_trigger: bit,
  out tx_ready: bit,
  in tx_byte: bit[8],
  out rx_byte: bit[8],
  out spi_clk: bit,
  out spi_tx: bit,
  in spi_rx: bit,
}

impl SpiMaster {
  ...
}

In addition to separating the entity definition, this could go a step further and allow declarations which implicitly generate our wires:

let spi = SpiMaster {
  rst: rst,
  spi_tx: MOSI,
  spi_rx: MISO,
};

let div_idx: uint{..4} = 0;
on clk.negedge {
    reset rst {
        if div_idx == 3 {
            spi.clk <= !spi.clk;
            div_idx <= 0;
        } else {
            div_idx <= div_idx + 1;
        }
    }
}

The question here is whether we should declare all variables, no variables, just in variables, or something else.

Macros/functions/templates

Codegen is a useful feature in any language, and also an obvious footgun.

  1. Extract macros into a language-level feature that allows templates to intersperse AST, FSM transitions, data, and more into code. This is the most flexible and hardest to prototype.
  2. Allow us to yield to a function, where the function executes and can terminate, but is expanded into FSM generation. This is a more structured approach to the above, but fits a narrow usecase.
  3. Require macros be expanded into their own entities and interfaced with. This probably doesn't fit all usecases either.

How "fsm" should work

The current "sequence" blocks are designed to convert sequential code into a state machine. This makes state machines easy to write, but hard to leverage their full expressive power.

A complementary "fsm" block may make it easy to author states:

fsm my_state {
    IDLE => {
        led <= 0;
        if start_condition {
            my_state <= ACTIVE;
        }
    }
    ACTIVE => {
        led <= 1;
        if some_condition {
            my_state <= IDLE; // maybe "next IDLE;" or "fsm <= IDLE;" for anonymous
        }
    }
}

Some questions:

  1. Can we elide my_state in most cases?
  2. Can we reference my_state outside of the fsm? Can we change (or check) its value from other blocks?
  3. Is an explicit my_state <= {value} allowed inside the fsm?
  4. How do you declare the initial state; is it just the first state?
  5. How does scoping of states work inside of fsm?
  6. Can we nest fsms?

Add fixed width literals.

Currently, you can specify a unfixed width literal with a leading 0:

115 => 115
0d115 => 115
0b1110011 => 115
0x73 => 115

You can specify a bit width by changing "0" to a different leading number, but this information is currently tossed. This should propagate to the final Verilog stage, as so:

115 => 7'd115
7d115 => 7'd115
7b1110011 => 7'b1110011
7x73 => 7'h73

Notably, even bare variables like 115 should be compiled with a bit width as much as possible, given that Verilog's default assumption of 32 bits is probably incorrect for many intents. When a bit width can't be determined or inferred by types, a compilation error should be thrown.

LALRPOP makes compilation stage very slow

Recompiling with AST changes takes upwards of 30s on my machine. While the Hoodlum parser is broken out into a sub-crate so this doesn't impact build time usually, it still makes it hard to address and debug AST issues.

This could be addressed by switching to another parser, or taking steps to reduce the amount of code that is involved in the AST step.

Different FSM types

For example,

fsm(one-hot) { ... }
fsm(index/binary) { ... }
fsm(gray) { ... }
fsm(one-cold) { ... }

This could be extended to sequences naturally.

Named instance port assignment support

Both Verilog and VHDL support named instance port assignment.
I understand that Hoodlum tries to stick to Rust syntax as much as possible,
but HDL modules can have a very large number of ports.
It is simply impossible to maintain such code with positional port assignment only.

What is the testing story?

Testing and verification is the part of Verilog I'm weakest at. What should it look like if it were redesigned and implemented in Hoodlum?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.