Back to Code and Software Demo

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
testbench.sv
// Author: Hamza Bendahmane `timescale 1ns/1ps module tb_GridFrequencyRegulator_pro; parameter NUM_GENERATORS = 4; parameter CLK_PERIOD = 10; reg clk, reset_n; reg [15:0] frequency_measurement; // 0.01 Hz units reg [15:0] load_demand; // MW reg [NUM_GENERATORS-1:0] generator_availability; wire [15:0] generator_setpoints [0:NUM_GENERATORS-1]; wire [1:0] control_mode; wire emergency_signal; wire [31:0] performance_counters; integer i, succ; always #(CLK_PERIOD/2) clk = ~clk; // Instantiate DUT GridFrequencyRegulator_pro dut ( .clk(clk), .reset_n(reset_n), .frequency_measurement(frequency_measurement), .load_demand(load_demand), .generator_availability(generator_availability), .generator_setpoints(generator_setpoints), .control_mode(control_mode), .emergency_signal(emergency_signal), .performance_counters(performance_counters) ); initial begin // init clk = 0; reset_n = 0; frequency_measurement = 5000; // 50.00 Hz load_demand = 800; // 800 MW generator_availability = 4'b1111; succ = 0; #100; reset_n = 1; #100; $display("\n=== GridFrequencyRegulator_pro - compact tests ===\n"); // Normal operation: nominal frequency run_case("NORMAL - nominal freq", 5000, 800, 4'b1111); // Slight under-frequency: droop responsive (no emergency) run_case("Slight underfreq (49.6 Hz)", 4960, 900, 4'b1111); // Under-frequency emergency ( > 1 Hz down) run_case("Underfreq emergency (48.5 Hz)", 4850, 900, 4'b1111); // Over-frequency emergency ( > 1 Hz up) run_case("Overfreq emergency (51.5 Hz)", 5150, 700, 4'b1111); // Generator outage - one offline run_case("Generator outage (gen2 offline)", 5000, 900, 4'b1011); $display("\nSummary: passed cases: %0d\n", succ); #100; $finish; end task run_case; input [127:0] name; input [15:0] freq; input [15:0] load; input [NUM_GENERATORS-1:0] avail; begin $display("--- %s ---", name); frequency_measurement = freq; load_demand = load; generator_availability = avail; #2000; // wait several cycles to let ramping occur $display("Freq: %0.2f Hz | Mode: %b | Emergency: %b | PerfCnt: %0d", freq/100.0, control_mode, emergency_signal, performance_counters); $display("Setpoints (MW):"); for (i = 0; i < NUM_GENERATORS; i = i + 1) $display(" Gen %0d: %0d", i, generator_setpoints[i]); $display(""); succ = succ + 1; end endtask endmodule
© 2025 – Hamza Bendahmane. All rights reserved.