- name
- yosys
- description
- Write synthesisable Verilog-2005 for an iCE40 FPGA and take it to a bitstream — the language subset that synthesises, the testbench shape the pane draws waves from, the iCEBreaker pinout, how to read the utilisation and Fmax report, the mistakes that cost you a day (inferred latches, multi-driven nets, blocking assignments in sequential logic, missing reset), and ready-made UART / PWM / debounce blocks. Use whenever writing, simulating, synthesising or debugging RTL in this workspace.
# Verilog to a bitstream, with the open-source flow
Six tools, one command, and a strict subset of one language. Everything below is what actually
passes through Icarus Verilog, Yosys, nextpnr and icepack — not what the standard permits.
## The flow
```sh
"$YOSYS_FLOW" blink # the whole thing, for top module `blink`
```
| step | tool | in | out |
|---|---|---|---|
| sim | `iverilog -g2012` + `vvp` | `rtl/*.v` + `tb/blink_tb.v` | `out/sim.vcd`, PASS/FAIL on stdout |
| waves | `vcd2json.py` | `out/sim.vcd` | `out/waves.json` — a signal summary for the verdict (the pane reads the VCD itself) |
| synth | `yosys synth_ice40` | `rtl/*.v` | `out/blink.json` — the iCE40 netlist, and cell counts |
| schematic | `yosys prep` | `rtl/*.v` | `out/blink_schematic.json` — technology-independent |
| svg | `netlistsvg` | that JSON | `out/blink.svg` — the top module drawn (the pane draws every module of the hierarchy) |
| pnr | `nextpnr-ice40 --up5k --package sg48` | netlist + `constraints/blink.pcf` | `out/blink.asc`, `out/blink_pnr.json` (utilisation, Fmax, critical paths), `out/blink_routed.json` (placement and routing — the pane's floorplan) |
| pack | `icepack` | `.asc` | **`out/blink.bin`** — the bitstream |
Each step's full output is at `out/logs/<step>.log`, with `<step>.start`, `<step>.time` and
`<step>.exit` beside it and `out/logs/run.json` for the run as a whole — the pane reads those to show
which step is running. The flow does not stop the world on a failure: synthesis still runs when
simulation fails, so you see every problem at once.
Single steps, when you are iterating on one thing:
```sh
iverilog -g2012 -o out/sim.vvp rtl/*.v tb/blink_tb.v && vvp out/sim.vvp # just the simulation
yosys -p "read_verilog rtl/*.v; synth_ice40 -top blink; stat" # just the cell count
```
Then always finish with the full `"$YOSYS_FLOW" <top>` so the pane and the verdict are current.
## The synthesisable subset
Yosys turns a description of hardware into gates. Anything that does not describe hardware is
either rejected or, worse, quietly turned into something you did not mean.
**Always**
```verilog
`default_nettype none // first line of every file: an undeclared name is now an error,
// not a silent 1-bit wire. This catches every typo'd port.
module counter #(
parameter integer WIDTH = 8 // parameters, not `define — they are per-instance
) (
input wire clk,
input wire rst,
input wire en,
output reg [WIDTH-1:0] count // a port assigned in an always block is `reg`
);
always @(posedge clk) begin // ONE clock, ONE edge, no other signal in the list
if (rst) count <= {WIDTH{1'b0}};
else if (en) count <= count + 1'b1;
end
endmodule
`default_nettype wire // last line: put it back, so other files are not surprised
```
Rules that are not negotiable:
- **`<=` in `always @(posedge clk)`, `=` in `always @(*)`.** Non-blocking for flip-flops, blocking
for combinational logic. Mixing them is the single most common source of "it simulates but does
not synthesise the same".
- **A signal is driven from exactly one `always` block** (or one `assign`), never two. Two drivers
is an error in synthesis and an `x` in simulation.
- **Every `always @(*)` assigns every output on every path.** An `if` without an `else`, or a
`case` without a `default`, infers a *latch* — see the pitfalls below.
- **Sized literals everywhere**: `8'd0`, `4'b1010`, `16'hBEEF`, `{WIDTH{1'b0}}`. A bare `0` is
32 bits and will silently widen an expression.
- **`$clog2(N)`** for a counter's width. It is Verilog-2005 and Yosys supports it.
- No `initial` blocks for logic (initial *values* on a `reg` are fine and do synthesise on iCE40 —
the bitstream sets the flip-flops). No `#delays`. No `fork`/`join`. No `while`/`forever`. No
`real`. `for` loops only with constant bounds — they unroll into copies of hardware.
- Multiplication by a constant power of two is a shift and is free; `*` by a variable costs a lot
of LUTs (the UP5K has 8 DSP blocks, `synth_ice40 -dsp` maps to them). Division is not free and
usually means you want a different algorithm.
**State machines** — two blocks, always:
```verilog
localparam [1:0] IDLE = 2'd0, RUN = 2'd1, DONE = 2'd2;
reg [1:0] state, next;
always @(posedge clk) // the register
if (rst) state <= IDLE; else state <= next;
always @(*) begin // the transition, fully assigned
next = state; // <-- the default that prevents a latch
case (state)
IDLE: if (start) next = RUN;
RUN: if (done) next = DONE;
DONE: next = IDLE;
default: next = IDLE;
endcase
end
```
## The testbench
One per top module, at `tb/<top>_tb.v`. The shape matters: the pane draws its waves from the VCD,
and the verdict reads the word `FAIL`.
```verilog
`timescale 1ns / 1ps
`default_nettype none
module counter_tb;
reg clk = 1'b0, rst = 1'b1, en = 1'b0;
wire [7:0] count;
integer errors = 0;
counter #(.WIDTH(8)) dut (.clk(clk), .rst(rst), .en(en), .count(count));
always #5 clk = ~clk; // a 100 MHz clock: 10 ns period
task check(input condition, input [8*40-1:0] what); // NOT `expect` — reserved in -g2012
begin
if (condition) $display(" ok %0s", what);
else begin errors = errors + 1; $display(" FAIL %0s (at %0t)", what, $time); end
end
endtask
initial begin
$dumpfile("out/sim.vcd"); // exactly this path — the flow reads it
$dumpvars(0, counter_tb); // 0 = this scope and everything under it
repeat (2) @(posedge clk); rst = 1'b0; en = 1'b1;
repeat (5) @(posedge clk); #1;
check(count == 8'd5, "counts five clocks");
if (errors == 0) $display("PASS counter: %0d checks", 3);
else $display("FAIL counter: %0d check(s) failed", errors);
$finish; // ALWAYS: without it vvp runs forever
end
endmodule
```
- **`#1` after `@(posedge clk)` before checking.** At the edge itself the non-blocking update has
not landed yet; one time unit later it has.
- **Simulate in shrunken time.** A 1 Hz blink off a 12 MHz clock is 12 million cycles. Parameterise
the design (`CLK_HZ`) and override it in the testbench (`.CLK_HZ(16)`), so the same RTL runs in a
hundred clocks. Never change the RTL to make the test fast.
- `$dumpvars(0, tb)` dumps everything including the DUT's internals — that is what you want: the
pane's Waves tab browses every scope, shows parameters with their values, and opens on the DUT's
ports and registers. A line named `tx`/`rx` is decoded as UART (baud measured off the line); an
8-bit bus named `data`/`byte`/`char` starts in ASCII. Keep a dump under a few tens of millions of
changes — `$dumpoff` around a long quiet stretch, as `hello_uart`-style testbenches do.
- The word `FAIL` anywhere in the output fails the verdict. Do not print it in passing messages.
## The board: iCEBreaker, iCE40UP5K-SG48
5280 logic cells, 30 × 4 kbit block RAMs, 4 × 16 kB single-port RAMs, 8 DSP blocks, 1 PLL.
Pin numbers are the board's, from
[the iCEBreaker project's own constraints file](https://codeberg.org/icebreaker-fpga/icebreaker-verilog-examples/src/branch/main/icebreaker/icebreaker.pcf).
```
set_io -nowarn clk 35 # 12 MHz oscillator
set_frequency clk 12 # nextpnr's extension: this is what Fmax is measured against
set_io -nowarn btn_n 10 # on-board button — ACTIVE LOW (0 = pressed)
set_io -nowarn ledr_n 11 # red LED — ACTIVE LOW (0 = lit)
set_io -nowarn ledg_n 37 # green LED — ACTIVE LOW (0 = lit)
set_io -nowarn rx 6 # UART from the on-board FTDI (FPGA's point of view)
set_io -nowarn tx 9 # UART to the FTDI
```
The full board — RGB LED (39/40/41), SPI flash, PMOD 1A/1B/2, and the snap-off section's five
**active-high** LEDs and three buttons — is commented out in `constraints/blink.pcf`; uncomment
what you use. `-nowarn` lets one PCF carry pins the current design does not have.
Every port of the top module needs a `set_io` line, or nextpnr fails with "unconstrained IO".
Nothing else in the design does; internal signals are routed automatically.
Another board: change `constraints/<top>.pcf` and set `YOSYS_DEVICE` / `YOSYS_PACKAGE`
(e.g. `YOSYS_DEVICE=--hx8k YOSYS_PACKAGE=ct256` for the HX8K breakout).
## Reading the report
`out/<top>.report.json` is what the pane draws; read it when you want the numbers in words.
- **`synthesis.byType`** — what Yosys mapped the design to. `SB_LUT4` is a 4-input lookup table,
`SB_DFFSR`/`SB_DFFE` are flip-flops, `SB_CARRY` is the fast carry chain an adder uses,
`SB_RAM40_4K` is block RAM. Roughly: a logic cell is one LUT4 + one flip-flop, so
`ICESTORM_LC` ≈ max(LUTs, FFs) after packing, not their sum.
- **`pnr.utilization`** — used / available per resource, with a percentage. Under 70 % is
comfortable; over 90 % and nextpnr starts to struggle to route.
- **`pnr.clocks[].achievedMHz` vs `constraintMHz`** — the design closes at the first, the PCF's
`set_frequency` asks for the second. `pass: false` means the critical path is too long: the fix
is to break it with a pipeline register, not to lower the clock, unless lowering it is honest.
The path itself, hop by hop with the RTL line of each net, is `critical_paths` in
`out/<top>_pnr.json` — and drawn on the floorplan in the pane's Chip tab.
- **`bitstream.path`** — `out/<top>.bin`, and `iceprog out/<top>.bin` flashes a board over USB
(install `icestorm`'s `iceprog`; the user needs the board plugged in).
## Pitfalls that cost a day
**Inferred latch.** A combinational block that does not assign an output on every path becomes a
level-sensitive latch — which on an FPGA is built out of a LUT feeding itself, is not timed, and
glitches. Yosys says `Warning: ... latch` and the verdict raises it.
```verilog
always @(*) if (sel) y = a; // BAD: what is y when sel is 0? A latch.
always @(*) begin y = 1'b0; if (sel) y = a; end // GOOD: a default first.
```
**Multi-driven net.** Two `always` blocks (or an `always` and an `assign`) writing the same signal.
Simulation shows `x`, synthesis errors with "conflicting drivers". One signal, one driver.
**Blocking assignment in sequential logic.** `always @(posedge clk) begin a = b; c = a; end` makes
*one* flip-flop and a wire; with `<=` it makes two flip-flops in a shift register. Simulation and
synthesis can disagree about which you meant. Use `<=`.
**An asynchronous input sampled directly.** A button or an incoming UART line is not synchronous to
your clock; sampling it straight into logic causes metastability. Two flip-flops first, always:
```verilog
reg [1:0] sync;
always @(posedge clk) sync <= {sync[0], btn_n};
wire btn_safe = sync[1];
```
**Reset that is not thought about.** On iCE40 a `reg x = 1'b0;` initial value *is* honoured — the
bitstream loads it — so a global reset is often unnecessary. If you do use one, use it
synchronously (`if (rst)` inside `@(posedge clk)`) and on every register in the block.
**A counter one bit too narrow.** `reg [7:0] c; if (c == 300)` never fires. Size from the constant:
`reg [$clog2(LIMIT)-1:0]`.
**Width mismatch.** `wire [7:0] a = b + c;` where `b`,`c` are 8-bit silently drops the carry. Widen
first: `{1'b0, b} + {1'b0, c}`.
**`$finish` missing.** `vvp` runs forever and the flow hangs. Every testbench ends with `$finish`.
## Blocks you will need
**Clock divider / strobe** — one cycle high every N clocks, which is how you make anything slow:
```verilog
localparam integer DIV = CLK_HZ / RATE_HZ;
reg [$clog2(DIV)-1:0] div = 0;
reg tick = 1'b0;
always @(posedge clk) begin
tick <= 1'b0;
if (div == DIV - 1) begin div <= 0; tick <= 1'b1; end
else div <= div + 1'b1;
end
```
**PWM** — `duty` out of 2^BITS, no multiplier, one adder:
```verilog
module pwm #(parameter integer BITS = 8) (
input wire clk, input wire [BITS-1:0] duty, output wire out
);
reg [BITS-1:0] acc = 0;
always @(posedge clk) acc <= acc + 1'b1;
assign out = (acc < duty);
endmodule
```
(For an LED, gamma matters: perceived brightness goes as roughly the square of `duty`.)
**Button debounce** — hold the input steady for a few milliseconds before believing it:
```verilog
module debounce #(parameter integer COUNT = 12_000) ( // 1 ms at 12 MHz
input wire clk, input wire in, output reg out = 1'b0
);
reg [1:0] sync = 2'b00;
reg [$clog2(COUNT)-1:0] n = 0;
always @(posedge clk) begin
sync <= {sync[0], in};
if (sync[1] == out) n <= 0;
else if (n == COUNT - 1) begin out <= sync[1]; n <= 0; end
else n <= n + 1'b1;
end
endmodule
```
**UART transmitter** — 8N1, at `CLK_HZ / BAUD` clocks per bit (12 MHz / 115200 = 104):
```verilog
module uart_tx #(parameter integer CLK_HZ = 12_000_000, parameter integer BAUD = 115_200) (
input wire clk,
input wire send, // pulse high for one clock
input wire [7:0] data,
output reg tx = 1'b1, // idles high
output wire busy
);
localparam integer DIV = CLK_HZ / BAUD;
reg [$clog2(DIV)-1:0] cnt = 0;
reg [3:0] bit_i = 4'd0; // 0 = idle, 1 = start, 2..9 = data, 10 = stop
reg [7:0] shift = 8'd0;
assign busy = (bit_i != 4'd0);
always @(posedge clk) begin
if (!busy) begin
GitHub에서 보기