Writing specifications
Spec examples
Worked prompts for counters, FIFOs, UARTs, state machines and bus interfaces.
Prompts you can copy, adapt and run. Each one is written at the level that reliably produces something reviewable on the first attempt; none of them is long.
Counters and timers
Loadable down counter, WIDTH parameter (default 12).
Ports: clk, rst_n, load, load_value, en, count, zero.
Synchronous active-low reset sets count to zero.
load takes priority over en and is captured on the next clock edge.
zero is high whenever count is 0; it is combinational, not registered.
Counting stops at zero rather than wrapping.Clock enable generator, not a gated clock.
Parameter DIVISOR (default 100). Ports: clk, rst_n, tick.
tick is high for exactly one cycle every DIVISOR cycles of clk.
Reset restarts the count and leaves tick low.
Do not generate a derived clock; this must be a single clock domain.Memories and buffers
Simple dual-port RAM: one write port, one read port, one clock.
Parameters DATA_WIDTH (32) and ADDR_WIDTH (10).
Ports: clk, we, waddr, wdata, raddr, rdata.
Read is synchronous; rdata appears the cycle after raddr.
Reading and writing the same address in one cycle returns the old data.
Infer block RAM; do not add a reset on the memory array.Asynchronous FIFO crossing two clock domains.
Parameters DATA_WIDTH (16), DEPTH (32, power of two).
Write side: wr_clk, wr_rst_n, wr_en, wr_data, full.
Read side: rd_clk, rd_rst_n, rd_en, rd_data, empty.
Use Gray-coded pointers with two-flop synchronisers on each crossing.
full and empty must be conservative; never report space that is not there.
No combinational paths between the two clock domains.Serial and bus interfaces
UART transmitter, 8N1.
Parameters CLK_FREQ_HZ (50_000_000) and BAUD (115200).
Ports: clk, rst_n, tx_start, tx_data (8 bits), tx, busy.
Idle line is high. One start bit low, eight data bits LSB first, one stop bit high.
tx_start is ignored while busy is high.
busy rises the cycle tx_start is accepted and falls after the stop bit completes.SPI master, CPOL=0 CPHA=0, single slave.
Parameters DATA_WIDTH (8), CLK_DIV (4).
Ports: clk, rst_n, start, tx_data, rx_data, done, sclk, mosi, miso, cs_n.
cs_n falls one sclk period before the first edge and rises one after the last.
MSB first. done pulses high for one cycle when rx_data is valid.
start is ignored unless the interface is idle.AXI4-Lite slave with four 32-bit registers at offsets 0x0, 0x4, 0x8, 0xC.
Register 0 is read/write control. Register 1 is read-only status.
Registers 2 and 3 are read/write scratch.
Full AXI4-Lite handshaking on all five channels; no combinational path
from AWVALID to AWREADY.
Writes to read-only registers complete with OKAY and change nothing.
Accesses outside the map return SLVERR.State machines and control
Traffic light controller for one intersection, one clock.
States: NS_GREEN, NS_YELLOW, EW_GREEN, EW_YELLOW.
Green lasts GREEN_TICKS (parameter, default 300), yellow lasts YELLOW_TICKS (60).
Ports: clk, rst_n, ns_lights (3 bits: red, yellow, green), ew_lights (3 bits).
Reset enters NS_GREEN with the counter cleared.
Exactly one lamp per direction is lit at all times.
One-hot state encoding; outputs registered.Round-robin arbiter for four requesters.
Ports: clk, rst_n, req (4 bits), grant (4 bits), grant_valid.
grant is one-hot and only asserted when grant_valid is high.
The winner of one cycle becomes lowest priority for the next.
A requester holding req high must not be able to starve the others.
Grant decisions are registered.Datapath blocks
CRC-32 (IEEE 802.3 polynomial 0x04C11DB7) over a byte stream.
Ports: clk, rst_n, data_valid, data_in (8 bits), crc_out (32 bits), crc_valid.
Initial value 0xFFFFFFFF, final XOR 0xFFFFFFFF, input and output reflected.
One byte per cycle when data_valid is high; hold when it is low.
crc_valid rises with the first valid crc_out and stays high until reset.
Table-free implementation; use the shift-and-xor form.Signed 16x16 multiplier with two pipeline stages.
Ports: clk, rst_n, in_valid, a, b, out_valid, product (32 bits).
Latency is exactly two cycles from in_valid to out_valid.
The pipeline accepts a new input every cycle.
Reset clears the valid pipeline but need not clear the data registers.Prompts for an existing project
In project mode you are describing a change rather than a module. These read differently, and shorter is usually better:
- Add an enable input to the counter in rtl/counter.sv and thread it through the top level.
- The FIFO reports full one entry early. Find out why and fix it.
- Write a self-checking testbench for sync_fifo_ctrl and run it.
- Which module drives the axi_awready signal, and under what conditions?
- Replace the blocking assignments in the always_ff blocks with non-blocking ones.