Skip to content

Commit

Permalink
Update Verilator top level and I2C DPI models
Browse files Browse the repository at this point in the history
The two DPI models did not model the post-pinmux changes to
the I2C buses; there are no longer just two physical I2C
buses electrically commoned within the FPGA.

Separate the I2C devices into three I2C DPI models representing
the physical buses that have devices at present. Tie the other
I2C buses inactive, modelling the pullups on the FPGA board.

Modify `i2cdpi` so that it properly handles traffic to an
address at which there is no connected device.
  • Loading branch information
alees24 authored and AlexJones0 committed Oct 25, 2024
1 parent 5db7c2c commit fc13b4b
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 62 deletions.
62 changes: 34 additions & 28 deletions dv/dpi/i2cdpi/i2cdpi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ uint32_t i2cdpi::decode(bool scl, bool sda, uint32_t oobIn, uint32_t &oobOut) {
sendAck = true;
} else {
// No target/device at the given address.
logText("No target/device at address 0x%0x\n", addr);
sendAck = false;
}
// Start counting the transmitted bits of this new byte.
Expand Down Expand Up @@ -118,6 +119,7 @@ uint32_t i2cdpi::decode(bool scl, bool sda, uint32_t oobIn, uint32_t &oobOut) {
if (!prev_scl && scl) {
// ACK/NAK response from the controller.
bool ack = !sda;
assert(currDevice);
if (ack && !currDevice->readByte(oobIn, currByte, oobOut)) {
currByte = 0xffu;
}
Expand Down Expand Up @@ -151,27 +153,29 @@ uint32_t i2cdpi::decode(bool scl, bool sda, uint32_t oobIn, uint32_t &oobOut) {
}
state = I2C_Addr;
numBits = 0u;
} else if (reading) {
// Emit the MS bit not yet completed; bytes are transferred Most Significant Bit first.
sda_out = (currByte & 0x80u) != 0u;
// When the operation is a read, we must launch the data during the interval for which
// SCL is deasserted; this avoids transitions during the interval for which SCL is asserted.
if (prev_scl && !scl) {
// Shift read byte; ensure SDA returns high.
currByte = (currByte << 1) | 1u;
if (++numBits >= 8u) {
state = I2C_SentData;
} else if (currDevice) {
if (reading) {
// Emit the MS bit not yet completed; bytes are transferred Most Significant Bit first.
sda_out = (currByte & 0x80u) != 0u;
// When the operation is a read, we must launch the data during the interval for which
// SCL is deasserted; this avoids transitions during the interval for which SCL is asserted.
if (prev_scl && !scl) {
// Shift read byte; ensure SDA returns high.
currByte = (currByte << 1) | 1u;
if (++numBits >= 8u) {
state = I2C_SentData;
}
}
}
} else {
// When the operation is a write, the data will be stable throughout the interval for which
// SCL is asserted; sample it on the rising edge.
if (!prev_scl && scl) {
currByte = (currByte << 1) | (sda ? 1u : 0u);
if (++numBits >= 8u) {
sendAck = currDevice->writeByte(currByte, oobIn);
numBits = 0u;
state = I2C_GotData;
} else {
// When the operation is a write, the data will be stable throughout the interval for which
// SCL is asserted; sample it on the rising edge.
if (!prev_scl && scl) {
currByte = (currByte << 1) | (sda ? 1u : 0u);
if (++numBits >= 8u) {
sendAck = currDevice->writeByte(currByte, oobIn);
numBits = 0u;
state = I2C_GotData;
}
}
}
}
Expand Down Expand Up @@ -206,14 +210,14 @@ void *i2cdpi_create(const char *id) {
i2cdpi *i2c = new i2cdpi();
assert(i2c);
// Create the appropriate devices for this bus.
if (!strcmp(id, "i2c0")) {
if (!strcmp(id, "i2c_rpi0")) {
// RPI Sense HAT ID EEPROM
const i2caddr_t id_addr = 0x50u;
i2cdevice *id_device = new i2c_hat_id(id_addr);
assert(id_device);
i2c->add_device(id_device);
i2c->logText("I2C0 created\n", id);
} else if (!strcmp(id, "i2c1")) {
i2c->logText("%s created\n", id);
} else if (!strcmp(id, "i2c_rpi1")) {
// RPI Sense HAT IMU; this presents at two separate I2C addresses.
const i2caddr_t imu_accgyr_addr = 0x6au; // Accelerometer/gyroscope.
i2cdevice *imu_accgyr_device = new i2c_lsm9ds1(imu_accgyr_addr);
Expand All @@ -223,12 +227,14 @@ void *i2cdpi_create(const char *id) {
assert(imu_mag_device);
i2c->add_device(imu_accgyr_device);
i2c->add_device(imu_mag_device);
i2c->logText("%s created\n", id);
} else if (!strcmp(id, "i2c1")) {
// Digital Temperature Sensor.
const i2caddr_t dst_addr = 0x48u;
i2cdevice *dst_device = new i2c_as621x(dst_addr);
assert(dst_device);
i2c->add_device(dst_device);
i2c->logText("I2C1 created\n", id);
const i2caddr_t dts_addr = 0x48u;
i2cdevice *dts_device = new i2c_as621x(dts_addr);
assert(dts_device);
i2c->add_device(dts_device);
i2c->logText("%s created\n", id);
} else {
i2c->logText("Info: I2C bus '%s' is unpopulated\n", id);
}
Expand Down
3 changes: 3 additions & 0 deletions dv/dpi/i2cdpi/i2cdpi.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#ifndef __DV_DPI_I2CDPI_I2CDPI_H_
#define __DV_DPI_I2CDPI_I2CDPI_H_
#include <assert.h>
#include <map>
#include "i2cdevice.hh"

Expand Down Expand Up @@ -36,6 +37,8 @@ public:

// Add a device to this bus.
void add_device(i2cdevice *dev) {
assert(dev);
logText(" - adding device at address 0x%02x\n", dev->getAddress());
(void)devs.insert(std::pair<i2caddr_t, i2cdevice *>(dev->getAddress(), dev));
}

Expand Down
109 changes: 75 additions & 34 deletions dv/verilator/top_verilator.sv
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,40 @@ module top_verilator (input logic clk_i, rst_ni);
logic uart_aux_rx, uart_aux_tx;
assign uart_aux_rx = 1'b1;

logic scl0_o, scl0_oe;
logic sda0_o, sda0_oe;
logic scl_rpi0_o, scl_rpi0_oe;
logic sda_rpi0_o, sda_rpi0_oe;

logic scl_rpi1_o, scl_rpi1_oe;
logic sda_rpi1_o, sda_rpi1_oe;

logic scl1_o, scl1_oe;
logic sda1_o, sda1_oe;

// Output clocks and data to the I2C buses.
wire scl0_out = scl0_oe ? scl0_o : 1'b1;
wire scl_rpi0_out = scl_rpi0_oe ? scl_rpi0_o : 1'b1;
wire sda_rpi0_out = sda_rpi0_oe ? sda_rpi0_o : 1'b1;

wire scl_rpi1_out = scl_rpi1_oe ? scl_rpi1_o : 1'b1;
wire sda_rpi1_out = sda_rpi1_oe ? sda_rpi1_o : 1'b1;

wire scl1_out = scl1_oe ? scl1_o : 1'b1;
wire sda0_out = sda0_oe ? sda0_o : 1'b1;
wire sda1_out = sda1_oe ? sda1_o : 1'b1;

// Clocks and data from the I2C DPI models.
wire scl0_dpi;
wire scl1_dpi;
wire sda0_dpi;
wire sda1_dpi;
wire scl_rpi0_dpi, sda_rpi0_dpi;
wire scl_rpi1_dpi, sda_rpi1_dpi;
wire scl1_dpi, sda1_dpi;

// Input clocks and data from the I2C buses; these signals must reflect the physical I2C bus,
// ie. they carry both the outbound and the inbound activity, because otherwise the controller
// will perceive a mismatch between its own transmissions and the inputs as bus contention.
wire scl_rpi0_in = scl_rpi0_out & scl_rpi0_dpi;
wire sda_rpi0_in = sda_rpi0_out & sda_rpi0_dpi;

wire scl_rpi1_in = scl_rpi1_out & scl_rpi1_dpi;
wire sda_rpi1_in = sda_rpi1_out & sda_rpi1_dpi;

// Input clocks and data from the I2C buses.
wire scl0_in = scl0_out & scl0_dpi;
wire scl1_in = scl1_out & scl1_dpi;
wire sda0_in = sda0_out & sda0_dpi;
wire sda1_in = sda1_out & sda1_dpi;

wire unused_ = uart_aux_tx;
Expand Down Expand Up @@ -174,27 +186,40 @@ module top_verilator (input logic clk_i, rst_ni);
assign appspi_clk = out_to_pins[OUT_PIN_APPSPI_CLK];
assign lcd_clk = out_to_pins[OUT_PIN_LCD_CLK];

assign {scl0_o, scl0_oe} = {inout_to_pins[INOUT_PIN_SCL0], inout_to_pins_en[INOUT_PIN_SCL0]};
assign {scl1_o, scl1_oe} = {inout_to_pins[INOUT_PIN_SCL1], inout_to_pins_en[INOUT_PIN_SCL1]};
assign {sda0_o, sda0_oe} = {inout_to_pins[INOUT_PIN_SDA0], inout_to_pins_en[INOUT_PIN_SDA0]};
assign {sda1_o, sda1_oe} = {inout_to_pins[INOUT_PIN_SDA1], inout_to_pins_en[INOUT_PIN_SDA1]};
// Output I2C traffic to the RPi HAT ID EEPROM.
assign {scl_rpi0_o, scl_rpi0_oe} = {inout_to_pins[INOUT_PIN_RPH_G1],
inout_to_pins_en[INOUT_PIN_RPH_G1]};
assign {sda_rpi0_o, sda_rpi0_oe} = {inout_to_pins[INOUT_PIN_RPH_G0],
inout_to_pins_en[INOUT_PIN_RPH_G0]};
// Output I2C traffic to the secondary I2C bus on the Raspberry Pi HAT (shared with GPIO2/3).
assign {scl_rpi1_o, scl_rpi1_oe} = {inout_to_pins[INOUT_PIN_RPH_G3_SCL],
inout_to_pins_en[INOUT_PIN_RPH_G3_SCL]};
assign {sda_rpi1_o, sda_rpi1_oe} = {inout_to_pins[INOUT_PIN_RPH_G2_SDA],
inout_to_pins_en[INOUT_PIN_RPH_G2_SDA]};

assign {scl1_o, scl1_oe} = {inout_to_pins[INOUT_PIN_SCL1], inout_to_pins_en[INOUT_PIN_SCL1]};
assign {sda1_o, sda1_oe} = {inout_to_pins[INOUT_PIN_SDA1], inout_to_pins_en[INOUT_PIN_SDA1]};

assign in_from_pins[IN_PIN_APPSPI_D1] = appspi_d1;
assign in_from_pins[IN_PIN_SER0_RX] = uart_sys_rx;
assign in_from_pins[IN_PIN_SER1_RX] = uart_aux_rx;

assign inout_from_pins[INOUT_PIN_SCL0] = scl0_in;
assign inout_from_pins[INOUT_PIN_SDA0] = sda0_in;
// SCL0/SDA0 pins are presently not connected to any I2C models; just pulled up on the PCB.
// - there is no model on either the QWIIC0 connector or the Arduino Shield.
assign inout_from_pins[INOUT_PIN_SCL0] = 1'b1;
assign inout_from_pins[INOUT_PIN_SDA0] = 1'b1;
// SCL1/SDA1 has a device model on the QWIIC1 connector.
assign inout_from_pins[INOUT_PIN_SCL1] = scl1_in;
assign inout_from_pins[INOUT_PIN_SDA1] = sda1_in;

// These unused inputs must be pulled high.
assign {inout_from_pins[INOUT_PIN_RPH_G0],
inout_from_pins[INOUT_PIN_RPH_G1],
inout_from_pins[INOUT_PIN_RPH_G2_SDA],
inout_from_pins[INOUT_PIN_RPH_G3_SCL],
inout_from_pins[INOUT_PIN_MB5],
inout_from_pins[INOUT_PIN_MB6]} = {6{1'b1}}; // SCL/SDA are shared with other pins.
// RPi HAT ID bus has a device model.
assign inout_from_pins[INOUT_PIN_RPH_G0] = sda_rpi0_in;
assign inout_from_pins[INOUT_PIN_RPH_G1] = scl_rpi0_in;
// RPi HAT secondary I2C bus also has a device model (Sense HAT).
assign inout_from_pins[INOUT_PIN_RPH_G2_SDA] = sda_rpi1_in;
assign inout_from_pins[INOUT_PIN_RPH_G3_SCL] = scl_rpi1_in;
// There is no device model on the mikroBUS Click I2C bus.
assign inout_from_pins[INOUT_PIN_MB5] = 1'b1;
assign inout_from_pins[INOUT_PIN_MB6] = 1'b1;

// SPI CS outputs from GPIO pins; these are scheduled to be dropped but they are still required
// by the `gp_o` port presently.
Expand Down Expand Up @@ -333,25 +358,41 @@ module top_verilator (input logic clk_i, rst_ni);
.inout_to_pins_en_o (inout_to_pins_en)
);

// I2C 0 DPI
// I2C HAT ID DPI - this I2C bus is to the ID EEPROM of a Raspberry Pi HAT.
i2cdpi #(
.ID ("i2c_rpi0")
) u_i2c_rpi0_dpi (
.rst_ni (rst_ni),
// The connected signal names are from the perspective of the controller.
.scl_i (scl_rpi0_out),
.sda_i (sda_rpi0_out),
.scl_o (scl_rpi0_dpi),
.sda_o (sda_rpi0_dpi),
// Out-Of-Band data.
.oob_in (1'b0),
.oob_out () // not used
);

// I2C GPIO2/3 - this I2c bus is also present on the Raspberry Pi HATs,
// and is used on the Raspberry Pi Sense HAT, for example.
i2cdpi #(
.ID ("i2c0")
) u_i2c0dpi (
.ID ("i2c_rpi1")
) u_i2c_rpi1_dpi (
.rst_ni (rst_ni),
// The connected signal names are from the perspective of the controller.
.scl_i (scl0_out),
.sda_i (sda0_out),
.scl_o (scl0_dpi),
.sda_o (sda0_dpi),
.scl_i (scl_rpi1_out),
.sda_i (sda_rpi1_out),
.scl_o (scl_rpi1_dpi),
.sda_o (sda_rpi1_dpi),
// Out-Of-Band data.
.oob_in (1'b0),
.oob_out () // not used
);

// I2C 1 DPI
// I2C QWIIC1
i2cdpi #(
.ID ("i2c1")
) u_i2c1dpi (
) u_i2c1_dpi (
.rst_ni (rst_ni),
// The connected signal names are from the perspective of the controller.
.scl_i (scl1_out),
Expand Down

0 comments on commit fc13b4b

Please sign in to comment.