diff --git a/9_Firmware/9_2_FPGA/chirp_scheduler.v b/9_Firmware/9_2_FPGA/chirp_scheduler.v index a749ae8..4783791 100644 --- a/9_Firmware/9_2_FPGA/chirp_scheduler.v +++ b/9_Firmware/9_2_FPGA/chirp_scheduler.v @@ -71,6 +71,13 @@ module chirp_scheduler ( input wire stm32_new_subframe, input wire stm32_new_frame, + // Master enable (PR-E). When low, the scheduler holds in S_IDLE and + // emits no chirp_pulse — the FSM resumes on the next clock edge after + // mixers_enable returns high. Keeps the radar quiet between operator + // commands and prevents stale chirp_pulses from being buffered by the + // TX-side cdc_async_fifo before mixers come up. + input wire mixers_enable, + // ====== Outputs ====== output reg [1:0] wave_sel, // canonical waveform identity output reg chirp_pulse, // 1-cycle pulse: chirp begins this clk @@ -256,6 +263,15 @@ always @(posedge clk or negedge reset_n) begin subframe_id <= 2'd0; track_mode_active <= 1'b0; track_remaining <= 6'd0; + end else if (!mixers_enable) begin + // Master disable — quiesce the FSM so chirp_pulse never asserts and + // the TX side stays at idle. Doesn't disturb track_mode_active so + // the host can still observe whether track was last requested. + state <= S_IDLE; + timer <= 17'd0; + chirp_pulse <= 1'b0; + subframe_pulse <= 1'b0; + frame_pulse <= 1'b0; end else begin // Pulses default low — set high for one cycle on relevant transitions. chirp_pulse <= 1'b0; diff --git a/9_Firmware/9_2_FPGA/long_chirp_lut.mem b/9_Firmware/9_2_FPGA/long_chirp_lut.mem deleted file mode 100644 index 39b9e7f..0000000 --- a/9_Firmware/9_2_FPGA/long_chirp_lut.mem +++ /dev/null @@ -1,3600 +0,0 @@ -FF -ED -BF -7F -40 -11 -01 -12 -41 -81 -C1 -EF -FE -EC -BC -7B -3C -0F -01 -15 -47 -88 -C7 -F2 -FE -E7 -B4 -72 -34 -0B -02 -1B -51 -94 -D1 -F7 -FC -DF -A7 -64 -28 -05 -05 -26 -61 -A4 -DD -FC -F7 -D1 -94 -51 -1B -01 -0C -37 -77 -B9 -EB -FE -ED -BD -7B -3A -0D -01 -19 -4F -92 -D1 -F7 -FC -DC -A2 -5D -23 -03 -08 -2F -6E -B2 -E7 -FE -F0 -C1 -7F -3D -0E -01 -19 -4F -93 -D2 -F8 -FB -D9 -9D -57 -1E -02 -0B -37 -78 -BC -EE -FE -E9 -B4 -70 -30 -08 -04 -25 -62 -A7 -E1 -FD -F3 -C6 -84 -40 -0F -01 -19 -50 -96 -D5 -FA -F9 -D3 -94 -4E -17 -01 -11 -44 -89 -CB -F6 -FC -DC -9F -58 -1E -02 -0D -3C -80 -C4 -F3 -FD -E1 -A6 -5E -22 -02 -0A -37 -7B -C0 -F1 -FE -E3 -A8 -60 -23 -03 -0A -37 -7B -C1 -F1 -FE -E2 -A6 -5E -21 -02 -0C -3A -80 -C5 -F4 -FD -DE -A0 -58 -1C -01 -0F -42 -88 -CC -F7 -FB -D6 -95 -4D -15 -01 -15 -4D -96 -D7 -FB -F7 -CB -86 -3F -0D -02 -20 -5E -A7 -E3 -FE -EF -BA -72 -2E -06 -06 -2F -73 -BB -F0 -FE -E2 -A4 -5A -1D -01 -10 -45 -8D -D1 -FA -F8 -CE -88 -40 -0D -02 -21 -61 -AB -E7 -FE -EB -B2 -68 -26 -03 -0B -3C -84 -CA -F7 -FA -D3 -8E -45 -0F -01 -1F -5F -AA -E6 -FE -EA -B0 -65 -23 -02 -0D -40 -8A -D0 -FA -F8 -CB -84 -3B -0A -03 -29 -6D -B8 -EF -FE -E0 -9F -53 -17 -01 -18 -54 -A1 -E1 -FE -ED -B5 -69 -26 -02 -0D -41 -8B -D2 -FB -F6 -C7 -7D -34 -07 -06 -32 -7A -C4 -F5 -FB -D3 -8D -41 -0D -03 -27 -6C -B8 -F0 -FD -DC -99 -4C -12 -01 -1F -62 -AF -EB -FE -E2 -A1 -53 -16 -01 -1B -5B -A9 -E8 -FE -E5 -A6 -57 -18 -01 -19 -58 -A7 -E6 -FF -E6 -A7 -58 -19 -01 -19 -59 -A8 -E7 -FE -E5 -A4 -55 -17 -01 -1C -5D -AC -EA -FE -E1 -9E -4F -13 -01 -21 -65 -B4 -EF -FD -DB -94 -46 -0E -03 -29 -71 -BF -F4 -FB -D1 -87 -3A -08 -06 -34 -80 -CC -FA -F6 -C3 -75 -2B -03 -0D -44 -94 -DB -FE -ED -B1 -61 -1D -01 -18 -59 -AA -E9 -FE -E0 -9A -49 -0F -02 -29 -73 -C2 -F6 -FA -CB -7E -31 -05 -0A -40 -90 -D9 -FD -ED -B0 -5F -1B -01 -1B -5F -B1 -EE -FD -D8 -8E -3E -09 -06 -35 -84 -D1 -FB -F2 -B9 -68 -20 -01 -17 -59 -AB -EB -FE -DB -91 -40 -0A -05 -35 -84 -D1 -FC -F1 -B6 -64 -1D -01 -1A -60 -B2 -F0 -FD -D4 -87 -37 -06 -09 -40 -92 -DC -FE -E9 -A7 -53 -13 -02 -27 -73 -C5 -F8 -F7 -C1 -6F -24 -01 -15 -58 -AC -ED -FD -D6 -89 -38 -06 -0A -42 -95 -DF -FE -E6 -A0 -4C -0E -03 -30 -7F -CF -FC -F1 -B3 -5E -18 -01 -22 -6D -C1 -F7 -F7 -C2 -6F -23 -01 -18 -5E -B3 -F1 -FB -CE -7C -2D -02 -11 -52 -A8 -EB -FD -D6 -87 -35 -04 -0D -49 -9F -E6 -FE -DC -8F -3B -06 -0A -44 -99 -E3 -FE -DF -94 -3F -08 -08 -41 -96 -E1 -FE -E1 -96 -40 -08 -08 -40 -96 -E1 -FE -E0 -94 -3F -08 -09 -42 -99 -E3 -FE -DE -90 -3B -06 -0B -47 -9E -E7 -FE -D9 -89 -35 -04 -0E -4F -A7 -EC -FD -D2 -7F -2D -02 -14 -5A -B2 -F2 -FA -C8 -72 -23 -01 -1C -68 -BF -F7 -F6 -BA -62 -18 -01 -27 -79 -CE -FC -ED -A9 -50 -0E -04 -37 -8D -DD -FE -E1 -94 -3C -06 -0C -4B -A4 -EB -FD -D0 -7B -28 -01 -19 -64 -BD -F7 -F6 -B9 -60 -16 -02 -2C -81 -D5 -FE -E7 -9D -43 -08 -09 -47 -A0 -EA -FD -D1 -7B -28 -01 -1A -67 -C1 -F9 -F3 -B2 -58 -11 -03 -35 -8D -DE -FF -DE -8D -35 -03 -12 -59 -B4 -F4 -F8 -BD -63 -17 -02 -2D -84 -D8 -FE -E3 -93 -3A -04 -0F -55 -B1 -F3 -F9 -BF -64 -17 -02 -2E -85 -D9 -FE -E0 -8F -36 -03 -12 -5B -B7 -F6 -F6 -B7 -5B -12 -03 -37 -91 -E2 -FE -D7 -81 -2A -01 -1C -6C -C7 -FB -ED -A5 -48 -09 -0A -4A -A7 -EF -FA -C3 -68 -19 -02 -2F -88 -DC -FE -DC -87 -2E -01 -1A -6A -C5 -FB -ED -A3 -46 -08 -0B -4F -AD -F2 -F8 -BC -5E -13 -03 -39 -95 -E5 -FE -D0 -76 -21 -01 -27 -7E -D6 -FE -E0 -8B -30 -02 -19 -6A -C7 -FC -EB -9E -40 -05 -0F -58 -B7 -F7 -F3 -AE -4F -0B -09 -4A -A9 -F1 -F9 -BB -5C -11 -05 -3E -9D -EB -FC -C6 -68 -17 -02 -35 -92 -E5 -FD -CE -71 -1D -01 -2E -8A -E0 -FE -D3 -78 -21 -01 -2A -84 -DC -FE -D7 -7D -24 -01 -27 -81 -DA -FE -D9 -7F -26 -01 -26 -7F -D9 -FE -D9 -7F -25 -01 -26 -81 -DA -FE -D8 -7D -24 -01 -29 -84 -DD -FE -D4 -78 -20 -01 -2D -8A -E1 -FE -CF -71 -1B -02 -33 -92 -E6 -FD -C8 -68 -16 -03 -3C -9D -EC -FA -BE -5C -0F -06 -47 -A9 -F3 -F6 -B2 -4F -09 -0C -54 -B7 -F8 -F0 -A2 -40 -04 -14 -65 -C7 -FD -E6 -90 -30 -01 -1F -79 -D6 -FE -D8 -7B -21 -01 -2F -8F -E5 -FD -C6 -64 -13 -05 -43 -A7 -F2 -F6 -B0 -4C -08 -0E -5C -BF -FB -EA -95 -34 -01 -1E -78 -D7 -FE -D6 -77 -1E -02 -35 -97 -EB -FA -BC -58 -0C -0A -52 -B7 -F9 -ED -9C -38 -02 -1C -75 -D5 -FE -D7 -77 -1D -02 -37 -9B -ED -F9 -B7 -51 -09 -0D -5B -C0 -FC -E7 -8F -2E -01 -26 -85 -E1 -FD -C8 -64 -11 -06 -4A -B1 -F7 -F0 -9F -3A -02 -1C -78 -D8 -FE -D2 -6F -17 -04 -42 -A8 -F4 -F3 -A6 -40 -03 -19 -72 -D4 -FF -D4 -72 -19 -03 -40 -A7 -F4 -F3 -A5 -3E -03 -1A -75 -D7 -FE -D1 -6D -15 -05 -47 -AE -F7 -EF -9D -36 -01 -21 -81 -DF -FD -C7 -60 -0E -09 -54 -BD -FB -E6 -8B -28 -01 -2F -94 -EB -F9 -B4 -4B -06 -14 -6B -D1 -FE -D5 -72 -17 -04 -46 -AF -F8 -ED -98 -31 -01 -27 -8B -E7 -FB -BA -51 -08 -12 -68 -CE -FE -D6 -72 -17 -05 -48 -B2 -F9 -EB -92 -2D -01 -2D -94 -EC -F8 -B0 -46 -04 -19 -76 -DA -FE -C9 -61 -0E -0B -5A -C4 -FD -DE -7B -1C -03 -42 -AD -F7 -ED -94 -2D -01 -2E -96 -EE -F7 -AB -40 -02 -1E -80 -E1 -FC -BE -53 -08 -12 -6B -D3 -FE -CF -66 -0F -0A -59 -C4 -FE -DC -77 -19 -04 -49 -B6 -FB -E6 -87 -23 -02 -3C -A8 -F6 -EE -95 -2D -01 -31 -9B -F1 -F4 -A1 -36 -01 -29 -90 -EC -F7 -AB -3E -02 -22 -87 -E7 -FA -B3 -46 -03 -1D -7F -E2 -FC -B9 -4C -05 -19 -7A -DF -FD -BD -50 -06 -17 -76 -DC -FD -C0 -53 -07 -15 -74 -DB -FD -C1 -54 -07 -15 -73 -DB -FD -C1 -53 -07 -16 -75 -DC -FD -BF -51 -06 -17 -78 -DE -FD -BB -4D -05 -1A -7D -E2 -FB -B6 -47 -03 -1F -84 -E6 -FA -AF -40 -02 -24 -8D -EB -F7 -A6 -38 -01 -2C -97 -F1 -F2 -9B -2E -01 -35 -A3 -F6 -EC -8E -25 -02 -41 -B1 -FA -E4 -7F -1B -05 -4F -BF -FD -D9 -6F -11 -0A -60 -CE -FE -CB -5C -09 -13 -73 -DC -FD -BA -49 -03 -1F -88 -E9 -F7 -A6 -36 -01 -30 -9E -F4 -ED -8F -24 -02 -44 -B6 -FC -DF -75 -14 -09 -5D -CC -FE -CB -5B -08 -15 -78 -E1 -FB -B2 -40 -01 -29 -96 -F1 -F1 -94 -27 -02 -42 -B4 -FC -DE -74 -13 -0A -61 -D1 -FE -C5 -53 -05 -1C -84 -E8 -F7 -A4 -33 -01 -36 -A8 -F9 -E6 -7F -19 -07 -58 -CA -FF -CA -58 -07 -19 -80 -E7 -F8 -A6 -34 -01 -37 -A9 -F9 -E4 -7B -16 -08 -5E -CF -FE -C3 -50 -04 -1F -8B -ED -F3 -99 -29 -02 -44 -B8 -FD -D8 -69 -0D -11 -72 -DF -FB -AF -3B -01 -32 -A4 -F8 -E6 -7D -16 -09 -60 -D2 -FE -BE -49 -02 -26 -96 -F3 -ED -8B -1E -05 -54 -C9 -FE -C8 -53 -04 -1F -8D -EF -F1 -92 -23 -03 -4F -C4 -FE -CB -57 -05 -1D -8A -EE -F2 -93 -23 -03 -4F -C5 -FE -CA -55 -05 -1F -8E -F0 -F0 -8E -20 -05 -55 -CA -FE -C3 -4D -03 -26 -97 -F4 -EB -83 -18 -08 -61 -D5 -FD -B7 -40 -01 -31 -A7 -FA -E0 -72 -0F -10 -74 -E2 -F9 -A4 -2F -01 -44 -BB -FE -D0 -5A -06 -1E -8D -F0 -EF -8A -1C -07 -5E -D3 -FD -B7 -3F -01 -34 -AB -FB -DC -69 -0B -15 -80 -EA -F4 -95 -23 -04 -55 -CC -FE -BD -45 -01 -30 -A7 -FA -DE -6B -0B -15 -7F -EA -F3 -93 -21 -05 -59 -D0 -FE -B8 -3E -01 -37 -B0 -FC -D6 -60 -07 -1C -8D -F1 -ED -84 -17 -0A -6A -DD -FA -A6 -2E -02 -4A -C4 -FE -C3 -49 -02 -2F -A7 -FB -DC -67 -09 -19 -89 -F0 -EE -85 -17 -0B -6B -DF -F9 -A2 -2A -03 -50 -CA -FE -BB -40 -01 -39 -B3 -FD -D1 -58 -04 -25 -9B -F7 -E2 -70 -0C -16 -84 -EE -EF -87 -17 -0B -6D -E1 -F8 -9D -25 -04 -58 -D2 -FD -B0 -35 -01 -46 -C2 -FE -C1 -45 -01 -36 -B2 -FD -D0 -55 -03 -29 -A2 -FA -DC -65 -08 -1E -92 -F5 -E6 -74 -0D -15 -84 -EF -ED -82 -14 -0E -77 -E8 -F3 -8E -1B -09 -6B -E1 -F7 -99 -22 -06 -61 -DA -FA -A2 -28 -04 -58 -D4 -FC -AB -2E -02 -51 -CE -FD -B1 -34 -01 -4B -C9 -FE -B6 -38 -01 -47 -C5 -FE -BA -3C -01 -43 -C2 -FE -BD -3E -01 -41 -C0 -FE -BF -40 -01 -40 -BF -FF -BF -40 -01 -40 -BF -FE -BE -3F -01 -42 -C1 -FE -BC -3D -01 -44 -C4 -FE -B9 -3A -01 -48 -C7 -FE -B5 -36 -01 -4D -CC -FD -AF -30 -02 -53 -D1 -FC -A8 -2A -04 -5B -D8 -FA -9F -24 -06 -64 -DE -F7 -96 -1D -09 -6F -E5 -F3 -8A -16 -0E -7B -EC -ED -7D -0F -15 -89 -F3 -E6 -6F -09 -1E -97 -F8 -DC -60 -04 -29 -A7 -FC -D0 -50 -01 -36 -B7 -FE -C1 -40 -01 -46 -C7 -FE -B0 -30 -03 -58 -D7 -FA -9D -21 -08 -6D -E5 -F3 -87 -13 -12 -84 -F1 -E7 -70 -09 -1F -9B -FA -D7 -58 -02 -32 -B3 -FE -C3 -40 -01 -48 -CA -FD -AB -2A -04 -62 -DF -F6 -8F -17 -0F -7F -F0 -E9 -71 -09 -20 -9D -FB -D4 -53 -02 -37 -BA -FE -BA -37 -02 -54 -D5 -FA -9B -1E -0A -75 -EB -ED -78 -0B -1C -99 -FA -D6 -55 -02 -37 -BB -FE -B8 -34 -02 -59 -DA -F8 -93 -19 -0E -7F -F1 -E6 -6B -07 -26 -A7 -FD -C9 -45 -01 -48 -CC -FC -A3 -23 -08 -71 -EA -ED -78 -0B -1E -9D -FB -D1 -4D -01 -41 -C7 -FD -A9 -26 -07 -6D -E8 -EF -7A -0B -1E -9D -FB -D0 -4B -01 -44 -C9 -FD -A4 -23 -09 -74 -EC -EB -72 -08 -24 -A7 -FD -C7 -40 -01 -50 -D5 -F9 -95 -18 -10 -85 -F4 -E0 -5F -03 -34 -BA -FE -B3 -2D -05 -67 -E5 -F0 -7B -0B -1F -A1 -FC -CA -43 -01 -4F -D4 -F9 -93 -17 -12 -8A -F6 -DB -57 -01 -3C -C4 -FD -A6 -23 -09 -78 -EF -E6 -68 -04 -2F -B6 -FE -B5 -2E -05 -6A -E8 -ED -74 -08 -26 -AB -FE -BE -36 -03 -60 -E2 -F2 -7D -0B -21 -A4 -FD -C4 -3B -02 -5B -DF -F3 -81 -0D -1F -A2 -FD -C6 -3C -02 -5A -DF -F3 -80 -0C -1F -A3 -FD -C3 -3A -02 -5E -E1 -F2 -7B -0A -23 -A9 -FE -BD -34 -04 -66 -E7 -ED -72 -07 -2B -B3 -FF -B3 -2A -07 -73 -EE -E6 -64 -03 -36 -C1 -FE -A4 -1F -0D -84 -F5 -DA -53 -01 -47 -D1 -FA -90 -13 -18 -99 -FC -C9 -3E -02 -5D -E2 -F1 -77 -08 -29 -B2 -FE -B2 -28 -08 -78 -F1 -E1 -5B -01 -41 -CC -FB -94 -14 -17 -99 -FC -C8 -3C -02 -61 -E5 -ED -70 -05 -30 -BB -FE -A6 -1F -0E -88 -F7 -D4 -49 -01 -53 -DC -F3 -7C -09 -27 -B2 -FE -AF -25 -0A -80 -F5 -D9 -4F -01 -4F -D9 -F5 -7F -0A -26 -B1 -FE -AF -25 -0B -82 -F6 -D7 -4C -01 -53 -DD -F2 -79 -08 -2C -B8 -FE -A6 -1E -10 -8D -FA -CE -40 -02 -61 -E6 -EB -69 -03 -39 -C7 -FB -94 -13 -1A -A1 -FE -BB -2E -07 -78 -F3 -DC -51 -01 -51 -DC -F2 -77 -07 -2F -BD -FD -9E -18 -15 -99 -FD -C1 -32 -05 -74 -F1 -DE -53 -01 -50 -DC -F2 -76 -06 -31 -C1 -FD -99 -14 -19 -A1 -FE -B9 -2A -09 -7F -F6 -D4 -46 -01 -5F -E7 -EA -64 -02 -42 -D1 -F7 -83 -0A -29 -B7 -FE -A1 -19 -15 -9B -FD -BC -2D -08 -7F -F6 -D4 -44 -02 -63 -E9 -E6 -5D -01 -49 -D8 -F3 -77 -06 -33 -C4 -FC -91 -0F -21 -AE -FE -A9 -1D -12 -97 -FD -BE -2E -08 -80 -F7 -D1 -40 -02 -6A -EE -E1 -54 -01 -55 -E2 -ED -68 -02 -42 -D4 -F6 -7B -07 -32 -C4 -FB -8E -0E -24 -B4 -FE -A0 -17 -19 -A4 -FE -B0 -21 -10 -94 -FC -BF -2D -09 -84 -F9 -CB -38 -05 -75 -F4 -D6 -45 -02 -68 -ED -E0 -51 -01 -5B -E7 -E7 -5C -01 -50 -DF -ED -67 -02 -46 -D8 -F2 -72 -04 -3D -D1 -F6 -7B -06 -36 -C9 -F9 -84 -09 -2F -C3 -FB -8B -0B -2A -BD -FC -92 -0E -25 -B7 -FD -98 -11 -21 -B2 -FE -9D -13 -1E -AE -FE -A0 -15 -1C -AB -FE -A3 -17 -1A -A9 -FE -A5 -18 -19 -A7 -FE -A6 -19 -19 -A7 -FF -A7 -19 -19 -A7 -FE -A6 -18 -19 -A8 -FE -A5 -17 -1B -AA -FE -A2 -16 -1C -AD -FE -9F -14 -1F -B1 -FE -9B -11 -22 -B5 -FD -96 -0F -26 -BA -FC -8F -0C -2B -C0 -FB -88 -09 -30 -C7 -F9 -80 -06 -37 -CE -F6 -77 -04 -3F -D5 -F2 -6E -02 -48 -DC -ED -63 -01 -52 -E4 -E7 -58 -01 -5E -EB -E0 -4C -02 -6A -F1 -D6 -40 -04 -78 -F7 -CB -34 -08 -87 -FB -BF -28 -0E -97 -FE -B0 -1D -17 -A7 -FE -A0 -13 -22 -B7 -FD -8E -0B -2F -C7 -F8 -7B -04 -3F -D6 -F1 -68 -01 -51 -E4 -E6 -54 -01 -65 -F0 -D8 -40 -04 -7B -F8 -C6 -2E -0C -92 -FD -B2 -1D -17 -A9 -FE -9B -0F -27 -C0 -FA -82 -06 -3C -D5 -F1 -68 -01 -53 -E6 -E3 -4E -02 -6E -F4 -CF -36 -08 -8A -FC -B7 -20 -15 -A7 -FE -9B -0F -29 -C2 -F9 -7D -04 -42 -DA -ED -5E -01 -5F -EE -D9 -40 -05 -7F -FA -BF -26 -12 -A1 -FE -9F -11 -27 -C1 -FA -7D -04 -43 -DC -EB -5A -01 -65 -F1 -D3 -38 -08 -8A -FD -B4 -1D -19 -AF -FD -8F -09 -35 -D1 -F2 -68 -01 -58 -EB -DC -42 -05 -80 -FB -BB -22 -15 -A9 -FE -94 -0B -32 -CE -F3 -69 -01 -58 -EB -DB -40 -05 -84 -FC -B7 -1E -19 -B0 -FD -8B -08 -3A -D6 -EE -5D -01 -65 -F2 -D0 -33 -0B -95 -FE -A6 -13 -26 -C2 -F8 -75 -02 -4F -E6 -E0 -46 -04 -80 -FB -B8 -1E -1A -B2 -FD -87 -06 -40 -DC -E9 -53 -02 -72 -F7 -C3 -26 -13 -A7 -FE -91 -09 -39 -D6 -ED -5A -01 -6C -F5 -C8 -2A -11 -A4 -FE -94 -0A -37 -D5 -EE -5B -01 -6D -F6 -C7 -28 -12 -A7 -FE -8F -08 -3C -D9 -EB -54 -02 -74 -F9 -BF -22 -18 -B1 -FC -84 -04 -47 -E2 -E3 -47 -04 -84 -FC -B0 -17 -23 -C1 -F8 -71 -01 -59 -EE -D4 -35 -0C -9A -FF -9A -0B -35 -D5 -ED -58 -01 -73 -F9 -BD -20 -1A -B6 -FB -7B -02 -51 -EA -D9 -3A -09 -96 -FE -9D -0C -34 -D5 -ED -56 -02 -77 -FA -B9 -1C -1F -BD -F9 -72 -01 -5B -F0 -D0 -2F -10 -A4 -FE -8C -06 -44 -E2 -E1 -43 -06 -8D -FE -A2 -0E -31 -D3 -ED -56 -02 -79 -FB -B5 -18 -23 -C4 -F6 -68 -01 -68 -F6 -C3 -23 -19 -B6 -FA -77 -01 -5A -F0 -CF -2D -12 -AA -FD -83 -03 -4F -EA -D7 -35 -0D -A1 -FE -8C -05 -47 -E5 -DC -3B -0A -9A -FE -92 -07 -42 -E2 -E0 -3F -09 -96 -FE -95 -08 -40 -E1 -E1 -40 -08 -96 -FE -95 -08 -41 -E1 -E0 -3F -09 -98 -FE -92 -07 -44 -E4 -DD -3B -0B -9D -FE -8D -05 -49 -E8 -D8 -35 -0E -A5 -FD -84 -03 -52 -ED -D1 -2D -13 -AF -FB -78 -01 -5E -F3 -C6 -23 -1B -BC -F7 -69 -01 -6D -F9 -B8 -18 -26 -CA -F1 -58 -02 -7F -FD -A7 -0E -35 -DA -E6 -45 -07 -95 -FE -91 -06 -49 -E8 -D6 -31 -11 -AC -FC -78 -01 -61 -F5 -C1 -1E -21 -C5 -F3 -5C -02 -7D -FD -A7 -0E -37 -DC -E3 -40 -09 -9D -FE -87 -03 -54 -F0 -CB -25 -1A -BD -F6 -64 -01 -78 -FC -AB -0F -35 -DB -E4 -40 -0A -9E -FE -84 -02 -59 -F2 -C6 -20 -1F -C5 -F2 -5A -02 -84 -FE -9D -09 -42 -E5 -D8 -31 -12 -B1 -FA -6F -01 -6F -FA -B0 -12 -32 -D9 -E4 -40 -0A -A1 -FD -7E -01 -61 -F6 -BC -19 -29 -D1 -EB -49 -07 -97 -FE -87 -03 -59 -F3 -C3 -1D -24 -CC -ED -4E -05 -94 -FE -8A -03 -57 -F2 -C3 -1D -24 -CC -ED -4D -06 -96 -FE -87 -02 -5B -F4 -BF -1A -29 -D1 -E9 -46 -09 -9E -FD -7D -01 -65 -F8 -B5 -13 -32 -DB -E1 -3A -0E -AC -FB -6E -01 -76 -FC -A4 -0B -42 -E7 -D4 -2A -19 -BF -F3 -58 -03 -8D -FF -8D -03 -58 -F4 -BF -18 -2B -D5 -E5 -3E -0C -A9 -FB -6F -01 -77 -FD -A1 -09 -47 -EB -CE -24 -1F -C9 -EE -4C -07 -9C -FD -7B -01 -6C -FB -AB -0D -3F -E6 -D3 -28 -1B -C4 -F0 -50 -06 -99 -FE -7D -01 -6B -FB -AA -0C -41 -E8 -D1 -26 -1E -C8 -ED -4A -08 -A1 -FD -74 -01 -75 -FD -9F -08 -4C -EF -C6 -1C -29 -D5 -E4 -3B -0F -B2 -F8 -61 -02 -8A -FE -89 -02 -62 -F8 -B0 -0E -3D -E6 -D2 -25 -1F -CB -EB -45 -0B -A9 -FA -69 -01 -84 -FE -8E -03 -5F -F7 -B2 -0F -3D -E7 -D1 -24 -21 -CE -E9 -40 -0D -AF -F8 -60 -03 -8D -FE -83 -01 -6B -FB -A4 -09 -4B -F0 -C3 -18 -2F -DC -DC -2E -19 -C4 -EF -49 -09 -A7 -FA -67 -02 -88 -FE -86 -01 -6A -FB -A4 -08 -4D -F1 -BF -15 -34 -E1 -D6 -28 -1F -CC -E9 -3E -0F -B4 -F6 -58 -05 -9A -FD -72 -01 -7F -FE -8D -02 -65 -FA -A6 -09 -4D -F1 -BD -14 -37 -E4 -D2 -23 -24 -D4 -E3 -35 -15 -C0 -F0 -49 -0A -AB -F9 -5E -04 -96 -FD -74 -01 -80 -FE -8A -02 -6A -FC -9F -06 -56 -F6 -B2 -0D -44 -ED -C4 -17 -33 -E2 -D3 -23 -25 -D5 -E0 -31 -19 -C7 -EB -40 -0F -B7 -F3 -50 -08 -A7 -F9 -60 -04 -97 -FD -70 -01 -88 -FE -7F -01 -78 -FE -8E -02 -6A -FC -9D -05 -5C -F8 -AA -09 -4F -F4 -B6 -0E -43 -EE -C1 -14 -39 -E7 -CB -1B -2F -E0 -D4 -23 -27 -D8 -DC -2B -1F -D1 -E3 -32 -19 -C9 -E9 -3A -14 -C1 -ED -42 -0F -B9 -F2 -49 -0C -B2 -F5 -51 -09 -AB -F7 -57 -06 -A4 -F9 -5E -05 -9E -FB -64 -03 -99 -FC -69 -02 -94 -FD -6E -02 -8F -FE -72 -01 -8B -FE -76 -01 -88 -FE -79 -01 -85 -FE -7B -01 -83 -FE -7D -01 -81 -FE -7F -01 -80 -FE -7F -01 -80 diff --git a/9_Firmware/9_2_FPGA/plfm_chirp_controller.v b/9_Firmware/9_2_FPGA/plfm_chirp_controller.v deleted file mode 100644 index f61b767..0000000 --- a/9_Firmware/9_2_FPGA/plfm_chirp_controller.v +++ /dev/null @@ -1,382 +0,0 @@ -`timescale 1ns / 1ps - -`include "radar_params.vh" - -module plfm_chirp_controller_enhanced ( - input wire clk_120m, - input wire clk_100m, - input wire reset_n, - input wire new_chirp, - input wire new_elevation, - input wire new_azimuth, - input wire mixers_enable, - output reg [7:0] chirp_data, - output reg chirp_valid, - output wire new_chirp_frame, - output reg chirp_done, - output reg rf_switch_ctrl, - output wire rx_mixer_en, - output wire tx_mixer_en, - output wire adar_tx_load_1, - output wire adar_rx_load_1, - output wire adar_tx_load_2, - output wire adar_rx_load_2, - output wire adar_tx_load_3, - output wire adar_rx_load_3, - output wire adar_tx_load_4, - output wire adar_rx_load_4, - output reg adar_tr_1, - output reg adar_tr_2, - output reg adar_tr_3, - output reg adar_tr_4, - output reg [5:0] chirp_counter, - output reg [5:0] elevation_counter, - output reg [5:0] azimuth_counter -); - -// Chirp parameters -parameter F_START = 30000000; // 30 MHz (starting frequency) -parameter F_END = 10000000; // 10 MHz (ending frequency) -parameter FS = 120000000; // 120 MHz - -// Timing parameters -parameter T1_SAMPLES = 3600; // 30us at 120MHz -parameter T1_RADAR_LISTENING = 16440; //137us at 120MHz -parameter T2_SAMPLES = 60; // 0.5us at 120MHz -parameter T2_RADAR_LISTENING = 20940; //174.5us at 120MHz -parameter GUARD_SAMPLES = 21048; // 175.4us at 120MHz - -// Chirp and beam parameters -parameter CHIRP_MAX = `RP_CHIRPS_PER_FRAME; -parameter ELEVATION_MAX = 31; -parameter AZIMUTH_MAX = 50; - -// State parameters -parameter IDLE = 3'b000; -parameter LONG_CHIRP = 3'b001; -parameter LONG_LISTEN = 3'b010; -parameter GUARD_TIME = 3'b011; -parameter SHORT_CHIRP = 3'b100; -parameter SHORT_LISTEN = 3'b101; -parameter DONE = 3'b110; - -reg [2:0] current_state; -reg [2:0] next_state; - -// Control registers -reg [15:0] sample_counter; - -// Edge detection for input signals -wire chirp__toggling, elevation__toggling, azimuth__toggling; - -// LUTs for chirp waveforms -(* ram_style = "block" *) reg [7:0] long_chirp_lut [0:3599]; // T1_SAMPLES-1 -reg [7:0] short_chirp_lut [0:59]; // T2_SAMPLES-1 - -// Registered BRAM read output (sync-only for BRAM inference) -reg [7:0] long_chirp_rd_data; - -// Edge detection -assign chirp__toggling = new_chirp; -assign elevation__toggling = new_elevation; -assign azimuth__toggling = new_azimuth; -assign new_chirp_frame = (current_state == IDLE && next_state == LONG_CHIRP); - -// Mixer TX/RX sequencing — mutually exclusive based on chirp FSM state. -// TX mixer active during chirp transmission, RX mixer during listen. -// Both require mixers_enable (STM32 master enable) to be high. -assign tx_mixer_en = mixers_enable && (current_state == LONG_CHIRP || - current_state == SHORT_CHIRP); -assign rx_mixer_en = mixers_enable && (current_state == LONG_LISTEN || - current_state == SHORT_LISTEN); - -// ADTR1000 pull to ground tx and rx load pins if not used -assign adar_tx_load_1 = 1'b0; -assign adar_rx_load_1 = 1'b0; -assign adar_tx_load_2 = 1'b0; -assign adar_rx_load_2 = 1'b0; -assign adar_tx_load_3 = 1'b0; -assign adar_rx_load_3 = 1'b0; -assign adar_tx_load_4 = 1'b0; -assign adar_rx_load_4 = 1'b0; - - - - -// LUT Initialization -// Long PLFM chirp LUT loaded from .mem file for BRAM inference -initial begin - $readmemh("long_chirp_lut.mem", long_chirp_lut); -end - -// Synchronous-only BRAM read (no async reset) for BRAM inference -always @(posedge clk_120m) begin - long_chirp_rd_data <= long_chirp_lut[sample_counter]; -end - -// Short PLFM chirp LUT initialization (too small for BRAM, keep inline) -// -// TX-I (analyzed 2026-04-28; tb/cosim/analyze_short_chirp_mismatch.py): -// 60 samples @ fs_dac=120 MHz over 0.5 us, real-valued passband. -// Hilbert analysis confirms an UPCHIRP from ~10 to ~30 MHz baseband -// (BW ~19.4 MHz). The old comment "30MHz to 10MHz" had the sweep -// direction reversed and is corrected below. -// -// End-to-end frequency plan (from adf4382a_manager.h + ddc_400m.v): -// TX upmix: LO=10.500 GHz, high-side -> RF: 10.510..10.530 GHz -// RX downmix: LO=10.380 GHz, high-side -> IF: 130..150 MHz -// DDC NCO: 120 MHz exactly -> baseband: 10..30 MHz -// The matched-filter reference in tb/cosim/gen_chirp_mem.py was updated -// to include the +10 MHz baseband offset to match this band. -initial begin - // Complete Short PLFM chirp LUT (0.5us, ~10MHz to ~30MHz upchirp) - short_chirp_lut[ 0] = 8'd255; short_chirp_lut[ 1] = 8'd237; short_chirp_lut[ 2] = 8'd187; short_chirp_lut[ 3] = 8'd118; short_chirp_lut[ 4] = 8'd 49; short_chirp_lut[ 5] = 8'd 6; short_chirp_lut[ 6] = 8'd 7; short_chirp_lut[ 7] = 8'd 54; - short_chirp_lut[ 8] = 8'd132; short_chirp_lut[ 9] = 8'd210; short_chirp_lut[10] = 8'd253; short_chirp_lut[11] = 8'd237; short_chirp_lut[12] = 8'd167; short_chirp_lut[13] = 8'd 75; short_chirp_lut[14] = 8'd 10; short_chirp_lut[15] = 8'd 10; - short_chirp_lut[16] = 8'd 80; short_chirp_lut[17] = 8'd180; short_chirp_lut[18] = 8'd248; short_chirp_lut[19] = 8'd237; short_chirp_lut[20] = 8'd150; short_chirp_lut[21] = 8'd 45; short_chirp_lut[22] = 8'd 1; short_chirp_lut[23] = 8'd 54; - short_chirp_lut[24] = 8'd167; short_chirp_lut[25] = 8'd249; short_chirp_lut[26] = 8'd228; short_chirp_lut[27] = 8'd118; short_chirp_lut[28] = 8'd 15; short_chirp_lut[29] = 8'd 18; short_chirp_lut[30] = 8'd127; short_chirp_lut[31] = 8'd238; - short_chirp_lut[32] = 8'd235; short_chirp_lut[33] = 8'd118; short_chirp_lut[34] = 8'd 10; short_chirp_lut[35] = 8'd 34; short_chirp_lut[36] = 8'd167; short_chirp_lut[37] = 8'd254; short_chirp_lut[38] = 8'd187; short_chirp_lut[39] = 8'd 45; - short_chirp_lut[40] = 8'd 8; short_chirp_lut[41] = 8'd129; short_chirp_lut[42] = 8'd248; short_chirp_lut[43] = 8'd201; short_chirp_lut[44] = 8'd 49; short_chirp_lut[45] = 8'd 10; short_chirp_lut[46] = 8'd145; short_chirp_lut[47] = 8'd254; - short_chirp_lut[48] = 8'd167; short_chirp_lut[49] = 8'd 17; short_chirp_lut[50] = 8'd 46; short_chirp_lut[51] = 8'd210; short_chirp_lut[52] = 8'd235; short_chirp_lut[53] = 8'd 75; short_chirp_lut[54] = 8'd 7; short_chirp_lut[55] = 8'd155; - short_chirp_lut[56] = 8'd253; short_chirp_lut[57] = 8'd118; short_chirp_lut[58] = 8'd 1; short_chirp_lut[59] = 8'd129; -end - -// chirp_counter is driven solely by the clk_120m FSM always block (line ~683). -// Removed redundant clk_100m driver that caused multi-driven register -// (synthesis failure, simulation race condition). -// The FSM internally sequences through CHIRP_MAX chirps per beam position, -// so external new_chirp edge counting is unnecessary here. - -// Elevation counter - -always @(posedge clk_100m or negedge reset_n) begin - if (!reset_n) begin - elevation_counter <= 6'b1; - end else begin - if (elevation__toggling) begin - if (elevation_counter == ELEVATION_MAX) begin - elevation_counter <= 6'b1; - end else begin - elevation_counter <= elevation_counter + 6'b1; - end - end -end -end - - -// Azimuth counter - -always @(posedge clk_100m or negedge reset_n) begin - if (!reset_n) begin - azimuth_counter <= 6'd1; - end else begin - if (azimuth__toggling) begin - if (azimuth_counter == AZIMUTH_MAX) begin - azimuth_counter <= 6'd1; - end else begin - azimuth_counter <= azimuth_counter + 6'd1; - end - end -end -end - -// State register -always @(posedge clk_120m or negedge reset_n) begin - if (!reset_n) begin - current_state <= IDLE; - end else begin - current_state <= next_state; - end -end - -// Next state logic -always @(*) begin - case (current_state) - IDLE: begin - if (chirp__toggling && mixers_enable) - next_state = LONG_CHIRP; - else - next_state = IDLE; - end - - LONG_CHIRP: begin - if (sample_counter == T1_SAMPLES-1) - next_state = LONG_LISTEN; - else - next_state = LONG_CHIRP; - end - - LONG_LISTEN: begin - if (sample_counter == T1_RADAR_LISTENING-1) begin - if (chirp_counter == (CHIRP_MAX/2)-1) - next_state = GUARD_TIME; - else - next_state = LONG_CHIRP; - end else begin - next_state = LONG_LISTEN; - end - end - - GUARD_TIME: begin - if (sample_counter == GUARD_SAMPLES-1) - next_state = SHORT_CHIRP; - else - next_state = GUARD_TIME; - end - - SHORT_CHIRP: begin - if (sample_counter == T2_SAMPLES-1) - next_state = SHORT_LISTEN; - else - next_state = SHORT_CHIRP; - end - - SHORT_LISTEN: begin - if (sample_counter == T2_RADAR_LISTENING-1) begin - if (chirp_counter == CHIRP_MAX-1) - next_state = DONE; - else - next_state = SHORT_CHIRP; - end else begin - next_state = SHORT_LISTEN; - end - end - - DONE: begin - next_state = IDLE; - end - - default: begin - next_state = IDLE; - end - endcase -end - -always @(posedge clk_120m or negedge reset_n) begin - if (!reset_n) begin - sample_counter <= 0; - chirp_counter <= 0; - chirp_valid <= 0; - chirp_done <= 0; - chirp_data <= 8'd128; - rf_switch_ctrl <= 1'b0; - adar_tr_1 <= 1'b0; - adar_tr_2 <= 1'b0; - adar_tr_3 <= 1'b0; - adar_tr_4 <= 1'b0; - end else if (mixers_enable) begin - // Default outputs - chirp_valid <= 0; - chirp_done <= 0; - rf_switch_ctrl <= 0; - {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000; - - // Sample counter increment logic - if (current_state == LONG_CHIRP || current_state == LONG_LISTEN || - current_state == GUARD_TIME || current_state == SHORT_CHIRP || - current_state == SHORT_LISTEN) begin - if (sample_counter == get_max_counter(current_state) - 1) begin - sample_counter <= 0; - // Increment chirp counter at end of listen states - if (current_state == LONG_LISTEN || current_state == SHORT_LISTEN) begin - chirp_counter <= chirp_counter + 1; - end - end else begin - sample_counter <= sample_counter + 1; - end - end else begin - sample_counter <= 0; - end - - // State-specific outputs - case (current_state) - IDLE: begin - chirp_data <= 8'd128; - end - - // chirp_valid policy (LONG_CHIRP + SHORT_CHIRP states): assert - // chirp_valid HIGH for the entire active-sample window of each - // chirp (sample_counter < T?_SAMPLES) so the downstream DAC sees a - // continuous data-valid pulse, then ride out the remaining state - // duration on idle code 8'd128. Without the per-cycle assert, - // downstream FIFOs underrun on the trailing samples of each chirp. - LONG_CHIRP: begin - rf_switch_ctrl <= 1'b1; - {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111; - - if (sample_counter < T1_SAMPLES) begin - chirp_data <= long_chirp_rd_data; - chirp_valid <= 1'b1; // Valid during entire chirp - end else begin - chirp_data <= 8'd128; - end - end - - LONG_LISTEN: begin - chirp_data <= 8'd128; - rf_switch_ctrl <= 1'b0; - end - - GUARD_TIME: begin - chirp_data <= 8'd128; - rf_switch_ctrl <= 1'b0; - end - - SHORT_CHIRP: begin - rf_switch_ctrl <= 1'b1; - {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111; - - /* see chirp_valid policy block above LONG_CHIRP */ - if (sample_counter < T2_SAMPLES) begin - chirp_data <= short_chirp_lut[sample_counter]; - chirp_valid <= 1'b1; // Valid during entire chirp - end else begin - chirp_data <= 8'd128; - end - end - - SHORT_LISTEN: begin - chirp_data <= 8'd128; - rf_switch_ctrl <= 1'b0; - end - - DONE: begin - // Reset chirp_counter so the next frame restarts at chirp 0. - // Without this, frame 2+ starts at chirp_counter == CHIRP_MAX - // and the LONG_LISTEN transition guard (== CHIRP_MAX/2-1) - // never matches on the correct chirp. - chirp_counter <= 0; - chirp_done <= 1'b1; - chirp_data <= 8'd128; - end - - default: begin - chirp_data <= 8'd128; - end - endcase - end else begin - // Mixers disabled - chirp_data <= 8'd128; - chirp_valid <= 0; - chirp_done <= 0; - rf_switch_ctrl <= 0; - {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000; - sample_counter <= 0; - end -end - -// Helper function to get max counter for each state -function [15:0] get_max_counter; - input [2:0] state; - begin - case (state) - LONG_CHIRP: get_max_counter = T1_SAMPLES; - LONG_LISTEN: get_max_counter = T1_RADAR_LISTENING; - GUARD_TIME: get_max_counter = GUARD_SAMPLES; - SHORT_CHIRP: get_max_counter = T2_SAMPLES; - SHORT_LISTEN: get_max_counter = T2_RADAR_LISTENING; - default: get_max_counter = 0; - endcase - end -endfunction - -endmodule \ No newline at end of file diff --git a/9_Firmware/9_2_FPGA/plfm_chirp_controller_v2.v b/9_Firmware/9_2_FPGA/plfm_chirp_controller_v2.v new file mode 100644 index 0000000..088c66f --- /dev/null +++ b/9_Firmware/9_2_FPGA/plfm_chirp_controller_v2.v @@ -0,0 +1,294 @@ +`timescale 1ns / 1ps + +`include "radar_params.vh" + +// ============================================================================ +// plfm_chirp_controller_v2.v — DAC playback driver for the 3-ladder waveform +// set (chirp-v2 PR-E). Replaces plfm_chirp_controller_enhanced. +// ============================================================================ +// chirp-v1 plfm_chirp_controller_enhanced owned an internal 6-state FSM +// (LONG_CHIRP / LONG_LISTEN / GUARD_TIME / SHORT_CHIRP / SHORT_LISTEN / DONE) +// that duplicated the timing the receiver-side mode controller already had. +// chirp-v2 makes the chirp_scheduler (clk_100m, RX side) authoritative for +// inter-chirp timing (LISTEN, GUARD, frame boundaries) and the wave_sel rail. +// +// This module is then a *pure DAC playback driver*: +// +// IDLE ──(dst_chirp_valid pulse)──> CHIRP ──(N samples played)──> IDLE +// +// where N depends on dst_wave_sel: +// +// RP_WAVE_SHORT → 120 samples @ 120 MHz = 1 µs +// RP_WAVE_MEDIUM → 600 samples @ 120 MHz = 5 µs +// RP_WAVE_LONG → 3600 samples @ 120 MHz = 30 µs +// +// Sample data sources (.mem files generated by tb/cosim/gen_chirp_mem.py +// PR-B; 8-bit unsigned offset-binary, idle code = 8'd128): +// +// tx_short_lut.mem (120 entries) — inline LUTRAM +// tx_medium_lut.mem (600 entries) — inline LUTRAM +// tx_long_lut.mem (3600 entries) — inferred BRAM (sync read) +// +// Inputs from the scheduler (clk_100m → clk_120m crossing handled in the +// parent radar_transmitter via cdc_async_fifo for {wave_sel} + toggle CDC +// for frame_pulse): +// +// dst_chirp_valid 1-cycle pulse on clk_120m, "begin a new chirp" +// dst_wave_sel[1:0] valid alongside dst_chirp_valid; one of RP_WAVE_* +// frame_pulse_120m 1-cycle pulse on clk_120m, "frame boundary" +// (clears chirp_counter so frame N+1 starts at 0) +// +// Mixer / RF-switch / ADAR T-R: +// tx_mixer_en, rf_switch_ctrl, adar_tr_* asserted during ST_CHIRP only. +// rx_mixer_en asserted whenever mixers_enable=1 and we're NOT in ST_CHIRP +// (i.e. the radar is listening). v1 gated rx_mixer_en strictly to +// {LONG,SHORT}_LISTEN; v2 also covers the scheduler's GUARD window. The +// GUARD window is RF-quiet by construction so the radar sees nothing +// useful and the change is benign — revisit in PR-H if a finer gate is +// needed. +// +// Beam steering: +// elevation_counter / azimuth_counter remain on clk_100m and are bumped +// by the STM32 toggle inputs (independent of the chirp FSM), unchanged +// from v1. +// +// chirp_counter: +// Increments by 1 each time we leave ST_CHIRP (i.e. one chirp finished). +// Cleared on frame_pulse_120m so it tracks chirp index within the current +// frame, matching the chirp_scheduler's chirp_counter. +// +// new_chirp_frame: +// Mirrors frame_pulse_120m. Same 1-cycle pulse semantics as the v1 output. +// ============================================================================ +module plfm_chirp_controller_v2 ( + input wire clk_120m, + input wire clk_100m, + input wire reset_n, // 120m-domain reset + input wire reset_100m_n, // 100m-domain reset (elev/az counters) + + input wire mixers_enable, // CDC-synced to clk_120m + + // From scheduler via cdc_async_fifo + toggle CDC (clk_120m domain) + input wire dst_chirp_valid, + input wire [1:0] dst_wave_sel, + input wire frame_pulse_120m, + + // STM32 beam-step toggle inputs (clk_100m, edge-detected upstream) + input wire new_elevation, + input wire new_azimuth, + + // DAC outputs + output reg [7:0] chirp_data, + output reg chirp_valid, + output wire new_chirp_frame, + output reg chirp_done, + output reg rf_switch_ctrl, + output wire rx_mixer_en, + output wire tx_mixer_en, + + // ADAR control (loads tied off; T/R pulsed during chirp) + output wire adar_tx_load_1, + output wire adar_rx_load_1, + output wire adar_tx_load_2, + output wire adar_rx_load_2, + output wire adar_tx_load_3, + output wire adar_rx_load_3, + output wire adar_tx_load_4, + output wire adar_rx_load_4, + output reg adar_tr_1, + output reg adar_tr_2, + output reg adar_tr_3, + output reg adar_tr_4, + + // Status counters + output reg [5:0] chirp_counter, + output reg [5:0] elevation_counter, + output reg [5:0] azimuth_counter +); + +// ---------------------------------------------------------------------------- +// Waveform sample counts (must match tb/cosim/gen_chirp_mem.py) +// ---------------------------------------------------------------------------- +localparam SHORT_SAMPLES = 12'd120; +localparam MEDIUM_SAMPLES = 12'd600; +localparam LONG_SAMPLES = 12'd3600; + +// Beam ranges +parameter ELEVATION_MAX = 31; +parameter AZIMUTH_MAX = 50; + +// ---------------------------------------------------------------------------- +// FSM +// ---------------------------------------------------------------------------- +localparam ST_IDLE = 1'b0; +localparam ST_CHIRP = 1'b1; + +reg state; +reg [1:0] active_wave_sel; +reg [11:0] sample_counter; +reg [11:0] active_max_samples; + +// ---------------------------------------------------------------------------- +// LUTs — long is BRAM (sync read), short/medium are inline LUTRAM (combinational) +// ---------------------------------------------------------------------------- +(* ram_style = "block" *) reg [7:0] tx_long_lut [0:LONG_SAMPLES-1]; + reg [7:0] tx_medium_lut [0:MEDIUM_SAMPLES-1]; + reg [7:0] tx_short_lut [0:SHORT_SAMPLES-1]; + +reg [7:0] long_rd_data; +always @(posedge clk_120m) begin + long_rd_data <= tx_long_lut[sample_counter]; +end + +initial begin + $readmemh("tx_long_lut.mem", tx_long_lut); + $readmemh("tx_medium_lut.mem", tx_medium_lut); + $readmemh("tx_short_lut.mem", tx_short_lut); +end + +// ---------------------------------------------------------------------------- +// Combinational helpers +// ---------------------------------------------------------------------------- +function [11:0] wave_to_samples; + input [1:0] w; + begin + case (w) + `RP_WAVE_SHORT: wave_to_samples = SHORT_SAMPLES; + `RP_WAVE_MEDIUM: wave_to_samples = MEDIUM_SAMPLES; + `RP_WAVE_LONG: wave_to_samples = LONG_SAMPLES; + default: wave_to_samples = SHORT_SAMPLES; // RESERVED → safe fallback + endcase + end +endfunction + +reg [7:0] active_sample_data; +always @(*) begin + case (active_wave_sel) + `RP_WAVE_SHORT: active_sample_data = tx_short_lut [sample_counter[6:0]]; + `RP_WAVE_MEDIUM: active_sample_data = tx_medium_lut[sample_counter[9:0]]; + `RP_WAVE_LONG: active_sample_data = long_rd_data; + default: active_sample_data = 8'd128; + endcase +end + +// ---------------------------------------------------------------------------- +// Static outputs +// ---------------------------------------------------------------------------- +assign new_chirp_frame = frame_pulse_120m; +assign tx_mixer_en = mixers_enable && (state == ST_CHIRP); +assign rx_mixer_en = mixers_enable && (state != ST_CHIRP); + +// ADTR1000 load pins are unused on this board — tie low. +assign adar_tx_load_1 = 1'b0; +assign adar_rx_load_1 = 1'b0; +assign adar_tx_load_2 = 1'b0; +assign adar_rx_load_2 = 1'b0; +assign adar_tx_load_3 = 1'b0; +assign adar_rx_load_3 = 1'b0; +assign adar_tx_load_4 = 1'b0; +assign adar_rx_load_4 = 1'b0; + +// ---------------------------------------------------------------------------- +// Main FSM (clk_120m) +// ---------------------------------------------------------------------------- +always @(posedge clk_120m or negedge reset_n) begin + if (!reset_n) begin + state <= ST_IDLE; + active_wave_sel <= `RP_WAVE_SHORT; + sample_counter <= 12'd0; + active_max_samples <= 12'd0; + chirp_data <= 8'd128; + chirp_valid <= 1'b0; + chirp_done <= 1'b0; + rf_switch_ctrl <= 1'b0; + adar_tr_1 <= 1'b0; + adar_tr_2 <= 1'b0; + adar_tr_3 <= 1'b0; + adar_tr_4 <= 1'b0; + chirp_counter <= 6'd0; + end else if (mixers_enable) begin + chirp_done <= 1'b0; // default: deassert pulse + + case (state) + ST_IDLE: begin + chirp_data <= 8'd128; + chirp_valid <= 1'b0; + rf_switch_ctrl <= 1'b0; + {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000; + + if (dst_chirp_valid) begin + state <= ST_CHIRP; + active_wave_sel <= dst_wave_sel; + active_max_samples <= wave_to_samples(dst_wave_sel); + sample_counter <= 12'd0; + end + end + + ST_CHIRP: begin + rf_switch_ctrl <= 1'b1; + {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b1111; + chirp_data <= active_sample_data; + chirp_valid <= 1'b1; + + if (sample_counter == active_max_samples - 12'd1) begin + // End of this chirp — return to IDLE, pulse chirp_done, + // and bump chirp_counter (frame_pulse will clear it below). + state <= ST_IDLE; + sample_counter <= 12'd0; + chirp_data <= 8'd128; + chirp_valid <= 1'b0; + chirp_done <= 1'b1; + rf_switch_ctrl <= 1'b0; + {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000; + chirp_counter <= chirp_counter + 6'd1; + end else begin + sample_counter <= sample_counter + 12'd1; + end + end + + default: state <= ST_IDLE; + endcase + + // frame_pulse always wins — restart chirp index for the next frame. + if (frame_pulse_120m) begin + chirp_counter <= 6'd0; + end + end else begin + // mixers_enable LOW: hold quiescent (and reset the FSM so a glitch on + // dst_chirp_valid during disable can't sneak us into ST_CHIRP). + state <= ST_IDLE; + chirp_data <= 8'd128; + chirp_valid <= 1'b0; + chirp_done <= 1'b0; + rf_switch_ctrl <= 1'b0; + {adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4} <= 4'b0000; + sample_counter <= 12'd0; + end +end + +// ---------------------------------------------------------------------------- +// Beam steering counters (clk_100m, independent of chirp FSM) — unchanged from v1. +// ---------------------------------------------------------------------------- +always @(posedge clk_100m or negedge reset_100m_n) begin + if (!reset_100m_n) begin + elevation_counter <= 6'd1; + end else if (new_elevation) begin + if (elevation_counter == ELEVATION_MAX) + elevation_counter <= 6'd1; + else + elevation_counter <= elevation_counter + 6'd1; + end +end + +always @(posedge clk_100m or negedge reset_100m_n) begin + if (!reset_100m_n) begin + azimuth_counter <= 6'd1; + end else if (new_azimuth) begin + if (azimuth_counter == AZIMUTH_MAX) + azimuth_counter <= 6'd1; + else + azimuth_counter <= azimuth_counter + 6'd1; + end +end + +endmodule diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index 5f49eee..a1de31a 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -69,6 +69,10 @@ module radar_receiver_final ( input wire stm32_new_elevation_rx, input wire stm32_new_azimuth_rx, + // PR-E: master mixers_enable in clk_100m domain — gates the scheduler + // so it stays in S_IDLE until the operator turns the radar on. + input wire mixers_enable_100m, + // CFAR integration: expose Doppler frame_complete to top level output wire doppler_frame_done_out, @@ -118,7 +122,15 @@ module radar_receiver_final ( // silent sample drop between the 400 MHz CIC output and the 100 MHz // FIR input; stays high until the next reset. OR'd into the GPIO // diagnostic bit at the top level. - output wire ddc_cic_fir_overrun + output wire ddc_cic_fir_overrun, + + // chirp_scheduler outputs exposed for the TX-side CDC bridge (PR-E). + // sched_chirp_pulse: 1-cycle pulse on clk that announces "begin chirp now" + // sched_wave_sel: waveform identity rail valid alongside chirp_pulse + // sched_frame_pulse: 1-cycle pulse on frame boundary (chirp_counter wrap) + output wire [1:0] sched_wave_sel_out, + output wire sched_chirp_pulse_out, + output wire sched_frame_pulse_out ); // ========== INTERNAL SIGNALS ========== @@ -215,6 +227,7 @@ wire mti_first_chirp; // wired here — the V2 sub-frame structure uses RP_DEF_CHIRPS_PER_SUBFRAME // (16) and PR-G renames the host register. chirp_scheduler sched ( + .mixers_enable(mixers_enable_100m), .clk(clk), .reset_n(reset_n), .host_mode(host_mode), @@ -250,6 +263,14 @@ chirp_scheduler sched ( .track_beam_az(sched_track_beam_az), .track_beam_el(sched_track_beam_el) ); + +// PR-E: forward scheduler pulses + wave_sel to the TX-side CDC bridge in +// radar_system_top. The transmitter does its own clk_100m → clk_120m_dac +// crossing via cdc_async_fifo + toggle CDC. +assign sched_wave_sel_out = wave_sel; +assign sched_chirp_pulse_out = chirp_pulse; +assign sched_frame_pulse_out = frame_pulse; + wire clk_400m; // NOTE: lvds_to_cmos_400m removed — ad9484_interface_400m now provides diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index b5bb872..1ae253f 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -177,6 +177,12 @@ wire [5:0] tx_current_chirp; // In clk_120m_dac domain wire [5:0] tx_current_chirp_sync; // Synchronized to clk_100m domain wire tx_current_chirp_sync_valid; +// PR-E: scheduler outputs from receiver_final, in clk_100m domain. +// Routed directly into radar_transmitter, which owns the 100→120 CDC. +wire [1:0] sched_wave_sel; +wire sched_chirp_pulse; +wire sched_frame_pulse; + // Receiver internal signals wire [31:0] rx_doppler_output; wire rx_doppler_valid; @@ -489,12 +495,16 @@ radar_transmitter tx_inst ( .rx_mixer_en(rx_mixer_en), .tx_mixer_en(tx_mixer_en), - // STM32 Control Interface - .stm32_new_chirp(stm32_new_chirp), + // Scheduler bridge (chirp-v2 PR-E): clk_100m signals from receiver_final + .sched_wave_sel(sched_wave_sel), + .sched_chirp_pulse(sched_chirp_pulse), + .sched_frame_pulse(sched_frame_pulse), + + // STM32 Control Interface (chirp moved to scheduler — only beam-step here) .stm32_new_elevation(stm32_new_elevation), .stm32_new_azimuth(stm32_new_azimuth), .stm32_mixers_enable(stm32_mixers_enable), - + // RF Switch Control .fpga_rf_switch(fpga_rf_switch), @@ -594,6 +604,8 @@ radar_receiver_final rx_inst ( .stm32_new_chirp_rx(stm32_new_chirp), .stm32_new_elevation_rx(stm32_new_elevation), .stm32_new_azimuth_rx(stm32_new_azimuth), + // PR-E: master enable for the scheduler (CDC-sync'd to clk_100m above) + .mixers_enable_100m(stm32_mixers_enable_100m), // CFAR: Doppler frame-complete pulse .doppler_frame_done_out(rx_frame_complete), // Ground clutter removal @@ -618,7 +630,11 @@ radar_receiver_final rx_inst ( .mti_saturation_count_out(rx_mti_saturation_count), // Range-bin decimator watchdog (audit F-6.4) .range_decim_watchdog(rx_range_decim_watchdog), - .ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun) + .ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun), + // PR-E: scheduler outputs forwarded to TX-side CDC bridge (clk_100m). + .sched_wave_sel_out(sched_wave_sel), + .sched_chirp_pulse_out(sched_chirp_pulse), + .sched_frame_pulse_out(sched_frame_pulse) ); // ============================================================================ diff --git a/9_Firmware/9_2_FPGA/radar_transmitter.v b/9_Firmware/9_2_FPGA/radar_transmitter.v index 682e8c6..eec75ea 100644 --- a/9_Firmware/9_2_FPGA/radar_transmitter.v +++ b/9_Firmware/9_2_FPGA/radar_transmitter.v @@ -1,46 +1,54 @@ -`timescale 1ns / 1ps -////////////////////////////////////////////////////////////////////////////////// -// Company: -// Engineer: -// -// Create Date: 19:04:35 12/14/2025 -// Design Name: -// Module Name: radar_transmitter -// Project Name: -// Target Devices: -// Tool versions: -// Description: -// -// Dependencies: -// -// Revision: -// Revision 0.01 - File Created -// Additional Comments: -// -////////////////////////////////////////////////////////////////////////////////// -module radar_transmitter( - // System Clocks - input wire clk_100m, // System clock - input wire clk_120m_dac, // 120MHz DAC clock - input wire reset_n, // Reset synchronized to clk_120m_dac +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// radar_transmitter — DAC-side wrapper around plfm_chirp_controller_v2. +// +// chirp-v2 PR-E reorganization: +// chirp_scheduler (clk_100m, in receiver_final) is now the master timekeeper. +// It emits {wave_sel[1:0], chirp_pulse, frame_pulse} on clk_100m. We bridge +// them to clk_120m_dac here: +// - wave_sel + chirp_pulse → cdc_async_fifo (Cummings style #2). Each +// chirp_pulse pushes wave_sel into the FIFO; the dst-side dst_valid +// pulse drives plfm_chirp_controller_v2.dst_chirp_valid. +// - frame_pulse → toggle CDC → 1-cycle pulse on clk_120m_dac for +// chirp_counter clear and the new_chirp_frame status output. +// +// Beam steering: +// stm32_new_elevation / stm32_new_azimuth still run through edge detectors +// on clk_100m and feed the controller's internal beam counters. These are +// independent of the chirp FSM and unchanged from chirp-v1. +// +// stm32_new_chirp: +// Removed from this module — the scheduler in receiver_final now owns chirp +// timing. The top-level GPIO is still wired to receiver_final via +// stm32_new_chirp_rx; the transmitter has no separate path. +////////////////////////////////////////////////////////////////////////////////// +module radar_transmitter( + // System Clocks + input wire clk_100m, // System clock + input wire clk_120m_dac, // 120MHz DAC clock + input wire reset_n, // Reset synchronized to clk_120m_dac input wire reset_100m_n, // Reset synchronized to clk_100m (for edge detectors/CDC) - + // DAC Interface output wire [7:0] dac_data, output wire dac_clk, output wire dac_sleep, output wire rx_mixer_en, - output wire tx_mixer_en, - - // STM32 Control Interface - input wire stm32_new_chirp, - input wire stm32_new_elevation, + output wire tx_mixer_en, + + // Scheduler outputs from receiver_final (clk_100m domain) — PR-E + input wire [1:0] sched_wave_sel, + input wire sched_chirp_pulse, + input wire sched_frame_pulse, + + // STM32 Control Interface (chirp moved to scheduler; beam-step still here) + input wire stm32_new_elevation, input wire stm32_new_azimuth, - input wire stm32_mixers_enable, - - output wire fpga_rf_switch, - - // ADAR1000 Control Interface + input wire stm32_mixers_enable, + + output wire fpga_rf_switch, + + // ADAR1000 Control Interface output wire adar_tx_load_1, output wire adar_rx_load_1, output wire adar_tx_load_2, @@ -53,7 +61,7 @@ module radar_transmitter( output wire adar_tr_2, output wire adar_tr_3, output wire adar_tr_4, - + // Level Shifter SPI Interface (STM32F7 to ADAR1000) input wire stm32_sclk_3v3, input wire stm32_mosi_3v3, @@ -62,188 +70,210 @@ module radar_transmitter( input wire stm32_cs_adar2_3v3, input wire stm32_cs_adar3_3v3, input wire stm32_cs_adar4_3v3, - + output wire stm32_sclk_1v8, output wire stm32_mosi_1v8, input wire stm32_miso_1v8, output wire stm32_cs_adar1_1v8, output wire stm32_cs_adar2_1v8, output wire stm32_cs_adar3_1v8, - output wire stm32_cs_adar4_1v8, - - // Beam Position Tracking - output wire [5:0] current_elevation, - output wire [5:0] current_azimuth, - output wire [5:0] current_chirp, - output wire new_chirp_frame + output wire stm32_cs_adar4_1v8, - - ); - -// ========== SPI LEVEL SHIFTER PASSTHROUGH ========== -// FPGA bridges 3.3V STM32 SPI bus (Bank 15) to 1.8V ADAR1000 SPI bus (Bank 34). -// The FPGA I/O banks handle the actual voltage translation; these assigns -// route the signals through the fabric. -assign stm32_sclk_1v8 = stm32_sclk_3v3; -assign stm32_mosi_1v8 = stm32_mosi_3v3; -assign stm32_miso_3v3 = stm32_miso_1v8; -assign stm32_cs_adar1_1v8 = stm32_cs_adar1_3v3; -assign stm32_cs_adar2_1v8 = stm32_cs_adar2_3v3; -assign stm32_cs_adar3_1v8 = stm32_cs_adar3_3v3; -assign stm32_cs_adar4_1v8 = stm32_cs_adar4_3v3; - -// Edge Detection Signals -wire new_chirp_pulse; -wire new_elevation_pulse; -wire new_azimuth_pulse; - -// CDC: Synchronized versions of async STM32 GPIO inputs to clk_100m -wire stm32_new_chirp_sync; -wire stm32_new_elevation_sync; -wire stm32_new_azimuth_sync; - -// CDC: Synchronized versions of signals crossing clk_100m -> clk_120m_dac -wire mixers_enable_120m; // stm32_mixers_enable sync'd to clk_120m_dac -wire new_chirp_pulse_120m; // new_chirp_pulse (toggle CDC) in clk_120m_dac domain - -// Chirp Control Signals -wire [7:0] chirp_data; -wire chirp_valid; -wire chirp_sequence_done; - -// Toggle CDC for new_chirp_pulse: clk_100m -> clk_120m_dac -// Edge detector produces a 1-cycle pulse on clk_100m. A level synchronizer -// would miss it (120/100 MHz ratio). Toggle CDC converts pulse to level toggle, -// syncs the toggle, then detects edges on the destination side. -reg chirp_toggle_100m; -always @(posedge clk_100m or negedge reset_100m_n) begin - if (!reset_100m_n) - chirp_toggle_100m <= 1'b0; - else if (new_chirp_pulse) - chirp_toggle_100m <= ~chirp_toggle_100m; -end - -// Sync the toggle to clk_120m_dac domain -wire chirp_toggle_120m; -cdc_single_bit #(.STAGES(3)) cdc_chirp_toggle ( - .src_clk(clk_100m), - .dst_clk(clk_120m_dac), - .reset_n(reset_n), - .src_signal(chirp_toggle_100m), - .dst_signal(chirp_toggle_120m) -); - -// Detect edges on synchronized toggle to recover pulse in clk_120m domain -reg chirp_toggle_120m_prev; -always @(posedge clk_120m_dac or negedge reset_n) begin - if (!reset_n) - chirp_toggle_120m_prev <= 1'b0; - else - chirp_toggle_120m_prev <= chirp_toggle_120m; -end -assign new_chirp_pulse_120m = chirp_toggle_120m ^ chirp_toggle_120m_prev; - -// Sync stm32_mixers_enable (async GPIO level) to clk_120m_dac domain -cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m ( - .src_clk(clk_100m), // Treat as pseudo-source (GPIO is async) - .dst_clk(clk_120m_dac), - .reset_n(reset_n), - .src_signal(stm32_mixers_enable), - .dst_signal(mixers_enable_120m) + // Beam Position Tracking + output wire [5:0] current_elevation, + output wire [5:0] current_azimuth, + output wire [5:0] current_chirp, + output wire new_chirp_frame ); - -// CDC synchronizers: async STM32 GPIO inputs -> clk_100m domain -// These prevent metastability in the edge detectors. Without these, -// the edge detector's first FF can go metastable, and the XOR output -// can glitch, producing false chirp/elevation/azimuth pulses. -cdc_single_bit #(.STAGES(2)) cdc_stm32_chirp ( - .src_clk(clk_100m), // Pseudo-source for async GPIO - .dst_clk(clk_100m), - .reset_n(reset_100m_n), - .src_signal(stm32_new_chirp), - .dst_signal(stm32_new_chirp_sync) -); - -cdc_single_bit #(.STAGES(2)) cdc_stm32_elevation ( - .src_clk(clk_100m), - .dst_clk(clk_100m), - .reset_n(reset_100m_n), - .src_signal(stm32_new_elevation), - .dst_signal(stm32_new_elevation_sync) -); - -cdc_single_bit #(.STAGES(2)) cdc_stm32_azimuth ( - .src_clk(clk_100m), - .dst_clk(clk_100m), - .reset_n(reset_100m_n), - .src_signal(stm32_new_azimuth), - .dst_signal(stm32_new_azimuth_sync) -); - -// Enhanced STM32 Input Edge Detection with Debouncing -// Inputs are now CDC-synchronized (safe from metastability) -edge_detector_enhanced chirp_edge ( - .clk(clk_100m), - .reset_n(reset_100m_n), - .signal_in(stm32_new_chirp_sync), - .rising_falling_edge(new_chirp_pulse) -); - -edge_detector_enhanced elevation_edge ( - .clk(clk_100m), - .reset_n(reset_100m_n), - .signal_in(stm32_new_elevation_sync), - .rising_falling_edge(new_elevation_pulse) -); - -edge_detector_enhanced azimuth_edge ( - .clk(clk_100m), - .reset_n(reset_100m_n), - .signal_in(stm32_new_azimuth_sync), - .rising_falling_edge(new_azimuth_pulse) + +// ========== SPI LEVEL SHIFTER PASSTHROUGH ========== +// FPGA bridges 3.3V STM32 SPI bus (Bank 15) to 1.8V ADAR1000 SPI bus (Bank 34). +// The FPGA I/O banks handle the actual voltage translation; these assigns +// route the signals through the fabric. +assign stm32_sclk_1v8 = stm32_sclk_3v3; +assign stm32_mosi_1v8 = stm32_mosi_3v3; +assign stm32_miso_3v3 = stm32_miso_1v8; +assign stm32_cs_adar1_1v8 = stm32_cs_adar1_3v3; +assign stm32_cs_adar2_1v8 = stm32_cs_adar2_3v3; +assign stm32_cs_adar3_1v8 = stm32_cs_adar3_3v3; +assign stm32_cs_adar4_1v8 = stm32_cs_adar4_3v3; + +// Beam-step edge detection (STM32 GPIO -> clk_100m pulses) +wire new_elevation_pulse; +wire new_azimuth_pulse; + +// CDC: Synchronized versions of async STM32 GPIO inputs to clk_100m +wire stm32_new_elevation_sync; +wire stm32_new_azimuth_sync; + +// CDC: stm32_mixers_enable into clk_120m_dac domain +wire mixers_enable_120m; + +// PR-E: scheduler bridge outputs in clk_120m_dac domain +wire dst_chirp_valid; +wire [1:0] dst_wave_sel; +wire sched_overrun_unused; +wire frame_pulse_120m; + +// Chirp Control Signals +wire [7:0] chirp_data; +wire chirp_valid; +wire chirp_sequence_done; + +// ============================================================================ +// PR-E: chirp_pulse + wave_sel CDC (clk_100m → clk_120m_dac) +// +// Each scheduler chirp_pulse on clk_100m pushes wave_sel into a Gray-coded +// async FIFO. The dst side auto-drains so dst_chirp_valid is a 1-cycle pulse +// on clk_120m_dac, and dst_wave_sel carries the matching waveform identity. +// ============================================================================ +cdc_async_fifo #( + .WIDTH(2), + .DEPTH(4) +) cdc_chirp_fifo ( + .src_clk (clk_100m), + .dst_clk (clk_120m_dac), + .src_reset_n (reset_100m_n), + .dst_reset_n (reset_n), + .src_data (sched_wave_sel), + .src_valid (sched_chirp_pulse), + .dst_data (dst_wave_sel), + .dst_valid (dst_chirp_valid), + .overrun (sched_overrun_unused) ); - -// Enhanced PLFM Chirp Generation -plfm_chirp_controller_enhanced plfm_chirp_inst ( - .clk_120m(clk_120m_dac), - .clk_100m(clk_100m), - .reset_n(reset_n), - .new_chirp(new_chirp_pulse_120m), // CDC-synchronized pulse in clk_120m domain - .new_elevation(new_elevation_pulse), - .new_azimuth(new_azimuth_pulse), - .new_chirp_frame(new_chirp_frame), - .mixers_enable(mixers_enable_120m), // CDC-synchronized level in clk_120m domain - .chirp_data(chirp_data), - .chirp_valid(chirp_valid), - .chirp_done(chirp_sequence_done), - .rf_switch_ctrl(fpga_rf_switch), - .rx_mixer_en(rx_mixer_en), - .tx_mixer_en(tx_mixer_en), - .adar_tx_load_1(adar_tx_load_1), - .adar_rx_load_1(adar_rx_load_1), - .adar_tx_load_2(adar_tx_load_2), - .adar_rx_load_2(adar_rx_load_2), - .adar_tx_load_3(adar_tx_load_3), - .adar_rx_load_3(adar_rx_load_3), - .adar_tx_load_4(adar_tx_load_4), - .adar_rx_load_4(adar_rx_load_4), - .adar_tr_1(adar_tr_1), - .adar_tr_2(adar_tr_2), - .adar_tr_3(adar_tr_3), - .adar_tr_4(adar_tr_4), - .elevation_counter(current_elevation), - .azimuth_counter(current_azimuth), - .chirp_counter(current_chirp) -); - -// Enhanced DAC Interface -dac_interface_enhanced dac_interface_inst ( - .clk_120m(clk_120m_dac), + +// ============================================================================ +// frame_pulse toggle CDC (clk_100m → clk_120m_dac) +// ============================================================================ +reg frame_toggle_100m; +always @(posedge clk_100m or negedge reset_100m_n) begin + if (!reset_100m_n) + frame_toggle_100m <= 1'b0; + else if (sched_frame_pulse) + frame_toggle_100m <= ~frame_toggle_100m; +end + +wire frame_toggle_120m; +cdc_single_bit #(.STAGES(3)) cdc_frame_toggle ( + .src_clk(clk_100m), + .dst_clk(clk_120m_dac), .reset_n(reset_n), - .chirp_data(chirp_data), + .src_signal(frame_toggle_100m), + .dst_signal(frame_toggle_120m) +); + +reg frame_toggle_120m_prev; +always @(posedge clk_120m_dac or negedge reset_n) begin + if (!reset_n) + frame_toggle_120m_prev <= 1'b0; + else + frame_toggle_120m_prev <= frame_toggle_120m; +end +assign frame_pulse_120m = frame_toggle_120m ^ frame_toggle_120m_prev; + +// ============================================================================ +// stm32_mixers_enable level CDC into clk_120m_dac +// ============================================================================ +cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m ( + .src_clk(clk_100m), // Treat as pseudo-source (GPIO is async) + .dst_clk(clk_120m_dac), + .reset_n(reset_n), + .src_signal(stm32_mixers_enable), + .dst_signal(mixers_enable_120m) +); + +// ============================================================================ +// Beam-step CDC + edge detection (clk_100m, unchanged from v1) +// ============================================================================ +cdc_single_bit #(.STAGES(2)) cdc_stm32_elevation ( + .src_clk(clk_100m), + .dst_clk(clk_100m), + .reset_n(reset_100m_n), + .src_signal(stm32_new_elevation), + .dst_signal(stm32_new_elevation_sync) +); + +cdc_single_bit #(.STAGES(2)) cdc_stm32_azimuth ( + .src_clk(clk_100m), + .dst_clk(clk_100m), + .reset_n(reset_100m_n), + .src_signal(stm32_new_azimuth), + .dst_signal(stm32_new_azimuth_sync) +); + +edge_detector_enhanced elevation_edge ( + .clk(clk_100m), + .reset_n(reset_100m_n), + .signal_in(stm32_new_elevation_sync), + .rising_falling_edge(new_elevation_pulse) +); + +edge_detector_enhanced azimuth_edge ( + .clk(clk_100m), + .reset_n(reset_100m_n), + .signal_in(stm32_new_azimuth_sync), + .rising_falling_edge(new_azimuth_pulse) +); + +// ============================================================================ +// PLFM Chirp Generator (chirp-v2) +// ============================================================================ +plfm_chirp_controller_v2 plfm_chirp_inst ( + .clk_120m (clk_120m_dac), + .clk_100m (clk_100m), + .reset_n (reset_n), + .reset_100m_n (reset_100m_n), + .mixers_enable (mixers_enable_120m), + + // Scheduler bridge (clk_120m_dac, post-CDC) + .dst_chirp_valid (dst_chirp_valid), + .dst_wave_sel (dst_wave_sel), + .frame_pulse_120m(frame_pulse_120m), + + // Beam-step pulses (clk_100m) + .new_elevation (new_elevation_pulse), + .new_azimuth (new_azimuth_pulse), + + // DAC outputs + .chirp_data (chirp_data), + .chirp_valid (chirp_valid), + .new_chirp_frame(new_chirp_frame), + .chirp_done (chirp_sequence_done), + .rf_switch_ctrl (fpga_rf_switch), + .rx_mixer_en (rx_mixer_en), + .tx_mixer_en (tx_mixer_en), + + // ADAR + .adar_tx_load_1 (adar_tx_load_1), + .adar_rx_load_1 (adar_rx_load_1), + .adar_tx_load_2 (adar_tx_load_2), + .adar_rx_load_2 (adar_rx_load_2), + .adar_tx_load_3 (adar_tx_load_3), + .adar_rx_load_3 (adar_rx_load_3), + .adar_tx_load_4 (adar_tx_load_4), + .adar_rx_load_4 (adar_rx_load_4), + .adar_tr_1 (adar_tr_1), + .adar_tr_2 (adar_tr_2), + .adar_tr_3 (adar_tr_3), + .adar_tr_4 (adar_tr_4), + + // Status counters + .chirp_counter (current_chirp), + .elevation_counter(current_elevation), + .azimuth_counter (current_azimuth) +); + +// ============================================================================ +// DAC Output Interface +// ============================================================================ +dac_interface_enhanced dac_interface_inst ( + .clk_120m (clk_120m_dac), + .reset_n (reset_n), + .chirp_data (chirp_data), .chirp_valid(chirp_valid), - .dac_data(dac_data), - .dac_clk(dac_clk), - .dac_sleep(dac_sleep) -); -endmodule + .dac_data (dac_data), + .dac_clk (dac_clk), + .dac_sleep (dac_sleep) +); + +endmodule diff --git a/9_Firmware/9_2_FPGA/run_regression.sh b/9_Firmware/9_2_FPGA/run_regression.sh index 7735f16..b449e67 100755 --- a/9_Firmware/9_2_FPGA/run_regression.sh +++ b/9_Firmware/9_2_FPGA/run_regression.sh @@ -52,7 +52,7 @@ PROD_RTL=( radar_system_top.v radar_transmitter.v dac_interface_single.v - plfm_chirp_controller.v + plfm_chirp_controller_v2.v radar_receiver_final.v tb/ad9484_interface_400m_stub.v ddc_400m.v @@ -112,7 +112,7 @@ RECEIVER_RTL=( # Full system top (receiver chain + TX + USB + detection + self-test) SYSTEM_RTL=( radar_system_top.v - radar_transmitter.v dac_interface_single.v plfm_chirp_controller.v + radar_transmitter.v dac_interface_single.v plfm_chirp_controller_v2.v "${RECEIVER_RTL[@]}" usb_data_interface.v usb_data_interface_ft2232h.v edge_detector.v cfar_ca.v fpga_self_test.v @@ -517,11 +517,11 @@ run_test "CIC Decimator" \ run_test "Chirp Controller (BRAM)" \ tb/tb_chirp_reg.vvp \ - tb/tb_chirp_controller.v plfm_chirp_controller.v + tb/tb_chirp_controller.v plfm_chirp_controller_v2.v run_test "Chirp Contract" \ tb/tb_chirp_ctr_reg.vvp \ - tb/tb_chirp_contract.v plfm_chirp_controller.v + tb/tb_chirp_contract.v plfm_chirp_controller_v2.v run_doppler_cosim "stationary" "" run_doppler_cosim "moving" "-DSCENARIO_MOVING" diff --git a/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl b/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl index 5fa250f..f6dfd10 100644 --- a/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl +++ b/9_Firmware/9_2_FPGA/scripts/200t/build_200t.tcl @@ -67,7 +67,7 @@ set rtl_files [list \ "${rtl_dir}/matched_filter_multi_segment.v" \ "${rtl_dir}/matched_filter_processing_chain.v" \ "${rtl_dir}/nco_400m_enhanced.v" \ - "${rtl_dir}/plfm_chirp_controller.v" \ + "${rtl_dir}/plfm_chirp_controller_v2.v" \ "${rtl_dir}/chirp_scheduler.v" \ "${rtl_dir}/radar_receiver_final.v" \ "${rtl_dir}/radar_system_top.v" \ diff --git a/9_Firmware/9_2_FPGA/tb/tb_chirp_contract.v b/9_Firmware/9_2_FPGA/tb/tb_chirp_contract.v index 9f0c252..25e3226 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_chirp_contract.v +++ b/9_Firmware/9_2_FPGA/tb/tb_chirp_contract.v @@ -1,76 +1,80 @@ `timescale 1ns / 1ps // ============================================================================ -// tb_chirp_contract.v — Architectural Contract Regression Test +// tb_chirp_contract.v — Architectural Contract Regression for plfm_chirp_controller_v2 // ============================================================================ -// Purpose: Encode the invariants of the chirp_counter signal path as hard -// assertions. If the original author (or anyone) modifies the RTL in a way -// that violates these contracts, this testbench will FAIL immediately. +// Encodes the chirp-v2 (PR-E) invariants of the chirp_counter signal path as +// hard assertions. If the RTL is modified in a way that violates one of these +// contracts, this testbench fails immediately. // // Contracts verified: -// C1. chirp_counter is 0-indexed, range [0, CHIRP_MAX-1] -// C2. chirp_counter resets to 0 (not 1) -// C3. chirp_counter increments only on clk_120m (never on clk_100m alone) -// C4. chirp_counter increments monotonically (no skips > 1) -// C5. chirp_counter increments only at end of listen states -// C6. new_chirp input does NOT directly drive chirp_counter -// C7. chirp_counter wraps correctly: 0 → CHIRP_MAX-1 → 0 -// C8. Frame sync compatibility: chirp_counter hits 0 at frame start -// C9. GUI mask compatibility: chirp_counter stays within [0, 31] (5-bit safe) -// C10. Receiver port connectivity: chirp_counter output matches input expectation +// C1. chirp_counter is 0-indexed, wraps via frame_pulse_120m +// C2. chirp_counter resets to 0 on frame_pulse_120m (not at chirp_done) +// C3. chirp_counter increments only on clk_120m edges (never clk_100m alone) +// C4. chirp_counter increments monotonically (no skips > 1) +// C5. chirp_counter increments exactly when the FSM leaves ST_CHIRP +// C6. dst_chirp_valid pulses (not stm32 toggles) drive chirp_counter +// C7. chirp_counter wraps cleanly via frame_pulse: N → 0 +// C8. chirp_counter stays in [0, 31] when frame ≤ 32 chirps (5-bit safe) +// C9. Receiver port-connectivity: TX-side chirp_counter still surfaces on +// radar_transmitter.current_chirp (for status_reg compatibility) // -// Related bugs: A5 (multi-driven fix), NEW-1 (receiver port fix) +// Related history: chirp-v1 had a multi-driven chirp_counter bug (A5). +// In chirp-v2 the counter has only ONE driver (the FSM in clk_120m), so +// the original A5 race is structurally unreachable — but C3 / C5 still +// guard against any future regression that re-introduces a clk_100m driver. // ============================================================================ +`include "radar_params.vh" module tb_chirp_contract; -// ---- Parameters (must match RTL) ---- -localparam CHIRP_MAX = 32; -localparam T1_SAMPLES = 3600; -localparam T1_RADAR_LISTENING = 16440; -localparam T2_SAMPLES = 60; -localparam T2_RADAR_LISTENING = 20940; -localparam GUARD_SAMPLES = 21048; - -// For fast simulation, use a reduced version -// Set USE_FAST_SIM=1 to use CHIRP_MAX=4 (completes in ~1ms sim time) -// Set USE_FAST_SIM=0 to use real parameters (very long sim time) -localparam USE_FAST_SIM = 1; -localparam SIM_CHIRP_MAX = USE_FAST_SIM ? 4 : CHIRP_MAX; +// ---- Sample-count constants ---- +localparam integer SHORT_SAMPLES = 120; +localparam integer MEDIUM_SAMPLES = 600; // ---- Clock generation ---- reg clk_120m, clk_100m; -reg reset_n; -reg new_chirp, new_elevation, new_azimuth, mixers_enable; +reg reset_n, reset_100m_n; +reg mixers_enable; +reg dst_chirp_valid; +reg [1:0] dst_wave_sel; +reg frame_pulse_120m; +reg new_elevation, new_azimuth; -// DUT outputs +// DUT outputs (subset — only those used in the contract checks) wire [7:0] chirp_data; wire chirp_valid; -wire new_chirp_frame; wire chirp_done; wire rf_switch_ctrl; -wire rx_mixer_en, tx_mixer_en; +wire tx_mixer_en, rx_mixer_en; wire adar_tx_load_1, adar_rx_load_1; wire adar_tx_load_2, adar_rx_load_2; wire adar_tx_load_3, adar_rx_load_3; wire adar_tx_load_4, adar_rx_load_4; wire adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4; +wire new_chirp_frame; wire [5:0] chirp_counter; -wire [5:0] elevation_counter; -wire [5:0] azimuth_counter; +wire [5:0] elevation_counter, azimuth_counter; -// ---- DUT instantiation ---- -plfm_chirp_controller_enhanced #( - .CHIRP_MAX(SIM_CHIRP_MAX), - .ELEVATION_MAX(31), - .AZIMUTH_MAX(50) -) dut ( +// 120 MHz: period = 8.333 ns +initial clk_120m = 0; +always #4.166 clk_120m = ~clk_120m; + +// 100 MHz: period = 10 ns +initial clk_100m = 0; +always #5 clk_100m = ~clk_100m; + +// ---- DUT ---- +plfm_chirp_controller_v2 dut ( .clk_120m(clk_120m), .clk_100m(clk_100m), .reset_n(reset_n), - .new_chirp(new_chirp), + .reset_100m_n(reset_100m_n), + .mixers_enable(mixers_enable), + .dst_chirp_valid(dst_chirp_valid), + .dst_wave_sel(dst_wave_sel), + .frame_pulse_120m(frame_pulse_120m), .new_elevation(new_elevation), .new_azimuth(new_azimuth), - .mixers_enable(mixers_enable), .chirp_data(chirp_data), .chirp_valid(chirp_valid), .new_chirp_frame(new_chirp_frame), @@ -95,456 +99,190 @@ plfm_chirp_controller_enhanced #( .azimuth_counter(azimuth_counter) ); -// ---- Clock generation ---- -// 120 MHz: period = 8.333ns -initial clk_120m = 0; -always #4.167 clk_120m = ~clk_120m; - -// 100 MHz: period = 10ns -initial clk_100m = 0; -always #5 clk_100m = ~clk_100m; - // ---- Test infrastructure ---- -integer pass_count = 0; -integer fail_count = 0; -integer total_tests = 0; +integer test_num; +integer pass_count; +integer fail_count; +integer total_tests; + +// C4 monitor: chirp_counter must change by ±1 or 0 per clk_120m edge. +// Wraps via frame_pulse_120m: the FSM samples the pulse at edge T and +// schedules chirp_counter <= 0; the wrap (K → 0) is observable on the +// next monitor sample (edge T+1). frame_pulse_seen carries the pulse +// forward one cycle so the (pre, post) = (K, 0) transition is allowed. +reg [5:0] prev_counter; +reg frame_pulse_seen; +reg c4_violated; + +always @(posedge clk_120m or negedge reset_n) begin + if (!reset_n) begin + prev_counter <= 6'd0; + frame_pulse_seen <= 1'b0; + c4_violated <= 1'b0; + end else begin + if (chirp_counter != prev_counter && + chirp_counter != prev_counter + 6'd1 && + !(chirp_counter == 6'd0 && (frame_pulse_120m || frame_pulse_seen))) + begin + c4_violated <= 1'b1; + end + frame_pulse_seen <= frame_pulse_120m; + prev_counter <= chirp_counter; + end +end task check; - input [255:0] name; // Reduced from 512 for Icarus compat + input [255:0] test_name; input condition; begin - total_tests = total_tests + 1; + test_num = test_num + 1; if (condition) begin + $display(" [PASS] Contract %0d: %0s", test_num, test_name); pass_count = pass_count + 1; - $display(" [PASS] %0s", name); end else begin + $display(" [FAIL] Contract %0d: %0s", test_num, test_name); fail_count = fail_count + 1; - $display(" [FAIL] %0s", name); end end endtask -// ---- Continuous monitors for contract violations ---- - -// Contract C1: Range check — chirp_counter must always be in [0, SIM_CHIRP_MAX] -// KNOWN BEHAVIOR: chirp_counter reaches CHIRP_MAX for exactly 1 cycle during DONE state. -// This is because the combinational next_state logic checks chirp_counter == CHIRP_MAX-1 -// at the same clock edge that the registered block increments chirp_counter. -// The value CHIRP_MAX only appears in DONE (state 6) and IDLE (state 0, briefly). -// This is benign: no chirp is transmitting during DONE, and the receiver doesn't use -// chirp_counter during that state. The counter resets to 0 on the next reset. -// We flag as a violation ONLY if chirp_counter exceeds CHIRP_MAX (should never happen). -reg reset_done; -initial reset_done = 0; - -always @(posedge clk_120m) begin - if (reset_done && chirp_counter > SIM_CHIRP_MAX) begin - $display(" [FAIL] CONTRACT C1 VIOLATION: chirp_counter=%0d > CHIRP_MAX=%0d at time %0t", - chirp_counter, SIM_CHIRP_MAX, $time); - fail_count = fail_count + 1; - end -end - -// Contract C4: Monotonicity — chirp_counter must not skip values -// It can increment by 0 (hold) or 1 (increment), or reset to 0 (via reset or new sequence) -reg [5:0] prev_chirp_counter; -reg prev_valid; -initial prev_valid = 0; - -always @(posedge clk_120m) begin - if (reset_done && prev_valid) begin - // Allowed transitions: - // same value (hold) - // +1 (increment, including CHIRP_MAX-1 → CHIRP_MAX overshoot) - // reset to 0 (from DONE/IDLE or hardware reset) - if (chirp_counter != prev_chirp_counter && - chirp_counter != prev_chirp_counter + 1 && - chirp_counter != 0) begin - $display(" [FAIL] CONTRACT C4 VIOLATION: chirp_counter jumped %0d -> %0d at time %0t", - prev_chirp_counter, chirp_counter, $time); - fail_count = fail_count + 1; - end - end - prev_chirp_counter <= chirp_counter; - if (reset_done) prev_valid <= 1; -end - -// ---- Helper: wait for N clk_120m rising edges ---- -task wait_120m_cycles; - input integer n; - integer i; +task issue_chirp; + input [1:0] wsel; + begin + @(posedge clk_120m); + dst_wave_sel <= wsel; + dst_chirp_valid <= 1'b1; + @(posedge clk_120m); + dst_chirp_valid <= 1'b0; + end +endtask + +task wait_for_idle; + input integer timeout_cycles; + integer i; + begin + for (i = 0; i < timeout_cycles; i = i + 1) begin + @(posedge clk_120m); + if (dut.state == 1'b0) i = timeout_cycles; + end + end +endtask + +task pulse_frame; begin - for (i = 0; i < n; i = i + 1) - @(posedge clk_120m); - end -endtask - -// ---- Helper: wait for N clk_100m rising edges ---- -task wait_100m_cycles; - input integer n; - integer i; - begin - for (i = 0; i < n; i = i + 1) - @(posedge clk_100m); - end -endtask - -// ---- Helper: run one full chirp sequence (IDLE → DONE) ---- -// Returns the final chirp_counter value -reg [5:0] final_chirp_value; -reg sequence_completed; - -task run_full_sequence; - begin - // Trigger: assert new_chirp and mixers_enable - mixers_enable = 1; - new_chirp = 1; - wait_100m_cycles(5); - - // Wait for FSM to leave IDLE @(posedge clk_120m); - while (dut.current_state == 3'd0) // IDLE = 0 - @(posedge clk_120m); - - // Now wait for DONE state (state 6) - while (dut.current_state != 3'd6) // DONE = 6 - @(posedge clk_120m); - - final_chirp_value = chirp_counter; - sequence_completed = 1; - - // Wait for return to IDLE + frame_pulse_120m <= 1'b1; @(posedge clk_120m); - while (dut.current_state != 3'd0) - @(posedge clk_120m); - - // Deassert - new_chirp = 0; - mixers_enable = 0; - wait_120m_cycles(5); + frame_pulse_120m <= 1'b0; end endtask -// ---- Main test sequence ---- +// ========================================================================= +// MAIN +// ========================================================================= initial begin $dumpfile("tb_chirp_contract.vcd"); $dumpvars(0, tb_chirp_contract); - - // Initialize - reset_n = 0; - new_chirp = 0; - new_elevation = 0; - new_azimuth = 0; - mixers_enable = 0; - sequence_completed = 0; - - $display("============================================================"); - $display("ARCHITECTURAL CONTRACT REGRESSION TEST — chirp_counter"); - $display("CHIRP_MAX (sim) = %0d", SIM_CHIRP_MAX); - $display("============================================================"); - - // ================================================================ - // TEST GROUP 1: Reset Contracts - // ================================================================ + + test_num = 0; + pass_count = 0; + fail_count = 0; + + reset_n = 0; + reset_100m_n = 0; + mixers_enable = 0; + dst_chirp_valid = 0; + dst_wave_sel = `RP_WAVE_SHORT; + frame_pulse_120m = 0; + new_elevation = 0; + new_azimuth = 0; + $display(""); - $display("--- GROUP 1: Reset Contracts ---"); - - // Apply reset + $display("============================================================"); + $display(" CHIRP CONTRACT REGRESSION (chirp-v2 PR-E)"); + $display("============================================================"); + $display(""); + #100; - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - // C2: Reset value is 0 - check("C2: chirp_counter resets to 0 (not 1)", chirp_counter == 6'd0); - - // ================================================================ - // TEST GROUP 2: clk_100m Isolation (Contract C3) - // ================================================================ - $display(""); - $display("--- GROUP 2: clk_100m Isolation (Contract C3) ---"); - - // C3a: Toggling new_chirp on clk_100m with mixers OFF should not change chirp_counter - new_chirp = 1; - wait_100m_cycles(20); - new_chirp = 0; - wait_100m_cycles(20); - new_chirp = 1; - wait_100m_cycles(20); - new_chirp = 0; - wait_100m_cycles(10); - check("C3a: new_chirp pulses (mixers off) don't change chirp_counter", chirp_counter == 6'd0); - - // C3b: Toggling new_chirp on clk_100m with mixers ON but before FSM starts - // chirp_counter should still be 0 until FSM actually enters a listen state - mixers_enable = 1; - wait_100m_cycles(5); - // FSM should transition out of IDLE now (chirp__toggling is high and mixers on) - // But chirp_counter should only change at end of listen, not from clk_100m - - // Record value immediately - begin : c3b_block - reg [5:0] val_before; - val_before = chirp_counter; - // Now toggle new_chirp rapidly on clk_100m only - new_chirp = 0; - wait_100m_cycles(3); - new_chirp = 1; - wait_100m_cycles(3); - new_chirp = 0; - wait_100m_cycles(3); - // If there was a clk_100m driver, chirp_counter would have changed - // But the clk_100m toggling alone should have no effect on chirp_counter - // (FSM may increment it on clk_120m — that's OK, we just check no EXTRA increments) - check("C3b: clk_100m toggling alone doesn't add extra increments", - chirp_counter >= val_before); // Must be >= (FSM may have started) - end - - // Reset for next test group - reset_n = 0; - reset_done = 0; - prev_valid = 0; - new_chirp = 0; - mixers_enable = 0; - wait_120m_cycles(5); - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - // ================================================================ - // TEST GROUP 3: Full Sequence Contracts (C1, C5, C7, C8, C9) - // ================================================================ - $display(""); - $display("--- GROUP 3: Full Sequence Contracts ---"); - - // Run a complete chirp sequence - run_full_sequence; - - // C1: Final value in DONE state is CHIRP_MAX (1-cycle overshoot — see C1 comment) - // The combinational FSM correctly sees CHIRP_MAX-1 for the state transition, - // but the registered increment on the same edge pushes it to CHIRP_MAX. - check("C1: Final chirp_counter = CHIRP_MAX (known DONE overshoot)", - final_chirp_value == SIM_CHIRP_MAX); - - // C7: After DONE → IDLE, chirp_counter should still be CHIRP_MAX - // (it resets to 0 on the next reset, not automatically) - check("C7a: chirp_counter holds at CHIRP_MAX after DONE", - chirp_counter == SIM_CHIRP_MAX); - - // C8: Verify that chirp_counter was 0 at the start of the sequence - // (we tested this via C2 — it starts at 0 after reset) - check("C8: Frame start aligns with chirp_counter=0 (from reset)", - 1'b1); // Verified by C2 above - - // C9: GUI mask compatibility — all OPERATIONAL values must be <= 31 (5-bit safe) - // The DONE-state overshoot to CHIRP_MAX is OK because no USB data is packed in DONE. - // With real CHIRP_MAX=32, the overshoot value (32) exceeds 5 bits, but it's never sent. - // For this test with SIM_CHIRP_MAX=4, the value is 4 which fits in 5 bits anyway. - check("C9: Overshoot value fits in 6 bits (port width safe)", - final_chirp_value <= 6'd63); - - // ================================================================ - // TEST GROUP 4: Contract C6 — new_chirp doesn't drive chirp_counter - // ================================================================ - $display(""); - $display("--- GROUP 4: new_chirp Independence (Contract C6) ---"); - - // Reset - reset_n = 0; - reset_done = 0; - prev_valid = 0; - new_chirp = 0; - mixers_enable = 0; - wait_120m_cycles(5); - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - // C6a: With mixers OFF, new_chirp pulses should not increment chirp_counter - new_chirp = 1; - wait_100m_cycles(10); - new_chirp = 0; - wait_100m_cycles(10); - check("C6a: new_chirp pulse (mixers off) -> chirp_counter stays 0", - chirp_counter == 6'd0); - - // C6b: 100 rapid new_chirp toggles should not cause any chirp_counter change - begin : c6b_block - integer k; - for (k = 0; k < 100; k = k + 1) begin - new_chirp = ~new_chirp; - #10; // 10ns per toggle = 100MHz-ish - end - new_chirp = 0; - wait_100m_cycles(5); - check("C6b: 100 rapid new_chirp toggles -> chirp_counter still 0", - chirp_counter == 6'd0); - end - - // C6c: Even with mixers ON, new_chirp should only START the FSM, - // not directly increment chirp_counter - mixers_enable = 1; - new_chirp = 1; - wait_100m_cycles(3); - // FSM should be transitioning, but chirp_counter should still be 0 - // (it only increments at end of first listen state) - check("C6c: FSM started but chirp_counter still 0 (no direct drive)", - chirp_counter == 6'd0); - - new_chirp = 0; - mixers_enable = 0; - - // ================================================================ - // TEST GROUP 5: Contract C5 — Increment only at listen state end - // ================================================================ - $display(""); - $display("--- GROUP 5: Increment Timing (Contract C5) ---"); - - // Reset - reset_n = 0; - reset_done = 0; - prev_valid = 0; - new_chirp = 0; - mixers_enable = 0; - wait_120m_cycles(5); - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - // Start sequence - mixers_enable = 1; - new_chirp = 1; - wait_100m_cycles(5); - - // Wait for LONG_CHIRP state (state 1) @(posedge clk_120m); - while (dut.current_state == 3'd0) - @(posedge clk_120m); - - // C5a: During LONG_CHIRP, chirp_counter should remain 0 - check("C5a: chirp_counter=0 during first LONG_CHIRP", chirp_counter == 6'd0); - - // Wait through LONG_CHIRP into LONG_LISTEN - while (dut.current_state == 3'd1) // LONG_CHIRP - @(posedge clk_120m); - - // Now in LONG_LISTEN (state 2) - // C5b: At start of LONG_LISTEN, chirp_counter should still be 0 - check("C5b: chirp_counter=0 at start of LONG_LISTEN", chirp_counter == 6'd0); - - // Wait for LONG_LISTEN to finish - while (dut.current_state == 3'd2) // LONG_LISTEN - @(posedge clk_120m); - - // C5c: After first LONG_LISTEN completes, chirp_counter should be 1 - check("C5c: chirp_counter=1 after first LONG_LISTEN", chirp_counter == 6'd1); - - // ================================================================ - // TEST GROUP 6: Multi-Reset Stability (C2 regression) - // ================================================================ - $display(""); - $display("--- GROUP 6: Multi-Reset Stability ---"); - - // Reset mid-sequence - reset_n = 0; - reset_done = 0; - prev_valid = 0; - wait_120m_cycles(3); - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - check("C2-repeat: chirp_counter=0 after mid-sequence reset", chirp_counter == 6'd0); - - // Another reset - reset_n = 0; - reset_done = 0; - prev_valid = 0; - wait_120m_cycles(10); - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - check("C2-long: chirp_counter=0 after long reset", chirp_counter == 6'd0); - - // ================================================================ - // TEST GROUP 7: Back-to-Back Sequences (C7 wrap behavior) - // ================================================================ - $display(""); - $display("--- GROUP 7: Back-to-Back Sequences (Wrap Behavior) ---"); - - // Run first sequence - run_full_sequence; - begin : c7b_check - reg [5:0] val_after_first; - val_after_first = chirp_counter; - check("C7b: First sequence ends at CHIRP_MAX (DONE overshoot)", - val_after_first == SIM_CHIRP_MAX); + reset_n <= 1; + @(posedge clk_100m); + reset_100m_n <= 1; + @(posedge clk_120m); + + mixers_enable = 1; + @(posedge clk_120m); + + // ---------- C2: counter is 0 after reset (before any chirp) ---------- + check("C2: chirp_counter == 0 after reset", chirp_counter == 6'd0); + + // ---------- C5/C6: dst_chirp_valid drives the counter ---------- + issue_chirp(`RP_WAVE_SHORT); + wait_for_idle(SHORT_SAMPLES + 20); + check("C5/C6: chirp_counter == 1 after first SHORT chirp", chirp_counter == 6'd1); + + issue_chirp(`RP_WAVE_SHORT); + wait_for_idle(SHORT_SAMPLES + 20); + check("C5/C6: chirp_counter == 2 after second SHORT chirp", chirp_counter == 6'd2); + + // ---------- C3: stm32 toggles do NOT drive chirp_counter ---------- + repeat (8) begin + new_elevation = ~new_elevation; + new_azimuth = ~new_azimuth; + @(posedge clk_100m); end - - // Reset and run second sequence - reset_n = 0; - reset_done = 0; - prev_valid = 0; - new_chirp = 0; - mixers_enable = 0; - wait_120m_cycles(5); - reset_n = 1; - wait_120m_cycles(3); - reset_done = 1; - - check("C7c: chirp_counter wraps to 0 after reset between sequences", - chirp_counter == 6'd0); - - // Run second sequence - run_full_sequence; - check("C7d: Second sequence also ends at CHIRP_MAX", - chirp_counter == SIM_CHIRP_MAX); - - // ================================================================ - // TEST GROUP 8: Contract C10 — Receiver Port Compatibility - // ================================================================ - $display(""); - $display("--- GROUP 8: Receiver Port Compatibility (C10) ---"); - - // Verify the output port width is 6 bits (compile-time check via the wire declaration) - // If someone changes it to 5 bits, the connection will produce warnings/errors - check("C10a: chirp_counter output is 6 bits wide", - $bits(chirp_counter) == 6); - - // Verify value range is compatible with receiver frame sync - // Receiver checks: chirp_counter == 0 || chirp_counter == 32 - // With CHIRP_MAX=32, value 32 is never reached (range is 0-31) - // So only chirp_counter==0 triggers frame sync — this is correct - check("C10b: CHIRP_MAX-1 < 32, so chirp_counter==32 never occurs (expected)", - SIM_CHIRP_MAX - 1 < 32 || SIM_CHIRP_MAX > 32); - - // ================================================================ + new_elevation = 0; + new_azimuth = 0; + check("C3: stm32 toggles do not change chirp_counter", chirp_counter == 6'd2); + + // ---------- C7: frame_pulse wraps to 0 ---------- + pulse_frame(); + @(posedge clk_120m); + check("C7: chirp_counter wraps to 0 on frame_pulse", chirp_counter == 6'd0); + + // ---------- C5/C6: incremental sequence after wrap ---------- + issue_chirp(`RP_WAVE_MEDIUM); + wait_for_idle(MEDIUM_SAMPLES + 20); + check("C5/C6: chirp_counter == 1 after MEDIUM post-wrap", chirp_counter == 6'd1); + + issue_chirp(`RP_WAVE_SHORT); + wait_for_idle(SHORT_SAMPLES + 20); + check("C5/C6: chirp_counter == 2 after SHORT post-wrap", chirp_counter == 6'd2); + + // ---------- C4: monotonic — confirmed by the running monitor ---------- + check("C4: monotonic ±1 increments only (monitor flag)", c4_violated == 1'b0); + + // ---------- C8: 5-bit safe over a 4-chirp run ---------- + issue_chirp(`RP_WAVE_SHORT); wait_for_idle(SHORT_SAMPLES + 20); + issue_chirp(`RP_WAVE_SHORT); wait_for_idle(SHORT_SAMPLES + 20); + check("C8: chirp_counter ≤ 31 during a normal frame", chirp_counter <= 6'd31); + + // ---------- C1: full sequence then frame wrap to 0 ---------- + pulse_frame(); + @(posedge clk_120m); + check("C1/C7: chirp_counter wraps cleanly back to 0", chirp_counter == 6'd0); + + // ===================================================================== // SUMMARY - // ================================================================ + // ===================================================================== $display(""); $display("============================================================"); - $display("ARCHITECTURAL CONTRACT TEST SUMMARY"); - $display("============================================================"); - $display(" Total : %0d", total_tests); - $display(" Passed: %0d", pass_count); - $display(" Failed: %0d", fail_count); - $display("============================================================"); - + total_tests = pass_count + fail_count; + $display(" CONTRACT RESULTS: %0d/%0d contracts upheld", pass_count, total_tests); if (fail_count == 0) - $display("ALL CONTRACTS VERIFIED — chirp_counter architecture is safe."); + $display(" STATUS: ALL CONTRACTS UPHELD"); else - $display("CONTRACT VIOLATIONS DETECTED — review changes to chirp_counter!"); - + $display(" STATUS: %0d CONTRACT VIOLATIONS", fail_count); $display("============================================================"); + $display(""); + + #100; $finish; end -// ---- Timeout watchdog ---- initial begin - #500_000_000; // 500ms sim time - $display("[TIMEOUT] Simulation exceeded 500ms — aborting"); - $display(" Tests run so far: %0d passed, %0d failed", pass_count, fail_count); + #500000; // 500 µs + $display("TIMEOUT: Simulation took too long!"); $finish; end diff --git a/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v b/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v index 87615fd..c1425b6 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v +++ b/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v @@ -1,33 +1,36 @@ `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////// -// Testbench: plfm_chirp_controller_enhanced -// Tests: A5 fix (multi-driven chirp_counter removed), FSM sequencing, -// chirp waveform output, T/R switch timing, beam scanning counters +// Testbench: plfm_chirp_controller_v2 (chirp-v2 PR-E) // -// NOTE: Uses shortened timing parameters for feasible simulation. -// The real module uses T1_SAMPLES=3600, T1_RADAR_LISTENING=16440, etc. -// We override to T1=8, LISTEN=4, T2=4, GUARD=4 for fast verification. +// The v2 module is a pure DAC playback driver — it no longer owns its own +// LISTEN/GUARD/DONE FSM (that moved into chirp_scheduler on the RX side). +// Tests here verify: +// - Reset behavior (IDLE, idle-code 128, all flags low) +// - IDLE hold while mixers_enable=0 +// - SHORT/MEDIUM/LONG chirp playback durations match LUT lengths +// - chirp_data exits idle code and rf_switch / adar_tr / chirp_valid go +// active during CHIRP, deassert after +// - chirp_counter increments per chirp and clears on frame_pulse_120m +// - mixer enables: tx_mixer_en active during CHIRP, rx_mixer_en otherwise +// - elevation_counter / azimuth_counter still bump on STM32 toggles +// +// Sample counts (must mirror plfm_chirp_controller_v2.v localparams): +// SHORT = 120, MEDIUM = 600, LONG = 3600 ////////////////////////////////////////////////////////////////////////////// +`include "radar_params.vh" module tb_chirp_controller; -// ========================================================================= -// PARAMETERS — shortened for simulation -// ========================================================================= -parameter T1_SAMPLES = 8; // was 3600 -parameter T1_RADAR_LISTENING = 4; // was 16440 -parameter T2_SAMPLES = 4; // was 60 -parameter T2_RADAR_LISTENING = 4; // was 20940 -parameter GUARD_SAMPLES = 4; // was 21048 -parameter CHIRP_MAX = 4; // was 32 (use 4: 2 long + 2 short) -parameter ELEVATION_MAX = 2; // was 31 -parameter AZIMUTH_MAX = 2; // was 50 +// ---- Sample-count constants (match the RTL) ---- +localparam integer SHORT_SAMPLES = 120; +localparam integer MEDIUM_SAMPLES = 600; +localparam integer LONG_SAMPLES = 3600; // ========================================================================= // CLOCK GENERATION // ========================================================================= reg clk_120m, clk_100m; -reg reset_n; +reg reset_n, reset_100m_n; // 120 MHz: period = 8.333 ns initial clk_120m = 0; @@ -40,8 +43,12 @@ always #5 clk_100m = ~clk_100m; // ========================================================================= // DUT SIGNALS // ========================================================================= -reg new_chirp, new_elevation, new_azimuth; -reg mixers_enable; +reg mixers_enable; +reg dst_chirp_valid; +reg [1:0] dst_wave_sel; +reg frame_pulse_120m; +reg new_elevation; +reg new_azimuth; wire [7:0] chirp_data; wire chirp_valid; @@ -59,25 +66,19 @@ wire [5:0] elevation_counter; wire [5:0] azimuth_counter; // ========================================================================= -// DUT INSTANTIATION with overridden parameters +// DUT // ========================================================================= -plfm_chirp_controller_enhanced #( - .T1_SAMPLES(T1_SAMPLES), - .T1_RADAR_LISTENING(T1_RADAR_LISTENING), - .T2_SAMPLES(T2_SAMPLES), - .T2_RADAR_LISTENING(T2_RADAR_LISTENING), - .GUARD_SAMPLES(GUARD_SAMPLES), - .CHIRP_MAX(CHIRP_MAX), - .ELEVATION_MAX(ELEVATION_MAX), - .AZIMUTH_MAX(AZIMUTH_MAX) -) dut ( +plfm_chirp_controller_v2 dut ( .clk_120m(clk_120m), .clk_100m(clk_100m), .reset_n(reset_n), - .new_chirp(new_chirp), + .reset_100m_n(reset_100m_n), + .mixers_enable(mixers_enable), + .dst_chirp_valid(dst_chirp_valid), + .dst_wave_sel(dst_wave_sel), + .frame_pulse_120m(frame_pulse_120m), .new_elevation(new_elevation), .new_azimuth(new_azimuth), - .mixers_enable(mixers_enable), .chirp_data(chirp_data), .chirp_valid(chirp_valid), .new_chirp_frame(new_chirp_frame), @@ -110,23 +111,6 @@ integer pass_count; integer fail_count; integer total_tests; -// State name decoder for debug -function [95:0] state_name; - input [2:0] state; - begin - case (state) - 3'b000: state_name = "IDLE "; - 3'b001: state_name = "LONG_CHIRP "; - 3'b010: state_name = "LONG_LISTEN "; - 3'b011: state_name = "GUARD_TIME "; - 3'b100: state_name = "SHORT_CHIRP "; - 3'b101: state_name = "SHORT_LISTEN"; - 3'b110: state_name = "DONE "; - default: state_name = "UNKNOWN "; - endcase - end -endfunction - task check; input [255:0] test_name; input condition; @@ -142,357 +126,190 @@ task check; end endtask -// Wait for N cycles of clk_120m -task wait_120m; - input integer n; - integer i; +// Pulse dst_chirp_valid for 1 cycle on clk_120m with the requested wave_sel +task issue_chirp; + input [1:0] wsel; begin - for (i = 0; i < n; i = i + 1) - @(posedge clk_120m); + @(posedge clk_120m); + dst_wave_sel <= wsel; + dst_chirp_valid <= 1'b1; + @(posedge clk_120m); + dst_chirp_valid <= 1'b0; end endtask -// Wait until DUT enters a specific state (with timeout) -task wait_for_state; - input [2:0] target_state; +// Wait until DUT enters ST_IDLE again (chirp finished), with timeout +task wait_for_idle; input integer timeout_cycles; integer i; begin for (i = 0; i < timeout_cycles; i = i + 1) begin @(posedge clk_120m); - if (dut.current_state == target_state) begin - i = timeout_cycles; // exit + if (dut.state == 1'b0) begin + i = timeout_cycles; end end end endtask +// Pulse frame_pulse_120m for 1 cycle on clk_120m +task pulse_frame; + begin + @(posedge clk_120m); + frame_pulse_120m <= 1'b1; + @(posedge clk_120m); + frame_pulse_120m <= 1'b0; + end +endtask + // ========================================================================= -// MAIN TEST SEQUENCE +// MAIN // ========================================================================= initial begin $dumpfile("tb_chirp_controller.vcd"); $dumpvars(0, tb_chirp_controller); - + test_num = 0; pass_count = 0; fail_count = 0; - - // Initialize - reset_n = 0; - new_chirp = 0; - new_elevation = 0; - new_azimuth = 0; - mixers_enable = 0; - + + reset_n = 0; + reset_100m_n = 0; + mixers_enable = 0; + dst_chirp_valid = 0; + dst_wave_sel = `RP_WAVE_SHORT; + frame_pulse_120m = 0; + new_elevation = 0; + new_azimuth = 0; + $display(""); $display("============================================================"); - $display(" CHIRP CONTROLLER TESTBENCH"); - $display(" Testing A5 fix: single-driver chirp_counter on clk_120m"); - $display(" Parameters: CHIRP_MAX=%0d, T1=%0d, T2=%0d", CHIRP_MAX, T1_SAMPLES, T2_SAMPLES); + $display(" PLFM CHIRP CONTROLLER V2 TESTBENCH (chirp-v2 PR-E)"); + $display(" SHORT=%0d, MEDIUM=%0d, LONG=%0d samples", + SHORT_SAMPLES, MEDIUM_SAMPLES, LONG_SAMPLES); $display("============================================================"); $display(""); - - // ===================================================================== - // TEST GROUP 1: RESET BEHAVIOR - // ===================================================================== - $display("--- Group 1: Reset Behavior ---"); - + + // ---------- Reset ---------- + $display("--- Group 1: Reset ---"); #100; - - // T1.1: After reset, should be in IDLE - check("Reset: state is IDLE", dut.current_state == 3'b000); - - // T1.2: chirp_counter should be 0 after reset (was the A5 bug: Driver1 reset to 1, Driver2 to 0) - check("Reset: chirp_counter is 0", chirp_counter == 6'd0); - - // T1.3: chirp_data should be 128 (midpoint) in IDLE - check("Reset: chirp_data is 128 (midpoint)", chirp_data == 8'd128); - - // T1.4: rf_switch should be off - check("Reset: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0); - - // T1.5: chirp_valid should be 0 - check("Reset: chirp_valid is 0", chirp_valid == 1'b0); - - // T1.6: chirp_done should be 0 - check("Reset: chirp_done is 0", chirp_done == 1'b0); - - // Release reset + check("Reset: state == IDLE", dut.state == 1'b0); + check("Reset: chirp_data == 128", chirp_data == 8'd128); + check("Reset: chirp_valid low", chirp_valid == 1'b0); + check("Reset: rf_switch_ctrl low", rf_switch_ctrl == 1'b0); + check("Reset: chirp_done low", chirp_done == 1'b0); + check("Reset: chirp_counter == 0", chirp_counter == 6'd0); + check("Reset: elevation_counter==1", elevation_counter == 6'd1); + check("Reset: azimuth_counter==1", azimuth_counter == 6'd1); + @(posedge clk_120m); - reset_n = 1; + reset_n <= 1; + @(posedge clk_100m); + reset_100m_n <= 1; @(posedge clk_120m); - - // ===================================================================== - // TEST GROUP 2: IDLE STATE — no transition without mixers_enable - // ===================================================================== - $display("--- Group 2: IDLE Hold ---"); - - // T2.1: With new_chirp but no mixers_enable, stay in IDLE - new_chirp = 1; - wait_120m(5); - check("IDLE hold: no transition without mixers_enable", dut.current_state == 3'b000); - new_chirp = 0; - - // ===================================================================== - // TEST GROUP 3: FULL FSM SEQUENCE - // ===================================================================== - $display("--- Group 3: Full FSM Sequence ---"); - - // Enable mixers and trigger chirp + + // ---------- IDLE hold without mixers_enable ---------- + $display("--- Group 2: IDLE Hold (mixers_enable=0) ---"); + issue_chirp(`RP_WAVE_SHORT); + repeat (4) @(posedge clk_120m); + check("Without mixers_enable, no transition into CHIRP", dut.state == 1'b0); + check("Without mixers_enable, chirp_data stays 128", chirp_data == 8'd128); + check("Without mixers_enable, chirp_valid stays 0", chirp_valid == 1'b0); + + // ---------- SHORT chirp playback ---------- + $display("--- Group 3: SHORT chirp playback (120 samples) ---"); mixers_enable = 1; @(posedge clk_120m); - new_chirp = 1; // chirp__toggling is just new_chirp pass-through + + issue_chirp(`RP_WAVE_SHORT); + + // 1 dst_clk for IDLE→CHIRP transition, 1 more for CHIRP-branch output + // registers (rf_switch / adar_tr / chirp_valid) to assert. @(posedge clk_120m); - - // T3.1: Should transition to LONG_CHIRP - wait_for_state(3'b001, 5); // LONG_CHIRP - check("FSM: enters LONG_CHIRP", dut.current_state == 3'b001); - - // T3.2: RF switch should be ON during LONG_CHIRP - @(posedge clk_120m); // one cycle for output to settle - check("LONG_CHIRP: rf_switch_ctrl is 1", rf_switch_ctrl == 1'b1); - - // T3.3: ADAR T/R switches should be 1 (transmit mode) - check("LONG_CHIRP: adar_tr_1 is 1", adar_tr_1 == 1'b1); - - // T3.4: chirp_valid should be 1 - check("LONG_CHIRP: chirp_valid is 1", chirp_valid == 1'b1); - - // T3.5: chirp_data should NOT be 128 (should be reading from LUT) - // Note: with shortened params, LUT index wraps, but data shouldn't be stuck at 128 - // Actually, with T1_SAMPLES=8, it reads long_chirp_lut[0..7] which has real data - check("LONG_CHIRP: chirp_data comes from LUT (not midpoint)", chirp_data != 8'd128); - - // Wait for LONG_CHIRP to finish (T1_SAMPLES = 8 cycles) - wait_for_state(3'b010, T1_SAMPLES + 5); // LONG_LISTEN - - // T3.6: Should reach LONG_LISTEN - check("FSM: enters LONG_LISTEN", dut.current_state == 3'b010); - - // T3.7: RF switch OFF during listen + @(posedge clk_120m); #1; + check("SHORT: enters CHIRP", dut.state == 1'b1); + check("SHORT: rf_switch_ctrl asserted", rf_switch_ctrl == 1'b1); + check("SHORT: adar_tr_1 asserted", adar_tr_1 == 1'b1); + check("SHORT: chirp_valid asserted", chirp_valid == 1'b1); + check("SHORT: tx_mixer_en asserted", tx_mixer_en == 1'b1); + check("SHORT: rx_mixer_en deasserted", rx_mixer_en == 1'b0); + + // Drain the chirp window and confirm we land back in IDLE within bound. + wait_for_idle(SHORT_SAMPLES + 20); + check("SHORT: returns to IDLE within 120+20 cycles", dut.state == 1'b0); + check("SHORT: rf_switch_ctrl deasserted in IDLE", rf_switch_ctrl == 1'b0); + check("SHORT: chirp_data idle code 128 in IDLE", chirp_data == 8'd128); + check("SHORT: chirp_counter incremented to 1", chirp_counter == 6'd1); + + // ---------- MEDIUM chirp playback ---------- + $display("--- Group 4: MEDIUM chirp playback (600 samples) ---"); + issue_chirp(`RP_WAVE_MEDIUM); @(posedge clk_120m); - check("LONG_LISTEN: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0); - - // T3.8: chirp_data should be 128 during listen - check("LONG_LISTEN: chirp_data is 128", chirp_data == 8'd128); - - // T3.9: chirp_counter should have incremented to 1 after first LONG_LISTEN - // Wait for listen to finish - wait_for_state(3'b001, T1_RADAR_LISTENING + 5); // back to LONG_CHIRP - check("chirp_counter: incremented to 1 after first listen", chirp_counter == 6'd1); - - // Now wait through second LONG_CHIRP + LONG_LISTEN cycle - // After CHIRP_MAX/2 = 2 long chirps, should go to GUARD_TIME - wait_for_state(3'b010, T1_SAMPLES + 5); // LONG_LISTEN again - wait_for_state(3'b011, T1_RADAR_LISTENING + 5); // GUARD_TIME - - // T3.10: After CHIRP_MAX/2 long chirps, enters GUARD_TIME - check("FSM: enters GUARD_TIME after CHIRP_MAX/2 long chirps", dut.current_state == 3'b011); - - // Wait through guard time - wait_for_state(3'b100, GUARD_SAMPLES + 5); // SHORT_CHIRP - - // T3.11: Enters SHORT_CHIRP - check("FSM: enters SHORT_CHIRP", dut.current_state == 3'b100); - - // T3.12: RF switch ON during SHORT_CHIRP + check("MEDIUM: enters CHIRP", dut.state == 1'b1); + check("MEDIUM: active_max_samples==600", dut.active_max_samples == 12'd600); + wait_for_idle(MEDIUM_SAMPLES + 20); + check("MEDIUM: returns to IDLE", dut.state == 1'b0); + check("MEDIUM: chirp_counter == 2", chirp_counter == 6'd2); + + // ---------- LONG chirp playback ---------- + $display("--- Group 5: LONG chirp playback (3600 samples) ---"); + issue_chirp(`RP_WAVE_LONG); @(posedge clk_120m); - check("SHORT_CHIRP: rf_switch_ctrl is 1", rf_switch_ctrl == 1'b1); - - // Wait through SHORT_CHIRP -> SHORT_LISTEN -> SHORT_CHIRP -> SHORT_LISTEN -> DONE - // That's 2 more chirps (chirp_counter goes from 2 to 3, then 3 to CHIRP_MAX-1=3) - wait_for_state(3'b101, T2_SAMPLES + 5); // SHORT_LISTEN - wait_for_state(3'b100, T2_RADAR_LISTENING + 5); // SHORT_CHIRP again - wait_for_state(3'b101, T2_SAMPLES + 5); // SHORT_LISTEN again - wait_for_state(3'b110, T2_RADAR_LISTENING + 5); // DONE - - // T3.13: FSM reaches DONE state - check("FSM: reaches DONE state", dut.current_state == 3'b110); - - // T3.14: chirp_done asserted — check on next clock edge - // Also deassert new_chirp NOW (during DONE state) so FSM stays in IDLE - // after DONE transitions. If we wait, FSM goes DONE→IDLE→LONG_CHIRP instantly. - new_chirp = 0; + check("LONG: enters CHIRP", dut.state == 1'b1); + check("LONG: active_max_samples==3600", dut.active_max_samples == 12'd3600); + wait_for_idle(LONG_SAMPLES + 20); + check("LONG: returns to IDLE", dut.state == 1'b0); + check("LONG: chirp_counter == 3", chirp_counter == 6'd3); + + // ---------- frame_pulse clears chirp_counter ---------- + $display("--- Group 6: frame_pulse clears chirp_counter ---"); + pulse_frame(); @(posedge clk_120m); - check("DONE: chirp_done is 1", chirp_done == 1'b1); - - // T3.15: Returns to IDLE - // Note: chirp_done check consumed one edge (DONE→IDLE already happened) - // With new_chirp=0, FSM should stay in IDLE - @(posedge clk_120m); - check("FSM: returns to IDLE after DONE", dut.current_state == 3'b000); + check("frame_pulse: chirp_counter back to 0", chirp_counter == 6'd0); - // ===================================================================== - // TEST GROUP 3b: MULTI-FRAME REGRESSION (C-3) - // - // Bug: plfm_chirp_controller_enhanced never reset chirp_counter when the - // frame completed. After frame 1 the counter sat at CHIRP_MAX, so the - // LONG_LISTEN -> GUARD transition guard (== CHIRP_MAX/2-1) never matched - // on subsequent frames and frame 2+ ran extra chirps until the 6-bit - // counter wrapped. - // - // These checks prove the counter is cleared at DONE and frame 2 matches - // frame 1 exactly. - // ===================================================================== - $display("--- Group 3b: Multi-Frame Regression (C-3) ---"); + // ---------- LUT data (chirp_data leaves idle during CHIRP) ---------- + $display("--- Group 7: LUT-driven chirp_data ---"); + issue_chirp(`RP_WAVE_SHORT); + repeat (4) @(posedge clk_120m); + check("SHORT mid-chirp: chirp_data != 128 (LUT-driven)", chirp_data != 8'd128); + wait_for_idle(SHORT_SAMPLES + 20); - // T3b.1: Immediately after frame 1 DONE -> IDLE, counter is back to 0. - check("C-3: chirp_counter reset to 0 after 1st DONE", chirp_counter == 6'd0); - - // Kick off frame 2 from the same IDLE state (no reset between frames). - @(posedge clk_120m); - new_chirp = 1; - @(posedge clk_120m); - - // T3b.2: Frame 2 enters LONG_CHIRP. - wait_for_state(3'b001, 10); - check("Frame 2: enters LONG_CHIRP", dut.current_state == 3'b001); - - // T3b.3: Frame 2 reaches GUARD_TIME after exactly CHIRP_MAX/2 long chirps. - // (If the counter were not reset, the FSM would stall in - // LONG_CHIRP/LONG_LISTEN until the 6-bit counter wrapped.) - wait_for_state(3'b011, - (T1_SAMPLES + T1_RADAR_LISTENING) * (CHIRP_MAX/2) + 20); - check("Frame 2: reaches GUARD_TIME after CHIRP_MAX/2 long chirps", - dut.current_state == 3'b011); - check("Frame 2: chirp_counter == CHIRP_MAX/2 at GUARD_TIME", - chirp_counter == CHIRP_MAX/2); - - // T3b.4: Frame 2 reaches DONE. - wait_for_state(3'b110, - GUARD_SAMPLES + - (T2_SAMPLES + T2_RADAR_LISTENING) * (CHIRP_MAX/2) + 20); - check("Frame 2: reaches DONE", dut.current_state == 3'b110); - - // Deassert new_chirp so FSM stays in IDLE after DONE. - new_chirp = 0; - @(posedge clk_120m); - - // T3b.5: Counter cleared again after frame 2 completes. - check("C-3: chirp_counter reset to 0 after 2nd DONE", chirp_counter == 6'd0); - - // ===================================================================== - // TEST GROUP 4: SINGLE-DRIVER VERIFICATION (A5 FIX CORE TEST) - // ===================================================================== - $display("--- Group 4: A5 Fix - Single Driver Verification ---"); - - // Reset and re-run with both clocks to verify no race condition - reset_n = 0; + // ---------- Mixer disable resets state ---------- + $display("--- Group 8: Mixer disable ---"); + issue_chirp(`RP_WAVE_MEDIUM); + repeat (10) @(posedge clk_120m); mixers_enable = 0; - new_chirp = 0; - #100; - reset_n = 1; - @(posedge clk_120m); - - // T4.1: After re-reset, chirp_counter is 0 - check("Re-reset: chirp_counter is 0", chirp_counter == 6'd0); - - // T4.2: Toggling new_chirp on clk_100m should NOT change chirp_counter - // (The old bug: clk_100m driver would increment it) - @(posedge clk_100m); - new_chirp = 1; - @(posedge clk_100m); - @(posedge clk_100m); - @(posedge clk_100m); - @(posedge clk_100m); - check("A5 fix: new_chirp pulses alone don't change chirp_counter", chirp_counter == 6'd0); - new_chirp = 0; - - // T4.3: Only the FSM (clk_120m) should drive chirp_counter - // Start a chirp sequence and verify counter increments only at listen end - mixers_enable = 1; - @(posedge clk_120m); - new_chirp = 1; - @(posedge clk_120m); - - // Wait for first LONG_CHIRP - wait_for_state(3'b001, 5); - check("A5 fix: chirp_counter still 0 at start of LONG_CHIRP", chirp_counter == 6'd0); - - // Wait for first LONG_LISTEN completion - wait_for_state(3'b010, T1_SAMPLES + 5); - // During listen, counter hasn't incremented yet - check("A5 fix: chirp_counter still 0 during LONG_LISTEN", chirp_counter == 6'd0); - - // Wait for listen to end and counter to increment - wait_for_state(3'b001, T1_RADAR_LISTENING + 5); // back to LONG_CHIRP - check("A5 fix: chirp_counter is 1 after first listen completes", chirp_counter == 6'd1); - - // ===================================================================== - // TEST GROUP 5: MIXER DISABLE - // ===================================================================== - $display("--- Group 5: Mixer Disable ---"); - - // T5.1: Disabling mixers should reset outputs - mixers_enable = 0; - wait_120m(3); - check("Mixer disable: chirp_data returns to 128", chirp_data == 8'd128); - check("Mixer disable: chirp_valid is 0", chirp_valid == 1'b0); - check("Mixer disable: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0); - - // ===================================================================== - // TEST GROUP 6: ELEVATION/AZIMUTH COUNTERS (clk_100m domain, separate) - // ===================================================================== - $display("--- Group 6: Beam Steering Counters ---"); - - // Reset - reset_n = 0; - mixers_enable = 0; - new_chirp = 0; - new_elevation = 0; - new_azimuth = 0; - #100; - reset_n = 1; - @(posedge clk_100m); - - // T6.1: Elevation counter resets to 1 - check("Reset: elevation_counter is 1", elevation_counter == 6'd1); - - // T6.2: Azimuth counter resets to 1 - check("Reset: azimuth_counter is 1", azimuth_counter == 6'd1); - - // T6.3: Elevation counter increments on new_elevation - // Note: elevation__toggling = new_elevation (level-sensitive pass-through) - // With ELEVATION_MAX=2, holding high oscillates 1->2->1->... + repeat (3) @(posedge clk_120m); + check("Mixer disable: chirp_data idle 128", chirp_data == 8'd128); + check("Mixer disable: chirp_valid 0", chirp_valid == 1'b0); + check("Mixer disable: rf_switch_ctrl 0", rf_switch_ctrl == 1'b0); + check("Mixer disable: tx_mixer_en 0", tx_mixer_en == 1'b0); + check("Mixer disable: rx_mixer_en 0", rx_mixer_en == 1'b0); + check("Mixer disable: state forced IDLE", dut.state == 1'b0); + + // ---------- Beam-step counters ---------- + $display("--- Group 9: Beam steering counters ---"); new_elevation = 1; @(posedge clk_100m); @(posedge clk_100m); - check("Elevation: increments on new_elevation", elevation_counter == 6'd2 || elevation_counter == 6'd1); - - // T6.4: Elevation counter wraps at ELEVATION_MAX - // Counter toggles between 1 and 2 each cycle when held high - @(posedge clk_100m); - check("Elevation: wraps at ELEVATION_MAX", - (elevation_counter == 6'd1) || (elevation_counter == 6'd2)); + check("Elevation: increments on toggle", + elevation_counter == 6'd2 || elevation_counter == 6'd3); new_elevation = 0; - @(posedge clk_100m); - - // T6.5: Azimuth counter increments on new_azimuth + new_azimuth = 1; @(posedge clk_100m); @(posedge clk_100m); - check("Azimuth: increments on new_azimuth", azimuth_counter == 6'd2 || azimuth_counter == 6'd1); + check("Azimuth: increments on toggle", + azimuth_counter == 6'd2 || azimuth_counter == 6'd3); new_azimuth = 0; - - // ===================================================================== - // TEST GROUP 7: MIXER ENABLE SIGNALS - // ===================================================================== - $display("--- Group 7: Mixer Control Outputs ---"); - - // T7.1: In IDLE state, both mixers are off even with mixers_enable=1 - // (Fix #4: mixers are state-dependent, not tied to mixers_enable directly) - mixers_enable = 1; - #1; - check("rx_mixer_en off in IDLE (state-dependent)", rx_mixer_en == 1'b0); - - // T7.2: tx_mixer_en also off in IDLE - check("tx_mixer_en off in IDLE (state-dependent)", tx_mixer_en == 1'b0); - - // T7.3: ADAR load pins tied low - check("ADAR load pins: adar_tx_load_1 is 0", adar_tx_load_1 == 1'b0); - check("ADAR load pins: adar_rx_load_1 is 0", adar_rx_load_1 == 1'b0); - + + // ---------- ADAR load pins tied low ---------- + $display("--- Group 10: ADAR load pins ---"); + check("adar_tx_load_1 tied low", adar_tx_load_1 == 1'b0); + check("adar_rx_load_1 tied low", adar_rx_load_1 == 1'b0); + check("adar_tx_load_4 tied low", adar_tx_load_4 == 1'b0); + check("adar_rx_load_4 tied low", adar_rx_load_4 == 1'b0); + // ===================================================================== // SUMMARY // ===================================================================== @@ -506,14 +323,14 @@ initial begin $display(" STATUS: %0d TESTS FAILED", fail_count); $display("============================================================"); $display(""); - + #100; $finish; end // Timeout watchdog initial begin - #500000; // 500 us max + #500000; // 500 µs — covers LONG playback (~30 µs) + headroom $display("TIMEOUT: Simulation took too long!"); $finish; end diff --git a/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v b/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v index f6bbd3f..9080d5b 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v @@ -181,7 +181,10 @@ radar_receiver_final dut ( // AUDIT-C3: ADC format select — offset-binary baseline .host_adc_format(2'b00), // CFAR: frame-complete output (not used in this TB) - .doppler_frame_done_out() + .doppler_frame_done_out(), + + // PR-E: pin mixers_enable HIGH so the scheduler runs in this TB + .mixers_enable_100m(1'b1) ); // ============================================================================