Skip to content

Lab 3 — Combinational Logic: Decoder on the Tang Nano 9K

Goal

Implement a 2-to-4 decoder in SystemVerilog, verify it exhaustively with a Verilator testbench, and deploy it to the Tang Nano 9K so that two buttons control which of four LEDs lights up. By the end you will have a concrete feel for the write–simulate–synthesize–deploy loop on real combinational hardware.

What you need

  • Lab 2 completed (OSS CAD Suite installed, board detected, blink working)
  • The Tang Nano 9K connected via USB

What is a decoder?

A decoder takes N binary inputs and activates exactly one of 2^N outputs. The 2-to-4 case maps a 2-bit select signal to a 4-bit one-hot output:

sel[1:0] y[3:0]
2'b00 4'b0001
2'b01 4'b0010
2'b10 4'b0100
2'b11 4'b1000

Decoders are everywhere in digital design: address decoders in memory maps, instruction decoders in CPUs, and one-hot FSM output encoders. You will use exactly this pattern in Project 2 when your bus needs to route an address to the right peripheral.

Step 1 — Write the module

Activity 1

Create decoder2to4.sv with the following content:

1
2
3
4
5
6
7
8
9
module decoder2to4 (
    input  logic [1:0] sel,
    output logic [3:0] y
);
    always_comb begin
        y = 4'b0000;
        y[sel] = 1'b1;
    end
endmodule

Before simulating, answer: what are the values of y when sel = 2'b10?

Variable part-select

The single-line body y[sel] = 1'b1 is a variable part-select — indexing a vector with another signal rather than a constant. Yosys handles this correctly for small vectors like this one. The always_comb block ensures the tool can verify there are no unintended latches.

Step 2 — Simulate with Verilator

Before touching the board, prove the design is correct in simulation. Simulation is free — it catches bugs in seconds rather than after a slow synthesis run.

Activity 2

Write a C++ testbench tb_decoder.cpp that:

  1. Instantiates Vdecoder2to4
  2. Loops sel through all 4 values (0–3)
  3. After each, asserts that y == (1 << sel) — exactly one bit set, and the correct one
  4. Prints PASS or FAIL for each case

Compile and run:

verilator --cc --exe --build decoder2to4.sv tb_decoder.cpp
./obj_dir/Vdecoder2to4

Expected output:

1
2
3
4
PASS sel=0 y=0001
PASS sel=1 y=0010
PASS sel=2 y=0100
PASS sel=3 y=1000
Hint — assert in C++
#include "Vdecoder2to4.h"
#include "verilated.h"
#include <iostream>

int main(int argc, char** argv) {
    Verilated::commandArgs(argc, argv);
    Vdecoder2to4* dut = new Vdecoder2to4;

    for (int s = 0; s < 4; s++) {
        dut->sel = s;
        dut->eval();
        if (dut->y != (1 << s)) {
            std::cout << "FAIL sel=" << s << " y=" << (int)dut->y << "\n";
        } else {
            std::cout << "PASS sel=" << s << " y=" << (int)dut->y << "\n";
        }
    }

    delete dut;
    return 0;
}

Step 3 — Add a top-level and deploy to the board

The decoder module is generic. To deploy it you need a top-level that connects it to the physical pins of the Tang Nano 9K.

Activity 3

Create top.sv that connects the board's 2 buttons to sel and 4 LEDs to y. Note: LEDs are active-low on the Tang Nano 9K — a 0 turns the LED on and a 1 turns it off — so invert the decoder output before driving the LED pins.

1
2
3
4
5
6
7
8
9
module top (
    input  logic [1:0] btn,
    output logic [5:0] led
);
    logic [3:0] decoded;
    decoder2to4 dec (.sel(btn), .y(decoded));
    assign led[3:0] = ~decoded;  // active-low: invert to light the selected LED
    assign led[5:4] = 2'b11;     // unused LEDs off
endmodule

Synthesize, place-and-route, and program the board:

1
2
3
4
5
yosys -p "read_verilog -sv top.sv decoder2to4.sv; synth_gowin -top top -json top.json"
nextpnr-himbaechel --json top.json --write top_routed.json \
    --device GW1NR-LV9QN88PC6/I5 --vopt family=GW1N-9C --vopt cst=tangnano9k.cst
gowin_pack -d GW1N-9C -o top.fs top_routed.json
openFPGALoader -b tangnano9k top.fs

Verify on the board: pressing each button combination should light exactly one LED. Try all four combinations and confirm the one-hot behavior.

Check your .cst file

The pin constraint file (tangnano9k.cst) must assign btn[0], btn[1], and led[0]led[5] to the correct physical pins for your board revision. If you reuse the constraint file from Lab 2's blink, verify that the button pins are included.

Step 4 — Extend it (challenge)

Activity 4

Change the design to a 3-to-8 decoder: sel[2:0] selecting among y[7:0]. If you only have 2 physical buttons, drive the third select bit from a slow clock divider so it toggles automatically, letting you observe all 8 outputs over time.

Update the testbench to cover all 8 cases (loop from 0 to 7).

On the board, you only have 6 LEDs — tie the top 2 outputs to unused signals or leave them unconnected. Observe that exactly one of the 6 visible LEDs lights at a time for the lower 6 states.

Hint

The module change is minimal: widen the ports to logic [2:0] sel and logic [7:0] y — the body y[sel] = 1'b1 stays identical. The testbench loop upper bound changes from 3 to 7. For the board, you only observe led[5:0] driven by ~y[5:0]; connect y[7:6] to open signals or leave them unwired in the top-level.

Step 5 — Think about it

Activity 5

A decoder is often the first stage of address decoding for memory-mapped peripherals. In Project 2, your bus will use a decoder to route a memory address to the right peripheral register.

Sketch (on paper or in a text file) a 2-to-4 decoder used as an address decoder: inputs are addr[1:0], outputs are chip-select signals cs_uart, cs_timer, cs_gpio, cs_cmac. Which address maps to which peripheral?

There is no single correct answer — this is your first sketch of the Project 2 memory map. Think about which peripheral is accessed most often and whether address ordering matters for the software layer.

Checklist

  • decoder2to4.sv implemented and simulates cleanly with Verilator (all 4 cases pass).
  • Design deployed to the Tang Nano 9K — button presses light exactly one LED.
  • 3-to-8 decoder variant working in simulation.
  • Address-decoder sketch done.

Summary

You implemented a decoder in SystemVerilog, verified it with an exhaustive testbench, and deployed it to real hardware. The y[sel] = 1'b1 idiom (variable part-select) is a compact way to encode one-hot outputs — you will see the same pattern in the FSM output logic in Lab 4, and in the bus address decoder in Project 2.