diff --git a/Bender.yml b/Bender.yml index 7e6fc43..0a35497 100644 --- a/Bender.yml +++ b/Bender.yml @@ -20,6 +20,7 @@ sources: - src/apb_to_reg.sv - src/axi_lite_to_reg.sv - src/axi_to_reg_v2.sv + - src/obi_to_reg.sv - src/periph_to_reg.sv - src/reg_cdc.sv - src/reg_cut.sv diff --git a/src/obi_to_reg.sv b/src/obi_to_reg.sv new file mode 100644 index 0000000..e428b37 --- /dev/null +++ b/src/obi_to_reg.sv @@ -0,0 +1,69 @@ +// Copyright 2025 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +/** + * Translates OBI to register_interface, + * ignores the r_optional and a_optional fields + */ +module obi_to_reg #( + parameter int unsigned DATA_WIDTH = 32, + parameter int unsigned ID_WIDTH = 0, + + parameter type obi_req_t = logic, + parameter type obi_rsp_t = logic, + + parameter type reg_req_t = logic, + parameter type reg_rsp_t = logic +) ( + input logic clk_i, + input logic rst_ni, + + input obi_req_t obi_req_i, + output obi_rsp_t obi_rsp_o, + + output reg_req_t reg_req_o, + input reg_rsp_t reg_rsp_i +); + logic req_q, req_d; + logic [ID_WIDTH-1:0] aid_q, aid_d; + logic [DATA_WIDTH-1:0] rdata_q, rdata_d; + logic error_q, error_d; + + assign req_d = obi_req_i.req; + assign aid_d = obi_req_i.a.aid; + assign rdata_d = reg_rsp_i.rdata; + assign error_d = reg_rsp_i.error; + + always_ff @(posedge clk_i or negedge rst_ni) begin : proc_seq + if (!rst_ni) begin + req_q <= '0; + rdata_q <= '0; + aid_q <= '0; + error_q <= '0; + end else begin + req_q <= req_d; + rdata_q <= rdata_d; + aid_q <= aid_d; + error_q <= error_d; + end + end + + assign reg_req_o.valid = obi_req_i.req; + assign reg_req_o.addr = obi_req_i.a.addr; + assign reg_req_o.write = obi_req_i.a.we; + assign reg_req_o.wdata = obi_req_i.a.wdata; + assign reg_req_o.wstrb = obi_req_i.a.be; + + assign obi_rsp_o.gnt = obi_req_i.req & reg_rsp_i.ready; + assign obi_rsp_o.rvalid = req_q; + assign obi_rsp_o.r.rdata = rdata_q; + assign obi_rsp_o.r.rid = aid_q; + assign obi_rsp_o.r.err = error_q; +endmodule diff --git a/src_files.yml b/src_files.yml index 4a4cada..c30216d 100644 --- a/src_files.yml +++ b/src_files.yml @@ -14,6 +14,7 @@ register_interface: - src/apb_to_reg.sv - src/axi_lite_to_reg.sv - src/axi_to_reg_v2.sv + - src/obi_to_reg.sv - src/periph_to_reg.sv - src/reg_cdc.sv - src/reg_cut.sv diff --git a/vendor/lowrisc_opentitan/util/reggen/bits.py b/vendor/lowrisc_opentitan/util/reggen/bits.py index c8d48f7..8d2ec75 100644 --- a/vendor/lowrisc_opentitan/util/reggen/bits.py +++ b/vendor/lowrisc_opentitan/util/reggen/bits.py @@ -19,6 +19,18 @@ def __init__(self, msb: int, lsb: int): def bitmask(self) -> int: return (1 << (self.msb + 1)) - (1 << self.lsb) + def bytemask(self) -> int: + bytemask = 0 + + bitmask = self.bitmask() + i = 0 + while bitmask >= 2**(i*8): + if bitmask & (0b1111_1111 << i*8): + bytemask |= 1 << i + i += 1 + + return bytemask + def width(self) -> int: return 1 + self.msb - self.lsb diff --git a/vendor/lowrisc_opentitan/util/reggen/reg_block.py b/vendor/lowrisc_opentitan/util/reggen/reg_block.py index 30a4f74..15a00c0 100644 --- a/vendor/lowrisc_opentitan/util/reggen/reg_block.py +++ b/vendor/lowrisc_opentitan/util/reggen/reg_block.py @@ -119,13 +119,14 @@ def add_raw(self, where: str, raw: object) -> None: 'reserved': self._handle_reserved, 'skipto': self._handle_skipto, 'window': self._handle_window, - 'multireg': self._handle_multireg + 'multireg': self._handle_multireg, + 'packed': self._handle_packed } entry_type = 'register' entry_body = entry # type: object - for t in ['reserved', 'skipto', 'window', 'multireg']: + for t in ['reserved', 'skipto', 'window', 'multireg', 'packed']: t_body = entry.get(t) if t_body is not None: # Special entries look like { window: { ... } }, so if we @@ -207,6 +208,25 @@ def _handle_multireg(self, where: str, body: object) -> None: self.entries.append(mr) self.offset = mr.next_offset(self._addrsep) + def _handle_packed(self, where: str, body: object) -> None: + reg_bodies = check_list(body, 'packed') + local_offset = 0 + for i, body in enumerate(reg_bodies): + reg = Register.from_raw(self._reg_width, self.offset, self._params, body) + if i < len(reg_bodies) - 1: + reg.doesnt_increment_offset = True + + byte_width = (reg.get_width() + 7) // 8 * 8 + + for field in reg.fields: + field.bits = field.bits.make_translated(local_offset) + + local_offset += byte_width + self.add_register(reg) + + if local_offset > self._reg_width: + raise ValueError(f'Packed register is larger than regwidth ({local_offset} > {self._reg_width})') + def add_register(self, reg: Register) -> None: assert reg.offset == self.offset diff --git a/vendor/lowrisc_opentitan/util/reggen/reg_pkg.sv.tpl b/vendor/lowrisc_opentitan/util/reggen/reg_pkg.sv.tpl index 1c5520a..1d1672f 100644 --- a/vendor/lowrisc_opentitan/util/reggen/reg_pkg.sv.tpl +++ b/vendor/lowrisc_opentitan/util/reggen/reg_pkg.sv.tpl @@ -290,24 +290,29 @@ value = "{}'h {:x}".format(aw, r.offset) % endfor } ${lpfx}_id_e; - // Register width information to check illegal writes${for_iface} - parameter logic [3:0] ${upfx}_PERMIT [${len(rb.flat_regs)}] = '{ + // Register bytemaks used to see if a register is to be written to ${for_iface} + parameter logic [3:0] ${upfx}_BYTEMASK [${len(rb.flat_regs)}] = '{ % for i, r in enumerate(rb.flat_regs): <% index_str = "{}".format(i).rjust(idx_len) - width = r.get_width() - if width > 24: - mask = '1111' - elif width > 16: - mask = '0111' - elif width > 8: - mask = '0011' - else: - mask = '0001' + mask = "4'b {:04b}".format(r.bytemask()) comma = ',' if i < len(rb.flat_regs) - 1 else ' ' %>\ - 4'b ${mask}${comma} // index[${index_str}] ${ublock}_${r.name.upper()} + ${mask}${comma} // index[${index_str}] ${ublock}_${r.name.upper()} + % endfor + }; + + // Register boudary crossing infromation to make sure we don't write to half of a field${for_iface} + parameter logic [2:0] ${upfx}_DISALLOWED_BOUNDARY_CROSSINGS [${len(rb.flat_regs)}] = '{ + % for i, r in enumerate(rb.flat_regs): +<% + index_str = "{}".format(i).rjust(idx_len) + mask = "3'b " + "{:03b}".format(r.crossed_byte_boundaries())[-3:] + + comma = ',' if i < len(rb.flat_regs) - 1 else ' ' +%>\ + ${mask}${comma} // index[${index_str}] ${ublock}_${r.name.upper()} % endfor }; % endif diff --git a/vendor/lowrisc_opentitan/util/reggen/reg_top.sv.tpl b/vendor/lowrisc_opentitan/util/reggen/reg_top.sv.tpl index bfab87f..42d93a8 100644 --- a/vendor/lowrisc_opentitan/util/reggen/reg_top.sv.tpl +++ b/vendor/lowrisc_opentitan/util/reggen/reg_top.sv.tpl @@ -438,7 +438,7 @@ ${finst_gen(f, finst_name, fsig_name, r.hwext, r.regwen, r.shadowed)} always_comb begin addr_hit = '0; % for i,r in enumerate(regs_flat): - addr_hit[${"{}".format(i).rjust(max_regs_char)}] = (reg_addr == ${ublock}_${r.name.upper()}_OFFSET); + addr_hit[${"{}".format(i).rjust(max_regs_char)}] = reg_addr == ${ublock}_${r.name.upper()}_OFFSET; % endfor end @@ -446,11 +446,13 @@ ${finst_gen(f, finst_name, fsig_name, r.hwext, r.regwen, r.shadowed)} % if regs_flat: <% - # We want to signal wr_err if reg_be (the byte enable signal) is true for - # any bytes that aren't supported by a register. That's true if a - # addr_hit[i] and a bit is set in reg_be but not in *_PERMIT[i]. + # We want to signal wr_err if reg_be wants to write to only part of a field. + # This is the case if the byte enable has a border (calculated by `reg_be ^ (reg_be >> 1)`) + # at a bit where a field is crossing (DISALLOWED_BOUNDARY_CROSSINGS) + # e.g. reg_be `0b0100` has a border at position 2 and 3 (-> `0b110`) and is not allowed to write + # to a field with bytemask `0b1100`, which disallows a border at bit position 3 (`0b100`) - wr_err_terms = ['(addr_hit[{idx}] & (|({mod}_PERMIT[{idx}] & ~reg_be)))' + wr_err_terms = ['(addr_hit[{idx}] & (|((reg_be ^ (reg_be >> 1)) & {mod}_DISALLOWED_BOUNDARY_CROSSINGS[{idx}])))' .format(idx=str(i).rjust(max_regs_char), mod=u_mod_base) for i in range(len(regs_flat))] @@ -478,26 +480,21 @@ ${we_gen(f, r.name.lower() + "_" + f.name.lower(), r.hwext, r.shadowed, i)}\ // Read data return always_comb begin reg_rdata_next = '0; - unique case (1'b1) - % for i, r in enumerate(regs_flat): - % if len(r.fields) == 1: - addr_hit[${i}]: begin + % for i, r in enumerate(regs_flat): + % if len(r.fields) == 1: + if (addr_hit[${i}]) begin ${rdata_gen(r.fields[0], r.name.lower())}\ - end + end - % else: - addr_hit[${i}]: begin - % for f in r.fields: + % else: + if (addr_hit[${i}]) begin + % for f in r.fields: ${rdata_gen(f, r.name.lower() + "_" + f.name.lower())}\ - % endfor - end + % endfor + end - % endif - % endfor - default: begin - reg_rdata_next = '1; - end - endcase + % endif + % endfor end % endif @@ -528,8 +525,6 @@ ${rdata_gen(f, r.name.lower() + "_" + f.name.lower())}\ // property by mistake //`ASSUME(reqParity, tl_reg_h2d.a_valid |-> tl_reg_h2d.a_user.chk_en == tlul_pkg::CheckDis) % endif - `ASSERT(en2addrHit, (reg_we || reg_re) |-> $onehot0(addr_hit)) - % endif endmodule @@ -770,11 +765,12 @@ ${bits.msb}\ needs_we = field.swaccess.allows_write() needs_re = (field.swaccess.allows_read() and hwext) or shadowed space = '\n' if needs_we or needs_re else '' + bytemask = "4'b {:04b}".format(field.bits.bytemask()) %>\ ${space}\ % if needs_we: % if field.swaccess.swrd() != SwRdAccess.RC: - assign ${sig_name}_we = addr_hit[${idx}] & reg_we & !reg_error; + assign ${sig_name}_we = addr_hit[${idx}] & reg_we & !reg_error & (|(${bytemask} & reg_be)); assign ${sig_name}_wd = reg_wdata[${str_bits_sv(field.bits)}]; % else: ## Generate WE based on read request, read should clear diff --git a/vendor/lowrisc_opentitan/util/reggen/register.py b/vendor/lowrisc_opentitan/util/reggen/register.py index 24f73d0..3ce8238 100644 --- a/vendor/lowrisc_opentitan/util/reggen/register.py +++ b/vendor/lowrisc_opentitan/util/reggen/register.py @@ -91,7 +91,8 @@ def __init__(self, shadowed: bool, fields: List[Field], update_err_alert: Optional[str], - storage_err_alert: Optional[str]): + storage_err_alert: Optional[str], + doesnt_increment_offset: bool): super().__init__(offset) self.name = name self.desc = desc @@ -173,6 +174,7 @@ def __init__(self, self.update_err_alert = update_err_alert self.storage_err_alert = storage_err_alert + self.doesnt_increment_offset = doesnt_increment_offset @staticmethod def from_raw(reg_width: int, @@ -261,9 +263,12 @@ def from_raw(reg_width: int, return Register(offset, name, desc, swaccess, hwaccess, hwext, hwqe, hwre, regwen, tags, resval, shadowed, fields, - update_err_alert, storage_err_alert) + update_err_alert, storage_err_alert, False) def next_offset(self, addrsep: int) -> int: + if self.doesnt_increment_offset: + return self.offset + return self.offset + addrsep def sw_readable(self) -> bool: @@ -285,6 +290,30 @@ def get_field_list(self) -> List[Field]: def is_homogeneous(self) -> bool: return len(self.fields) == 1 + def crossed_byte_boundaries(self) -> int: + ''' + Returns the byte boundaries that are crossed by a field contained in this register. + e.g. `fields: [ { bits: "23:16" }, { bits: "15:0" } ]` + crosses the third and first byte boundary, so it returns 0b101. + This is used to determine whether a write using bytestrobe is valid. + ''' + boundaries = 0 + + for field in self.fields: + bytemask = field.bits.bytemask() + boundaries |= bytemask & (bytemask >> 1) + + return boundaries + + def bytemask(self) -> int: + bytemask = 0 + + for field in self.fields: + bytemask |= field.bits.bytemask() + + return bytemask + + def get_width(self) -> int: '''Get the width of the fields in the register in bits @@ -350,7 +379,7 @@ def make_multi(self, self.swaccess, self.hwaccess, self.hwext, self.hwqe, self.hwre, new_regwen, self.tags, new_resval, self.shadowed, new_fields, - self.update_err_alert, self.storage_err_alert) + self.update_err_alert, self.storage_err_alert, False) def _asdict(self) -> Dict[str, object]: rd = { @@ -364,6 +393,7 @@ def _asdict(self) -> Dict[str, object]: 'hwre': str(self.hwre), 'tags': self.tags, 'shadowed': str(self.shadowed), + 'doesnt_increment_offset': str(self.doesnt_increment_offset), } if self.regwen is not None: rd['regwen'] = self.regwen