diff --git a/docs/cpuif/apb4.rst b/docs/cpuif/apb4.rst new file mode 100644 index 0000000..79d3c01 --- /dev/null +++ b/docs/cpuif/apb4.rst @@ -0,0 +1,31 @@ +AMBA 4 APB +========== + +Implements the register block using an +`AMBA 4 APB `_ +CPU interface. + +The APB4 CPU interface comes in two i/o port flavors: + +SystemVerilog Interface + Class: :class:`peakrdl_regblock.cpuif.apb4.APB4_Cpuif` + + Interface Definition: :download:`apb4_intf.sv <../../tests/lib/cpuifs/apb4/apb4_intf.sv>` + +Flattened inputs/outputs + Flattens the interface into discrete input and output ports. + + Class: :class:`peakrdl_regblock.cpuif.apb4.APB4_Cpuif_flattened` + + +.. warning:: + Some IP vendors will incorrectly implement the address signalling + assuming word-addresses. (that each increment of ``PADDR`` is the next word) + + For this exporter, values on the interface's ``PADDR`` input are interpreted + as byte-addresses. (a 32-bit APB bus increments ``PADDR`` in steps of 4) + Although APB protocol does not allow for unaligned transfers, this is in + accordance to the official AMBA bus specification. + + Be sure to double-check the interpretation of your interconnect IP. A simple + bit-shift operation can be used to correct this if necessary. diff --git a/docs/index.rst b/docs/index.rst index c89031f..dfbbef3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -95,6 +95,7 @@ Links cpuif/introduction cpuif/apb3 + cpuif/apb4 cpuif/axi4lite cpuif/passthrough cpuif/internal_protocol diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py index f8380fa..36771c3 100644 --- a/src/peakrdl_regblock/__peakrdl__.py +++ b/src/peakrdl_regblock/__peakrdl__.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING from .exporter import RegblockExporter -from .cpuif import apb3, axi4lite, passthrough +from .cpuif import apb3, apb4, axi4lite, passthrough if TYPE_CHECKING: import argparse @@ -12,6 +12,8 @@ if TYPE_CHECKING: CPUIF_DICT = { "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 diff --git a/src/peakrdl_regblock/cpuif/apb4/__init__.py b/src/peakrdl_regblock/cpuif/apb4/__init__.py new file mode 100644 index 0000000..b244dd4 --- /dev/null +++ b/src/peakrdl_regblock/cpuif/apb4/__init__.py @@ -0,0 +1,36 @@ +from ..base import CpuifBase + +class APB4_Cpuif(CpuifBase): + template_path = "apb4_tmpl.sv" + + @property + def port_declaration(self) -> str: + return "apb4_intf.slave s_apb" + + def signal(self, name:str) -> str: + return "s_apb." + name.upper() + + @property + def data_width_bytes(self) -> int: + return self.data_width // 8 + + +class APB4_Cpuif_flattened(APB4_Cpuif): + @property + def port_declaration(self) -> str: + lines = [ + "input wire " + self.signal("psel"), + "input wire " + self.signal("penable"), + "input wire " + self.signal("pwrite"), + "input wire [2:0] " + self.signal("pprot"), + f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"), + f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"), + f"input wire [{self.data_width_bytes-1}:0] " + self.signal("pstrb"), + "output logic " + self.signal("pready"), + f"output logic [{self.data_width-1}:0] " + self.signal("prdata"), + "output logic " + self.signal("pslverr"), + ] + return ",\n".join(lines) + + def signal(self, name:str) -> str: + return "s_apb_" + name diff --git a/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv b/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv new file mode 100644 index 0000000..97cf5e8 --- /dev/null +++ b/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv @@ -0,0 +1,39 @@ +// Request +logic is_active; +always_ff {{get_always_ff_event(cpuif.reset)}} begin + if({{get_resetsignal(cpuif.reset)}}) begin + is_active <= '0; + cpuif_req <= '0; + cpuif_req_is_wr <= '0; + cpuif_addr <= '0; + cpuif_wr_data <= '0; + cpuif_wr_biten <= '0; + end else begin + if(~is_active) begin + if({{cpuif.signal("psel")}}) begin + is_active <= '1; + cpuif_req <= '1; + cpuif_req_is_wr <= {{cpuif.signal("pwrite")}}; + {%- if cpuif.data_width == 8 %} + cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0]; + {%- else %} + cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width//8)}}], {{clog2(cpuif.data_width//8)}}'b0}; + {%- endif %} + cpuif_wr_data <= {{cpuif.signal("pwdata")}}; + for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin + cpuif_wr_biten[i*8 +: 8] <= {8{ {{-cpuif.signal("pstrb")}}[i]}}; + end + end + end else begin + cpuif_req <= '0; + if(cpuif_rd_ack || cpuif_wr_ack) begin + is_active <= '0; + end + end + end +end + +// Response +assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack; +assign {{cpuif.signal("prdata")}} = cpuif_rd_data; +assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err; diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py index de104e1..12489fd 100644 --- a/src/peakrdl_regblock/exporter.py +++ b/src/peakrdl_regblock/exporter.py @@ -11,7 +11,7 @@ from .readback import Readback from .identifier_filter import kw_filter as kwf from .cpuif import CpuifBase -from .cpuif.apb3 import APB3_Cpuif +from .cpuif.apb4 import APB4_Cpuif from .hwif import Hwif from .utils import get_always_ff_event from .scan_design import DesignScanner @@ -57,7 +57,7 @@ class RegblockExporter: Output includes two files: a module definition and package definition. cpuif_cls: :class:`peakrdl_regblock.cpuif.CpuifBase` Specify the class type that implements the CPU interface of your choice. - Defaults to AMBA APB3. + Defaults to AMBA APB4. module_name: str Override the SystemVerilog module name. By default, the module name is the top-level node's name. @@ -100,7 +100,7 @@ class RegblockExporter: self.top_node = node - cpuif_cls = kwargs.pop("cpuif_cls", None) or APB3_Cpuif # type: Type[CpuifBase] + cpuif_cls = kwargs.pop("cpuif_cls", None) or APB4_Cpuif # type: Type[CpuifBase] module_name = kwargs.pop("module_name", None) or kwf(self.top_node.inst_name) # type: str package_name = kwargs.pop("package_name", None) or (module_name + "_pkg") # type: str reuse_hwif_typedefs = kwargs.pop("reuse_hwif_typedefs", True) # type: bool diff --git a/tests/lib/base_testcase.py b/tests/lib/base_testcase.py index bead1ee..c4c77ef 100644 --- a/tests/lib/base_testcase.py +++ b/tests/lib/base_testcase.py @@ -11,7 +11,7 @@ from systemrdl import RDLCompiler from peakrdl_regblock import RegblockExporter from .cpuifs.base import CpuifTestMode -from .cpuifs.apb3 import APB3 +from .cpuifs.apb4 import APB4 class BaseTestCase(unittest.TestCase): @@ -28,7 +28,7 @@ class BaseTestCase(unittest.TestCase): rdl_elab_params = {} #: Define what CPUIF to use for this testcase - cpuif = APB3() # type: CpuifTestMode + cpuif = APB4() # type: CpuifTestMode # Other exporter args: retime_read_fanin = False diff --git a/tests/lib/cpuifs/apb4/__init__.py b/tests/lib/cpuifs/apb4/__init__.py new file mode 100644 index 0000000..6ee31c9 --- /dev/null +++ b/tests/lib/cpuifs/apb4/__init__.py @@ -0,0 +1,18 @@ +from ..base import CpuifTestMode + +from peakrdl_regblock.cpuif.apb4 import APB4_Cpuif, APB4_Cpuif_flattened + +class APB4(CpuifTestMode): + cpuif_cls = APB4_Cpuif + rtl_files = [ + "apb4_intf.sv", + ] + tb_files = [ + "apb4_intf.sv", + "apb4_intf_driver.sv", + ] + tb_template = "tb_inst.sv" + +class FlatAPB4(APB4): + cpuif_cls = APB4_Cpuif_flattened + rtl_files = [] diff --git a/tests/lib/cpuifs/apb4/apb4_intf.sv b/tests/lib/cpuifs/apb4/apb4_intf.sv new file mode 100644 index 0000000..4a554f8 --- /dev/null +++ b/tests/lib/cpuifs/apb4/apb4_intf.sv @@ -0,0 +1,46 @@ +interface apb4_intf #( + parameter DATA_WIDTH = 32, + parameter ADDR_WIDTH = 32 +); + // Command + logic PSEL; + logic PENABLE; + logic PWRITE; + logic [2:0] PPROT; + logic [ADDR_WIDTH-1:0] PADDR; + logic [DATA_WIDTH-1:0] PWDATA; + logic [DATA_WIDTH/8-1:0] PSTRB; + + // Response + logic [DATA_WIDTH-1:0] PRDATA; + logic PREADY; + logic PSLVERR; + + modport master ( + output PSEL, + output PENABLE, + output PWRITE, + output PPROT, + output PADDR, + output PWDATA, + output PSTRB, + + input PRDATA, + input PREADY, + input PSLVERR + ); + + modport slave ( + input PSEL, + input PENABLE, + input PWRITE, + input PPROT, + input PADDR, + input PWDATA, + input PSTRB, + + output PRDATA, + output PREADY, + output PSLVERR + ); +endinterface diff --git a/tests/lib/cpuifs/apb4/apb4_intf_driver.sv b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv new file mode 100644 index 0000000..d5ebe00 --- /dev/null +++ b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv @@ -0,0 +1,128 @@ +interface apb4_intf_driver #( + parameter DATA_WIDTH = 32, + parameter ADDR_WIDTH = 32 + )( + input wire clk, + input wire rst, + apb4_intf.master m_apb + ); + + timeunit 1ps; + timeprecision 1ps; + + logic PSEL; + logic PENABLE; + logic PWRITE; + logic [2:0] PPROT; + logic [ADDR_WIDTH-1:0] PADDR; + logic [DATA_WIDTH-1:0] PWDATA; + logic [DATA_WIDTH/8-1:0] PSTRB; + logic [DATA_WIDTH-1:0] PRDATA; + logic PREADY; + logic PSLVERR; + + assign m_apb.PSEL = PSEL; + assign m_apb.PENABLE = PENABLE; + assign m_apb.PWRITE = PWRITE; + assign m_apb.PPROT = PPROT; + assign m_apb.PADDR = PADDR; + assign m_apb.PWDATA = PWDATA; + assign m_apb.PSTRB = PSTRB; + assign PRDATA = m_apb.PRDATA; + assign PREADY = m_apb.PREADY; + assign PSLVERR = m_apb.PSLVERR; + + default clocking cb @(posedge clk); + default input #1step output #1; + output PSEL; + output PENABLE; + output PWRITE; + output PPROT; + output PADDR; + output PWDATA; + output PSTRB; + input PRDATA; + input PREADY; + input PSLVERR; + endclocking + + task automatic reset(); + cb.PSEL <= '0; + cb.PENABLE <= '0; + cb.PWRITE <= '0; + cb.PPROT <= '0; + cb.PADDR <= '0; + cb.PWDATA <= '0; + cb.PSTRB <= '0; + endtask + + semaphore txn_mutex = new(1); + + task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data); + txn_mutex.get(); + ##0; + + // Initiate transfer + cb.PSEL <= '1; + cb.PENABLE <= '0; + cb.PWRITE <= '1; + cb.PPROT <= '0; + cb.PADDR <= addr; + cb.PWDATA <= data; + cb.PSTRB <= '1; + @(cb); + + // active phase + cb.PENABLE <= '1; + @(cb); + + // Wait for response + while(cb.PREADY !== 1'b1) @(cb); + reset(); + txn_mutex.put(); + endtask + + task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data); + txn_mutex.get(); + ##0; + + // Initiate transfer + cb.PSEL <= '1; + cb.PENABLE <= '0; + cb.PWRITE <= '0; + cb.PPROT <= '0; + cb.PADDR <= addr; + cb.PWDATA <= '0; + cb.PSTRB <= '0; + @(cb); + + // active phase + cb.PENABLE <= '1; + @(cb); + + // Wait for response + while(cb.PREADY !== 1'b1) @(cb); + assert(!$isunknown(cb.PRDATA)) else $error("Read from 0x%0x returned X's on PRDATA", addr); + assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr); + data = cb.PRDATA; + reset(); + txn_mutex.put(); + 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.PREADY)) else $error("Saw X on PREADY!"); + end + +endinterface diff --git a/tests/lib/cpuifs/apb4/tb_inst.sv b/tests/lib/cpuifs/apb4/tb_inst.sv new file mode 100644 index 0000000..d769854 --- /dev/null +++ b/tests/lib/cpuifs/apb4/tb_inst.sv @@ -0,0 +1,36 @@ +{% sv_line_anchor %} +apb4_intf #( + .DATA_WIDTH({{exporter.cpuif.data_width}}), + .ADDR_WIDTH({{exporter.cpuif.addr_width}}) +) s_apb(); +apb4_intf_driver #( + .DATA_WIDTH({{exporter.cpuif.data_width}}), + .ADDR_WIDTH({{exporter.cpuif.addr_width}}) +) cpuif ( + .clk(clk), + .rst(rst), + .m_apb(s_apb) +); +{% if type(cpuif).__name__.startswith("Flat") %} +{% sv_line_anchor %} +wire s_apb_psel; +wire s_apb_penable; +wire s_apb_pwrite; +wire [2:0] s_apb_pprot; +wire [{{exporter.cpuif.addr_width - 1}}:0] s_apb_paddr; +wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_pwdata; +wire [{{exporter.cpuif.data_width_bytes - 1}}:0] s_apb_pstrb; +wire s_apb_pready; +wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_prdata; +wire s_apb_pslverr; +assign s_apb_psel = s_apb.PSEL; +assign s_apb_penable = s_apb.PENABLE; +assign s_apb_pwrite = s_apb.PWRITE; +assign s_apb_pprot = s_apb.PPROT; +assign s_apb_paddr = s_apb.PADDR; +assign s_apb_pwdata = s_apb.PWDATA; +assign s_apb_pstrb = s_apb.PSTRB; +assign s_apb.PREADY = s_apb_pready; +assign s_apb.PRDATA = s_apb_prdata; +assign s_apb.PSLVERR = s_apb_pslverr; +{% endif %} diff --git a/tests/lib/test_params.py b/tests/lib/test_params.py index 14ffd68..8e2318b 100644 --- a/tests/lib/test_params.py +++ b/tests/lib/test_params.py @@ -1,6 +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 @@ -8,6 +9,8 @@ from .cpuifs.passthrough import Passthrough all_cpuif = [ APB3(), FlatAPB3(), + APB4(), + FlatAPB4(), AXI4Lite(), FlatAXI4Lite(), Passthrough(),