Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ESI] Make MMIO data 64-bit #7283

Merged
merged 1 commit into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontends/PyCDE/integration_test/esitester.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# RUN: mkdir %t && cd %t
# RUN: %PYTHON% %s %t 2>&1
# RUN: esi-cosim.py --source %t -- esitester cosim env wait | FileCheck %s
# RUN: ESI_COSIM_MANIFEST_MMIO=1 esi-cosim.py --source %t -- esiquery cosim env info

import pycde
from pycde import AppID, Clock, Module, Reset, generator
Expand Down
2 changes: 1 addition & 1 deletion frontends/PyCDE/integration_test/test_software/esi_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
mmio = acc.get_service_mmio()
data = mmio.read(8)
print(f"mmio data@8: {data:X}")
assert data == 0xe5100e51
assert data == 0x207D98E5E5100E51

assert acc.sysinfo().esi_version() == 1
m = acc.manifest()
Expand Down
45 changes: 20 additions & 25 deletions frontends/PyCDE/src/pycde/bsp/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@

from typing import Dict, Tuple

MagicNumberLo = 0xE5100E51 # ESI__ESI
MagicNumberHi = 0x207D98E5 # Random
MagicNumber = 0x207D98E5_E5100E51 # random + ESI__ESI
VersionNumber = 0 # Version 0: format subject to change


Expand All @@ -23,33 +22,30 @@ class ESI_Manifest_ROM(Module):
module_name = "__ESI_Manifest_ROM"

clk = Clock()
address = Input(Bits(30))
address = Input(Bits(29))
# Data is two cycles delayed after address changes.
data = Output(Bits(32))
data = Output(Bits(64))


class ChannelMMIO(esi.ServiceImplementation):
"""MMIO service implementation with an AXI-lite protocol. This assumes a 20
bit address bus for 1MB of addressable MMIO space. Which should be fine for
now, though nothing should assume this limit. It also only supports 32-bit
aligned accesses and just throws away the lower two bits of address.
"""MMIO service implementation with an AXI-lite protocol. This assumes a 32
bit address bus. It also only supports 64-bit aligned accesses and just throws
away the lower three bits of address.

Only allows for one outstanding request at a time. If a client doesn't return
a response, the MMIO service will hang. TODO: add some kind of timeout.

Implementation-defined MMIO layout:
- 0x0: 0 constant
- 0x4: 0 constant
- 0x8: Magic number low (0xE5100E51)
- 0xC: Magic number high (random constant: 0x207D98E5)
- 0x10: ESI version number (0)
- 0x14: Location of the manifest ROM (absolute address)
- 0x8: Magic number (0x207D98E5_E5100E51)
- 0x12: ESI version number (0)
- 0x18: Location of the manifest ROM (absolute address)

- 0x100: Start of MMIO space for requests. Mapping is contained in the
manifest so can be dynamically queried.

- addr(Manifest ROM) + 0: Size of compressed manifest
- addr(Manifest ROM) + 4: Start of compressed manifest
- addr(Manifest ROM) + 8: Start of compressed manifest

