Start of public OpenTitan development history
Code contributors:
Alex Bradbury <asb@lowrisc.org>
Cindy Chen <chencindy@google.com>
Eunchan Kim <eunchan@google.com>
Gaurang Chitroda <gaurangg@google.com>
Mark Hayter <mark.hayter@gmail.com>
Michael Schaffner <msf@google.com>
Miguel Osorio <miguelosorio@google.com>
Nils Graf <nilsg@google.com>
Philipp Wagner <phw@lowrisc.org>
Pirmin Vogel <vogelpi@lowrisc.org>
Ram Babu Penugonda <rampenugonda@google.com>
Scott Johnson <scottdj@google.com>
Shail Kushwah <kushwahs@google.com>
Srikrishna Iyer <sriyer@google.com>
Steve Nelson <Steve.Nelson@wdc.com>
Tao Liu <taliu@google.com>
Timothy Chen <timothytim@google.com>
Tobias Wölfel <tobias.woelfel@mailbox.org>
Weicai Yang <weicai@google.com>
diff --git a/util/reggen/reg_top.tpl.sv b/util/reggen/reg_top.tpl.sv
new file mode 100644
index 0000000..d47c927
--- /dev/null
+++ b/util/reggen/reg_top.tpl.sv
@@ -0,0 +1,552 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Register Top module auto-generated by `reggen`
+<%
+ num_wins = len(block.wins)
+ num_wins_width = ((num_wins+1).bit_length()) - 1
+ num_dsp = num_wins + 1
+%>
+
+module ${block.name}_reg_top (
+ input clk_i,
+ input rst_ni,
+
+ // Below Regster interface can be changed
+ input tlul_pkg::tl_h2d_t tl_i,
+ output tlul_pkg::tl_d2h_t tl_o,
+% if num_wins != 0:
+
+ // Output port for window
+ output tlul_pkg::tl_h2d_t tl_win_o [${num_wins}],
+ input tlul_pkg::tl_d2h_t tl_win_i [${num_wins}],
+
+% endif
+ // To HW
+ output ${block.name}_reg_pkg::${block.name}_reg2hw_t reg2hw, // Write
+ input ${block.name}_reg_pkg::${block.name}_hw2reg_t hw2reg // Read
+);
+
+ import ${block.name}_reg_pkg::* ;
+
+ localparam AW = ${block.addr_width};
+ localparam IW = $bits(tl_i.a_source);
+ localparam DW = ${block.width};
+ localparam DBW = DW/8; // Byte Width
+ localparam logic [$clog2($clog2(DBW)+1)-1:0] FSZ = $clog2(DBW); // Full Size 2^(FSZ) = DBW;
+
+ // register signals
+ logic reg_we;
+ logic reg_re;
+ logic [AW-1:0] reg_addr;
+ logic [DW-1:0] reg_wdata;
+ logic reg_valid;
+ logic [DW-1:0] reg_rdata;
+ logic tl_malformed, tl_addrmiss;
+
+ // Bus signals
+ tlul_pkg::tl_d_op_e rsp_opcode; // AccessAck or AccessAckData
+ logic reqready;
+ logic [IW-1:0] reqid;
+ logic [IW-1:0] rspid;
+
+ logic outstanding;
+
+ tlul_pkg::tl_h2d_t tl_reg_h2d;
+ tlul_pkg::tl_d2h_t tl_reg_d2h;
+
+% if num_wins == 0:
+ assign tl_reg_h2d = tl_i;
+ assign tl_o = tl_reg_d2h;
+% else:
+ tlul_pkg::tl_h2d_t tl_socket_h2d [${num_dsp}];
+ tlul_pkg::tl_d2h_t tl_socket_d2h [${num_dsp}];
+
+ logic [${num_wins_width}:0] reg_steer;
+
+ // socket_1n connection
+ assign tl_reg_h2d = tl_socket_h2d[${num_wins}];
+ assign tl_socket_d2h[${num_wins}] = tl_reg_d2h;
+
+ % for i,t in enumerate(block.wins):
+ assign tl_win_o[${i}] = tl_socket_h2d[${i}];
+ assign tl_socket_d2h[${i}] = tl_win_i[${i}];
+ % endfor
+
+ // Create Socket_1n
+ tlul_socket_1n #(
+ .N (${num_dsp}),
+ .HReqPass (1'b1),
+ .HRspPass (1'b1),
+ .DReqPass ({${num_dsp}{1'b1}}),
+ .DRspPass ({${num_dsp}{1'b1}}),
+ .HReqDepth (4'h1),
+ .HRspDepth (4'h1),
+ .DReqDepth ({${num_dsp}{4'h1}}),
+ .DRspDepth ({${num_dsp}{4'h1}})
+ ) u_socket (
+ .clk_i,
+ .rst_ni,
+ .tl_h_i (tl_i),
+ .tl_h_o (tl_o),
+ .tl_d_o (tl_socket_h2d),
+ .tl_d_i (tl_socket_d2h),
+ .dev_select (reg_steer)
+ );
+
+ // Create steering logic
+ always_comb begin
+ reg_steer = ${num_dsp-1}; // Default set to register
+
+ // TODO: Can below codes be unique case () inside ?
+ % for i,w in enumerate(block.wins):
+ % if w.limit_addr == 2**block.addr_width:
+ if (tl_i.a_address[AW-1:0] >= ${w.base_addr}) begin
+ // Exceed or meet the address range. Removed the comparison of limit addr ${"'h %x" % w.limit_addr}
+ % else:
+ if (tl_i.a_address[AW-1:0] >= ${w.base_addr} && tl_i.a_address[AW-1:0] < ${w.limit_addr}) begin
+ % endif
+ reg_steer = ${i};
+ end
+ % endfor
+ end
+% endif
+
+ // TODO(eunchan): Fix it after bus interface is finalized
+ assign reg_we = tl_reg_h2d.a_valid && tl_reg_d2h.a_ready &&
+ ((tl_reg_h2d.a_opcode == tlul_pkg::PutFullData) ||
+ (tl_reg_h2d.a_opcode == tlul_pkg::PutPartialData));
+ assign reg_re = tl_reg_h2d.a_valid && tl_reg_d2h.a_ready &&
+ (tl_reg_h2d.a_opcode == tlul_pkg::Get);
+ assign reg_addr = tl_reg_h2d.a_address[AW-1:0];
+ assign reg_wdata = tl_reg_h2d.a_data;
+
+ assign tl_reg_d2h.d_valid = reg_valid;
+ assign tl_reg_d2h.d_opcode = rsp_opcode;
+ assign tl_reg_d2h.d_param = '0;
+ assign tl_reg_d2h.d_size = FSZ; // always Full Size
+ assign tl_reg_d2h.d_source = rspid;
+ assign tl_reg_d2h.d_sink = '0; // Used in TL-C
+ assign tl_reg_d2h.d_data = reg_rdata;
+ assign tl_reg_d2h.d_user = '0; // Doesn't allow additional features yet
+ assign tl_reg_d2h.d_error = tl_malformed | tl_addrmiss;
+
+ assign tl_reg_d2h.a_ready = reqready;
+
+ assign reqid = tl_reg_h2d.a_source;
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ tl_malformed <= 1'b1;
+ end else if (tl_reg_h2d.a_valid && tl_reg_d2h.a_ready) begin
+ if ((tl_reg_h2d.a_opcode != tlul_pkg::Get) &&
+ (tl_reg_h2d.a_opcode != tlul_pkg::PutFullData) &&
+ (tl_reg_h2d.a_opcode != tlul_pkg::PutPartialData)) begin
+ tl_malformed <= 1'b1;
+ // Only allow Full Write with full mask
+ end else if (tl_reg_h2d.a_size != FSZ || tl_reg_h2d.a_mask != {DBW{1'b1}}) begin
+ tl_malformed <= 1'b1;
+ end else if (tl_reg_h2d.a_user.parity_en == 1'b1) begin
+ tl_malformed <= 1'b1;
+ end else begin
+ tl_malformed <= 1'b0;
+ end
+ end
+ end
+ // TODO(eunchan): Revise Register Interface logic after REG INTF finalized
+ // TODO(eunchan): Make concrete scenario
+ // 1. Write: No response, so that it can guarantee a request completes a clock after we
+ // It means, bus_reg_ready doesn't have to be lowered.
+ // 2. Read: response. So bus_reg_ready should assert after reg_bus_valid & reg_bus_ready
+ // _____ _____
+ // a_valid _____/ \_______/ \______
+ // ___________ _____
+ // a_ready \_______/ \______ <- ERR though no logic malfunction
+ // _____________
+ // d_valid ___________/ \______
+ // _____
+ // d_ready ___________________/ \______
+ //
+ // Above example is fine but if r.b.r doesn't assert within two cycle, then it can be wrong.
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ // Not to accept new request when a request is handling
+ // #Outstanding := 1
+ if (!rst_ni) begin
+ reqready <= 1'b0;
+ end else if (reg_we || reg_re) begin
+ reqready <= 1'b0;
+ end else if (outstanding == 1'b0) begin
+ reqready <= 1'b1;
+ end
+ end
+
+ // Request/ Response ID
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ rspid <= '0;
+ end else if (reg_we || reg_re) begin
+ rspid <= reqid;
+ end
+ end
+
+ // Define SW related signals
+ // Format: <reg>_<field>_{wd|we|qs}
+ // or <reg>_{wd|we|qs} if field == 1 or 0
+ % for r in block.regs:
+ % if len(r.fields) == 1:
+<%
+ msb = r.fields[0].msb
+ lsb = r.fields[0].lsb
+ sig_name = r.name
+ f = r.fields[0]
+ swwraccess = f.swwraccess
+ swrdaccess = f.swrdaccess
+ hwext = r.hwext
+ regwen = r.regwen
+%>\
+${sig_gen(msb, lsb, sig_name, swwraccess, swrdaccess, hwext, regwen)}\
+ % else:
+ % for f in r.fields:
+<%
+ msb = f.msb
+ lsb = f.lsb
+ sig_name = r.name + "_" + f.name
+ swwraccess = f.swwraccess
+ swrdaccess = f.swrdaccess
+ hwext = r.hwext
+ regwen = r.regwen
+%>\
+${sig_gen(msb, lsb, sig_name, swwraccess, swrdaccess, hwext, regwen)}\
+ % endfor
+ % endif
+ % endfor
+
+ // Register instances
+ % for r in block.regs:
+ // R[${r.name}]: V(${str(r.hwext)})
+ % if len(r.fields) == 1:
+<%
+ f = r.fields[0]
+ finst_name = r.name
+ fsig_name = r.name
+ msb = f.msb
+ lsb = f.lsb
+ swaccess = f.swaccess
+ swrdaccess = f.swrdaccess
+ swwraccess = f.swwraccess
+ hwaccess = f.hwaccess
+ hwqe = f.hwqe
+ hwre = f.hwre
+ hwext = r.hwext
+ resval = f.resval
+ regwen = r.regwen
+%>
+${finst_gen(finst_name, fsig_name, msb, lsb, swaccess, swrdaccess, swwraccess, hwaccess, hwqe, hwre, hwext, resval, regwen)}
+ % else:
+ % for f in r.fields:
+<%
+ finst_name = r.name + "_" + f.name
+ fsig_name = r.name + "." + f.name
+ msb = f.msb
+ lsb = f.lsb
+ swaccess = f.swaccess
+ swrdaccess = f.swrdaccess
+ swwraccess = f.swwraccess
+ hwaccess = f.hwaccess
+ hwqe = f.hwqe
+ hwre = f.hwre
+ hwext = r.hwext
+ resval = f.resval
+ regwen = r.regwen
+%>
+ // F[${f.name}]: ${f.msb}:${f.lsb}
+${finst_gen(finst_name, fsig_name, msb, lsb, swaccess, swrdaccess, swwraccess, hwaccess, hwqe, hwre, hwext, resval, regwen)}
+ % endfor
+ % endif
+
+ ## for: block.regs
+ % endfor
+
+ logic [${len(block.regs)-1}:0] addr_hit;
+ always_comb begin
+ addr_hit = '0;
+ % for i,r in enumerate(block.regs):
+ addr_hit[${i}] = (reg_addr == ${block.name.upper()}_${r.name.upper()}_OFFSET);
+ % endfor
+ end
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ tl_addrmiss <= 1'b0;
+ end else if (reg_re || reg_we) begin
+ tl_addrmiss <= ~|addr_hit;
+ end
+ end
+
+ // Write Enable signal
+ % for i, r in enumerate(block.regs):
+ % if len(r.fields) == 1:
+<%
+ f = r.fields[0]
+ sig_name = r.name
+ inst_name = r.name
+ msb = f.msb
+ lsb = f.lsb
+ swrdaccess = f.swrdaccess
+ swwraccess = f.swwraccess
+ hwext = r.hwext
+%>
+${we_gen(sig_name, msb, lsb, swrdaccess, swwraccess, hwext, i)}\
+ % else:
+ % for f in r.fields:
+<%
+ sig_name = r.name + "_" + f.name
+ inst_name = r.name + "." + f.name
+ msb = f.msb
+ lsb = f.lsb
+ swrdaccess = f.swrdaccess
+ swwraccess = f.swwraccess
+ hwext = r.hwext
+%>
+${we_gen(sig_name, msb, lsb, swrdaccess, swwraccess, hwext, i)}\
+ % endfor
+ % endif
+ % endfor
+
+ // Read data return
+ logic [DW-1:0] reg_rdata_next;
+ always_comb begin
+ reg_rdata_next = '0;
+ unique case (1'b1)
+ % for i, r in enumerate(block.regs):
+ % if len(r.fields) == 1:
+<%
+ f = r.fields[0]
+ sig_name = r.name
+ inst_name = r.name
+ msb = f.msb
+ lsb = f.lsb
+ swrdaccess = f.swrdaccess
+%>\
+ addr_hit[${i}]: begin
+${rdata_gen(sig_name, msb, lsb, swrdaccess)}\
+ end
+
+ % else:
+ addr_hit[${i}]: begin
+ % for f in r.fields:
+<%
+ sig_name = r.name + "_" + f.name
+ inst_name = r.name + "." + f.name
+ msb = f.msb
+ lsb = f.lsb
+ swrdaccess = f.swrdaccess
+%>\
+${rdata_gen(sig_name, msb, lsb, swrdaccess)}\
+ % endfor
+ end
+
+ % endif
+ % endfor
+ default: begin
+ reg_rdata_next = '1;
+ end
+ endcase
+ end
+
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ reg_valid <= 1'b0;
+ reg_rdata <= '0;
+ rsp_opcode <= tlul_pkg::AccessAck;
+ end else if (reg_re || reg_we) begin
+ // Guarantee to return data in a cycle
+ reg_valid <= 1'b1;
+ if (reg_re) begin
+ reg_rdata <= reg_rdata_next;
+ rsp_opcode <= tlul_pkg::AccessAckData;
+ end else begin
+ rsp_opcode <= tlul_pkg::AccessAck;
+ end
+ end else if (tl_reg_h2d.d_ready) begin
+ reg_valid <= 1'b0;
+ end
+ end
+
+ // Outstanding: 1 outstanding at a time. Identical to `reg_valid`
+ always_ff @(posedge clk_i or negedge rst_ni) begin
+ if (!rst_ni) begin
+ outstanding <= 1'b0;
+ end else if (tl_reg_h2d.a_valid && tl_reg_d2h.a_ready) begin
+ outstanding <= 1'b1;
+ end else if (tl_reg_d2h.d_valid && tl_reg_h2d.d_ready) begin
+ outstanding <= 1'b0;
+ end
+ end
+
+ // Assertions for Register Interface
+ `ASSERT_PULSE(wePulse, reg_we, clk_i, !rst_ni)
+ `ASSERT_PULSE(rePulse, reg_re, clk_i, !rst_ni)
+
+ `ASSERT(reAfterRv, $rose(reg_re || reg_we) |=> reg_valid, clk_i, !rst_ni)
+
+ `ASSERT(en2addrHit, (reg_we || reg_re) |-> $onehot0(addr_hit), clk_i, !rst_ni)
+
+ `ASSERT(reqParity, tl_reg_h2d.a_valid |-> tl_reg_h2d.a_user.parity_en == 1'b0, clk_i, !rst_ni)
+
+endmodule
+<%def name="str_bits_sv(msb, lsb)">\
+% if msb != lsb:
+${msb}:${lsb}\
+% else:
+${msb}\
+% endif
+</%def>\
+<%def name="str_arr_sv(msb, lsb)">\
+% if msb != lsb:
+[${msb-lsb}:0] \
+% endif
+</%def>\
+<%def name="sig_gen(msb, lsb, sig_name, swwraccess, swrdaccess, hwext, regwen)">\
+ % if swrdaccess != SwRdAccess.NONE:
+ logic ${str_arr_sv(msb, lsb)}${sig_name}_qs;
+ % endif
+ % if swwraccess != SwWrAccess.NONE:
+ logic ${str_arr_sv(msb, lsb)}${sig_name}_wd;
+ logic ${sig_name}_we;
+ % endif
+ % if swrdaccess != SwRdAccess.NONE and hwext:
+ logic ${sig_name}_re;
+ % endif
+</%def>\
+<%def name="finst_gen(finst_name, fsig_name, msb, lsb, swaccess, swrdaccess, swwraccess, hwaccess, hwqe, hwre, hwext, resval, regwen)">\
+ % if hwext: ## if hwext, instantiate prim_subreg_ext
+ prim_subreg_ext #(
+ .DW (${msb - lsb + 1})
+ ) u_${finst_name} (
+ % if swrdaccess != SwRdAccess.NONE:
+ .re (${finst_name}_re),
+ % else:
+ .re (1'b0),
+ % endif
+ % if swwraccess != SwWrAccess.NONE:
+ % if regwen:
+ // qualified with register enable
+ .we (${finst_name}_we & ${regwen}_qs),
+ % else:
+ .we (${finst_name}_we),
+ % endif
+ .wd (${finst_name}_wd),
+ % else:
+ .we (1'b0),
+ .wd ('0),
+ % endif
+ % if hwaccess == HwAccess.HRO:
+ .d ('0),
+ % else:
+ .d (hw2reg.${fsig_name}.d),
+ % endif
+ % if hwre:
+ .qre (reg2hw.${fsig_name}.re),
+ % else:
+ .qre (),
+ % endif
+ % if hwaccess == HwAccess.HWO:
+ .qe (),
+ .q (),
+ % else:
+ % if hwqe:
+ .qe (reg2hw.${fsig_name}.qe),
+ % else:
+ .qe (),
+ % endif
+ .q (reg2hw.${fsig_name}.q ),
+ % endif
+ % if swrdaccess != SwRdAccess.NONE:
+ .qs (${finst_name}_qs)
+ % else:
+ .qs ()
+ % endif
+ );
+ % else: ## if not hwext, instantiate prim_subreg or constant assign
+ % if hwaccess == HwAccess.NONE and swrdaccess == SwRdAccess.RD and swwraccess == SwWrAccess.NONE:
+ // constant-only read
+ assign ${finst_name}_qs = ${msb-lsb+1}'h${"%x" % resval};
+ % else: ## not hwext not constant
+ prim_subreg #(
+ .DW (${msb - lsb + 1}),
+ .SWACCESS("${swaccess.name}"),
+ .RESVAL (${msb-lsb+1}'h${"%x" % resval})
+ ) u_${finst_name} (
+ .clk_i (clk_i ),
+ .rst_ni (rst_ni ),
+
+ % if swwraccess != SwWrAccess.NONE: ## non-RO types
+ % if regwen:
+ // from register interface (qualified with register enable)
+ .we (${finst_name}_we & ${regwen}_qs),
+ % else:
+ // from register interface
+ .we (${finst_name}_we),
+ % endif
+ .wd (${finst_name}_wd),
+ % else: ## RO types
+ .we (1'b0),
+ .wd ('0 ),
+ % endif
+
+ // from internal hardware
+ % if hwaccess == HwAccess.HRO or hwaccess == HwAccess.NONE:
+ .de (1'b0),
+ .d ('0 ),
+ % else:
+ .de (hw2reg.${fsig_name}.de),
+ .d (hw2reg.${fsig_name}.d ),
+ % endif
+
+ // to internal hardware
+ % if hwaccess == HwAccess.HWO or hwaccess == HwAccess.NONE:
+ .qe (),
+ .q (),
+ % else:
+ % if hwqe:
+ .qe (reg2hw.${fsig_name}.qe),
+ % else:
+ .qe (),
+ % endif
+ .q (reg2hw.${fsig_name}.q ),
+ % endif
+
+ % if swrdaccess != SwRdAccess.NONE:
+ // to register interface (read)
+ .qs (${finst_name}_qs)
+ % else:
+ .qs ()
+ % endif
+ );
+ % endif ## end non-constant prim_subreg
+ % endif
+</%def>\
+<%def name="we_gen(sig_name, msb, lsb, swrdaccess, swwraccess, hwext, idx)">\
+% if swwraccess != SwWrAccess.NONE:
+ % if swrdaccess != SwRdAccess.RC:
+ assign ${sig_name}_we = addr_hit[${idx}] && reg_we;
+ assign ${sig_name}_wd = reg_wdata[${str_bits_sv(msb,lsb)}];
+ % else:
+ ## Generate WE based on read request, read should clear
+ assign ${sig_name}_we = addr_hit[${idx}] && reg_re;
+ assign ${sig_name}_wd = '1;
+ % endif
+% endif
+% if swrdaccess != SwRdAccess.NONE and hwext:
+ assign ${sig_name}_re = addr_hit[${idx}] && reg_re;
+% endif
+</%def>\
+<%def name="rdata_gen(sig_name, msb, lsb, swrdaccess)">\
+% if swrdaccess != SwRdAccess.NONE:
+ reg_rdata_next[${str_bits_sv(msb,lsb)}] = ${sig_name}_qs;
+% else:
+ reg_rdata_next[${str_bits_sv(msb,lsb)}] = '0;
+% endif
+</%def>\