diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 91581d4..36ed5ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,8 +6,8 @@ jobs: ubuntu-build-icebreaker-r31: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: yosys --version - run: make HW_REV=HW_R31 BOARD=icebreaker CORE=mirror -C gateware @@ -19,8 +19,8 @@ jobs: ubuntu-build-icebreaker-r33: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: yosys --version - run: make HW_REV=HW_R33 BOARD=icebreaker CORE=mirror -C gateware @@ -29,6 +29,19 @@ jobs: name: ubuntu-build-icebreaker.bin path: gateware/build/icebreaker/top.bin + ubuntu-build-icebreaker-r33-touch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 + - run: git submodule update --init gateware/external/no2misc + - run: yosys --version + - run: make HW_REV=HW_R33 BOARD=icebreaker CORE=touch_cv TOUCH=TOUCH_SENSE_ENABLED -C gateware + - uses: actions/upload-artifact@v3 + with: + name: ubuntu-build-icebreaker.bin + path: gateware/build/icebreaker/top.bin + windows-build-icebreaker: runs-on: windows-latest defaults: @@ -40,8 +53,8 @@ jobs: install: >- git make - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: | export PATH=$PATH:$RUNNER_TEMP/oss-cad-suite/bin @@ -56,8 +69,8 @@ jobs: macos-build-icebreaker: runs-on: macos-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: | yosys --version @@ -70,8 +83,8 @@ jobs: ubuntu-build-colorlight-i5: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: yosys --version - run: make HW_REV=HW_R33 BOARD=colorlight_i5 CORE=mirror -C gateware @@ -83,8 +96,8 @@ jobs: ubuntu-build-colorlight-i9: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: yosys --version - run: make HW_REV=HW_R33 BOARD=colorlight_i9 CORE=mirror -C gateware @@ -96,8 +109,8 @@ jobs: ubuntu-build-ecpix-5: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: yosys --version - run: make HW_REV=HW_R33 BOARD=ecpix5 CORE=mirror -C gateware @@ -109,8 +122,8 @@ jobs: ubuntu-build-pico-ice: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: yosys --version - run: make HW_REV=HW_R33 BOARD=pico_ice CORE=mirror -C gateware @@ -122,8 +135,8 @@ jobs: run-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: cocotb-config -v - run: cd gateware/sim && ./00_run.sh @@ -131,8 +144,8 @@ jobs: run-linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: YosysHQ/setup-oss-cad-suite@v2 + - uses: actions/checkout@v4 + - uses: YosysHQ/setup-oss-cad-suite@v3 - run: git submodule update --init gateware/external/no2misc - run: verilator --version - run: cd gateware && scripts/verilator_lint.sh diff --git a/gateware/Makefile b/gateware/Makefile index baf7592..76488ed 100644 --- a/gateware/Makefile +++ b/gateware/Makefile @@ -1,14 +1,17 @@ ALL_BOARDS = $(shell ls boards) ALL_CORES = $(shell basename --suffix=.sv -- cores/*.sv) ALL_HW_REV = "HW_R31 HW_R33" +ALL_TOUCH = "TOUCH_SENSE_DISABLED TOUCH_SENSE_ENABLED" CORE ?= mirror +TOUCH ?= TOUCH_SENSE_DISABLED all prog: ifeq ($(BOARD),) @echo "Valid HW_REV values are: $(ALL_HW_REV)". @echo "Valid BOARD values are: $(ALL_BOARDS)". @echo "Valid CORE values are: $(ALL_CORES)". + @echo "Valid TOUCH values are: $(ALL_TOUCH) (default disabled, valid for R3.3+ only, required for any examples that use touch)". @echo "For example:" @echo " $$ make clean" @echo " $$ # Build bitstream with specific core and program it" @@ -33,7 +36,7 @@ endif mkdir -p build/$(BOARD) # For now we always force a re-build since we can pass different DSP cores # through environment vars and we need a re-build to happen in this case. - $(MAKE) -B -f boards/$(BOARD)/Makefile BUILD=build/$(BOARD) CORE=$(CORE) $(MAKECMDGOALS) + $(MAKE) -B -f boards/$(BOARD)/Makefile BUILD=build/$(BOARD) CORE=$(CORE) TOUCH=$(TOUCH) $(MAKECMDGOALS) clean: rm -rf build/ diff --git a/gateware/cal/cal.py b/gateware/cal/cal.py index ed43ce8..b41798f 100755 --- a/gateware/cal/cal.py +++ b/gateware/cal/cal.py @@ -112,9 +112,17 @@ def run_calibration(self): "eeprom_dev": raw[3], "eeprom_serial": int.from_bytes(raw[4:8], "big"), "jack": raw[8], + "touch0": raw[9], + "touch1": raw[10], + "touch2": raw[11], + "touch3": raw[12], + "touch4": raw[13], + "touch5": raw[14], + "touch6": raw[15], + "touch7": raw[16], } [print(k, hex(v)) for k, v in values.items()] - self._decode_raw_samples(raw[9:]) + self._decode_raw_samples(raw[17:]) self._handle_user_input() self._calculate_calibration_strings() time.sleep(0.1) diff --git a/gateware/cal/debug_uart.sv b/gateware/cal/debug_uart.sv index 5900e1e..5962a2e 100644 --- a/gateware/cal/debug_uart.sv +++ b/gateware/cal/debug_uart.sv @@ -19,6 +19,14 @@ module debug_uart #( input [7:0] eeprom_dev, input [31:0] eeprom_serial, input [7:0] jack, + input [7:0] touch0, + input [7:0] touch1, + input [7:0] touch2, + input [7:0] touch3, + input [7:0] touch4, + input [7:0] touch5, + input [7:0] touch6, + input [7:0] touch7, input signed [W-1:0] adc0, input signed [W-1:0] adc1, input signed [W-1:0] adc2, @@ -70,31 +78,39 @@ always_ff @(posedge clk) begin 6: dout <= eeprom_serial[32-2*8-1:32-3*8]; 7: dout <= eeprom_serial[32-3*8-1: 0]; 8: dout <= jack; + 9: dout <= touch0; + 10: dout <= touch1; + 11: dout <= touch2; + 12: dout <= touch3; + 13: dout <= touch4; + 14: dout <= touch5; + 15: dout <= touch6; + 16: dout <= touch7; // Channel 0 - 9: dout <= adc0_ex[WM -1:WM-1*8]; - 10: dout <= adc0_ex[WM-1*8-1:WM-2*8]; - 11: dout <= adc0_ex[WM-2*8-1:WM-3*8]; - 12: dout <= adc0_ex[WM-3*8-1: 0]; + 17: dout <= adc0_ex[WM -1:WM-1*8]; + 18: dout <= adc0_ex[WM-1*8-1:WM-2*8]; + 19: dout <= adc0_ex[WM-2*8-1:WM-3*8]; + 20: dout <= adc0_ex[WM-3*8-1: 0]; // Channel 1 - 13: dout <= adc1_ex[WM -1:WM-1*8]; - 14: dout <= adc1_ex[WM-1*8-1:WM-2*8]; - 15: dout <= adc1_ex[WM-2*8-1:WM-3*8]; - 16: dout <= adc1_ex[WM-3*8-1: 0]; + 21: dout <= adc1_ex[WM -1:WM-1*8]; + 22: dout <= adc1_ex[WM-1*8-1:WM-2*8]; + 23: dout <= adc1_ex[WM-2*8-1:WM-3*8]; + 24: dout <= adc1_ex[WM-3*8-1: 0]; // Channel 2 - 17: dout <= adc2_ex[WM -1:WM-1*8]; - 18: dout <= adc2_ex[WM-1*8-1:WM-2*8]; - 19: dout <= adc2_ex[WM-2*8-1:WM-3*8]; - 20: dout <= adc2_ex[WM-3*8-1: 0]; + 25: dout <= adc2_ex[WM -1:WM-1*8]; + 26: dout <= adc2_ex[WM-1*8-1:WM-2*8]; + 27: dout <= adc2_ex[WM-2*8-1:WM-3*8]; + 28: dout <= adc2_ex[WM-3*8-1: 0]; // Channel 3 - 21: dout <= adc3_ex[WM -1:WM-1*8]; - 22: dout <= adc3_ex[WM-1*8-1:WM-2*8]; - 23: dout <= adc3_ex[WM-2*8-1:WM-3*8]; - 24: dout <= adc3_ex[WM-3*8-1: 0]; + 29: dout <= adc3_ex[WM -1:WM-1*8]; + 30: dout <= adc3_ex[WM-1*8-1:WM-2*8]; + 31: dout <= adc3_ex[WM-2*8-1:WM-3*8]; + 32: dout <= adc3_ex[WM-3*8-1: 0]; default: begin // Should never get here end endcase - if (state != 24) state <= state + 1; + if (state != 32) state <= state + 1; else state <= 0; end end diff --git a/gateware/cores/touch_cv.sv b/gateware/cores/touch_cv.sv new file mode 100644 index 0000000..5662176 --- /dev/null +++ b/gateware/cores/touch_cv.sv @@ -0,0 +1,43 @@ +// +// Touch-to-CV. Touches on input jacks 1-4 are +// translated into CV outputs on outputs 1-4. +// +// When building with touch sensing, be sure to build with +// TOUCH=TOUCH_SENSE_ENABLED, for example: +// +// $ make CORE=touch_cv HW_REV=HW_R33 TOUCH=TOUCH_SENSE_ENABLED +// + +`default_nettype none + +module touch_cv #( + parameter W = 16 +)( + input rst, + input clk, + input sample_clk, + input signed [W-1:0] sample_in0, + input signed [W-1:0] sample_in1, + input signed [W-1:0] sample_in2, + input signed [W-1:0] sample_in3, + output signed [W-1:0] sample_out0, + output signed [W-1:0] sample_out1, + output signed [W-1:0] sample_out2, + output signed [W-1:0] sample_out3, + input [7:0] jack, + input [7:0] touch0, + input [7:0] touch1, + input [7:0] touch2, + input [7:0] touch3, + input [7:0] touch4, + input [7:0] touch5, + input [7:0] touch6, + input [7:0] touch7 +); + +assign sample_out0 = W'(touch0) <<< (W-10); +assign sample_out1 = W'(touch1) <<< (W-10); +assign sample_out2 = W'(touch2) <<< (W-10); +assign sample_out3 = W'(touch3) <<< (W-10); + +endmodule diff --git a/gateware/drivers/cy8cmbr3108-cfg.hex b/gateware/drivers/cy8cmbr3108-cfg.hex new file mode 100644 index 0000000..a0e928c --- /dev/null +++ b/gateware/drivers/cy8cmbr3108-cfg.hex @@ -0,0 +1,130 @@ +6e +00 +ff +00 +00 +00 +00 +00 +00 +00 +ff +ff +00 +00 +80 +80 +80 +80 +80 +80 +80 +80 +00 +00 +00 +00 +00 +00 +00 +00 +04 +9f +00 +B2 +94 +94 +00 +00 +00 +00 +00 +80 +05 +00 +00 +02 +00 +02 +00 +00 +00 +00 +00 +00 +00 +1e +1e +00 +00 +1e +1e +00 +00 +00 +01 +01 +00 +ff +ff +ff +ff +00 +00 +00 +00 +00 +00 +00 +10 +03 +00 +20 +00 +37 +01 +0f +00 +0a +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +86 +c1 diff --git a/gateware/drivers/cy8cmbr3108.py b/gateware/drivers/cy8cmbr3108.py new file mode 100755 index 0000000..9f775bb --- /dev/null +++ b/gateware/drivers/cy8cmbr3108.py @@ -0,0 +1,170 @@ +#!/bin/python3 + +""" +Touch IC utility -- verify the register indices and CRC in `cy8cmbr3108.hex`. +This was mostly just pulled out of Cypress' documentation. They have tools +to create the configuration dumps, however you aren't allowed to touch every +register which I needed to do for this use-case :) + +If you run this utility from this directory, you'll get something like: + +``` +head 6e int 110 +head 00 int 0 +reg 0x0 hex 0xff int 255 SENSOR_EN +reg 0x1 hex 0x0 int 0 +reg 0x2 hex 0x0 int 0 FSS_EN +reg 0x3 hex 0x0 int 0 +... +reg 0x7d hex 0x0 int 0 +reg 0x7e hex 0x86 int 134 CONFIG_CRC +reg 0x7f hex 0xc1 int 193 +total bytes in file 130 +bytes to crc 126 +crc0 0x86 +crc1 0xc1 +CRC OK +``` + +If the CRC did not match (i.e you tweaked a register), you should copy the crc0 and crc1 (calculated CRC) +lines into the correct lines of the .hex file (CONFIG_CRC above). Then, if you re-run this tool it should +show CRC OK, which means the touch IC will accept your configuration. + +""" + +CY8CMBR3xxx_CONFIG_DATA_LENGTH = 126 +CY8CMBR3xxx_CRC_BIT_WIDTH = 2 * 8 +CY8CMBR3xxx_CRC_BIT4_MASK = 0x0F +CY8CMBR3xxx_CRC_BIT4_SHIFT = 4 +CY8CMBR3xxx_CCITT16_DEFAULT_SEED = 0xffff +CY8CMBR3xxx_CCITT16_POLYNOM = 0x1021 + + +def CY8CMBR3xxx_Calc4BitsCRC(value, remainder): + # Divide the value by polynomial, via the CRC polynomial + tableIndex = (value & CY8CMBR3xxx_CRC_BIT4_MASK) ^ (remainder >> (CY8CMBR3xxx_CRC_BIT_WIDTH - CY8CMBR3xxx_CRC_BIT4_SHIFT)) + remainder = (CY8CMBR3xxx_CCITT16_POLYNOM * tableIndex) ^ (remainder << CY8CMBR3xxx_CRC_BIT4_SHIFT) + return remainder + + +def CY8CMBR3xxx_CalculateCrc(configuration): + seed = CY8CMBR3xxx_CCITT16_DEFAULT_SEED + + # don't make count down cycle! CRC will be different! + for byteValue in configuration: + seed = CY8CMBR3xxx_Calc4BitsCRC(byteValue >> CY8CMBR3xxx_CRC_BIT4_SHIFT, seed) & 0xffff + seed = CY8CMBR3xxx_Calc4BitsCRC(byteValue, seed) & 0xffff + + return seed + +# From the datasheet for this chip, offsets of each register. +REG_MAP = """SENSOR_EN 0x00 +FSS_EN 0x02 +TOGGLE_EN 0x04 +LED_ON_EN 0x06 +SENSITIVITY0 0x08 +SENSITIVITY1 0x09 +SENSITIVITY2 0x0a +SENSITIVITY3 0x0b +BASE_THRESHOLD0 0x0c +BASE_THRESHOLD1 0x0d +FINGER_THRESHOLD2 0x0e +FINGER_THRESHOLD3 0x0f +FINGER_THRESHOLD4 0x10 +FINGER_THRESHOLD5 0x11 +FINGER_THRESHOLD6 0x12 +FINGER_THRESHOLD7 0x13 +FINGER_THRESHOLD8 0x14 +FINGER_THRESHOLD9 0x15 +FINGER_THRESHOLD10 0x16 +FINGER_THRESHOLD11 0x17 +FINGER_THRESHOLD12 0x18 +FINGER_THRESHOLD13 0x19 +FINGER_THRESHOLD14 0x1a +FINGER_THRESHOLD15 0x1b +SENSOR_DEBOUNCE 0x1c +BUTTON_HYS 0x1d +BUTTON_LBR 0x1f +BUTTON_NNT 0x20 +BUTTON_NT 0x21 +PROX_EN 0x26 +PROX_CFG 0x27 +PROX_CFG2 0x28 +PROX_TOUCH_TH0 0x2a +PROX_TOUCH_TH1 0x2c +PROX_RESOLUTION0 0x2e +PROX_RESOLUTION1 0x2f +PROX_HYS 0x30 +PROX_LBR 0x32 +PROX_NNT 0x33 +PROX_NT 0x34 +PROX_POSITIVE_TH0 0x35 +PROX_POSITIVE_TH1 0x36 +PROX_NEGATIVE_TH0 0x39 +PROX_NEGATIVE_TH1 0x3a +LED_ON_TIME 0x3d +BUZZER_CFG 0x3e +BUZZER_ON_TIME 0x3f +GPO_CFG 0x40 +PWM_DUTYCYCLE_CFG0 0x41 +PWM_DUTYCYCLE_CFG1 0x42 +PWM_DUTYCYCLE_CFG2 0x43 +PWM_DUTYCYCLE_CFG3 0x44 +PWM_DUTYCYCLE_CFG4 0x45 +PWM_DUTYCYCLE_CFG5 0x46 +PWM_DUTYCYCLE_CFG6 0x47 +PWM_DUTYCYCLE_CFG7 0x48 +SPO_CFG 0x4c +DEVICE_CFG0 0x4d +DEVICE_CFG1 0x4e +DEVICE_CFG2 0x4f +DEVICE_CFG3 0x50 +I2C_ADDR 0x51 +REFRESH_CTRL 0x52 +STATE_TIMEOUT 0x55 +SLIDER_CFG 0x5d +SLIDER1_CFG 0x61 +SLIDER1_RESOLUTION 0x62 +SLIDER1_THRESHOLD 0x63 +SLIDER2_CFG 0x67 +SLIDER2_RESOLUTION 0x68 +SLIDER2_THRESHOLD 0x69 +SLIDER_LBR 0x71 +SLIDER_NNT 0x72 +SLIDER_NT 0x73 +SCRATCHPAD0 0x7a +SCRATCHPAD1 0x7b +CONFIG_CRC 0x7e""" + +REG_DICT = {} + +for line in REG_MAP.split("\n"): + name, addr = line.split(" ") + REG_DICT[int(addr.strip(), 16)] = name.strip() + +with open("cy8cmbr3108-cfg.hex", "r") as f: + xs = [] + ix = 0 + for line in f.readlines(): + raw = line.strip() + v = int(raw, 16) + if ix >= 2: + reg_ix = ix-2 + name = "" + if reg_ix in REG_DICT: + name = REG_DICT[reg_ix] + print("reg", hex(reg_ix), "hex", hex(int(raw, 16)), "int", v, name) + else: + print("head", raw, "int", v) + xs.append(v) + ix += 1 + print("total bytes in file", len(xs)) + xs_crc = xs[2:-2] + print("bytes to crc", len(xs_crc)) + crc = CY8CMBR3xxx_CalculateCrc(xs_crc) + print("crc0", hex(crc & 0x00FF)) + print("crc1", hex((crc & 0xFF00)>>8)) + if xs[-2] == crc & 0x00FF and xs[-1] == ((crc & 0xFF00) >> 8): + print("CRC OK") + else: + print("CRC NOT OK") diff --git a/gateware/drivers/pmod_i2c_master.sv b/gateware/drivers/pmod_i2c_master.sv index d533d11..2746e0f 100644 --- a/gateware/drivers/pmod_i2c_master.sv +++ b/gateware/drivers/pmod_i2c_master.sv @@ -15,10 +15,14 @@ `default_nettype none module pmod_i2c_master #( - parameter CODEC_CFG = "drivers/ak4619-cfg.hex", - parameter CODEC_CFG_BYTES = 16'd23, - parameter LED_CFG = "drivers/pca9635-cfg.hex", - parameter LED_CFG_BYTES = 16'd26 + parameter CODEC_CFG = "drivers/ak4619-cfg.hex" + , parameter CODEC_CFG_BYTES = 16'd23 + , parameter LED_CFG = "drivers/pca9635-cfg.hex" + , parameter LED_CFG_BYTES = 16'd26 +`ifdef TOUCH_SENSE_ENABLED + , parameter TOUCH_CFG = "drivers/cy8cmbr3108-cfg.hex" + , parameter TOUCH_CFG_BYTES = 16'd130 // 0x80 + 2 +`endif )( input clk, input rst, @@ -42,6 +46,17 @@ module pmod_i2c_master #( input signed [7:0] led6, input signed [7:0] led7, +`ifdef TOUCH_SENSE_ENABLED + output logic [7:0] touch0, + output logic [7:0] touch1, + output logic [7:0] touch2, + output logic [7:0] touch3, + output logic [7:0] touch4, + output logic [7:0] touch5, + output logic [7:0] touch6, + output logic [7:0] touch7, +`endif // TOUCH_SENSE_ENABLED + // Jack detection outputs, 1 == inserted. (bit 0 is input 0, bit 4 is output 0). output logic [7:0] jack, @@ -52,20 +67,24 @@ module pmod_i2c_master #( ); // Overall state machine of this core. -// Basically we bring up the EEPROM and CODEC, and then proceed to +// Basically we bring up the EEPROM, touch sensing and CODEC, and then proceed to // update the LED outputs and read the jack insertion GPIOS in a loop. localparam I2C_DELAY1 = 0, I2C_EEPROM1 = 1, I2C_EEPROM2 = 2, I2C_INIT_TOUCH1 = 3, I2C_INIT_TOUCH2 = 4, - I2C_INIT_CODEC1 = 5, - I2C_INIT_CODEC2 = 6, - I2C_LED1 = 7, // <<--\ LED/JACK re-runs indefinitely. - I2C_LED2 = 8, // | - I2C_JACK1 = 9, // | - I2C_JACK2 = 10, // >>--/ - I2C_IDLE = 11; + I2C_INIT_TOUCH3 = 5, + I2C_INIT_TOUCH4 = 6, + I2C_INIT_CODEC1 = 7, + I2C_INIT_CODEC2 = 8, + I2C_LED1 = 9, // <<--\ LED/JACK/TOUCH re-runs indefinitely. + I2C_LED2 = 10, // | + I2C_JACK1 = 11, // | + I2C_JACK2 = 12, // | + I2C_TOUCH_SCAN1 = 13, // | | these 2 only if TOUCH is enabled + I2C_TOUCH_SCAN2 = 14, // >>--/ | + I2C_IDLE = 15; `ifdef COCOTB_SIM localparam STARTUP_DELAY_BIT = 4; @@ -88,6 +107,15 @@ initial $readmemh(LED_CFG, led_config); // Index at which PWM values start in the led config. localparam PCA9635_PWM0 = 4; +`ifdef TOUCH_SENSE_ENABLED +// Logic for startup configuration of touch sensor IC over I2C. +logic [7:0] touch_config [0:TOUCH_CFG_BYTES-1]; +initial $readmemh(TOUCH_CFG, touch_config); + +// Which touch sensor we are currently reading +logic [2:0] nsensor; +`endif + // Valid commands for `i2c_master` core. localparam [1:0] I2CMASTER_START = 2'b00, I2CMASTER_STOP = 2'b01, @@ -181,11 +209,152 @@ always_ff @(posedge clk) begin i2c_state <= I2C_INIT_TOUCH2; i2c_config_pos <= 0; end - // Switch off the CY8CMBR3108 by default, as it can cause the - // LEDs to flicker (due to NACKs) and increase noise in the - // audio chain, unless it is configured correctly (currently - // touch sensing prototyping is on a separate branch, let's - // keep it out of master for now) +`ifdef TOUCH_SENSE_ENABLED + // If touch sensing is enabled, send out one long transaction + // with configuration data, then issue a SAVE_CHECK_CRC cmd. + I2C_INIT_TOUCH2: begin + case (i2c_config_pos) + default: begin + data_in <= touch_config[8'(i2c_config_pos)]; + cmd <= I2CMASTER_WRITE; + end + 1: begin + // Make sure the first byte (address) is acknowledged. If it + // isn't, restart the configuration process. + if (ack_out) begin + i2c_state <= I2C_INIT_TOUCH1; + cmd <= I2CMASTER_STOP; + end else begin + data_in <= touch_config[8'(i2c_config_pos)]; + cmd <= I2CMASTER_WRITE; + end + end + TOUCH_CFG_BYTES: begin + cmd <= I2CMASTER_STOP; + end + + TOUCH_CFG_BYTES+1: begin + cmd <= I2CMASTER_START; + end + TOUCH_CFG_BYTES+2: begin + // 0x37 << 1 | 0 (W) + data_in <= 8'h6E; + cmd <= I2CMASTER_WRITE; + end + TOUCH_CFG_BYTES+3: begin + // Command register + data_in <= 8'h86; + cmd <= I2CMASTER_WRITE; + end + TOUCH_CFG_BYTES+4: begin + // SAVE_CHECK_CRC. + data_in <= 8'h02; + cmd <= I2CMASTER_WRITE; + end + TOUCH_CFG_BYTES+5: begin + cmd <= I2CMASTER_STOP; + i2c_state <= I2C_INIT_TOUCH3; + end + endcase + i2c_config_pos <= i2c_config_pos + 1; + ack_in <= 1'b1; + stb <= 1'b1; + end + I2C_INIT_TOUCH3: begin + cmd <= I2CMASTER_START; + stb <= 1'b1; + i2c_state <= I2C_INIT_TOUCH4; + i2c_config_pos <= 0; + end + // Finally we issue a SW_RESET command to reset the touch + // sense IC and apply all the settings we just sent. + I2C_INIT_TOUCH4: begin + case (i2c_config_pos) + // Write the slave register pointer + 0: begin + cmd <= I2CMASTER_START; + end + 1: begin + // 0x37 << 1 | 0 (W) + data_in <= 8'h6E; + cmd <= I2CMASTER_WRITE; + end + 2: begin + if (ack_out) begin + // Wait until ack succeeds before continuing + i2c_state <= I2C_INIT_TOUCH3; + cmd <= I2CMASTER_STOP; + end else begin + // Command register + data_in <= 8'h86; + cmd <= I2CMASTER_WRITE; + end + end + 3: begin + cmd <= I2CMASTER_STOP; + end + + // Read the command register, retry if chip is busy + 4: begin + cmd <= I2CMASTER_START; + end + 5: begin + // 0x37 << 1 | 1 (R) + data_in <= 8'h6F; + cmd <= I2CMASTER_WRITE; + end + 6: begin + cmd <= I2CMASTER_READ; + end + 7: begin + if (data_out != 8'h00) begin + // Retry until command register is 0 before + // issuing a reset. + i2c_state <= I2C_INIT_TOUCH3; + end + cmd <= I2CMASTER_STOP; + end + + + // Write the command register + 8: begin + cmd <= I2CMASTER_START; + end + 9: begin + // 0x37 << 1 | 0 (W) + data_in <= 8'h6E; + cmd <= I2CMASTER_WRITE; + end + 10: begin + if (ack_out) begin + // Wait until ack succeeds before continuing + i2c_state <= I2C_INIT_TOUCH3; + cmd <= I2CMASTER_STOP; + end else begin + // Only issue reset if we got acknowledged + // Command register + data_in <= 8'h86; + cmd <= I2CMASTER_WRITE; + end + end + 11: begin + // NVM write & reset command. + data_in <= 8'hff; + cmd <= I2CMASTER_WRITE; + end + default: begin + cmd <= I2CMASTER_STOP; + i2c_state <= I2C_INIT_CODEC1; + end + endcase + i2c_config_pos <= i2c_config_pos + 1; + ack_in <= 1'b1; + stb <= 1'b1; + end +`else + // If touch sensing is not enabled, we disable the touch sense + // IC. This also improves noise performance a little, so might + // be desired for some audio scenarios. I2C_INIT_TOUCH2: begin case (i2c_config_pos) 0: begin @@ -220,6 +389,7 @@ always_ff @(posedge clk) begin ack_in <= 1'b1; stb <= 1'b1; end +`endif // TOUCH_SENSE_ENABLED I2C_INIT_CODEC1: begin cmd <= I2CMASTER_START; stb <= 1'b1; @@ -308,13 +478,23 @@ always_ff @(posedge clk) begin data_in <= 8'h31; cmd <= I2CMASTER_WRITE; end - 9: cmd <= I2CMASTER_READ; - + 9: begin + if (ack_out == 1'b0) begin + cmd <= I2CMASTER_READ; + end else begin + cmd <= I2CMASTER_STOP; + i2c_state <= I2C_TOUCH_SCAN1; + end + end // 4) Save the result. 10: begin jack <= data_out; cmd <= I2CMASTER_STOP; +`ifdef TOUCH_SENSE_ENABLED + i2c_state <= I2C_TOUCH_SCAN1; +`else i2c_state <= I2C_LED1; +`endif // TOUCH_SENSE_ENABLED delay_cnt <= 0; end default: begin @@ -325,6 +505,76 @@ always_ff @(posedge clk) begin ack_in <= 1'b1; stb <= 1'b1; end +`ifdef TOUCH_SENSE_ENABLED + I2C_TOUCH_SCAN1: begin + i2c_state <= I2C_TOUCH_SCAN2; + i2c_config_pos <= 0; + stb <= 1'b0; + end + I2C_TOUCH_SCAN2: begin + case (i2c_config_pos) + // Set slave read pointer + 0: cmd <= I2CMASTER_START; + 1: begin + data_in <= 8'h6E; + cmd <= I2CMASTER_WRITE; + end + // Sensor 0 difference counts + 2: begin + if (ack_out == 1'b1) begin + i2c_state <= I2C_LED1; + cmd <= I2CMASTER_STOP; + end else begin + case (nsensor) + 0: data_in <= 8'hBA; + 1: data_in <= 8'hBC; + 2: data_in <= 8'hBE; + 3: data_in <= 8'hC0; + 4: data_in <= 8'hC2; + 5: data_in <= 8'hC4; + 6: data_in <= 8'hC6; + 7: data_in <= 8'hC8; + endcase + end + end + 3: cmd <= I2CMASTER_STOP; + + // Read out the data + 4: cmd <= I2CMASTER_START; + 5: begin + data_in <= 8'h6F; + cmd <= I2CMASTER_WRITE; + end + 6: begin + if (ack_out == 1'b1) begin + i2c_state <= I2C_LED1; + cmd <= I2CMASTER_STOP; + end else begin + cmd <= I2CMASTER_READ; + ack_in <= 1'b1; + end + end + 7: begin + case (nsensor) + 0: touch0 <= data_out; + 1: touch1 <= data_out; + 2: touch2 <= data_out; + 3: touch3 <= data_out; + // R3.3 hw swaps last four vs R3.2 to improve PCB routing + 4: touch7 <= data_out; + 5: touch6 <= data_out; + 6: touch5 <= data_out; + 7: touch4 <= data_out; + endcase + cmd <= I2CMASTER_STOP; + i2c_state <= I2C_LED1; + nsensor <= nsensor + 1; + end + endcase + i2c_config_pos <= i2c_config_pos + 1; + stb <= 1'b1; + end +`endif // TOUCH_SENSE_ENABLED default: begin i2c_state <= I2C_IDLE; end diff --git a/gateware/eurorack_pmod.sv b/gateware/eurorack_pmod.sv index 8cdf403..3a240f6 100644 --- a/gateware/eurorack_pmod.sv +++ b/gateware/eurorack_pmod.sv @@ -49,6 +49,16 @@ module eurorack_pmod #( // Jack detection inputs read constantly over I2C. // Logic '1' == jack is inserted. Bit 0 is input 0. output [7:0] jack, + // Touch sense outputs, one per channel. + // If touch sensing is disabled these are just zeroes. + output logic [7:0] touch0, + output logic [7:0] touch1, + output logic [7:0] touch2, + output logic [7:0] touch3, + output logic [7:0] touch4, + output logic [7:0] touch5, + output logic [7:0] touch6, + output logic [7:0] touch7, // Signals used for bringup / debug / calibration. // @@ -68,6 +78,18 @@ logic signed [W-1:0] sample_dac1; logic signed [W-1:0] sample_dac2; logic signed [W-1:0] sample_dac3; +`ifndef TOUCH_SENSE_ENABLED +assign touch0 = 0; +assign touch1 = 0; +assign touch2 = 0; +assign touch3 = 0; +assign touch4 = 0; +assign touch5 = 0; +assign touch6 = 0; +assign touch7 = 0; +`endif + + // Raw sample calibrator, both for input and output channels. // Compensates for DC bias in CODEC, gain differences, resistor // tolerances and so on. @@ -149,6 +171,17 @@ pmod_i2c_master #( .led6(force_dac_output == 0 ? cal_out2[W-1:W-8] : force_dac_output[W-1:W-8]), .led7(force_dac_output == 0 ? cal_out3[W-1:W-8] : force_dac_output[W-1:W-8]), +`ifdef TOUCH_SENSE_ENABLED + .touch0(touch0), + .touch1(touch1), + .touch2(touch2), + .touch3(touch3), + .touch4(touch4), + .touch5(touch5), + .touch6(touch6), + .touch7(touch7), +`endif // TOUCH_SENSE_ENABLED + .jack(jack), .eeprom_mfg_code(eeprom_mfg), diff --git a/gateware/mk/common.mk b/gateware/mk/common.mk index 5873876..0686490 100644 --- a/gateware/mk/common.mk +++ b/gateware/mk/common.mk @@ -15,6 +15,7 @@ SRC_COMMON = eurorack_pmod.sv \ cores/pitch_shift.sv \ cores/stereo_echo.sv \ cores/filter.sv \ + cores/touch_cv.sv \ cores/util/filter/karlsen_lpf_pipelined.sv \ cores/util/filter/karlsen_lpf.sv \ cores/util/transpose.sv \ diff --git a/gateware/mk/ecp5.mk b/gateware/mk/ecp5.mk index 78749ac..1fa6ff6 100644 --- a/gateware/mk/ecp5.mk +++ b/gateware/mk/ecp5.mk @@ -1,4 +1,4 @@ -DEFINES = "$(ADD_DEFINES) -DECP5 -D$(HW_REV)" +DEFINES = "$(ADD_DEFINES) -DECP5 -D$(HW_REV) -D$(TOUCH)" all: $(BUILD)/$(PROJ).bin diff --git a/gateware/mk/ice40.mk b/gateware/mk/ice40.mk index 8687941..4efba94 100644 --- a/gateware/mk/ice40.mk +++ b/gateware/mk/ice40.mk @@ -1,4 +1,4 @@ -DEFINES = "$(ADD_DEFINES) -DICE40 -D$(HW_REV)" +DEFINES = "$(ADD_DEFINES) -DICE40 -D$(HW_REV) -D$(TOUCH)" all: $(BUILD)/$(PROJ).bin diff --git a/gateware/scripts/verilator_lint.sh b/gateware/scripts/verilator_lint.sh index 0f69147..2c63734 100755 --- a/gateware/scripts/verilator_lint.sh +++ b/gateware/scripts/verilator_lint.sh @@ -28,6 +28,21 @@ verilator --lint-only -DVERILATOR_LINT_ONLY \ -Wno-INITIALDLY \ top.sv +# Lint an entire ICE40 design with touch scanning enabled. +verilator --lint-only -DVERILATOR_LINT_ONLY \ + -DICE40 \ + -DSELECTED_DSP_CORE=touch_cv \ + -DTOUCH_SENSE_ENABLED \ + -Iboards/icebreaker \ + -Ical \ + -Idrivers \ + -Iexternal \ + -Iexternal/no2misc/rtl \ + -Icores \ + -Icores/util \ + -Wno-INITIALDLY \ + top.sv + # Lint each core which can be selected verilator --lint-only -Icores mirror.sv verilator --lint-only -Icores bitcrush.sv @@ -35,6 +50,7 @@ verilator --lint-only -Icores clkdiv.sv verilator --lint-only -Icores sampler.sv verilator --lint-only -Icores seqswitch.sv verilator --lint-only -Icores vca.sv +verilator --lint-only -Icores touch_cv.sv verilator --lint-only -Icores -Icores/util vco.sv verilator --lint-only cores/util/filter/karlsen_lpf.sv verilator --lint-only cores/util/filter/karlsen_lpf_pipelined.sv diff --git a/gateware/sim/pmod_i2c_master/tb_pmod_i2c_master.py b/gateware/sim/pmod_i2c_master/tb_pmod_i2c_master.py index 982f418..16af83e 100644 --- a/gateware/sim/pmod_i2c_master/tb_pmod_i2c_master.py +++ b/gateware/sim/pmod_i2c_master/tb_pmod_i2c_master.py @@ -27,7 +27,7 @@ async def test_i2cinit_00(dut): dut.rst.value = 0 - dut.i2c_state.value = 5 # Jump to I2C_INIT_CODEC1 + dut.i2c_state.value = 7 # Jump to I2C_INIT_CODEC1 await RisingEdge(dut.sda_oe) diff --git a/gateware/top.sv b/gateware/top.sv index 56a6e1e..8a81131 100644 --- a/gateware/top.sv +++ b/gateware/top.sv @@ -53,6 +53,14 @@ logic [7:0] eeprom_mfg; logic [7:0] eeprom_dev; logic [31:0] eeprom_serial; logic [7:0] jack; +logic [7:0] touch0; +logic [7:0] touch1; +logic [7:0] touch2; +logic [7:0] touch3; +logic [7:0] touch4; +logic [7:0] touch5; +logic [7:0] touch6; +logic [7:0] touch7; // Tristated I2C signals must be broken out at the top level as // ECP5 flow does not support tristate signals in nested modules. @@ -91,18 +99,28 @@ sysmgr sysmgr_instance ( `SELECTED_DSP_CORE #( .W(W) ) dsp_core_instance ( - .rst (rst), - .clk (clk_256fs), - .sample_clk (clk_fs), - .sample_in0 (in0), - .sample_in1 (in1), - .sample_in2 (in2), - .sample_in3 (in3), - .sample_out0 (out0), - .sample_out1 (out1), - .sample_out2 (out2), - .sample_out3 (out3), - .jack (jack) + .rst (rst) + , .clk (clk_256fs) + , .sample_clk (clk_fs) + , .sample_in0 (in0) + , .sample_in1 (in1) + , .sample_in2 (in2) + , .sample_in3 (in3) + , .sample_out0 (out0) + , .sample_out1 (out1) + , .sample_out2 (out2) + , .sample_out3 (out3) + , .jack (jack) +`ifdef TOUCH_SENSE_ENABLED + , .touch0 (touch0) + , .touch1 (touch1) + , .touch2 (touch2) + , .touch3 (touch3) + , .touch4 (touch4) + , .touch5 (touch5) + , .touch6 (touch6) + , .touch7 (touch7) +`endif ); `ifdef ECP5 @@ -156,6 +174,14 @@ eurorack_pmod #( .cal_out2 (out2), .cal_out3 (out3), .jack (jack), + .touch0 (touch0), + .touch1 (touch1), + .touch2 (touch2), + .touch3 (touch3), + .touch4 (touch4), + .touch5 (touch5), + .touch6 (touch6), + .touch7 (touch7), .eeprom_mfg (eeprom_mfg), .eeprom_dev (eeprom_dev), .eeprom_serial(eeprom_serial), @@ -187,7 +213,15 @@ debug_uart #( .eeprom_mfg(eeprom_mfg), .eeprom_dev(eeprom_dev), .eeprom_serial(eeprom_serial), - .jack(jack) + .jack(jack), + .touch0(touch0), + .touch1(touch1), + .touch2(touch2), + .touch3(touch3), + .touch4(touch4), + .touch5(touch5), + .touch6(touch6), + .touch7(touch7) ); `ifdef COCOTB_SIM