This layout _should_ be pretty standard, but different BSPs may have various
different restrictions.
Expand Down Expand Up @@ -81,10 +77,10 @@ def build_table(
for bundle in bundles.to_client_reqs:
if bundle.direction == ChannelDirection.Input:
read_table[offset] = bundle
offset += 4
offset += 8
elif bundle.direction == ChannelDirection.Output:
write_table[offset] = bundle
offset += 4
offset += 8

manifest_loc = 1 << offset.bit_length()
return read_table, write_table, manifest_loc
Expand All @@ -95,7 +91,7 @@ def build_read(self, manifest_loc: int, bundles):
# Currently just exposes the header and manifest. Not any of the possible
# service requests.

i32 = Bits(32)
i64 = Bits(64)
i2 = Bits(2)
i1 = Bits(1)

Expand All @@ -121,12 +117,12 @@ def build_read(self, manifest_loc: int, bundles):
address_written.assign(arvalid & ~req_outstanding)
address = araddr.reg(self.clk, ce=address_written, name="address")
address_valid = address_written.reg(name="address_valid")
address_words = address[2:] # Lop off the lower two bits.
address_words = address[3:] # Lop off the lower three bits.

# Set up the output of the data response pipeline. `data_pipeline*` are to
# be connected below.
data_pipeline_valid = NamedWire(i1, "data_pipeline_valid")
data_pipeline = NamedWire(i32, "data_pipeline")
data_pipeline = NamedWire(i64, "data_pipeline")
data_pipeline_rresp = NamedWire(i2, "data_pipeline_rresp")
data_out_valid = ControlReg(self.clk,
self.rst, [data_pipeline_valid],
Expand All @@ -148,17 +144,16 @@ def build_read(self, manifest_loc: int, bundles):
header_sel = (header_upper == header_upper.type(0))
header_sel.name = "header_sel"
# Layout the header as an array.
header = Array(Bits(32), 6)(
[0, 0, MagicNumberLo, MagicNumberHi, VersionNumber, manifest_loc])
header = Array(Bits(64), 4)([0, MagicNumber, VersionNumber, manifest_loc])
header.name = "header"
header_response_valid = address_valid # Zero latency read.
header_out = header[address[2:5]]
header_out = header[address_words[:2]]
header_out.name = "header_out"
header_rresp = i2(0)

# Handle reads from the manifest.
rom_address = NamedWire(
(address_words.as_uint() - (manifest_loc >> 2)).as_bits(30),
(address_words.as_uint() - (manifest_loc >> 3)).as_bits(29),
"rom_address")
mani_rom = ESI_Manifest_ROM(clk=self.clk, address=rom_address)
mani_valid = address_valid.reg(
Expand All @@ -168,10 +163,10 @@ def build_read(self, manifest_loc: int, bundles):
cycles=2, # Two cycle read to match the ROM latency.
name="mani_valid_reg")
mani_rresp = i2(0)
# mani_sel = (address.as_uint() >= manifest_loc)
mani_sel = (address.as_uint() >= manifest_loc).as_bits(1)

# Mux the output depending on whether or not the address is in the header.
sel = NamedWire(~header_sel, "sel")
sel = NamedWire(mani_sel, "sel")
data_mux_inputs = [header_out, mani_rom.data]
data_pipeline.assign(Mux(sel, *data_mux_inputs))
data_valid_mux_inputs = [header_response_valid, mani_valid]
Expand Down
136 changes: 9 additions & 127 deletions frontends/PyCDE/src/pycde/bsp/xrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
from ..types import Array, Bits
from .. import esi

from .common import (ESI_Manifest_ROM, MagicNumberHi, MagicNumberLo,
VersionNumber)

import glob
import pathlib
import shutil
Expand All @@ -22,6 +19,7 @@
__dir__ = pathlib.Path(__file__).parent


# Purposely breaking this. TODO: fix it by using ChannelMMIO.
class AxiMMIO(esi.ServiceImplementation):
"""MMIO service implementation with an AXI-lite protocol. This assumes a 20
bit address bus for 1MB of addressable MMIO space. Which should be fine for
Expand Down Expand Up @@ -49,8 +47,6 @@ class AxiMMIO(esi.ServiceImplementation):
different restrictions.
"""

# Moved from bsp/common.py. TODO: adapt this to use ChannelMMIO.

clk = Clock()
rst = Input(Bits(1))

Expand Down Expand Up @@ -85,130 +81,16 @@ class AxiMMIO(esi.ServiceImplementation):

@generator
def generate(self, bundles: esi._ServiceGeneratorBundles):
read_table, write_table, manifest_loc = AxiMMIO.build_table(self, bundles)
AxiMMIO.build_read(self, manifest_loc, read_table)
AxiMMIO.build_write(self, write_table)
self.arready = Bits(1)(0)
self.rvalid = Bits(1)(0)
self.rdata = Bits(32)(0)
self.rresp = Bits(2)(0)
self.awready = Bits(1)(0)
self.wready = Bits(1)(0)
self.bvalid = Bits(1)(0)
self.bresp = Bits(2)(0)
return True

def build_table(
self,
bundles) -> Tuple[Dict[int, BundleSignal], Dict[int, BundleSignal], int]:
"""Build a table of read and write addresses to BundleSignals."""
offset = AxiMMIO.initial_offset
read_table = {}
write_table = {}
for bundle in bundles.to_client_reqs:
if bundle.direction == ChannelDirection.Input:
read_table[offset] = bundle
offset += 4
elif bundle.direction == ChannelDirection.Output:
write_table[offset] = bundle
offset += 4

manifest_loc = 1 << offset.bit_length()
return read_table, write_table, manifest_loc

def build_read(self, manifest_loc: int, bundles):
"""Builds the read side of the MMIO service."""

# Currently just exposes the header and manifest. Not any of the possible
# service requests.

i32 = Bits(32)
i2 = Bits(2)
i1 = Bits(1)

address_written = NamedWire(i1, "address_written")
response_written = NamedWire(i1, "response_written")

# Only allow one outstanding request at a time. Don't clear it until the
# output has been transmitted. This way, we don't have to deal with
# backpressure.
req_outstanding = ControlReg(self.clk,
self.rst, [address_written],
[response_written],
name="req_outstanding")
self.arready = ~req_outstanding

# Capture the address if a the bus transaction occured.
address_written.assign(self.arvalid & ~req_outstanding)
address = self.araddr.reg(self.clk, ce=address_written, name="address")
address_valid = address_written.reg(name="address_valid")
address_words = address[2:] # Lop off the lower two bits.

# Set up the output of the data response pipeline. `data_pipeline*` are to
# be connected below.
data_pipeline_valid = NamedWire(i1, "data_pipeline_valid")
data_pipeline = NamedWire(i32, "data_pipeline")
data_pipeline_rresp = NamedWire(i2, "data_pipeline_rresp")
data_out_valid = ControlReg(self.clk,
self.rst, [data_pipeline_valid],
[response_written],
name="data_out_valid")
self.rvalid = data_out_valid
self.rdata = data_pipeline.reg(self.clk,
self.rst,
ce=data_pipeline_valid,
name="data_pipeline_reg")
self.rresp = data_pipeline_rresp.reg(self.clk,
self.rst,
ce=data_pipeline_valid,
name="data_pipeline_rresp_reg")
# Clear the `req_outstanding` flag when the response has been transmitted.
response_written.assign(data_out_valid & self.rready)

# Handle reads from the header (< 0x100).
header_upper = address_words[AxiMMIO.initial_offset.bit_length() - 2:]
# Is the address in the header?
header_sel = (header_upper == header_upper.type(0))
header_sel.name = "header_sel"
# Layout the header as an array.
header = Array(Bits(32), 6)(
[0, 0, MagicNumberLo, MagicNumberHi, VersionNumber, manifest_loc])
header.name = "header"
header_response_valid = address_valid # Zero latency read.
header_out = header[address[2:5]]
header_out.name = "header_out"
header_rresp = i2(0)

# Handle reads from the manifest.
rom_address = NamedWire(
(address_words.as_uint() - (manifest_loc >> 2)).as_bits(30),
"rom_address")
mani_rom = ESI_Manifest_ROM(clk=self.clk, address=rom_address)
mani_valid = address_valid.reg(
self.clk,
self.rst,
rst_value=i1(0),
cycles=2, # Two cycle read to match the ROM latency.
name="mani_valid_reg")
mani_rresp = i2(0)
# mani_sel = (address.as_uint() >= manifest_loc)

# Mux the output depending on whether or not the address is in the header.
sel = NamedWire(~header_sel, "sel")
data_mux_inputs = [header_out, mani_rom.data]
data_pipeline.assign(Mux(sel, *data_mux_inputs))
data_valid_mux_inputs = [header_response_valid, mani_valid]
data_pipeline_valid.assign(Mux(sel, *data_valid_mux_inputs))
rresp_mux_inputs = [header_rresp, mani_rresp]
data_pipeline_rresp.assign(Mux(sel, *rresp_mux_inputs))

def build_write(self, bundles):
# TODO: this.

# So that we don't wedge the AXI-lite for writes, just ack all of them.
write_happened = Wire(Bits(1))
latched_aw = ControlReg(self.clk, self.rst, [self.awvalid],
[write_happened])
latched_w = ControlReg(self.clk, self.rst, [self.wvalid], [write_happened])
write_happened.assign(latched_aw & latched_w)

self.awready = 1
self.wready = 1
self.bvalid = write_happened
self.bresp = 0


def XrtBSP(user_module):
"""Use the Xilinx RunTime (XRT) shell to implement ESI services and build an
Expand Down
2 changes: 1 addition & 1 deletion frontends/PyCDE/src/pycde/esi.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ def param(name: str, type: Type = None):
esi.ESIPureModuleParamOp(name, type_attr)


MMIOReadDataResponse = Bits(32)
MMIOReadDataResponse = Bits(64)


@ServiceDecl
Expand Down
12 changes: 6 additions & 6 deletions frontends/PyCDE/test/test_esi.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,20 +181,20 @@ def build(self):


# CHECK-LABEL: hw.module @MMIOReq()
# CHECK-NEXT: %c0_i32 = hw.constant 0 : i32
# CHECK-NEXT: %c0_i64 = hw.constant 0 : i64
# CHECK-NEXT: %false = hw.constant false
# CHECK-NEXT: [[B:%.+]] = esi.service.req <@MMIO::@read>(#esi.appid<"mmio_req">) : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i32> from "data"]>
# CHECK-NEXT: %chanOutput, %ready = esi.wrap.vr %c0_i32, %false : i32
# CHECK-NEXT: %offset = esi.bundle.unpack %chanOutput from [[B]] : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i32> from "data"]>
# CHECK-NEXT: [[B:%.+]] = esi.service.req <@MMIO::@read>(#esi.appid<"mmio_req">) : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
# CHECK-NEXT: %chanOutput, %ready = esi.wrap.vr %c0_i64, %false : i64
# CHECK-NEXT: %offset = esi.bundle.unpack %chanOutput from [[B]] : !esi.bundle<[!esi.channel<i32> to "offset", !esi.channel<i64> from "data"]>
@unittestmodule(esi_sys=True)
class MMIOReq(Module):

@generator
def build(ports):
c32 = Bits(32)(0)
c64 = Bits(64)(0)
c1 = Bits(1)(0)

read_bundle = MMIO.read(AppID("mmio_req"))

data, _ = Channel(Bits(32)).wrap(c32, c1)
data, _ = Channel(Bits(64)).wrap(c64, c1)
_ = read_bundle.unpack(data=data)
1 change: 1 addition & 0 deletions frontends/PyCDE/test/test_xrt.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# XFAIL: *
# RUN: rm -rf %t
# RUN: %PYTHON% %s %t 2>&1
# RUN: ls %t/hw/XrtTop.sv
Expand Down
4 changes: 2 additions & 2 deletions lib/Dialect/ESI/ESIStdServices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ void MMIOServiceDeclOp::getPortList(SmallVectorImpl<ServicePortInfo> &ports) {
{BundledChannel{StringAttr::get(ctxt, "offset"), ChannelDirection::to,
ChannelType::get(ctxt, IntegerType::get(ctxt, 32))},
BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::from,
ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}},
ChannelType::get(ctxt, IntegerType::get(ctxt, 64))}},
/*resettable=*/UnitAttr())});
// Write only port.
ports.push_back(ServicePortInfo{
Expand All @@ -112,6 +112,6 @@ void MMIOServiceDeclOp::getPortList(SmallVectorImpl<ServicePortInfo> &ports) {
{BundledChannel{StringAttr::get(ctxt, "offset"), ChannelDirection::to,
ChannelType::get(ctxt, IntegerType::get(ctxt, 32))},
BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::to,
ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}},
ChannelType::get(ctxt, IntegerType::get(ctxt, 64))}},
/*resettable=*/UnitAttr())});
}
Loading
Loading