Add Intel Avalon MM cpuif. #40

This commit is contained in:
Alex Mykyta
2023-05-13 21:24:03 -07:00
parent b350da3e7c
commit fadb8ce19d
16 changed files with 408 additions and 43 deletions

32
docs/cpuif/avalon.rst Normal file
View File

@@ -0,0 +1,32 @@
Intel Avalon
============
Implements the register block using an
`Intel Avalon MM <https://www.intel.com/content/www/us/en/docs/programmable/683091/22-3/memory-mapped-interfaces.html>`_
CPU interface.
The Avalon interface comes in two i/o port flavors:
SystemVerilog Interface
Class: :class:`peakrdl_regblock.cpuif.avalon.Avalon_Cpuif`
Interface Definition: :download:`avalon_mm_intf.sv <../../hdl-src/avalon_mm_intf.sv>`
Flattened inputs/outputs
Flattens the interface into discrete input and output ports.
Class: :class:`peakrdl_regblock.cpuif.avalon.Avalon_Cpuif_flattened`
Implementation Details
----------------------
This implementation of the Avalon protocol has the following features:
* Interface uses word addressing.
* Supports `pipelined transfers <https://www.intel.com/content/www/us/en/docs/programmable/683091/22-3/pipelined-transfers.html>`_
* Responses may have variable latency
In most cases, latency is fixed and is determined by how many retiming
stages are enabled in your design.
However if your design contains external components, access latency is
not guaranteed to be uniform.

View File

@@ -112,6 +112,7 @@ Links
cpuif/introduction
cpuif/apb
cpuif/axi4lite
cpuif/avalon
cpuif/passthrough
cpuif/internal_protocol
cpuif/customizing

46
hdl-src/avalon_mm_intf.sv Normal file
View File

@@ -0,0 +1,46 @@
interface avalon_mm_intf #(
parameter DATA_WIDTH = 32,
parameter ADDR_WIDTH = 32 // Important! Avalon uses word addressing
);
// Command
logic read;
logic write;
logic waitrequest;
logic [ADDR_WIDTH-1:0] address;
logic [DATA_WIDTH-1:0] writedata;
logic [DATA_WIDTH/8-1:0] byteenable;
// Response
logic readdatavalid;
logic writeresponsevalid;
logic [DATA_WIDTH-1:0] readdata;
logic [1:0] response;
modport host (
output read,
output write,
input waitrequest,
output address,
output writedata,
output byteenable,
input readdatavalid,
input writeresponsevalid,
input readdata,
input response
);
modport agent (
input read,
input write,
output waitrequest,
input address,
input writedata,
input byteenable,
output readdatavalid,
output writeresponsevalid,
output readdata,
output response
);
endinterface

View File

@@ -6,7 +6,7 @@ from peakrdl.plugins.exporter import ExporterSubcommandPlugin #pylint: disable=i
from peakrdl.config import schema #pylint: disable=import-error
from .exporter import RegblockExporter
from .cpuif import apb3, apb4, axi4lite, passthrough, CpuifBase
from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon
from .udps import ALL_UDPS
from . import entry_points
@@ -48,13 +48,15 @@ class Exporter(ExporterSubcommandPlugin):
# All built-in CPUIFs
cpuifs = {
"passthrough": passthrough.PassthroughCpuif,
"apb3": apb3.APB3_Cpuif,
"apb3-flat": apb3.APB3_Cpuif_flattened,
"apb4": apb4.APB4_Cpuif,
"apb4-flat": apb4.APB4_Cpuif_flattened,
"axi4-lite": axi4lite.AXI4Lite_Cpuif,
"axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
"passthrough": passthrough.PassthroughCpuif
"avalon-mm": avalon.Avalon_Cpuif,
"avalon-mm-flat": avalon.Avalon_Cpuif_flattened,
}
# Load any cpuifs specified via entry points

View File

@@ -0,0 +1,37 @@
from ..base import CpuifBase
from ...utils import clog2
class Avalon_Cpuif(CpuifBase):
template_path = "avalon_tmpl.sv"
@property
def port_declaration(self) -> str:
return "avalon_mm_intf.agent avalon"
def signal(self, name:str) -> str:
return "avalon." + name
@property
def word_addr_width(self) -> int:
# Avalon agents use word addressing, therefore address width is reduced
return self.addr_width - clog2(self.data_width_bytes)
class Avalon_Cpuif_flattened(Avalon_Cpuif):
@property
def port_declaration(self) -> str:
lines = [
"input wire " + self.signal("read"),
"input wire " + self.signal("write"),
"output logic " + self.signal("waitrequest"),
f"input wire [{self.word_addr_width-1}:0] " + self.signal("address"),
f"input wire [{self.data_width-1}:0] " + self.signal("writedata"),
f"input wire [{self.data_width_bytes-1}:0] " + self.signal("byteenable"),
"output logic " + self.signal("readdatavalid"),
"output logic " + self.signal("writeresponsevalid"),
f"output logic [{self.data_width-1}:0] " + self.signal("readdata"),
"output logic [1:0] " + self.signal("response"),
]
return ",\n".join(lines)
def signal(self, name:str) -> str:
return "avalon_" + name

