Design Synthesis Using (System) Verilog: LE LE LE LE LE LE M E M
Design Synthesis Using (System) Verilog: LE LE LE LE LE LE M E M
LE LE
x x
LE LE
M E M
LE LE
LE LE
x x
LE LE
M E M
LE LE
The order of continuous signal assignments (assign statements), always blocks or module instantiations DOES NOT matter! The order of procedural signal assignments in any always block DOES matter!
module and_xor_1_bit (input logic x, y, output logic f, g); assign f = x & y; always_comb begin g = x | y; // this statement will be dismissed! g = x ^ y; end endmodule
or
module mux_2_to_1 (input logic sel, input logic[7:0] x, y, output logic[7:0] f); always_comb begin f = y; if (sel) f = x; end endmodule
Short circuit between two signal paths: impossible to implement unless the targeted fabric provides some form of resolution for it (not in our case)!
sel2 x y
0 1
f sel1
0 1
problem
Multiple Drivers Problem The designer MUST decide which signal path takes priority!
module multiple_drivers_fixed (input logic sel1, sel2, input logic[7:0] x, y, output logic[7:0] f); logic[7:0] g; // internal signal within the module always_comb begin if (sel1) f = x; else f = g; end always_comb begin if (sel2) g = y; else g = x; end endmodule
or
module multiple_drivers_fixed (input logic sel1, sel2, input logic[7:0] x, y, output logic[7:0] f); assign f = sel1 ? x : (sel2 ? y : x); endmodule
Priority Encoders
module priority_encoder (input logic c1, c2, c3, input logic[7:0] w, x, y, z, output logic[7:0] f); always_comb begin if (c1) f = w; else if (c2) f = x; else if (c3) f = y; else f = z; end endmodule
Equivalent code
module priority_encoder (input logic c1, c2, c3, input logic[7:0] w, x, y, z, output logic[7:0] f); always_comb begin f = z; if (c3) f = y; if (c2) f = x; if (c1) f = w; end endmodule
Sequential Logic Fundamental sequential element edge-triggered Data (or D) flip-flop with asynchronous reset (active low)
module d_flip_flop (input logic clock, resetn, input logic d, output logic q); always_ff @(posedge clock or negedge resetn) // sensitivity list begin if (!resetn) q <= 1'b0; else q <= d; // this is a non-blocking assignment end endmodule
Combinational logic can be described either using continuous assignments or procedural assignments in always or always_comb blocks Sequential logic can be described using always or always_ff blocks Latches can be described using always or always_latch blocks (to be discussed later)
All the signals on the left hand side of non-blocking procedural assignments in always_ff blocks will end up as state elements (flip-flops)! IMPORTANT coding conventions: in always_comb blocks use only blocking assignments in always_ff blocks use only non-blocking assignments
Important note: if a signal is driven in a sequential block (if it appears on the left-hand side of the assignments in an always_ff or always block with the sensitivity list as shown in these examples), then its occurrences on the right-hand side of assignments stand for the present (or current) state that was computed in the previous clock cycle!
module gray_code_counter (input logic clock, resetn, output logic[3:0] gray_code_count); logic[3:0] binary_count; always_ff @ (posedge clock or negedge resetn) if (resetn == 1'b0) binary_count <= 4'd0; else binary_count <= binary_count + 4'd1; always_comb // next state logic case (binary_count) 4'd1: gray_code_count = 4'd1; // blocking assignment 4'd2: gray_code_count = 4'd3; 4'd3: gray_code_count = 4'd2; 4'd4: gray_code_count = 4'd6; 4'd5: gray_code_count = 4'd7; 4'd6: gray_code_count = 4'd5; 4'd7: gray_code_count = 4'd4; 4'd8: gray_code_count = 4'd12; 4'd9: gray_code_count = 4'd13; 4'd10: gray_code_count = 4'd15; 4'd11: gray_code_count = 4'd14; 4'd12: gray_code_count = 4'd10; 4'd13: gray_code_count = 4'd11; 4'd14: gray_code_count = 4'd9; 4'd15: gray_code_count = 4'd8; default: gray_code_count = 4'd0; endcase endmodule
0 8
1 9
11 10 14 15 13 12
gray_code_count
module gray_code_counter (input logic clock, resetn, output logic[3:0] gray_code_count); always_ff @ (posedge clock or negedge resetn) if (resetn == 1'b0) gray_code_count <= 4'd0; else case (gray_code_count) // next state logic 4'd0: gray_code_count <= 4'd1; // non-blocking assignment 4'd1: gray_code_count <= 4'd3; 4'd3: gray_code_count <= 4'd2; 4'd2: gray_code_count <= 4'd6; 4'd6: gray_code_count <= 4'd7; 4'd7: gray_code_count <= 4'd5; 4'd5: gray_code_count <= 4'd4; 4'd4: gray_code_count <= 4'd12; 4'd12: gray_code_count <= 4'd13; 4'd13: gray_code_count <= 4'd15; 4'd15: gray_code_count <= 4'd14; 4'd14: gray_code_count <= 4'd10; 4'd10: gray_code_count <= 4'd11; 4'd11: gray_code_count <= 4'd9; 4'd9: gray_code_count <= 4'd8; default: gray_code_count <= 4'd0; endcase endmodule
This implementation uses only four LEs (in each LE we have one flip-flop used to store the state and one 4-LUT that implements the next state logic).
Arbitrary Counting Sequences What happens if we re-specify the next state logic as a priority encoder?
module gray_code_counter (input logic clock, resetn, output logic[3:0] gray_code_count); always_ff @ (posedge clock or negedge resetn) if (resetn == 1'b0) gray_code_count <= 4'd0; else begin gray_code_count <= 4'd0; // non-blocking assignments if (gray_code_count == 4'd0) gray_code_count <= 4'd1; if (gray_code_count == 4'd1) gray_code_count <= 4'd3; if (gray_code_count == 4'd3) gray_code_count <= 4'd2; if (gray_code_count == 4'd2) gray_code_count <= 4'd6; if (gray_code_count == 4'd6) gray_code_count <= 4'd7; if (gray_code_count == 4'd7) gray_code_count <= 4'd5; if (gray_code_count == 4'd5) gray_code_count <= 4'd4; if (gray_code_count == 4'd4) gray_code_count <= 4'd12; if (gray_code_count == 4'd12) gray_code_count <= 4'd13; if (gray_code_count == 4'd13) gray_code_count <= 4'd15; if (gray_code_count == 4'd15) gray_code_count <= 4'd14; if (gray_code_count == 4'd14) gray_code_count <= 4'd10; if (gray_code_count == 4'd10) gray_code_count <= 4'd11; if (gray_code_count == 4'd11) gray_code_count <= 4'd9; if (gray_code_count == 4'd9) gray_code_count <= 4'd8; end endmodule
Control/data path Control path finite state machine (FSM) Data path registers, arithmetic units (adders, multipliers), logic units, shift registers, counters, comparators, embedded memories,
Status signals comparator results, zero detect, Control signals synchronous enable, load, shift,
S1/ z=0
logic[1:0] state; parameter S0 = 2'b00; parameter S1 = 2'b01; parameter S2 = 2'b10; parameter S3 = 2'b11; always_ff @ (posedge clock or negedge resetn) begin if (!resetn) state <= S0; else case (state) S0: if (w) state <= S1; else state S1: if (w) state <= S2; else state S2: if (w) state <= S3; else state S3: if (w) state <= S0; else state endcase end assign z = (state == S0); endmodule
FSM
module fsm
logic[1:0] present_state, next_state; parameter S0 = 2'b00; parameter S1 = 2'b01; parameter S2 = 2'b10; parameter S3 = 2'b11; // present state logic always_ff @ (posedge clock or negedge resetn) begin if (!resetn) present_state <= S0; else present_state <= next_state; end // next state logic always_comb begin case (present_state) S0: if (w) next_state S1: if (w) next_state S2: if (w) next_state S3: if (w) next_state endcase end // output logic always_comb begin z = (present_state == S0); end endmodule
= = = =
= = = =
average heartbeat approx 70 pulses per minute if ventricle contraction is not sensed in due time, a ventricle stimulation is applied by the digital circuitry treats bradycardia (slow heart rate) can be extended to monitoring and stimulating also the right atrium - called atrioventricular or dual-chamber pacemakers can be further extended to adapt to motion and breathing rhythms called rate-responsive more complex (yet similar) digital circuitry can be used for implantable cardioverterdefibrillators
module pacemaker (input logic clock, resetn // clock period is assumed 10 ms input logic ventricle_contraction, output logic ventricle_stimulation); logic[7:0] counter; // keeps track of how much time has passed since last contraction logic reload; // used to re-initialize the counter (control signal) logic zero_detect; // used to point that no contraction was sensed (status signal) logic[1:0] present_state, next_state; // pacemaker states parameter S_REINITIALIZE_COUNTER = 2'b00; parameter S_WAIT_VENTRICLE_CONTRACTION = 2'b01; parameter S_APPLY_VENTRICLE_STIMULATION = 2'b10; always_ff @ (posedge clock or negedge resetn) if (!resetn) counter <= 8'd0; else if (reload) counter <= 8'd84; // approx 70 pulses per minute else counter <= counter - 8'd1; always_ff @ (posedge clock or negedge resetn) if (!resetn) present_state <= S_REINITIALIZE_COUNTER; else present_state <= next_state; always_comb case (present_state) S_REINITIALIZE_COUNTER: next_state = S_WAIT_VENTRICLE_CONTRACTION; S_WAIT_VENTRICLE_CONTRACTION: begin if (ventricle_contraction) next_state = S_REINITIALIZE_COUNTER; else if (zero_detect) next_state = S_APPLY_VENTRICLE_STIMULATION; else next_state = S_WAIT_VENTRICLE_CONTRACTION; end S_APPLY_VENTRICLE_STIMULATION: next_state = S_REINITIALIZE_COUNTER; default: next_state = S_REINITIALIZE_COUNTER; endcase assign zero_detect = (counter == 8'd0); assign reload = (present_state == S_REINITIALIZE_COUNTER); assign ventricle_stimulation = (present_state == S_APPLY_VENTRICLE_STIMULATION); endmodule
Pacemaker: Control/Data Path There is no need to declare explicitly all the interface signals between the control path and the data path Shorter equivalent code (implicit)
module pacemaker (input logic clock, resetn, input logic ventricle_contraction, output logic ventricle_stimulation); logic[7:0] counter; enum logic[1:0] {S_REINITIALIZE_COUNTER, S_WAIT_VENTRICLE_CONTRACTION, S_APPLY_VENTRICLE_STIMULATION} state; // pacemaker states always_ff @ (posedge clock or negedge resetn) if (!resetn) begin state <= S_REINITIALIZE_COUNTER; counter <= 8'd0; end else begin counter <= counter - 8'd1; case (state) S_REINITIALIZE_COUNTER: begin state <= S_WAIT_VENTRICLE_CONTRACTION; counter <= 8'd84; end S_WAIT_VENTRICLE_CONTRACTION: begin if (ventricle_contraction) state <= S_REINITIALIZE_COUNTER; else if (counter == 8'd0) state <= S_APPLY_VENTRICLE_STIMULATION; end S_APPLY_VENTRICLE_STIMULATION: state <= S_REINITIALIZE_COUNTER; default: state <= S_REINITIALIZE_COUNTER; endcase end assign ventricle_stimulation = (state == S_APPLY_VENTRICLE_STIMULATION); endmodule
Pacemaker: Control/Data Path How does the design change for dual-chamber pacemakers?
Most importantly, the design approach and the coding style stay the same!
Shift registers Can be described using a for loop; or signal concatenation {,,,}; or using the shift operators (e.g., >>)
`define DATA_WIDTH 16 // used outside the module to // parameterize the design ports module shift_register (input logic resetn, clock, serial_in, load, input logic[`DATA_WIDTH-1:0] data_in, output logic serial_out, output logic[`DATA_WIDTH-1:0] data_out); // right shift register always_ff @(posedge clock or negedge resetn) begin if (!resetn) data_out <= {`DATA_WIDTH{1'b0}}; // signal concatenation // equivalent to: data_out <= `DATA_WIDTH'd0; else if (load) data_out <= data_in; else begin : right_shift // label for a block of statements integer i; // integer variable used as an iterator for (i=`DATA_WIDTH-1; i>0; i=i-1) data_out[i-1] <= data_out[i]; data_out[`DATA_WIDTH-1] <= serial_in; end // alternative code based on signal concatenation /* data_out[`DATA_WIDTH-1:0] <= {serial_in, data_out[`DATA_WIDTH-1:1]}; */ // alternative code based on the >> operator /* data_out <= (data_out >> 1); data_out[`DATA_WIDTH-1] <= serial_in; */ end assign serial_out = data_out[0]; endmodule
Bit counting
Combinational implementation for loop
`define INPUT_WIDTH 16 `define OUTPUT_WIDTH 5 module bit_count (input logic[`INPUT_WIDTH-1:0] data_in, output logic[`OUTPUT_WIDTH-1:0] data_out);
always_comb begin : combinational_solution integer i; data_out = `OUTPUT_WIDTH'd0; for (i=0; i <`INPUT_WIDTH; i+=1) if (data_in[i]) data_out = data_out + `OUTPUT_WIDTH'd1; end endmodule
+1
data_in[0] 1'b0 1'b1
0 1 0
data_in[1]
data_in[14]
data_in[15] 0
1
data_in
0
Load SO
+1
Load
0 1 0
16
data_out
5
data_out Clock
5
<<
SI
+1
shift register
Combinational implementation
Sequential implementation
// sequential implementation that uses one shift register and one counter module bit_count (input logic resetn, clock, load, input logic[`INPUT_WIDTH-1:0] data_in, output logic[`OUTPUT_WIDTH-1:0] data_out);
logic[`INPUT_WIDTH-1:0] shift_register; always_ff @(posedge clock, negedge resetn) begin if (!resetn) begin shift_register <= `INPUT_WIDTH'd0; data_out <= `OUTPUT_WIDTH'd0; end else begin if (load) begin shift_register <= data_in; data_out <= `OUTPUT_WIDTH'd0; end else begin shift_register <= (shift_register << 1); // left shift if (shift_register[`INPUT_WIDTH-1]) data_out <= data_out + `OUTPUT_WIDTH'd1; end end end endmodule
module latch_example2 (input logic c1, c2, a, b, output logic f, g); always_latch begin if (c1) begin f = a; end else if (c2) begin g = b; end end endmodule
module latch_example3 (input logic c1, c2, input logic a, b, output logic f, g); always_latch begin if (c1) begin f = a; g = b; end else if (c2) begin f = b; g = a; end end endmodule
= = = =
a; b; a | b; a & b;
module latch_example5 (input logic c1, c2, input logic a, b, output logic f); always_latch begin case ({c1,c2}) 2'b00: f = a; 2'b01: f = b; 2'b10: f = a | b; endcase end endmodule
Latches What happens if we use incompletely specified case or if statements with always_comb?
module latch_incorrect (input logic c1, c2, input logic a, b, output logic f); always_comb begin case ({c1,c2}) 2'b00: f = a; 2'b01: f = b; 2'b10: f = a | b; endcase end endmodule
module combinational_correct (input logic c1, c2, input logic a, b, output logic f); always_comb begin unique case ({c1,c2}) 2'b00: f = a; 2'b01: f = b; 2'b10: f = a | b; default: f = 1'bx; endcase end endmodule
Blocking vs. Non-blocking Simple circuit for illustrative purposes (in reality some additional inputs/outputs will be required)
module example (input logic clock, output logic[7:0] f); logic[7:0] a, b, c; always_ff @(posedge clock) begin a <= b + c; b <= c + a; c <= a + b; end assign f = c; endmodule
clock
clock
clock
Blocking vs. Non-blocking What happens if the assignment to a is blocking and the output f is driven by the a signal?
module example (input logic clock, output logic[7:0] f); logic[7:0] a, b, c; always_ff @(posedge clock) begin a = b + c; b <= c + a; c <= a + b; end assign f = a; endmodule
clock
Blocking vs. Non-blocking Can we take the combinational a signal to the output of the circuit?
module example (input logic clock, output logic[7:0] f); logic[7:0] a, b, c; always_comb begin a = b + c; end always_ff @(posedge clock) begin b <= c + a; c <= a + b; end assign f = a; endmodule