Skip to content

Commit 316b03c

Browse files
Initial CSR work
Largely based on (and somewhat hard coding for) checking the `custom` CSR in NERV with the `rvfi_csrc_any_check`.
1 parent 4145c3d commit 316b03c

File tree

6 files changed

+321
-0
lines changed

6 files changed

+321
-0
lines changed

src/checks/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
InstructionChecker,
88
CompleteISAChecker,
99
)
10+
11+
from .csr_checker import CsrChecker

src/checks/base_csr.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import json
2+
import click
3+
4+
from . import CsrChecker
5+
from ..csrs import Csr, base_csrs
6+
from ..named_set import NamedSet
7+
8+
def dump_csr(name: str, csrs: NamedSet[Csr], xlen: int, format: str) -> str:
9+
csr_checker = CsrChecker(
10+
name = name,
11+
csrs = csrs,
12+
)
13+
14+
if format == "json":
15+
return json.dumps(csr_checker)
16+
elif format == "verilog":
17+
return csr_checker.to_verilog()
18+
19+
@click.option('-x', '--xlen', type=int, default=32)
20+
@click.option('--format', type=click.Choice(['json', 'verilog']), default='json')
21+
@click.command()
22+
def base_csr(xlen: int, format: str):
23+
csrs: NamedSet[Csr] = NamedSet()
24+
for csr in base_csrs():
25+
if csr.name not in ["cycle", "mcycle"]:
26+
continue
27+
28+
csrs.add(csr)
29+
30+
click.echo(dump_csr("csr_check", csrs, xlen, format))
31+
32+
if __name__ == "__main__":
33+
base_csr()

src/checks/csr_checker.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from dataclasses import dataclass
2+
from textwrap import dedent
3+
4+
from . import GenericGroupChecker
5+
from ..csrs import Csr
6+
from ..named_set import NamedSet
7+
8+
@dataclass
9+
class CsrChecker(GenericGroupChecker):
10+
csrs: NamedSet[Csr]
11+
12+
def _subchecks(self) -> NamedSet[Csr]:
13+
return self.csrs

src/csrs/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .model import (
2+
Csr,
3+
ShadowCsr,
4+
MachineCsr,
5+
base_csrs,
6+
)

