Reusable RTL recipes¶
These are starting points. Match reset polarity, widths, latency, and interface behavior to the project.
Register with enable¶
process(clk_i)
begin
if rising_edge(clk_i) then
if reset_i = '1' then
data_q <= (others => '0');
elsif enable_i = '1' then
data_q <= data_i;
end if;
end if;
end process;
Up counter¶
process(clk_i)
begin
if rising_edge(clk_i) then
if reset_i = '1' then
count_q <= (others => '0');
elsif enable_i = '1' then
count_q <= count_q + 1;
end if;
end if;
end process;
Unsigned overflow wraps naturally. If saturation is required, compare with the maximum before incrementing.
Modulo-N counter with tick¶
process(clk_i)
begin
if rising_edge(clk_i) then
tick_o <= '0';
if reset_i = '1' then
count_q <= 0;
elsif count_q = G_MODULUS - 1 then
count_q <= 0;
tick_o <= '1';
else
count_q <= count_q + 1;
end if;
end if;
end process;
Require G_MODULUS > 0 with an assertion.
Two-flop synchronizer¶
signal sync_q : std_logic_vector(1 downto 0) := (others => '0');
attribute ASYNC_REG : string;
attribute ASYNC_REG of sync_q : signal is "TRUE";
process(clk_i)
begin
if rising_edge(clk_i) then
sync_q <= sync_q(0) & async_i;
end if;
end process;
sync_o <= sync_q(1);
Check concatenation direction carefully: after the first edge, bit 0 receives the asynchronous input; after the second, bit 1 receives the previous bit 0.
Rising-edge detector¶
process(clk_i)
begin
if rising_edge(clk_i) then
if reset_i = '1' then
previous_q <= '0';
pulse_o <= '0';
else
pulse_o <= level_i and not previous_q;
previous_q <= level_i;
end if;
end if;
end process;
Use only on a signal already synchronous to clk_i.
Debouncer¶
Algorithm:
- Synchronize the input.
- If sampled input equals accepted output, clear the stability counter.
- Otherwise increment the counter.
- When the counter reaches the threshold, accept the new value and clear the counter.
At 100 MHz, 10 ms corresponds to 1,000,000 cycles. Make the duration a generic expressed in cycles or derive it from G_CLOCK_HZ and milliseconds.
Shift register¶
process(clk_i)
begin
if rising_edge(clk_i) then
if enable_i = '1' then
shift_q <= shift_q(shift_q'high - 1 downto 0) & serial_i;
end if;
end if;
end process;
Define which direction and which end is the newest bit.
Priority encoder¶
process(all)
begin
valid_o <= '0';
index_o <= (others => '0');
for index in request_i'reverse_range loop
if request_i(index) = '1' then
valid_o <= '1';
index_o <= to_unsigned(index, index_o'length);
exit;
end if;
end loop;
end process;
Loop direction defines priority. Test multiple simultaneous requests.
Clock-enable divider calculation¶
For input clock F_CLK and desired tick frequency F_TICK:
This simple integer divider is exact only when the frequencies divide evenly. For fractional ratios, use a phase accumulator:
Handshake register¶
Valid/ready transfer occurs on a rising edge when both are high:
Rules:
- Producer keeps
validand data stable until transfer. - Consumer asserts
readywhen it can accept. - Neither side should create a combinational loop through valid/ready.
Seven-segment scan skeleton¶
Use:
- A scan tick generator.
- A two-bit digit index.
- A mux selecting the current nibble.
- A hexadecimal decoder.
- One-hot active-low digit enables.
Blank digits for one system cycle during selection changes if ghosting becomes visible.
UART baud tick¶
For simple 115,200 baud transmission at 100 MHz:
Calculate and document the actual baud. For receiving, generate an oversample tick, commonly 8× or 16×, and sample near the bit center.
Recipe verification rule¶
Every reusable recipe needs tests for:
- Reset.
- Enable/hold.
- Minimum and maximum values.
- Boundary transition.
- Generic extremes.
- Timing/latency contract.