diff --git a/pwm_gen.sv b/pwm_gen.sv new file mode 100644 index 0000000..d2549bb --- /dev/null +++ b/pwm_gen.sv @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// pwm_gen.sv +// Konstantin Pavlov, pavlovconst@gmail.com +//------------------------------------------------------------------------------ + +// INFO ------------------------------------------------------------------------ +// PWM generator module +// +// - expecting 8-bit control signal input +// - system clock is 100 MHz by default +// - PWM clock is 1.5KHz by default + + +/* --- INSTANTIATION TEMPLATE BEGIN --- + +pwm_gen #( + .CLK_HZ( 100_000_000 ), // 100 MHz + .PWM_PERIOD_DIV( 16 ) // 100MHz/2^16= ~1.526 KHz + + .MOD_WIDTH( 8 ) // from 0 to 255 +) pwm1 ( + .clk( clk ), + .nrst( nrst ), + + .control( ), + .pwm_out( ), + + .start_strobe( ), + .busy( ) +); + +--- INSTANTIATION TEMPLATE END ---*/ + +module pwm_gen #( parameter + CLK_HZ = 100_000_000, + PWM_PERIOD_DIV = 16, // must be > MOD_WIDTH + PWM_PERIOD_HZ = CLK_HZ / (2**PWM_PERIOD_DIV), + + MOD_WIDTH = 8 // modulation bitness +)( + input clk, // system clock + input nrst, // negative reset + + input [MOD_WIDTH-1:0] mod_setpoint, // modulation setpoint + output pwm_out, // active HIGH output + + // status outputs + output start_strobe, // period start strobe + output busy // busy output +); + + +// period generator +logic [31:0] div_clk; +clk_divider #( + .WIDTH( 32 ) +) cd1 ( + .clk( clk ), + .nrst( nrst ), + .ena( 1'b1 ), + .out( div_clk[31:0] ) +); + + +// optional setpoint inversion +logic [MOD_WIDTH-1:0] mod_setpoint_inv; +assign mod_setpoint_inv[MOD_WIDTH-1:0] = {MOD_WIDTH{1'b1}} - mod_setpoint[MOD_WIDTH-1:0]; + + +// pulse generator +pulse_gen #( + .CNTR_WIDTH( MOD_WIDTH+1 ) +) pg1 ( + .clk( div_clk[(PWM_PERIOD_DIV-1)-MOD_WIDTH] ), + .nrst( nrst ), + + .start( 1'b1 ), + .cntr_max( {1'b0, {MOD_WIDTH{1'b1}} } ), + .cntr_low( {1'b0, mod_setpoint_inv[MOD_WIDTH-1:0] } ), + + .pulse_out( pwm_out ), + + .start_strobe( start_strobe ), + .busy( busy ) +); + + +endmodule + diff --git a/pwm_gen_tb.sv b/pwm_gen_tb.sv new file mode 100644 index 0000000..f581b3f --- /dev/null +++ b/pwm_gen_tb.sv @@ -0,0 +1,136 @@ +//------------------------------------------------------------------------------ +// pwm_gen_tb.sv +// Konstantin Pavlov, pavlovconst@gmail.com +//------------------------------------------------------------------------------ + +// INFO ------------------------------------------------------------------------ +// testbench for pwm_gen.sv module + + +`timescale 1ns / 1ps + +module pwm_gen_tb(); + +logic clk200; +initial begin + #0 clk200 = 1'b0; + forever + #2.5 clk200 = ~clk200; +end + +// external device "asynchronous" clock +logic clk33; +initial begin + #0 clk33 = 1'b0; + forever + #15.151 clk33 = ~clk33; +end + +logic rst; +initial begin + #0 rst = 1'b0; + #10.2 rst = 1'b1; + #5 rst = 1'b0; + //#10000; + forever begin + #9985 rst = ~rst; + #5 rst = ~rst; + end +end + +logic nrst; +assign nrst = ~rst; + +logic rst_once; +initial begin + #0 rst_once = 1'b0; + #10.2 rst_once = 1'b1; + #5 rst_once = 1'b0; +end + +logic nrst_once; +assign nrst_once = ~rst_once; + +logic [31:0] DerivedClocks; +clk_divider #( + .WIDTH( 32 ) +) cd1 ( + .clk( clk200 ), + .nrst( nrst_once ), + .ena( 1'b1 ), + .out( DerivedClocks[31:0] ) +); + +logic [31:0] E_DerivedClocks; +edge_detect ed1[31:0] ( + .clk( {32{clk200}} ), + .nrst( {32{nrst_once}} ), + .in( DerivedClocks[31:0] ), + .rising( E_DerivedClocks[31:0] ), + .falling( ), + .both( ) +); + +logic [31:0] RandomNumber1; +c_rand rng1 ( + .clk( clk200 ), + .rst( 1'b0 ), + .reseed( rst_once ), + .seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 1) ), + .out( RandomNumber1[15:0] ) +); + +c_rand rng2 ( + .clk( clk200 ), + .rst( 1'b0 ), + .reseed( rst_once ), + .seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 2) ), + .out( RandomNumber1[31:16] ) +); + +logic start; +initial begin + #0 start = 1'b0; + #100 start = 1'b1; + #20 start = 1'b0; +end + +// Modules under test ========================================================== + +localparam MOD_WIDTH = 5; + +logic [MOD_WIDTH-1:0] sp = '0; +logic [31:0][MOD_WIDTH-1:0] sin_table = +{ 5'd16, 5'd19, 5'd22, 5'd25, 5'd27, 5'd29, 5'd31, 5'd31, + 5'd31, 5'd31, 5'd30, 5'd28, 5'd26, 5'd23, 5'd20, 5'd17, + 5'd14, 5'd11, 5'd8, 5'd5, 5'd3, 5'd1, 5'd0, 5'd0, + 5'd0, 5'd0, 5'd2, 5'd4, 5'd6, 5'd9, 5'd12, 5'd15}; + +logic strobe; +always_ff @(posedge clk200) begin + if( ~nrst_once ) begin + sp[MOD_WIDTH-1:0] <= '0; + end else begin + if( strobe ) begin + sp[MOD_WIDTH-1:0] <= sp[MOD_WIDTH-1:0] + 1'b1; + end + end +end + +pwm_gen #( + .CLK_HZ( 200_000_000 ), + .PWM_PERIOD_DIV( MOD_WIDTH+1 ), // MOD_WIDTH+1 is a minimum + .MOD_WIDTH( MOD_WIDTH ) +) pwm1 ( + .clk( clk200 ), + .nrst( nrst_once ), + + .mod_setpoint( sin_table[sp[MOD_WIDTH-1:0]][MOD_WIDTH-1:0] ), + .pwm_out( ), + + .start_strobe( strobe ), + .busy( ) +); + + +endmodule