Add multichannel and sample rate accurate version

This commit is contained in:
Nikolay Puzanov 2021-02-02 23:54:49 +03:00
parent 00b07d114f
commit 622191172e
5 changed files with 247 additions and 3 deletions

View File

@ -1,6 +1,8 @@
`timescale 1ns/100ps
`default_nettype none
`define MULTICHANNEL_VERSION
module dual_mcp3201_pmod #(parameter CLOCK_FREQ = 25000000,
parameter SAMPLE_RATE = 50000)
(input wire clock,
@ -19,6 +21,29 @@ module dual_mcp3201_pmod #(parameter CLOCK_FREQ = 25000000,
output wire [11:0] ldata,
output wire lstrb);
`ifdef MULTICHANNEL_VERSION
/* Multichannel and sample rate accurate version */
localparam SPI_SCLK_FREQ = 1000000;
mcp3201_ma #(.CHANNELS(2),
.CLOCK_FREQ(CLOCK_FREQ),
.SCLK_FREQ(SPI_SCLK_FREQ),
.SAMPLE_RATE(SAMPLE_RATE)) adcs
(.clock, .reset,
.spi_clk_o(radc_clk),
.spi_ssn_o(radc_ssn),
.spi_miso_i({ radc_dat, ladc_dat }),
.data_o({ rdata, ldata }),
.strb_o(rstrb));
assign ladc_clk = radc_clk;
assign ladc_ssn = radc_ssn;
assign lstrb = rstrb;
`else
/* Single channel inaccurate version */
localparam MCP3201_CLOCK_PER_SAMPLE = 17;
localparam SCLK_FREQ = SAMPLE_RATE * MCP3201_CLOCK_PER_SAMPLE;
@ -47,5 +72,6 @@ module dual_mcp3201_pmod #(parameter CLOCK_FREQ = 25000000,
assign ladc_clk = spi_clk;
assign radc_clk = spi_clk;
`endif
endmodule // dual_mcp3201_pmod

131
source/mcp3201_ma.sv Normal file
View File

