From 622191172e18da9edbcd16451f8370f07e3e9c0e Mon Sep 17 00:00:00 2001 From: Nikolay Puzanov Date: Tue, 2 Feb 2021 23:54:49 +0300 Subject: [PATCH] Add multichannel and sample rate accurate version --- source/dual_mcp3201_pmod.sv | 28 +++++++- source/mcp3201_ma.sv | 131 ++++++++++++++++++++++++++++++++++++ testbench/.dir-locals.el | 5 ++ testbench/Makefile | 3 +- testbench/tb_mcp3201_ma.sv | 83 +++++++++++++++++++++++ 5 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 source/mcp3201_ma.sv create mode 100644 testbench/.dir-locals.el create mode 100644 testbench/tb_mcp3201_ma.sv diff --git a/source/dual_mcp3201_pmod.sv b/source/dual_mcp3201_pmod.sv index a56af65..8bf5fb9 100644 --- a/source/dual_mcp3201_pmod.sv +++ b/source/dual_mcp3201_pmod.sv @@ -1,8 +1,10 @@ `timescale 1ns/100ps `default_nettype none +`define MULTICHANNEL_VERSION + module dual_mcp3201_pmod #(parameter CLOCK_FREQ = 25000000, - parameter SAMPLE_RATE = 50000) + parameter SAMPLE_RATE = 50000) (input wire clock, input wire reset, @@ -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 diff --git a/source/mcp3201_ma.sv b/source/mcp3201_ma.sv new file mode 100644 index 0000000..86d4d2e --- /dev/null +++ b/source/mcp3201_ma.sv @@ -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 diff --git a/testbench/.dir-locals.el b/testbench/.dir-locals.el new file mode 100644 index 0000000..61e855c --- /dev/null +++ b/testbench/.dir-locals.el @@ -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"))))) diff --git a/testbench/Makefile b/testbench/Makefile index 6087a94..4476b48 100644 --- a/testbench/Makefile +++ b/testbench/Makefile @@ -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) diff --git a/testbench/tb_mcp3201_ma.sv b/testbench/tb_mcp3201_ma.sv new file mode 100644 index 0000000..4c77af3 --- /dev/null +++ b/testbench/tb_mcp3201_ma.sv @@ -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