Skip to content

Commit 380f3a1

Browse files
committed
Added PWM modulator module and a testbench
1 parent d5030cf commit 380f3a1

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

pwm_gen.sv

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//------------------------------------------------------------------------------
2+
// pwm_gen.sv
3+
// Konstantin Pavlov, pavlovconst@gmail.com
4+
//------------------------------------------------------------------------------
5+
6+
// INFO ------------------------------------------------------------------------
7+
// PWM generator module
8+
//
9+
// - expecting 8-bit control signal input
10+
// - system clock is 100 MHz by default
11+
// - PWM clock is 1.5KHz by default
12+
13+
14+
/* --- INSTANTIATION TEMPLATE BEGIN ---
15+
16+
pwm_gen #(
17+
.CLK_HZ( 100_000_000 ), // 100 MHz
18+
.PWM_PERIOD_DIV( 16 ) // 100MHz/2^16= ~1.526 KHz
19+
20+
.MOD_WIDTH( 8 ) // from 0 to 255
21+
) pwm1 (
22+
.clk( clk ),
23+
.nrst( nrst ),
24+
25+
.control( ),
26+
.pwm_out( ),
27+
28+
.start_strobe( ),
29+
.busy( )
30+
);
31+
32+
--- INSTANTIATION TEMPLATE END ---*/
33+
34+
module pwm_gen #( parameter
35+
CLK_HZ = 100_000_000,
36+
PWM_PERIOD_DIV = 16, // must be > MOD_WIDTH
37+
PWM_PERIOD_HZ = CLK_HZ / (2**PWM_PERIOD_DIV),
38+
39+
MOD_WIDTH = 8 // modulation bitness
40+
)(
41+
input clk, // system clock
42+
input nrst, // negative reset
43+
44+
input [MOD_WIDTH-1:0] mod_setpoint, // modulation setpoint
45+
output pwm_out, // active HIGH output
46+
47+
// status outputs
48+
output start_strobe, // period start strobe
49+
output busy // busy output
50+
);
51+
52+
53+
// period generator
54+
logic [31:0] div_clk;
55+
clk_divider #(
56+
.WIDTH( 32 )
57+
) cd1 (
58+
.clk( clk ),
59+
.nrst( nrst ),
60+
.ena( 1'b1 ),
61+
.out( div_clk[31:0] )
62+
);
63+
64+
65+
// optional setpoint inversion
66+
logic [MOD_WIDTH-1:0] mod_setpoint_inv;
67+
assign mod_setpoint_inv[MOD_WIDTH-1:0] = {MOD_WIDTH{1'b1}} - mod_setpoint[MOD_WIDTH-1:0];
68+
69+
70+
// pulse generator
71+
pulse_gen #(
72+
.CNTR_WIDTH( MOD_WIDTH+1 )
73+
) pg1 (
74+
.clk( div_clk[(PWM_PERIOD_DIV-1)-MOD_WIDTH] ),
75+
.nrst( nrst ),
76+
77+
.start( 1'b1 ),
78+
.cntr_max( {1'b0, {MOD_WIDTH{1'b1}} } ),
79+
.cntr_low( {1'b0, mod_setpoint_inv[MOD_WIDTH-1:0] } ),
80+
81+
.pulse_out( pwm_out ),
82+
83+
.start_strobe( start_strobe ),
84+
.busy( busy )
85+
);
86+
87+
88+
endmodule
89+

pwm_gen_tb.sv

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//------------------------------------------------------------------------------
2+
// pwm_gen_tb.sv
3+
// Konstantin Pavlov, pavlovconst@gmail.com
4+
//------------------------------------------------------------------------------
5+
6+
// INFO ------------------------------------------------------------------------
7+
// testbench for pwm_gen.sv module
8+
9+
10+
`timescale 1ns / 1ps
11+
12+
module pwm_gen_tb();
13+
14+
logic clk200;
15+
initial begin
16+
#0 clk200 = 1'b0;
17+
forever
18+
#2.5 clk200 = ~clk200;
19+
end
20+
21+
// external device "asynchronous" clock
22+
logic clk33;
23+
initial begin
24+
#0 clk33 = 1'b0;
25+
forever
26+
#15.151 clk33 = ~clk33;
27+
end
28+
29+
logic rst;
30+
initial begin
31+
#0 rst = 1'b0;
32+
#10.2 rst = 1'b1;
33+
#5 rst = 1'b0;
34+
//#10000;
35+
forever begin
36+
#9985 rst = ~rst;
37+
#5 rst = ~rst;
38+
end
39+
end
40+
41+
logic nrst;
42+
assign nrst = ~rst;
43+
44+
logic rst_once;
45+
initial begin
46+
#0 rst_once = 1'b0;
47+
#10.2 rst_once = 1'b1;
48+
#5 rst_once = 1'b0;
49+
end
50+
51+
logic nrst_once;
52+
assign nrst_once = ~rst_once;
53+
54+
logic [31:0] DerivedClocks;
55+
clk_divider #(
56+
.WIDTH( 32 )
57+
) cd1 (
58+
.clk( clk200 ),
59+
.nrst( nrst_once ),
60+
.ena( 1'b1 ),
61+
.out( DerivedClocks[31:0] )
62+
);
63+
64+
logic [31:0] E_DerivedClocks;
65+
edge_detect ed1[31:0] (
66+
.clk( {32{clk200}} ),
67+
.nrst( {32{nrst_once}} ),
68+
.in( DerivedClocks[31:0] ),
69+
.rising( E_DerivedClocks[31:0] ),
70+
.falling( ),
71+
.both( )
72+
);
73+
74+
logic [31:0] RandomNumber1;
75+
c_rand rng1 (
76+
.clk( clk200 ),
77+
.rst( 1'b0 ),
78+
.reseed( rst_once ),
79+
.seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 1) ),
80+
.out( RandomNumber1[15:0] )
81+
);
82+
83+
c_rand rng2 (
84+
.clk( clk200 ),
85+
.rst( 1'b0 ),
86+
.reseed( rst_once ),
87+
.seed_val( DerivedClocks[31:0] ^ (DerivedClocks[31:0] << 2) ),
88+
.out( RandomNumber1[31:16] )
89+
);
90+
91+
logic start;
92+
initial begin
93+
#0 start = 1'b0;
94+
#100 start = 1'b1;
95+
#20 start = 1'b0;
96+
end
97+
98+
// Modules under test ==========================================================
99+
100+
localparam MOD_WIDTH = 5;
101+
102+
logic [MOD_WIDTH-1:0] sp = '0;
103+
logic [31:0][MOD_WIDTH-1:0] sin_table =
104+
{ 5'd16, 5'd19, 5'd22, 5'd25, 5'd27, 5'd29, 5'd31, 5'd31,
105+
5'd31, 5'd31, 5'd30, 5'd28, 5'd26, 5'd23, 5'd20, 5'd17,
106+
5'd14, 5'd11, 5'd8, 5'd5, 5'd3, 5'd1, 5'd0, 5'd0,
107+
5'd0, 5'd0, 5'd2, 5'd4, 5'd6, 5'd9, 5'd12, 5'd15};
108+
109+
logic strobe;
110+
always_ff @(posedge clk200) begin
111+
if( ~nrst_once ) begin
112+
sp[MOD_WIDTH-1:0] <= '0;
113+
end else begin
114+
if( strobe ) begin
115+
sp[MOD_WIDTH-1:0] <= sp[MOD_WIDTH-1:0] + 1'b1;
116+
end
117+
end
118+
end
119+
120+
pwm_gen #(
121+
.CLK_HZ( 200_000_000 ),
122+
.PWM_PERIOD_DIV( MOD_WIDTH+1 ), // MOD_WIDTH+1 is a minimum
123+
.MOD_WIDTH( MOD_WIDTH )
124+
) pwm1 (
125+
.clk( clk200 ),
126+
.nrst( nrst_once ),
127+
128+
.mod_setpoint( sin_table[sp[MOD_WIDTH-1:0]][MOD_WIDTH-1:0] ),
129+
.pwm_out( ),
130+
131+
.start_strobe( strobe ),
132+
.busy( )
133+
);
134+
135+
136+
endmodule

0 commit comments

Comments
 (0)