From b8254bc7a177077abc939152cf0f3d3648f6a925 Mon Sep 17 00:00:00 2001 From: Owen Kirby Date: Sat, 18 Jul 2020 18:15:57 -0700 Subject: [PATCH] Add test bitstream to verify the ADC. --- .gitignore | 6 + verilog/adc/Makefile | 32 ++++++ verilog/adc/adc.v | 220 +++++++++++++++++++++++++++++++++++++ verilog/adc/top.v | 42 +++++++ verilog/logicbone-rev0.lpf | 17 ++- 5 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 verilog/adc/Makefile create mode 100644 verilog/adc/adc.v create mode 100644 verilog/adc/top.v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3921d75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.json +*.asc +*.rpt +*.svf +*.bit +*.dfu diff --git a/verilog/adc/Makefile b/verilog/adc/Makefile new file mode 100644 index 0000000..9ceb276 --- /dev/null +++ b/verilog/adc/Makefile @@ -0,0 +1,32 @@ +PROJ=adc +DEVICE=--um5g-45k +SOURCES=top.v adc.v + +all: $(PROJ).bit $(PROJ).svf $(PROJ).dfu + +$(PROJ).json: $(SOURCES) + yosys -p "synth_ecp5 -top top -json $@" $^ + +%_out.config: %.json + nextpnr-ecp5 --json $< --textcfg $@ $(DEVICE) --package CABGA381 --lpf ../logicbone-rev0.lpf + +%.bit: %_out.config + ecppack --spimode qspi $< $@ + +%.svf: %_out.config + ecppack --svf $@ $< + +%.dfu: %.bit + dfu-tool -d 1d50:615d convert dfu $^ $@ + +prog: ${PROJ}.svf + openocd -f ../logicbone-jlink.cfg -c "transport select jtag; init; svf $<; exit" + +dfu: ${PROJ}.dfu + dfu-util -d 1d50:615d -a0 -D $< + +clean: + rm -f *.svf *.bit *.dfu *.config *.json + +.PHONY: prog clean + diff --git a/verilog/adc/adc.v b/verilog/adc/adc.v new file mode 100644 index 0000000..04a6c17 --- /dev/null +++ b/verilog/adc/adc.v @@ -0,0 +1,220 @@ +module adc #( + parameter CHANNEL_MASK = 8'b01111111, +) ( + input clk, + input reset, + + // SPI Bus Pins + output reg csel, + output reg sclk, + output reg mosi, + input miso, + + // ADC measurement data. + output reg [11:0] adc_data, + output reg [3:0] adc_channel, + output reg adc_valid +); + + localparam ADC_STATE_INIT = 4'h0; + localparam ADC_STATE_RESET = 4'h1; + localparam ADC_STATE_READ_STATUS = 4'h2; + localparam ADC_STATE_POLL_STATUS = 4'h3; + localparam ADC_STATE_START_CAL = 4'h4; + localparam ADC_STATE_READ_CAL = 4'h5; + localparam ADC_STATE_POLL_CAL = 4'h6; + localparam ADC_STATE_CFG_DATA = 4'h7; + localparam ADC_STATE_CFG_CHAN = 4'h8; + localparam ADC_STATE_CFG_SEQ = 4'h9; + localparam ADC_STATE_READOUT = 4'hA; + + localparam ADC_OPCODE_NOP = 8'b00000000; + localparam ADC_OPCODE_READ = 8'b00010000; + localparam ADC_OPCODE_WRITE = 8'b00001000; + localparam ADC_OPCODE_SETBIT = 8'b00011000; + localparam ADC_OPCODE_CLRBIT = 8'b00100000; + + localparam ADC_REG_SYSTEM_STATUS = 8'h00; + localparam ADC_REG_GENERAL_CFG = 8'h01; + localparam ADC_REG_DATA_CFG = 8'h02; + localparam ADC_REG_SEQUENCE_CFG = 8'h10; + localparam ADC_REG_AUTO_SEQ_CH_SEL = 8'h12; + + reg adc_reset_done = 1'b0; + reg adc_cal_done = 1'b0; + + reg [3:0] adc_state = ADC_STATE_INIT; + reg [3:0] next_adc_state = ADC_STATE_INIT; + reg [23:0] next_adc_outdata = 0; + reg [4:0] next_adc_bitcount = 0; + always @(*) begin + next_adc_state <= adc_state; + next_adc_bitcount <= 0; + next_adc_outdata <= 0; + + case (adc_state) + ADC_STATE_INIT : begin + // After init, start by resetting the ADC. + next_adc_outdata <= {ADC_OPCODE_WRITE, ADC_REG_GENERAL_CFG, 8'h01}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_RESET; + end + + ADC_STATE_RESET: begin + // After reset, check the status register for the BOR bit to be set. + next_adc_outdata <= {ADC_OPCODE_READ, ADC_REG_SYSTEM_STATUS, 8'h00}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_READ_STATUS; + end + + ADC_STATE_READ_STATUS: begin + // Issue a read command to get the BOR status bit. + next_adc_outdata <= {ADC_OPCODE_READ, ADC_REG_SYSTEM_STATUS, 8'h00}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_POLL_STATUS; + end + + ADC_STATE_POLL_STATUS: begin + // Keep sending reads until the BOR status bit is set. + if (~adc_reset_done) begin + next_adc_outdata <= {ADC_OPCODE_READ, ADC_REG_SYSTEM_STATUS, 8'h00}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_POLL_STATUS; + + // Start ADC offset calibration once the reset has finished. + end else begin + next_adc_outdata <= {ADC_OPCODE_WRITE, ADC_REG_GENERAL_CFG, 8'h02}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_START_CAL; + end + end + + ADC_STATE_START_CAL: begin + // Issue a read command to get the CAL status bit. + next_adc_outdata <= {ADC_OPCODE_READ, ADC_REG_GENERAL_CFG, 8'h00}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_READ_CAL; + end + + ADC_STATE_READ_CAL: begin + next_adc_outdata <= {ADC_OPCODE_READ, ADC_REG_GENERAL_CFG, 8'h00}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_POLL_CAL; + end + + ADC_STATE_POLL_CAL: begin + // Keep sending reads until the CAL bit is clear. + if (~adc_cal_done) begin + next_adc_outdata <= {ADC_OPCODE_READ, ADC_REG_GENERAL_CFG, 8'h00}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_READ_CAL; + + // Configure the data channel after calibration is complete. + end else begin + next_adc_outdata <= {ADC_OPCODE_WRITE, ADC_REG_DATA_CFG, 8'h10}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_CFG_DATA; + end + end + + ADC_STATE_CFG_DATA: begin + // After data configuration, select the channels to readout. + next_adc_outdata <= {ADC_OPCODE_WRITE, ADC_REG_AUTO_SEQ_CH_SEL, CHANNEL_MASK}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_CFG_CHAN; + end + + ADC_STATE_CFG_CHAN: begin + // After data configuration, enable auto-sequence mode. + next_adc_outdata <= {ADC_OPCODE_WRITE, ADC_REG_SEQUENCE_CFG, 8'h11}; + next_adc_bitcount <= 24; + next_adc_state <= ADC_STATE_CFG_SEQ; + end + + ADC_STATE_CFG_SEQ: begin + // After the auto-sequence mode has been enable, start ADC readout. + next_adc_outdata <= {ADC_OPCODE_NOP, 8'h00}; + next_adc_bitcount <= 16; + next_adc_state <= ADC_STATE_READOUT; + end + + ADC_STATE_READOUT: begin + // Continue reading ADC data. + next_adc_outdata <= {ADC_OPCODE_NOP, 8'h00}; + next_adc_bitcount <= 16; + next_adc_state <= ADC_STATE_READOUT; + end + endcase + end + + // SPI shift registers. + reg [4:0] spi_bitcount = 0; + reg [3:0] spi_cseldelay = 0; + reg [23:0] spi_shiftreg_out = 0; + reg [23:0] spi_shiftreg_in = 0; + + always @(posedge clk) begin + adc_valid <= 0; + + if (reset) begin + csel <= 1'b1; + sclk <= 1'b0; + msoi <= 1'b0; + + adc_reset_done <= 1'b0; + adc_cal_done <= 1'b0; + end + + // Clock bits in and out of the SPI interface. + else if (spi_bitcount) begin + csel <= 1'b0; + if (sclk) begin + sclk <= 1'b0; + mosi <= spi_shiftreg_out[spi_bitcount-1]; + end else begin + sclk <= 1'b1; + spi_shiftreg_in <= {spi_shiftreg_in[22:0], miso}; + spi_bitcount <= spi_bitcount - 1; + end + end + + // Deactivate the chipselect and latch the output. + else if (spi_cseldelay) begin + if (adc_state == ADC_STATE_POLL_STATUS) begin + adc_reset_done <= spi_shiftreg_in[16]; + end + if (adc_state == ADC_STATE_POLL_CAL) begin + adc_cal_done <= ~spi_shiftreg_in[17]; + end + if (adc_state == ADC_STATE_READOUT) begin + adc_data <= spi_shiftreg_in[15:4]; + adc_channel <= spi_shiftreg_in[3:0]; + adc_valid <= 1; + end + + if (sclk) begin + csel <= 1'b0; + sclk <= 1'b0; + msoi <= 1'b0; + end else begin + csel <= 1'b1; + sclk <= 1'b0; + spi_cseldelay <= spi_cseldelay - 1; + end + end + + // Start the next command. + else if (next_adc_bitcount) begin + adc_state <= next_adc_state; + + spi_bitcount <= next_adc_bitcount; + spi_cseldelay <= 4; + spi_shiftreg_out <= next_adc_outdata; + + // Activate the chipselect and start the next word. + sclk <= 1'b0; + csel <= 1'b0; + mosi <= next_adc_outdata[next_adc_bitcount-1]; + end + end +endmodule diff --git a/verilog/adc/top.v b/verilog/adc/top.v new file mode 100644 index 0000000..4846f33 --- /dev/null +++ b/verilog/adc/top.v @@ -0,0 +1,42 @@ +module top(input refclk, + input pwr_button, + output [3:0] led, + + output adc_csel, + output adc_sclk, + output adc_mosi, + input adc_miso, + + output [3:0] debug +); + +reg [7:0] por_delay = 8'hff; +always @(posedge refclk) if (por_delay) por_delay <= por_delay - 1; + +reg [3:0] led_data = 3'b0; +always @(posedge refclk) begin + if (adc_valid && (adc_channel == 0)) led_data <= adc_data[11:8]; +end +assign led = ~led_data; + +wire [11:0] adc_data; +wire [3:0] adc_channel; +wire adc_valid; + +adc adc_instance( + .clk(refclk), + .reset(por_delay != 0), + + .csel(adc_csel), + .sclk(adc_sclk), + .mosi(adc_mosi), + .miso(adc_miso), + + .adc_data(adc_data), + .adc_channel(adc_channel), + .adc_valid(adc_valid) +); + +assign debug = {adc_miso, adc_mosi, adc_sclk, adc_csel}; + +endmodule diff --git a/verilog/logicbone-rev0.lpf b/verilog/logicbone-rev0.lpf index e60b356..da1d5ae 100644 --- a/verilog/logicbone-rev0.lpf +++ b/verilog/logicbone-rev0.lpf @@ -1,5 +1,18 @@ -LOCATE COMP "clk" SITE "M19"; -IOBUF PORT "clk" IO_TYPE=LVCMOS18; +LOCATE COMP "refclk" SITE "M19"; +IOBUF PORT "refclk" IO_TYPE=LVCMOS18; + +#====================================== +# Bank 2: Expansion Header P8 - DEBUG +#====================================== +LOCATE COMP "debug[0]" SITE "C20"; +LOCATE COMP "debug[1]" SITE "D20"; +LOCATE COMP "debug[2]" SITE "E20"; +LOCATE COMP "debug[3]" SITE "F20"; + +IOBUF PORT "debug[0]" IO_TYPE=LVCMOS33; +IOBUF PORT "debug[1]" IO_TYPE=LVCMOS33; +IOBUF PORT "debug[2]" IO_TYPE=LVCMOS33; +IOBUF PORT "debug[3]" IO_TYPE=LVCMOS33; #====================================== # Bank 0: Expansion Header P9 and ADC