@ -0,0 +1,131 @@
`timescale 1ns/100ps
`default_nettype none
/*
* MCP3201 controller
* Multichannel and sample rate accurate version.
*/
module mcp3201_ma #(parameter CHANNELS = 1,
parameter CLOCK_FREQ = 12000000,
parameter SCLK_FREQ = 1000000,
parameter SAMPLE_RATE = 44100)
(input wire clock,
input wire reset,
output reg spi_clk_o,
output reg spi_ssn_o,
input wire [CHANNELS-1:0] spi_miso_i,
output reg [CHANNELS*12-1:0] data_o,
output reg strb_o);
initial
if (CHANNELS < 1)
$error("Channels count must be > 1");
/* SCLK frequency not need accuracy */
localparam SCLK_PERIOD = CLOCK_FREQ/SCLK_FREQ;
localparam SCLK_CW = $clog2(SCLK_PERIOD);
logic [SCLK_CW-1:0] sclk_cnt;
logic sclk_posedge;
/* Make SPI SCLK */
always_ff @(posedge clock)
if (reset | spi_ssn_o) begin
spi_clk_o <= 1'b1;
sclk_cnt <= '0;
sclk_posedge <= 1'b0;
end
else begin
sclk_posedge <= 1'b0;
sclk_cnt <= sclk_cnt + 1'b1;
if (sclk_cnt == SCLK_CW'(SCLK_PERIOD/2))
spi_clk_o <= 1'b0;
else
if (sclk_cnt == SCLK_CW'(SCLK_PERIOD-1)) begin
spi_clk_o <= 1'b1;
sclk_cnt <= '0;
sclk_posedge <= 1'b1;
end
end
/* Sample rate need more accuracy */
localparam SRATE_PERIOD = integer'($floor(real'(CLOCK_FREQ)/real'(SAMPLE_RATE) + 0.5));
localparam SRATE_CW = $clog2(SRATE_PERIOD);
logic [SRATE_CW-1:0] srate_cnt;
logic sample;
always_ff @(posedge clock)
if (reset) begin
sample <= 1'b0;
srate_cnt <= '0;
end
else
if (srate_cnt == SRATE_CW'(SRATE_PERIOD-1)) begin
sample <= 1'b1;
srate_cnt <= '0;
end
else begin
sample <= 1'b0;
srate_cnt <= srate_cnt + 1'b1;
end
/* Receive data FSM */
enum int unsigned {
ST_RELAX = 0,
ST_SHIFT,
ST_STROBE
} state;
logic [3:0] bit_cnt;
logic [11:0] data_sr[CHANNELS];
always_ff @(posedge clock)
if (reset) begin
state <= ST_RELAX;
bit_cnt <= '0;
spi_ssn_o <= 1'b1;
strb_o <= 1'b0;
data_o <= '0;
for (int i = 0; i < CHANNELS; i ++)
data_sr[i] <= '0;
end
else begin
strb_o <= 1'b0;
case (state)
ST_RELAX:
if (sample) begin
bit_cnt <= '0;
spi_ssn_o <= 1'b0;
state <= ST_SHIFT;
end
ST_SHIFT:
if (sclk_posedge) begin
for (int i = 0; i < CHANNELS; i ++)
data_sr[i] <= { data_sr[i][10:0], spi_miso_i[i] };
bit_cnt <= bit_cnt + 1'b1;
if (bit_cnt == 4'd14) begin
spi_ssn_o <= 1'b1;
state <= ST_STROBE;
end
end
ST_STROBE: begin
for (int i = 0; i < CHANNELS; i ++)
data_o[i*12 +: 12] <= data_sr[i];
strb_o <= 1'b1;
state <= ST_RELAX;
end
endcase
end
endmodule // mcp3201_ma

5
testbench/.dir-locals.el Normal file
View File

@ -0,0 +1,5 @@
;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")
((verilog-mode . ((flycheck-verilator-include-path . ("." "../source"))
(verilog-library-directories . ("." "../source")))))

View File

@ -1,10 +1,9 @@
VC = iverilog
VI = vvp
SOURCES = ../rtl/mcp3201.sv ../rtl/spi_sclk_gen.sv
SOURCES = ../source/mcp3201.sv ../source/mcp3201_ma.sv
VFLAGS = -g2012
TBS = $(wildcard tb_*.sv)
DEFINES = -D TESTBENCH
VCDDEPS = $(TBS:.sv=.vcd)

View File

@ -0,0 +1,83 @@
`timescale 1ns/100ps
module tb_mcp3201_ma;
logic clock = 1'b0;
logic reset = 1'b1;
/* Master clock 50MHz (20ns period) */
always #(20ns/2) clock <= ~clock;
localparam ITERATIONS = 10;
localparam CHANNELS = 3;
logic spi_clk;
logic spi_ssn;
logic [CHANNELS-1:0] spi_miso;
logic [CHANNELS*12-1:0] data;
logic strb;
mcp3201_ma #(.CHANNELS(CHANNELS),
.CLOCK_FREQ(50000000),
.SCLK_FREQ(1000000),
.SAMPLE_RATE(44100)) DUT
(.clock, .reset,
.spi_clk_o(spi_clk),
.spi_ssn_o(spi_ssn),
.spi_miso_i(spi_miso),
.data_o(data),
.strb_o(strb));
always_ff @ (posedge clock)
if (strb) begin
$display("");
for (int i = 0; i < CHANNELS; i ++)
$display("o_CH%d: %15b", i, data[i*12 +: 12]);
end
logic [14:0] ds[CHANNELS];
initial begin
reset = 1'b1;
repeat(10) @(posedge clock);
reset = 1'b0;
repeat(50) @(posedge clock);
for (int i = 0; i < ITERATIONS; i ++) begin
wait(spi_ssn == 1'b0);
$display("");
for (int c = 0; c < CHANNELS; c ++) begin
ds[c][14:13] = 2'bzz;
ds[c][12] = 1'b0;
ds[c][11:0] = $random;
$display("i_CH%d: %15b", c, ds[c][11:0]);
end
for (int n = 0; n < 15; n ++) begin
@(negedge spi_clk);
for (int c = 0; c < CHANNELS; c ++) begin
spi_miso[c] = ds[c][14];
ds[c] = ds[c] << 1;
end
end
wait(spi_ssn == 1'b1);
end
repeat(50) @(posedge clock);
$finish;
end
initial begin
$dumpfile("tb_mcp3201_ma.vcd");
$dumpvars;
end
endmodule // tb_mcp3201_ma