Verilog – Smart Grid Frequency Regulation Controller
Goal
The objective of this simulation-based prototype is to design and validate a Smart Grid Frequency Regulation Controller capable of dynamically adjusting generator power setpoints based on real-time grid conditions. The controller must:
- Detect deviations in grid frequency from the nominal value (50.00 Hz) and apply corrective actions according to supervisory policy.
- Operate under two distinct control states:
01 — GRID_CONNECTED (Normal Mode): Frequency is within acceptable limits, generators react gradually using droop control logic.
10 — EMERGENCY Mode: Frequency deviation exceeds a critical threshold. Emergency dispatch logic overrides normal droop adjustments.
- Handle generator availability constraints and provide realistic engineering behavior avoiding unrealistic jumps.
Engineering Approach and Tools
The regulator is implemented as a parametric SystemVerilog hardware module, scalable from 4 to N generators. Each generator has capacity, ramp rate, and availability flags. Droop control is applied in normal mode, emergency logic engages when frequency deviates critically. A testbench automates multiple scenarios. Simulation tracks control_mode, emergency_signal, and setpoints.
Execution Behavior and Output Interpretation
Simulation results are summarized in the table below. Each scenario shows how the controller behaves under nominal, under-frequency, over-frequency, and generator outage conditions.
Scenario
Frequency (Hz)
Control Mode (bits)
Emergency
Observed Behavior
Validation
Nominal
50.00
01
0
Proportional dispatch; droop used for minor corrections
Correct
Slight under-frequency
49.60
01
0
Increased setpoints gradually (droop); no emergency
Correct
Severe under-frequency
48.50
10
1
Emergency mode: maximize available generation
Correct
Over-frequency
51.50
10
1
Emergency mode: controlled reduction of generation
Correct
Generator outage (gen2 offline)
50.00
01
0
Gen2 setpoint = 0; remaining units compensate
Correct
Legend: 01 = GRID_CONNECTED (normal); 10 = EMERGENCY.
“Correct”indicates the observed behavior matches the design specification.
Code Cells
design.sv
// Author: Hamza Bendahmane
// GridFrequencyRegulator_pro.sv
// Professional 4-generator regulator with:
// - capacity-based economic dispatch
// - droop control (bi-directional)
// - ramp-rate limiting (mechanical limits)
// - emergency handling (under/over-frequency)
`timescale 1ns/1ps
module GridFrequencyRegulator_pro
#(
parameter NUM_GENERATORS = 4,
// frequency units: input frequency_measurement is in 0.01 Hz (e.g. 5000 => 50.00 Hz)
parameter integer NOMINAL_FREQ_HZ = 50,
// droop coefficient (fixed-point scaled): DR_COEFF = 1000 => 1.000 scaling factor base
// Effective correction factor = 1 + (DR_COEFF * freq_error_01Hz) / 100000
// Tune DR_COEFF to tune sensitivity (positive -> amplify dispatch when freq low)
parameter integer DR_COEFF = 500, // moderate droop sensitivity
// emergency thresholds in 0.01 Hz units (e.g. 100 => 1.00 Hz)
parameter integer EMERGENCY_DELTA = 100
)
(
input wire clk,
input wire reset_n,
input wire [15:0] frequency_measurement, // 0.01 Hz units
input wire [15:0] load_demand, // MW
input wire [NUM_GENERATORS-1:0] generator_availability,
output reg [15:0] generator_setpoints [0:NUM_GENERATORS-1], // MW (16-bit)
output reg [1:0] control_mode, // 01 normal, 10 emergency
output reg emergency_signal,
output reg [31:0] performance_counters
);
// Local parameters for modes
localparam MODE_GRID_CONNECTED = 2'b01;
localparam MODE_EMERGENCY = 2'b10;
// Generator static properties (realistic MW)
reg [15:0] generator_capacity [0:NUM_GENERATORS-1];
reg [15:0] generator_ramp_rate [0:NUM_GENERATORS-1]; // MW per cycle (clock tick)
reg [15:0] generator_cost [0:NUM_GENERATORS-1]; // $/MWh (for future use)
// Internal bookkeeping
integer i, j;
reg signed [15:0] freq_error_01Hz; // nominal - measured in 0.01Hz units
reg [31:0] total_available_capacity; // MW sum (fits 32 bits)
reg [15:0] desired_dispatch [0:NUM_GENERATORS-1]; // MW (before droop)
reg [15:0] droop_adjusted [0:NUM_GENERATORS-1]; // MW (after droop)
reg [31:0] nominal_freq_01Hz;
// Initialize generator parameters
initial begin
nominal_freq_01Hz = NOMINAL_FREQ_HZ * 100;
// realistic capacities
generator_capacity[0] = 500;
generator_capacity[1] = 300;
generator_capacity[2] = 200;
generator_capacity[3] = 100;
// ramp rates (MW per tick) - tune for realism (small -> slow)
generator_ramp_rate[0] = 10; // slow base load
generator_ramp_rate[1] = 15;
generator_ramp_rate[2] = 20;
generator_ramp_rate[3] = 30; // fast peaking
// costs (unused here but included for realism)
generator_cost[0] = 20;
generator_cost[1] = 30;
generator_cost[2] = 40;
generator_cost[3] = 80;
end
// Utility: compute total available capacity (sum capacities of available units)
function [31:0] compute_total_available_capacity;
integer k;
reg [31:0] sum;
begin
sum = 0;
for (k = 0; k < NUM_GENERATORS; k = k + 1)
if (generator_availability[k])
sum = sum + generator_capacity[k];
compute_total_available_capacity = sum;
end
endfunction
// Utility: apply ramp limit (ensures change from current to target <= ramp_rate)
function [15:0] apply_ramp_limit;
input integer gen_id;
input [31:0] target; // may be wider to avoid truncation while computing
reg signed [31:0] cur;
reg signed [31:0] targ;
reg signed [31:0] max_up;
reg signed [31:0] max_down;
reg signed [31:0] limited;
begin
cur = {16'd0, generator_setpoints[gen_id]}; // extend to signed 32
targ = {16'd0, target};
max_up = {16'd0, generator_ramp_rate[gen_id]};
max_down = -{16'd0, generator_ramp_rate[gen_id]};
if (targ > cur + max_up) limited = cur + max_up;
else if (targ < cur + max_down) limited = cur + max_down;
else limited = targ;
// saturate to 0..capacity
if (limited < 0) limited = 0;
if (limited > generator_capacity[gen_id]) limited = generator_capacity[gen_id];
apply_ramp_limit = limited[15:0];
end
endfunction
// Calculate desired dispatch proportionally to capacity to meet load_demand
task compute_proportional_dispatch;
integer k;
reg [31:0] avail;
reg [63:0] scaled; // for fractional division
begin
avail = compute_total_available_capacity();
if (avail == 0) begin
for (k = 0; k < NUM_GENERATORS; k = k + 1) desired_dispatch[k] = 0;
end else begin
for (k = 0; k < NUM_GENERATORS; k = k + 1) begin
if (generator_availability[k]) begin
// desired_dispatch = round( load_demand * capacity_k / avail )
scaled = generator_capacity[k];
scaled = scaled * load_demand; // MW * MW -> scaled (use 64-bit intermediate)
scaled = scaled / avail;
desired_dispatch[k] = scaled[31:0];
end else desired_dispatch[k] = 0;
end
end
end
endtask
// Apply droop correction: small global scaling based on frequency error
// We compute a correction factor in fixed-point:
// correction_factor = 1 + (DR_COEFF * freq_error_01Hz) / 100000
// (100000 chosen to give sensible scaling: if DR_COEFF=500 and freq_error=100 (1Hz),
// add = 500*100/100000 = 0.5 => +50% total dispatch)
task apply_droop;
integer k;
reg signed [31:0] add_num;
reg signed [31:0] corr_fp; // fixed point (scale 100000)
reg signed [63:0] temp;
begin
add_num = DR_COEFF * freq_error_01Hz; // can be negative
corr_fp = 100000 + add_num; // base 100000 => represents 1.00000
if (corr_fp < 20000) corr_fp = 20000; // floor to 0.2 (avoid negative)
// apply correction to each desired_dispatch: droop_adjusted = desired * corr_fp / 100000
for (k = 0; k < NUM_GENERATORS; k = k + 1) begin
temp = desired_dispatch[k];
temp = temp * corr_fp; // large intermediate
temp = temp / 100000;
// enforce bounds: 0 .. capacity
if (temp < 0) droop_adjusted[k] = 0;
else if (temp > generator_capacity[k]) droop_adjusted[k] = generator_capacity[k];
else droop_adjusted[k] = temp[15:0];
end
end
endtask
// Emergency behavior:
// - if under-frequency beyond EMERGENCY_DELTA -> try to maximize available generation
// - if over-frequency beyond EMERGENCY_DELTA -> reduce generation proportionally (not zero)
task apply_emergency_action;
integer k;
reg [31:0] avail;
reg [31:0] baseline;
begin
avail = compute_total_available_capacity();
if (freq_error_01Hz > EMERGENCY_DELTA) begin
// Under-frequency (nominal > measured by > EMERGENCY_DELTA): maximize available generators
for (k = 0; k < NUM_GENERATORS; k = k + 1) begin
if (generator_availability[k]) generator_setpoints[k] <= generator_capacity[k];
else generator_setpoints[k] <= 0;
end
end else begin
// Over-frequency: reduce to a safe fraction of desired dispatch (e.g., 60%),
// but keep a minimum generation (10% of capacity) if available.
for (k = 0; k < NUM_GENERATORS; k = k + 1) begin
if (generator_availability[k]) begin
// target = max( desired_dispatch * 0.6, capacity*0.10 )
baseline = (desired_dispatch[k] * 60) / 100; // 60%
if (baseline < (generator_capacity[k] / 10))
baseline = generator_capacity[k] / 10;
generator_setpoints[k] <= apply_ramp_limit(k, baseline);
end else generator_setpoints[k] <= 0;
end
end
end
endtask
// Main control loop: register outputs (clocked)
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
// reset setpoints to 50% of capacity
for (i = 0; i < NUM_GENERATORS; i = i + 1)
generator_setpoints[i] <= generator_capacity[i] / 2;
control_mode <= MODE_GRID_CONNECTED;
emergency_signal <= 0;
performance_counters <= 0;
end else begin
// update frequency error (nominal - measured)
freq_error_01Hz <= $signed(nominal_freq_01Hz) - $signed(frequency_measurement);
total_available_capacity <= compute_total_available_capacity();
// compute proportional dispatch to meet load_demand
compute_proportional_dispatch();
// droop adjusts desired dispatch according to frequency deviation
apply_droop();
// Check emergency thresholds (absolute deviation from nominal)
if ((frequency_measurement + EMERGENCY_DELTA) < nominal_freq_01Hz ||
(frequency_measurement > (nominal_freq_01Hz + EMERGENCY_DELTA))) begin
// emergency
control_mode <= MODE_EMERGENCY;
emergency_signal <= 1;
// emergency action uses desired_dispatch (pre-droop) to reduce/increase safely
apply_emergency_action();
end else begin
// normal operation: set generators to droop_adjusted but obey ramp limits
control_mode <= MODE_GRID_CONNECTED;
emergency_signal <= 0;
for (i = 0; i < NUM_GENERATORS; i = i + 1) begin
if (generator_availability[i]) begin
generator_setpoints[i] <= apply_ramp_limit(i, droop_adjusted[i]);
end else generator_setpoints[i] <= 0;
end
end
performance_counters <= performance_counters + 1;
end
end
endmodule