View File

@@ -0,0 +1,29 @@
// Request
always_comb begin
cpuif_req = {{cpuif.signal("read")}} | {{cpuif.signal("write")}};
cpuif_req_is_wr = {{cpuif.signal("write")}};
{%- if cpuif.data_width_bytes == 1 %}
cpuif_addr = {{cpuif.signal("address")}};
{%- else %}
cpuif_addr = { {{-cpuif.signal("address")}}, {{clog2(cpuif.data_width_bytes)}}'b0};
{%- endif %}
cpuif_wr_data = {{cpuif.signal("writedata")}};
for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
cpuif_wr_biten[i*8 +: 8] <= {8{ {{-cpuif.signal("byteenable")}}[i]}};
end
{{cpuif.signal("waitrequest")}} = (cpuif_req_stall_rd & {{cpuif.signal("read")}}) | (cpuif_req_stall_wr & {{cpuif.signal("write")}});
end
// Response
always_comb begin
{{cpuif.signal("readdatavalid")}} = cpuif_rd_ack;
{{cpuif.signal("writeresponsevalid")}} = cpuif_wr_ack;
{{cpuif.signal("readdata")}} = cpuif_rd_data;
if(cpuif_rd_err || cpuif_wr_err) begin
// SLVERR
{{cpuif.signal("response")}} = 2'b10;
end else begin
// OK
{{cpuif.signal("response")}} = 2'b00;
end
end

View File

@@ -0,0 +1,17 @@
from .passthrough import Passthrough
from .apb3 import APB3, FlatAPB3
from .apb4 import APB4, FlatAPB4
from .axi4lite import AXI4Lite, FlatAXI4Lite
from .avalon import Avalon, FlatAvalon
ALL_CPUIF = [
Passthrough(),
APB3(),
FlatAPB3(),
APB4(),
FlatAPB4(),
AXI4Lite(),
FlatAXI4Lite(),
Avalon(),
FlatAvalon(),
]

View File

@@ -58,7 +58,7 @@ interface apb4_intf_driver #(
semaphore txn_mutex = new(1);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
txn_mutex.get();
##0;
@@ -69,7 +69,7 @@ interface apb4_intf_driver #(
cb.PPROT <= '0;
cb.PADDR <= addr;
cb.PWDATA <= data;
cb.PSTRB <= '1;
cb.PSTRB <= strb;
@(cb);
// active phase

View File

@@ -0,0 +1,18 @@
from ..base import CpuifTestMode
from peakrdl_regblock.cpuif.avalon import Avalon_Cpuif, Avalon_Cpuif_flattened
class Avalon(CpuifTestMode):
cpuif_cls = Avalon_Cpuif
rtl_files = [
"../../../../hdl-src/avalon_mm_intf.sv",
]
tb_files = [
"../../../../hdl-src/avalon_mm_intf.sv",
"avalon_mm_intf_driver.sv",
]
tb_template = "tb_inst.sv"
class FlatAvalon(Avalon):
cpuif_cls = Avalon_Cpuif_flattened
rtl_files = []

View File

@@ -0,0 +1,138 @@
interface avalon_mm_intf_driver #(
parameter DATA_WIDTH = 32,
parameter ADDR_WIDTH = 32
)(
input wire clk,
input wire rst,
avalon_mm_intf.host avalon
);
localparam ADDR_PAD = $clog2(DATA_WIDTH/8);
localparam WORD_ADDR_WIDTH = ADDR_WIDTH - ADDR_PAD;
timeunit 1ps;
timeprecision 1ps;
logic av_read;
logic av_write;
logic av_waitrequest;
logic [WORD_ADDR_WIDTH-1:0] av_address;
logic [DATA_WIDTH-1:0] av_writedata;
logic [DATA_WIDTH/8-1:0] av_byteenable;
logic av_readdatavalid;
logic av_writeresponsevalid;
logic [DATA_WIDTH-1:0] av_readdata;
logic [1:0] av_response;
assign avalon.read = av_read;
assign avalon.write = av_write;
assign av_waitrequest = avalon.waitrequest;
assign avalon.address = av_address;
assign avalon.writedata = av_writedata;
assign avalon.byteenable = av_byteenable;
assign av_readdatavalid = avalon.readdatavalid;
assign av_writeresponsevalid = avalon.writeresponsevalid;
assign av_readdata = avalon.readdata;
assign av_response = avalon.response;
default clocking cb @(posedge clk);
default input #1step output #1;
output av_read;
output av_write;
input av_waitrequest;
output av_address;
output av_writedata;
output av_byteenable;
input av_readdatavalid;
input av_writeresponsevalid;
input av_readdata;
input av_response;
endclocking
task automatic reset();
cb.av_read <= '0;
cb.av_write <= '0;
cb.av_address <= '0;
cb.av_writedata <= '0;
cb.av_byteenable <= '0;
endtask
semaphore req_mutex = new(1);
semaphore resp_mutex = new(1);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
fork
begin
req_mutex.get();
##0;
// Initiate transfer
cb.av_write <= '1;
cb.av_address <= (addr >> ADDR_PAD);
cb.av_writedata <= data;
cb.av_byteenable <= strb;
@(cb);
// Wait for transfer to be accepted
while(cb.av_waitrequest == 1'b1) @(cb);
reset();
req_mutex.put();
end
begin
resp_mutex.get();
@cb;
// Wait for response
while(cb.av_writeresponsevalid !== 1'b1) @(cb);
assert(!$isunknown(cb.av_response)) else $error("Read from 0x%0x returned X's on av_response", addr);
resp_mutex.put();
end
join
endtask
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
fork
begin
req_mutex.get();
##0;
// Initiate transfer
cb.av_read <= '1;
cb.av_address <= (addr >> ADDR_PAD);
@(cb);
// Wait for transfer to be accepted
while(cb.av_waitrequest == 1'b1) @(cb);
reset();
req_mutex.put();
end
begin
resp_mutex.get();
@cb;
// Wait for response
while(cb.av_readdatavalid !== 1'b1) @(cb);
assert(!$isunknown(cb.av_readdata)) else $error("Read from 0x%0x returned X's on av_response", av_readdata);
assert(!$isunknown(cb.av_response)) else $error("Read from 0x%0x returned X's on av_response", addr);
data = cb.av_readdata;
resp_mutex.put();
end
join
endtask
task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
logic [DATA_WIDTH-1:0] data;
read(addr, data);
data &= mask;
assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data);
endtask
initial begin
reset();
end
initial forever begin
@cb;
if(!rst) assert(!$isunknown(cb.av_waitrequest)) else $error("Saw X on av_waitrequest!");
if(!rst) assert(!$isunknown(cb.av_readdatavalid)) else $error("Saw X on av_readdatavalid!");
if(!rst) assert(!$isunknown(cb.av_writeresponsevalid)) else $error("Saw X on av_writeresponsevalid!");
end
endinterface

View File

@@ -0,0 +1,36 @@
{% sv_line_anchor %}
avalon_mm_intf #(
.DATA_WIDTH({{exporter.cpuif.data_width}}),
.ADDR_WIDTH({{exporter.cpuif.word_addr_width}})
) avalon();
avalon_mm_intf_driver #(
.DATA_WIDTH({{exporter.cpuif.data_width}}),
.ADDR_WIDTH({{exporter.cpuif.addr_width}})
) cpuif (
.clk(clk),
.rst(rst),
.avalon(avalon)
);
{% if type(cpuif).__name__.startswith("Flat") %}
{% sv_line_anchor %}
wire avalon_read;
wire avalon_write;
wire avalon_waitrequest;
wire [{{exporter.cpuif.word_addr_width - 1}}:0] avalon_address;
wire [{{exporter.cpuif.data_width - 1}}:0] avalon_writedata;
wire [{{exporter.cpuif.data_width_bytes - 1}}:0] avalon_byteenable;
wire avalon_readdatavalid;
wire avalon_writeresponsevalid;
wire [{{exporter.cpuif.data_width - 1}}:0] avalon_readdata;
wire [1:0] avalon_response;
assign avalon_read = avalon.read;
assign avalon_write = avalon.write;
assign avalon.waitrequest = avalon_waitrequest;
assign avalon_address = avalon.address;
assign avalon_writedata = avalon.writedata;
assign avalon_byteenable = avalon.byteenable;
assign avalon.readdatavalid = avalon_readdatavalid;
assign avalon.writeresponsevalid = avalon_writeresponsevalid;
assign avalon.readdata = avalon_readdata;
assign avalon.response = avalon_response;
{% endif %}

View File

@@ -101,7 +101,7 @@ interface axi4lite_intf_driver #(
semaphore txn_ar_mutex = new(1);
semaphore txn_r_mutex = new(1);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
bit w_before_aw;
w_before_aw = $urandom_range(1,0);
@@ -125,7 +125,7 @@ interface axi4lite_intf_driver #(
if(!w_before_aw) repeat($urandom_range(2,0)) @cb;
cb.WVALID <= '1;
cb.WDATA <= data;
cb.WSTRB <= '1; // TODO: Support byte strobes
cb.WSTRB <= strb;
@(cb);
while(cb.WREADY !== 1'b1) @(cb);
cb.WVALID <= '0;

View File

@@ -1,32 +1,7 @@
from itertools import product
from .cpuifs.apb3 import APB3, FlatAPB3
from .cpuifs.apb4 import APB4, FlatAPB4
from .cpuifs.axi4lite import AXI4Lite, FlatAXI4Lite
from .cpuifs.passthrough import Passthrough
all_cpuif = [
APB3(),
FlatAPB3(),
APB4(),
FlatAPB4(),
AXI4Lite(),
FlatAXI4Lite(),
Passthrough(),
]
def get_permutations(spec):
param_list = []
for v in product(*spec.values()):
param_list.append(dict(zip(spec, v)))
return param_list
#-------------------------------------------------------------------------------
# TODO: this wont scale well. Create groups of permutations. not necessary to permute everything all the time.
TEST_PARAMS = get_permutations({
"cpuif": all_cpuif,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
"reuse_hwif_typedefs": [True, False],
})

View File

@@ -6,7 +6,7 @@ from ..lib.cpuifs.apb4 import APB4
from ..lib.cpuifs.axi4lite import AXI4Lite
from ..lib.cpuifs.passthrough import Passthrough
TEST_PARAMS = get_permutations({
@parameterized_class(get_permutations({
"cpuif": [
APB4(),
AXI4Lite(),
@@ -15,9 +15,7 @@ TEST_PARAMS = get_permutations({
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
"retime_external": [True, False],
})
@parameterized_class(TEST_PARAMS)
}))
class Test(SimTestCase):
extra_tb_files = [
"../lib/external_reg.sv",

View File

@@ -1,9 +1,14 @@
from parameterized import parameterized_class
from ..lib.sim_testcase import SimTestCase
from ..lib.test_params import TEST_PARAMS
from ..lib.test_params import get_permutations
from ..lib.cpuifs import ALL_CPUIF
@parameterized_class(TEST_PARAMS)
@parameterized_class(get_permutations({
"cpuif": ALL_CPUIF,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
}))
class Test(SimTestCase):
def test_dut(self):
self.run_test()

View File

@@ -1,18 +1,37 @@
import os
from parameterized import parameterized_class
import pytest
from ..lib.sim_testcase import SimTestCase
from ..lib.synth_testcase import SynthTestCase
from ..lib.test_params import TEST_PARAMS, get_permutations
from ..lib.test_params import get_permutations
from ..lib.cpuifs import ALL_CPUIF
from ..lib.simulators.xilinx import Xilinx
import pytest
@parameterized_class(TEST_PARAMS)
class TestParameterizations(SimTestCase):
@parameterized_class(get_permutations({
"cpuif": ALL_CPUIF,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
}))
class TestCPUIFS(SimTestCase):
def test_dut(self):
self.run_test()
@parameterized_class(get_permutations({
"reuse_hwif_typedefs": [True, False],
}))
class TestTypedefs(SimTestCase):
def test_dut(self):
self.run_test()
@parameterized_class(get_permutations({
"default_reset_activelow": [True, False],
"default_reset_async": [True, False],
@@ -22,14 +41,26 @@ class TestDefaultResets(SimTestCase):
self.run_test()
@parameterized_class(TEST_PARAMS)
@parameterized_class(get_permutations({
"cpuif": ALL_CPUIF,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
"reuse_hwif_typedefs": [True, False],
}))
class TestSynth(SynthTestCase):
def test_dut(self):
self.run_synth()
@pytest.mark.skipif(os.environ.get("STUB_SIMULATOR", False) or os.environ.get("NO_XSIM", False), reason="user skipped")
@parameterized_class(TEST_PARAMS)
@parameterized_class(get_permutations({
"cpuif": ALL_CPUIF,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
"reuse_hwif_typedefs": [True, False],
}))
class TestVivado(SimTestCase):
"""
Vivado XSIM's implementation of clocking blocks is broken, which is heavily used