src/csrs/model.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
from dataclasses import dataclass, field
2+
from textwrap import dedent, indent
3+
from typing import Optional
4+
5+
from ..checks.generic_checker import GenericChecker
6+
from ..named_set import NamedSet
7+
from ..rvfi import SpeculativeObserver
8+
9+
@dataclass(kw_only=True)
10+
class Csr(GenericChecker):
11+
width: str
12+
privilege: Optional[str] = None
13+
index: Optional[int] = None
14+
indexh: Optional[int] = None
15+
16+
has_rvfi: bool = False
17+
read_insn: bool = True
18+
19+
def _v_insn_under_test(self) -> str:
20+
v_str = f"(csr_insn_addr == 12'h {self.index:X})"
21+
try:
22+
priv = self.privilege[0]
23+
except IndexError:
24+
# no privilege
25+
pass
26+
else:
27+
priv_mode = {
28+
"M": 3,
29+
"S": 1,
30+
"U": 0,
31+
}[priv]
32+
v_str += f" && (rvfi.mode >= {priv_mode})"
33+
return v_str
34+
35+
def _v_insn_check(self, xlen: int) -> str:
36+
v_str = dedent(f"""\
37+
// insn mapping
38+
wire csr_insn_valid = rvfi.valid && (rvfi.insn[6:0] == 7'b 1110011) && (rvfi.insn[13:12] != 0) && ((rvfi.insn >> 16 >> 16) == 0);
39+
wire [11:0] csr_insn_addr = rvfi.insn[31:20];
40+
41+
wire csr_write = !rvfi.insn[13] || rvfi.insn[19:15];
42+
wire csr_read = rvfi.insn[11:7] != 0;
43+
wire csr_write_valid = csr_write && csr_insn_valid;
44+
wire csr_read_valid = csr_read && csr_insn_valid;
45+
46+
wire [1:0] csr_mode = rvfi.insn[13:12];
47+
wire [{xlen-1}:0] csr_rsval = rvfi.insn[14] ? rvfi.insn[19:15] : rvfi.rs1_rdata;
48+
wire csr_insn_under_test = (""")
49+
50+
v_str += self._v_insn_under_test() + ");\n"
51+
52+
return v_str
53+
54+
def _v_rvfi_map(self, xlen: int) -> str:
55+
v_str = "// rvfi mapping\n"
56+
for val in ["rmask", "wmask", "rdata", "wdata"]:
57+
v_str += f"wire [{xlen-1}:0] csr_insn_{val} = rvfi.csr_{self.name}_{val};\n"
58+
return v_str
59+
60+
def _v_signal_map(self, xlen: int) -> str:
61+
v_str = dedent("""\
62+
// setup for csrs
63+
localparam [11:0] csr_none = 12'hFFF;
64+
""")
65+
66+
if self.has_rvfi:
67+
v_str += "\n" + self._v_rvfi_map(xlen)
68+
69+
if self.read_insn:
70+
v_str += "\n" + self._v_insn_check(xlen)
71+
72+
return v_str
73+
74+
def _behavioral_regs(self) -> NamedSet[SpeculativeObserver]:
75+
return NamedSet([
76+
SpeculativeObserver("rsval_shadow", "`RISCV_FORMAL_XLEN"),
77+
SpeculativeObserver("wdata_shadow", "`RISCV_FORMAL_XLEN"),
78+
SpeculativeObserver("csr_written", "1"),
79+
SpeculativeObserver("csr_mode_shadow", "2"),
80+
])
81+
82+
def _v_process(self) -> str:
83+
v_str = "// setup for testing\n"
84+
85+
resets: list[str] = []
86+
for reg in self._behavioral_regs():
87+
reset = f"{reg.name} = {reg.spec_value};"
88+
v_str += f"reg {reg.bitrange()} {reset}\n"
89+
resets.append(reset)
90+
reset_str = "\n ".join(resets)
91+
92+
v_str += dedent(f"""
93+
// test
94+
always @(posedge clock) begin
95+
if (reset) begin
96+
{reset_str}
97+
end else begin
98+
if (check) begin
99+
if (csr_written && csr_read_valid && csr_insn_under_test) begin
100+
case (csr_mode_shadow)
101+
2'b 00 /* None */,
102+
2'b 01 /* RW */: begin
103+
assert(rsval_shadow == csr_insn_rdata || csr_insn_rdata == wdata_shadow);
104+
assert(rsval_shadow == wdata_shadow);
105+
end
106+
// Currently not testing set/clear from rsval
107+
2'b 10 /* RS */,
108+
2'b 11 /* RC */: begin assert(csr_insn_rdata == wdata_shadow); end
109+
endcase
110+
end
111+
end else begin
112+
if (csr_write_valid && csr_insn_under_test) begin
113+
rsval_shadow = csr_rsval;
114+
wdata_shadow = csr_insn_wdata;
115+
csr_written = 1;
116+
csr_mode_shadow = csr_mode;
117+
end
118+
end
119+
end
120+
end
121+
""")
122+
return v_str
123+
124+
def _v_body(self, xlen: int) -> str:
125+
v_str = self._v_format_block(self._v_rvfi_channel())
126+
v_str += self._v_format_block(self._v_signal_map(xlen))
127+
v_str += self._v_format_block(self._v_process())
128+
return v_str
129+
130+
@dataclass(kw_only=True)
131+
class ShadowCsr(Csr):
132+
source: str
133+
134+
135+
@dataclass(kw_only=True)
136+
class MachineCsr(Csr):
137+
shadows: NamedSet[ShadowCsr] = field(default_factory=NamedSet)
138+
139+
def shadow(self, name: str, privilege: str,
140+
index: int, indexh: Optional[int] = None,
141+
**kwargs
142+
) -> ShadowCsr:
143+
shadow = ShadowCsr(
144+
name = name,
145+
width = self.width,
146+
privilege = privilege,
147+
index = index,
148+
indexh = indexh,
149+
source = self.name,
150+
**kwargs
151+
)
152+
self.shadows.add(shadow)
153+
return shadow
154+
155+
156+
def csr(name: str, width: str, privilege: str, index: int, indexh: Optional[int] = None) -> Csr:
157+
return Csr(
158+
name = name,
159+
width = width,
160+
privilege = privilege,
161+
index = index,
162+
indexh = indexh,
163+
)
164+
165+
def mcsr(name: str, width: str, privilege: str, index: int, indexh: Optional[int] = None) -> MachineCsr:
166+
return MachineCsr(
167+
name = name,
168+
width = width,
169+
privilege = privilege,
170+
index = index,
171+
indexh = indexh,
172+
)
173+
174+
def mcsr_with_shadow(mname: str, width: str, mprivilege: str, mindex: int, mindexh: Optional[int],
175+
sname: str, sprivilege: str, sindex: int, sindexh: Optional[int],
176+
) -> tuple[MachineCsr, Csr]:
177+
machinecsr = mcsr(mname, width, mprivilege, mindex, mindexh)
178+
shadow = machinecsr.shadow(sname, sprivilege, sindex, sindexh)
179+
return (machinecsr, shadow)
180+
181+
def base_csrs() -> NamedSet[Csr]:
182+
return NamedSet([
183+
mcsr("mvendorid", "xlen", "MRO", 0xF11),
184+
mcsr("marchid", "xlen", "MRO", 0xF12),
185+
mcsr("mimpid", "xlen", "MRO", 0xF13),
186+
mcsr("mhartid", "xlen", "MRO", 0xF14),
187+
mcsr("mconfigptr", "xlen", "MRO", 0xF15),
188+
mcsr("mstatus", "xlen", "MRW", 0x300),
189+
mcsr("mstatush", "xlen", "MRW", 0x310),
190+
mcsr("misa", "xlen", "MRW", 0x301),
191+
mcsr("medeleg", "xlen", "MRW", 0x302),
192+
mcsr("mideleg", "xlen", "MRW", 0x303),
193+
mcsr("mie", "xlen", "MRW", 0x304),
194+
mcsr("mtvec", "xlen", "MRW", 0x305),
195+
mcsr("mcounteren", "xlen", "MRW", 0x306),
196+
mcsr("mscratch", "xlen", "MRW", 0x340),
197+
mcsr("mepc", "xlen", "MRW", 0x341),
198+
mcsr("mcause", "xlen", "MRW", 0x342),
199+
mcsr("mtval", "xlen", "MRW", 0x343),
200+
mcsr("mip", "xlen", "MRW", 0x344),
201+
mcsr("mtinst", "xlen", "MRW", 0x34A),
202+
mcsr("mtval2", "xlen", "MRW", 0x34B),
203+
mcsr("mcountinhibit", "xlen", "MRW", 0x320),
204+
mcsr("menvcfg", "xlen", "MRW", 0x30A),
205+
mcsr("menvcfgh", "xlen", "MRW", 0x31A),
206+
*mcsr_with_shadow(
207+
"mcycle", "64", "MRW", 0xB00, 0xB80,
208+
"cycle", "URO", 0xC00, 0xC80,
209+
),
210+
csr("time", "64", "URO", 0xC01, 0xC81),
211+
*mcsr_with_shadow(
212+
"minstret", "64", "MRW", 0xB02, 0xB82,
213+
"instret", "URO", 0xC02, 0xC82,
214+
),
215+
])
216+
217+
def hpm_csrs(max_idx: int = 32) -> NamedSet[Csr]:
218+
return NamedSet([
219+
*(
220+
Csr(f"mhpmevent{i}", "xlen", 0x320 + i, None, None)
221+
for i in range(3, max_idx)
222+
),
223+
*(
224+
Csr(f"mhpmcounter{i}", "64", 0xB00 + i, None, 0xC00 + i,
225+
0xB80 + i, None, 0xC80 + i)
226+
for i in range(3, max_idx)
227+
),
228+
])
229+
230+
def pmp_csrs(entries: int = 64) -> NamedSet[Csr]:
231+
return NamedSet([
232+
*(
233+
MachineCsr(f"pmpcfg{i}", "xlen", "MRW", 0x3A0 + i)
234+
for i in range(entries >> 2)
235+
),
236+
*(
237+
MachineCsr(f"pmpaddr{i}", "xlen", "MRW", 0x3B0 + i)
238+
for i in range(entries)
239+
),
240+
])
241+
242+
def fext_csrs() -> NamedSet[Csr]:
243+
return NamedSet([
244+
Csr("fflags", "xlen", None, None, None),
245+
Csr("frm", "xlen", None, None, None),
246+
Csr("fcsr", "xlen", None, None, None),
247+
])

src/csrs/nerv_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import click
2+
3+
from .model import Csr
4+
5+
def custom_csr():
6+
return Csr(
7+
name = "custom",
8+
width = "xlen",
9+
privilege = "URW",
10+
index = 0xBC0,
11+
has_rvfi = True,
12+
)
13+
14+
@click.command
15+
def nerv_test():
16+
csr = custom_csr()
17+
print(csr.to_verilog(xlen=32))
18+
19+
if __name__ == "__main__":
20+
nerv_test()

0 commit comments

Comments
 (0)