diff --git a/docs/cpuif/avalon.rst b/docs/cpuif/avalon.rst new file mode 100644 index 0000000..547c508 --- /dev/null +++ b/docs/cpuif/avalon.rst @@ -0,0 +1,32 @@ +Intel Avalon +============ + +Implements the register block using an +`Intel Avalon MM `_ +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 `_ +* 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. diff --git a/docs/index.rst b/docs/index.rst index a068d90..be9e00e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -112,6 +112,7 @@ Links cpuif/introduction cpuif/apb cpuif/axi4lite + cpuif/avalon cpuif/passthrough cpuif/internal_protocol cpuif/customizing diff --git a/hdl-src/avalon_mm_intf.sv b/hdl-src/avalon_mm_intf.sv new file mode 100644 index 0000000..1d3d0c3 --- /dev/null +++ b/hdl-src/avalon_mm_intf.sv @@ -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 diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py index b08131a..c7de7a5 100644 --- a/src/peakrdl_regblock/__peakrdl__.py +++ b/src/peakrdl_regblock/__peakrdl__.py @@ -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 diff --git a/src/peakrdl_regblock/cpuif/avalon/__init__.py b/src/peakrdl_regblock/cpuif/avalon/__init__.py new file mode 100644 index 0000000..aaed77d --- /dev/null +++ b/src/peakrdl_regblock/cpuif/avalon/__init__.py @@ -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 diff --git a/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv b/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv new file mode 100644 index 0000000..be7e1d0 --- /dev/null +++ b/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv @@ -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 diff --git a/tests/lib/cpuifs/__init__.py b/tests/lib/cpuifs/__init__.py index e69de29..6ea672d 100644 --- a/tests/lib/cpuifs/__init__.py +++ b/tests/lib/cpuifs/__init__.py @@ -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(), +] diff --git a/tests/lib/cpuifs/apb4/apb4_intf_driver.sv b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv index d5ebe00..cf5258f 100644 --- a/tests/lib/cpuifs/apb4/apb4_intf_driver.sv +++ b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv @@ -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 diff --git a/tests/lib/cpuifs/avalon/__init__.py b/tests/lib/cpuifs/avalon/__init__.py new file mode 100644 index 0000000..79672ab --- /dev/null +++ b/tests/lib/cpuifs/avalon/__init__.py @@ -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 = [] diff --git a/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv b/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv new file mode 100644 index 0000000..2c5480a --- /dev/null +++ b/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv @@ -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 diff --git a/tests/lib/cpuifs/avalon/tb_inst.sv b/tests/lib/cpuifs/avalon/tb_inst.sv new file mode 100644 index 0000000..e5b2303 --- /dev/null +++ b/tests/lib/cpuifs/avalon/tb_inst.sv @@ -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 %} diff --git a/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv b/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv index a05f4f5..b55aada 100644 --- a/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv +++ b/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv @@ -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; diff --git a/tests/lib/test_params.py b/tests/lib/test_params.py index 8e2318b..3615561 100644 --- a/tests/lib/test_params.py +++ b/tests/lib/test_params.py @@ -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], -}) diff --git a/tests/test_external/testcase.py b/tests/test_external/testcase.py index e5b3969..623ad67 100644 --- a/tests/test_external/testcase.py +++ b/tests/test_external/testcase.py @@ -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", diff --git a/tests/test_pipelined_cpuif/testcase.py b/tests/test_pipelined_cpuif/testcase.py index 422861b..27fe9ae 100644 --- a/tests/test_pipelined_cpuif/testcase.py +++ b/tests/test_pipelined_cpuif/testcase.py @@ -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() diff --git a/tests/test_structural_sw_rw/testcase.py b/tests/test_structural_sw_rw/testcase.py index d6599a6..9c947c4 100644 --- a/tests/test_structural_sw_rw/testcase.py +++ b/tests/test_structural_sw_rw/testcase.py @@ -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