Any signal that crosses a clock domain boundary — or comes from outside the chip entirely (button press, UART RX) — can arrive at any phase relative to the destination clock. If it violates the DFF's setup or hold time, the output may never settle to a valid 0 or 1: this is called metastability.
Solution: cascade two DFFs clocked by the destination domain.
module sync2ff #(parameter WIDTH = 1) (
input logic clk,
input logic [WIDTH-1:0] async_in,
output logic [WIDTH-1:0] sync_out
);
logic [WIDTH-1:0] meta;
always_ff @(posedge clk) begin
meta <= async_in; // stage 1: may be metastable
sync_out <= meta; // stage 2: stable with high probability
end
endmodule
Level detection: logic acts every cycle that a signal holds a value.
if (btn_sync == 1) ... // fires every clock while button is held — usually wrong
Edge detection: logic acts exactly once, on the transition. Implemented by comparing the current value to a one-cycle-delayed copy:
logic btn_prev, btn_rising;
always_ff @(posedge clk)
btn_prev <= btn_sync;
assign btn_rising = btn_sync & ~btn_prev; // one-cycle pulse on rising edge
Why it matters for UART RX:
Common bug: using level detection for a button → an action (e.g., counter increment) repeats millions of times per second while the button is held.
always_ff — describing flip-flops in SystemVerilogmodule dff (
input logic clk, rst, d,
output logic q
);
always_ff @(posedge clk) begin
if (rst)
q <= 1'b0; // synchronous reset
else
q <= d;
end
endmodule
Key rules for always_ff:
<= — never = inside always_ff.@(posedge clk) (or @(negedge clk) for falling-edge). Tools reject other patterns.Both styles are common; your project's choice must be consistent throughout:
// Synchronous reset — reset only takes effect on the clock edge
always_ff @(posedge clk) begin
if (rst) q <= '0;
else q <= d;
end
// Asynchronous reset — reset takes effect immediately, regardless of clock
always_ff @(posedge clk or posedge rst) begin
if (rst) q <= '0;
else q <= d;
end
rst pin.'0 is the aggregate zero literal — fills the entire left-hand side with zeros, regardless of width. Equivalent to 4'b0000 for a 4-bit signal but works for any width.always_ff @(posedge clk) begin
b = a; // b gets new a
c = b; // c gets new b — which is already updated!
end
always_ff @(posedge clk) begin
b <= a;
c <= b; // c gets old b (the value b held before this edge)
end
<= (non-blocking) inside always_ff — models registers: all right-hand sides are evaluated first using old values, then all assignments happen simultaneously.= (blocking) inside always_comb — models wires: evaluation proceeds top-to-bottom like sequential software.Mixing the two inside one block is a synthesis error.
An
module register #(parameter WIDTH = 32) (
input logic clk, rst,
input logic [WIDTH-1:0] d,
output logic [WIDTH-1:0] q
);
always_ff @(posedge clk) begin
if (rst) q <= '0;
else q <= d;
end
endmodule
With an enable signal (load only when en is high):
always_ff @(posedge clk) begin
if (rst) q <= '0;
else if (en) q <= d;
// else: q holds its value
end
This pattern — register with synchronous reset and enable — is the template for every register in the RV32I datapath: PC, IR, MDR, ALUOUT, and the 32 general-purpose registers.
A serial-in, serial-out (SISO) shift register delays a 1-bit signal by
module shift_reg #(parameter DEPTH = 8) (
input logic clk, rst,
input logic d,
output logic q
);
logic [DEPTH-1:0] sr;
always_ff @(posedge clk) begin
if (rst) sr <= '0;
else sr <= {sr[DEPTH-2:0], d}; // shift left; new bit enters at LSB
end
assign q = sr[DEPTH-1]; // output: the oldest bit
endmodule
{sr[DEPTH-2:0], d} is concatenation: drop the MSB (which exits as q) and append the new input at the LSB.d, q equals d. This is a A synchronous counter increments its value every clock cycle:
module counter #(parameter WIDTH = 25) (
input logic clk, rst,
output logic [WIDTH-1:0] count
);
always_ff @(posedge clk) begin
if (rst) count <= '0;
else count <= count + 1'b1;
end
endmodule
count[24] toggles at half that rate (once every ~0.62 s), giving a visible blink.Extend the basic counter with load and en control inputs:
rst |
load |
en |
Behaviour |
|---|---|---|---|
| 1 | × | × | Synchronous reset → 0 |
| 0 | 1 | × | Load the value on d |
| 0 | 0 | 1 | Increment by 1 |
| 0 | 0 | 0 | Hold current value |
module counter_le #(parameter WIDTH = 4) (
input logic clk, rst, load, en,
input logic [WIDTH-1:0] d,
output logic [WIDTH-1:0] count
);
always_ff @(posedge clk) begin
// your code here — three branches in priority order
end
endmodule
Trace on paper (start count = 4'h3): apply load=1, d=4'hA; then en=1 for 3 cycles; then en=0 for 1 cycle. Write count after each clock edge.
Expected sequence:
3 → A → B → C → D → D. Any difference? Check the priority of yourif/else ifbranches — the order matters.
Separating state storage from next-state logic keeps code readable and synthesizable:
module up_counter #(parameter WIDTH = 4) (
input logic clk, rst, en,
output logic [WIDTH-1:0] count
);
logic [WIDTH-1:0] count_next;
// Process 1: register (state storage)
always_ff @(posedge clk) begin
if (rst) count <= '0;
else count <= count_next;
end
// Process 2: next-state logic (combinational)
always_comb begin
count_next = count; // default: hold
if (en) count_next = count + 1'b1;
end
endmodule
This two-process pattern is the standard template for FSMs (next class) and datapath components. The always_ff block is always trivial; all the interesting logic lives in always_comb.
The clock period must be long enough for the longest combinational path between two registers:
where
After place-and-route, nextpnr reports the worst negative slack (WNS):
The Tang Nano 9K runs at 27 MHz. A minimal RV32I core easily meets 27 MHz. But if you add a deep combinational path (e.g., a multi-cycle multiplier in the ALU), you may need to pipeline it.
module blink (
input logic clk, // 27 MHz oscillator
input logic rst_n, // active-low reset (Tang Nano button)
output logic [5:0] led // active-low LEDs
);
logic [24:0] count;
always_ff @(posedge clk) begin
if (~rst_n) count <= '0;
else count <= count + 1'b1;
end
// MSB toggles at ~0.8 Hz; each lower bit is twice as fast
assign led[0] = ~count[24];
assign led[1] = ~count[23];
assign led[2] = ~count[22];
assign led[3] = ~count[21];
assign led[4] = ~count[20];
assign led[5] = ~count[19];
endmodule
Synthesize and load. You should see LEDs blinking at different rates — a direct visualization of binary counting.
Consistent names make code readable and catch bugs at review time. The conventions below are used throughout this course and match industry practice.
| Signal | Convention | Example | Notes |
|---|---|---|---|
| Clock | clk |
clk, clk_fast |
One clock per domain; prefix if multiple |
| Reset, active-high | rst |
rst |
Asserted when 1; released on clock edge (sync) |
| Reset, active-low | rst_n |
rst_n |
_n suffix = active-low; 0 means reset |
| Enable | en or *_en |
uart_en, cnt_en |
Allows operation when 1 |
| Write enable | we |
regfile_we |
Enables a write on the next rising edge |
| Chip select | cs_n |
sram_cs_n |
Active-low select; common in memory interfaces |
| Data valid | valid |
rx_valid |
Producer asserts: "this data is good right now" |
| Ready | ready |
tx_ready |
Consumer asserts: "I can accept data right now" |
| Load / latch | load |
ir_load |
Capture input into a register this cycle |
| Done / done flag | done |
mult_done |
One-cycle pulse when an operation completes |
| Next-state copy | *_next |
state_next, pc_next |
Combinational; wired to the <= in always_ff |
_n suffix and are driven 0 to assertbtn[0] pressed → 0; led[0] on → 0)posedge clk = rising-edge clocked (default)negedge clk = falling-edgeclk_n only when referring to the inverted clock line itself, not to falling-edge sensitivityFSMs in SystemVerilog: Moore and Mealy machines, state encoding with typedef enum, and typedef struct packed for grouping control signals — exactly the patterns you will use in Project 1's control FSM.