diff --git a/README.md b/README.md index 4d0992e..9c50c28 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,6 @@ This project implements a simple SPI where the loaded 16b data can be output in In addition, a first- and second-order delta-sigma modulator is implemented create a simple low-frequency voltage output with 16b. +Further, a sine generator with programmable frequency can be used to drive the DS-modulator. + The input and output list documentation can be found in `info.yaml`. diff --git a/docs/info.md b/docs/info.md index e9bd956..7f03b5a 100644 --- a/docs/info.md +++ b/docs/info.md @@ -11,13 +11,16 @@ You can also include images in this folder and reference them in the markdown. E A simple serial 16b register (mini SPI) with magic cookie detection is implemented. In addition, a 16bit sigma-delta modulator of 1st or 2nd order is included. +Further, a sine generator (based on a LUT) with programmable frequency can be selected to drive the input of the DAC. + ## How to test - Load the shift register in a serial way. - Check the magic cookie detection. - Check the digital output (low- and high-byte) of the loaded data word. -- Check the output voltage of the delta-sigma modulator by using an external RC lowpass filter. +- Check the dc output voltage of the delta-sigma modulator by using an external RC lowpass filter. +- Check the sine output of the delta-sigma modulator by using an external RC lowpass filter. ## External hardware -Just a way to set digital inputs is needed. A scope for monitoring output signals would be good. A voltmeter can be used to inspect the DAC output voltage. +Just a way to set digital inputs is needed. A scope for monitoring output signals would be good. A voltmeter can be used to inspect the DAC output voltage. If the sine generator is used for the DAC input, a scope can be used to monitor the sine signal. diff --git a/info.yaml b/info.yaml index a6a3f03..0dc79df 100644 --- a/info.yaml +++ b/info.yaml @@ -28,9 +28,9 @@ pinout: ui[1]: "SPI data in (MOSI)" ui[2]: "SPI load (CS)" ui[3]: "select output byte (0 = low, 1 = high)" - ui[4]: "" - ui[5]: "" - ui[6]: "" + ui[4]: "sinegen scale factor (LSB)" + ui[5]: "sinegen scale factor (MSB)" + ui[6]: "select ds-modulator input (0 = SPI register, 1 = sine generator)" ui[7]: "order of delta-sigma modulator (0 = 1st, 1 = 2nd)" # Outputs diff --git a/src/sinegen1.v b/src/sinegen1.v new file mode 100644 index 0000000..01d2f1c --- /dev/null +++ b/src/sinegen1.v @@ -0,0 +1,74 @@ +/* +* SPDX-FileCopyrightText: 2022-2024 Harald Pretl +* Johannes Kepler University, Institute for Integrated Circuits +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* SPDX-License-Identifier: Apache-2.0 +* +* Simple sine-generator based on LUT. +*/ + +`default_nettype none +`ifndef __SINEGEN1__ +`define __SINEGEN1__ + +module sinegen1 ( + output wire [15:0] o_data, + input i_rst_n, + input i_clk, + input [15:0] i_step, + input [1:0] i_scale +); + + reg [4:0] read_ptr_r; + reg [15:0] ctr_r; + reg ctr_msb_last_r; + wire [3:0] scale_w; + + // this bitpattern is a 16b UINT sine with a period of 32 samples with + // 90% amplitude and an offset of 0x8000 + /* verilator lint_off LITENDIAN */ + localparam [0:(32*16)-1] sin_const = { + 16'h8000,16'h9679,16'hAC16,16'hC000,16'hD175,16'hDFC9,16'hEA6E,16'hF0FD, + 16'hF333,16'hF0FD,16'hEA6E,16'hDFC9,16'hD175,16'hC000,16'hAC16,16'h9679, + 16'h8000,16'h6987,16'h53EA,16'h4000,16'h2E8B,16'h2037,16'h1592,16'h0F03, + 16'h0CCD,16'h0F03,16'h1592,16'h2037,16'h2E8B,16'h4000,16'h53EA,16'h6987 + }; + /* verilator lint_on LITENDIAN */ + + assign scale_w = i_scale << 2; + + assign o_data = sin_const[read_ptr_r*16 +: 16] >> scale_w; + + always @(posedge i_clk or negedge i_rst_n) begin + if (i_rst_n === 1'b0) begin + // reset all registers + read_ptr_r <= 5'b0; + ctr_r <= 16'b0; + ctr_msb_last_r <= 1'b0; + end else begin + // the ctr is incremented by the step size control from outside + ctr_r <= ctr_r + i_step; + ctr_msb_last_r <= ctr_r[15]; + + // on a ctr overflow the read pointer is incremented; the input step + // allows thus to control the frequency of the generated sine + if ((ctr_r[15] === 1'b0) && (ctr_msb_last_r === 1'b1)) + read_ptr_r <= read_ptr_r + 1'b1; + end + end + +endmodule // sinegen1 + +`endif +`default_nettype wire diff --git a/src/tt_um_hpretl_spi.v b/src/tt_um_hpretl_spi.v index 83758aa..e88917e 100644 --- a/src/tt_um_hpretl_spi.v +++ b/src/tt_um_hpretl_spi.v @@ -6,6 +6,7 @@ `default_nettype none `include "chain2.v" `include "dsmod1.v" +`include "sinegen1.v" module tt_um_hpretl_spi ( input wire [7:0] ui_in, // Dedicated inputs @@ -18,10 +19,14 @@ module tt_um_hpretl_spi ( input wire rst_n // reset_n - low to reset ); - wire [15:0] out_w; + wire [15:0] reg_out_w; + wire [15:0] sine_out_w; + wire [15:0] dac_input; assign uio_oe = 8'b11111111; // using IO for output - assign uio_out = ui_in[3] ? out_w[15:8] : out_w[7:0]; + assign uio_out = ui_in[3] ? reg_out_w[15:8] : reg_out_w[7:0]; + + assign dac_input = ui_in[6] ? sine_out_w : reg_out_w; chain2 spi( .i_resetn(rst_n), @@ -32,11 +37,11 @@ module tt_um_hpretl_spi ( .o_spi_dat(uo_out[0]), .o_det(uo_out[1]), .o_check(uo_out[2]), - .o_data(out_w) + .o_data(reg_out_w) ); dsmod1 dac( - .i_data(out_w), + .i_data(dac_input), .i_rst_n(rst_n), .i_clk(clk), .i_mode(ui_in[7]), @@ -44,10 +49,18 @@ module tt_um_hpretl_spi ( .o_ds_n(uo_out[6]) ); + sinegen1 sine( + .i_rst_n(rst_n), + .i_clk(clk), + .i_step(reg_out_w), + .i_scale(ui_in[5:4]), + .o_data(sine_out_w) + ); + // All output pins must be assigned. If not used, assign to 0. assign uo_out[5:3] = 3'b000; // List all unused inputs to prevent warnings - wire _unused = &{ena, uio_in[7:0], ui_in[6:4], 1'b0}; + wire _unused = &{ena, uio_in[7:0], 1'b0}; endmodule // tt_um_hpretl_spi diff --git a/test/test.py b/test/test.py index 9099263..396272b 100644 --- a/test/test.py +++ b/test/test.py @@ -68,6 +68,7 @@ async def test_project(dut): await ClockCycles(dut.clk, 1) assert dut.uio_out.value == 0xca + dut._log.info("Check DSMOD minimum value") # Shift 0 in for i in range(16): # b0 is clk, b1 is dat, b2 is load, b3 is select @@ -92,6 +93,7 @@ async def test_project(dut): # Wait a few cycles to watch DAC operation await ClockCycles(dut.clk, 100) + dut._log.info("Check DSMOD maximum value") # Shift 1 in for i in range(16): # b0 is clk, b1 is dat, b2 is load, b3 is select @@ -115,3 +117,37 @@ async def test_project(dut): # Wait a few cycles to watch DAC operation await ClockCycles(dut.clk, 100) + + dut._log.info("Check DSMOD driven by sinegen") + + load_word = "3000" + load_word_bin = format(int(load_word, 16), '016b') + load_word_bits = [int(bit) for bit in load_word_bin] + + # Shift frequency control in + for i in range(16): + # b0 is clk, b1 is dat, b2 is load, b3 is select + dut.ui_in.value = 1*0 + 2*load_word_bits[i] + 4*0 + 8*0 + + await ClockCycles(dut.clk, 3) + + # b0 is clk, b1 is dat, b2 is load, b3 is select + dut.ui_in.value = 1*1 + 2*load_word_bits[i] + 4*0 + 8*0 + + await ClockCycles(dut.clk, 3) + + assert (dut.uo_out.value & 2) == 0 + + # serial register is loaded, now store it + # b0 is clk, b1 is dat, b2 is load, b3 is select + dut.ui_in.value = 1*0 + 2*0 + 4*0 + 8*0 + await ClockCycles(dut.clk, 3) + dut.ui_in.value = 1*0 + 2*0 + 4*1 + 8*0 + await ClockCycles(dut.clk, 3) + + # Now select sinegen and let run for a few clock cycles + # b6 is sinegen select + dut.ui_in.value = 2**6 + + # Wait a few cycles to watch DAC operation + await ClockCycles(dut.clk, 500)