| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- `timescale 1ns/1ps
- // -----------------------------------------------------------------------------
- // Motor controller used for the single-UART HIL teaching demo.
- //
- // The FPGA is real. The motor is simulated on the PC. Each control exchange is
- // one simulated 10 ms step:
- // FPGA -> PC : PWM/enable/state command (packet type 0x90)
- // PC -> FPGA: RPM/current/fault feedback (packet type 0x91)
- //
- // The controller intentionally uses only integer arithmetic so that the RTL is
- // easy to synthesize and easy to understand.
- // -----------------------------------------------------------------------------
- module motor_controller_hil (
- input wire clk,
- input wire reset_n,
- input wire start,
- input wire feedback_valid,
- input wire [15:0] feedback_rpm,
- input wire [15:0] feedback_current_ma,
- input wire feedback_fault,
- input wire feedback_encoder_ok,
- input wire [15:0] feedback_time_ms,
- output wire busy,
- output wire done,
- output reg [6:0] pwm_duty,
- output reg motor_enable,
- output reg direction,
- output wire [15:0] state_id,
- output reg [7:0] fault_code,
- output wire trace_valid,
- output wire [15:0] trace_id,
- output wire [15:0] trace_timestamp,
- output wire [31:0] trace_data
- );
- // Stable IDs. Python scans these @TRACE annotations to build the map.
- localparam [15:0] M_IDLE = 16'h4000; // @TRACE IDLE | Waiting for PC start command
- localparam [15:0] M_START = 16'h4001; // @TRACE START | First motor command after start
- localparam [15:0] M_ACCEL = 16'h4002; // @TRACE ACCEL | Increase PWM until measured speed reaches target band
- localparam [15:0] M_RUN = 16'h4003; // @TRACE RUN | Closed-loop speed trim around 1000 RPM
- localparam [15:0] M_STOP = 16'h4004; // @TRACE STOP | Disable drive and wait for simulated motor to coast down
- localparam [15:0] M_FAULT = 16'h4005; // @TRACE FAULT | Protection state before finishing the test
- localparam [15:0] M_DONE = 16'h4006; // @TRACE DONE | Test finished; top-level will dump the Trace RAM
- reg [15:0] state;
- reg [15:0] step_count;
- reg [7:0] run_hold_count;
- reg [7:0] stop_hold_count;
- assign busy = (state != M_IDLE) && (state != M_DONE);
- assign done = (state == M_DONE);
- assign state_id = state;
- // One Trace record per HIL feedback/control step, not one record per 25 MHz
- // FPGA clock. This makes TRACE_DEPTH=1024 useful for a slow mechanical plant.
- assign trace_valid = feedback_valid && (state != M_IDLE) && (state != M_DONE);
- assign trace_id = state;
- assign trace_timestamp = feedback_time_ms;
- // 32-bit motor snapshot, no division/multiplier required:
- // [31:20] measured RPM, saturated to 12 bits by truncation
- // [19:13] PWM duty 0..100
- // [12:1] current in mA, low 12 bits (demo stays < 4096 mA)
- // [0] feedback fault flag
- assign trace_data = {
- feedback_rpm[11:0],
- pwm_duty[6:0],
- feedback_current_ma[11:0],
- feedback_fault
- };
- always @(posedge clk or negedge reset_n) begin
- if (!reset_n) begin
- state <= M_IDLE;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- direction <= 1'b1;
- fault_code <= 8'd0;
- step_count <= 16'd0;
- run_hold_count <= 8'd0;
- stop_hold_count <= 8'd0;
- end else begin
- if (start && (state == M_IDLE || state == M_DONE)) begin
- state <= M_START;
- pwm_duty <= 7'd25;
- motor_enable <= 1'b1;
- direction <= 1'b1;
- fault_code <= 8'd0;
- step_count <= 16'd0;
- run_hold_count <= 8'd0;
- stop_hold_count <= 8'd0;
- end else if (feedback_valid && (state != M_IDLE) && (state != M_DONE)) begin
- step_count <= step_count + 1'b1;
- // Once FAULT is entered, keep one explicit FAULT exchange in the
- // Trace and then finish, even if the external fault remains asserted.
- if (state == M_FAULT) begin
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- state <= M_DONE;
- end else if (feedback_fault) begin
- state <= M_FAULT;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- fault_code <= 8'd1; // simulator/driver fault input
- end else if (!feedback_encoder_ok && (step_count >= 16'd5)) begin
- state <= M_FAULT;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- fault_code <= 8'd2; // encoder feedback lost
- end else if (feedback_current_ma > 16'd2200) begin
- state <= M_FAULT;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- fault_code <= 8'd3; // over-current
- end else begin
- case (state)
- M_IDLE: begin // @TRACE_EXEC
- state <= M_IDLE;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- end
- M_START: begin // @TRACE_EXEC
- state <= M_ACCEL;
- pwm_duty <= 7'd35;
- motor_enable <= 1'b1;
- end
- M_ACCEL: begin // @TRACE_EXEC
- if (feedback_rpm >= 16'd950) begin
- state <= M_RUN;
- run_hold_count <= 8'd0;
- end else if (step_count >= 16'd80) begin
- state <= M_FAULT;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- fault_code <= 8'd4; // startup timeout
- end else begin
- // Three-stage acceleration command. The virtual motor
- // is modeled as about 1800 RPM at 100% PWM, so 55..58%
- // is close to the 1000 RPM target and avoids large overshoot.
- if (feedback_rpm < 16'd300)
- pwm_duty <= 7'd45;
- else if (feedback_rpm < 16'd700)
- pwm_duty <= 7'd55;
- else
- pwm_duty <= 7'd58;
- end
- end
- M_RUN: begin // @TRACE_EXEC
- // Very small integral-like trim around the 1000 RPM target.
- if (feedback_rpm < 16'd980) begin
- if (pwm_duty < 7'd90)
- pwm_duty <= pwm_duty + 1'b1;
- end else if (feedback_rpm > 16'd1020) begin
- if (pwm_duty > 7'd5)
- pwm_duty <= pwm_duty - 1'b1;
- end
- if (run_hold_count >= 8'd49) begin
- state <= M_STOP;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- stop_hold_count <= 8'd0;
- end else begin
- run_hold_count <= run_hold_count + 1'b1;
- end
- end
- M_STOP: begin // @TRACE_EXEC
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- if (feedback_rpm <= 16'd20) begin
- state <= M_DONE;
- end else if (stop_hold_count >= 8'd80) begin
- state <= M_FAULT;
- fault_code <= 8'd5; // failed to stop in time
- end else begin
- stop_hold_count <= stop_hold_count + 1'b1;
- end
- end
- M_FAULT: begin // @TRACE_EXEC
- // Keep one explicit FAULT exchange in the Trace, then finish.
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- state <= M_DONE;
- end
- M_DONE: begin // @TRACE_EXEC
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- state <= M_DONE;
- end
- default: begin
- state <= M_FAULT;
- pwm_duty <= 7'd0;
- motor_enable <= 1'b0;
- fault_code <= 8'hFF;
- end
- endcase
- end
- end
- end
- end
- endmodule
|