cpphdl-verilator-rtl
Use when writing or debugging C++HDL tests that mix C++ testbench models with Verilated RTL, especially reset ordering, register inputs, AXI4 responder wiring, eval sequencing, _assign/_strobe discipline, and avoiding C++/SystemVerilog simulation mismatches.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기포함된 파일(1)
- SKILL.md10.1 KB
SKILL.md(원문)
설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
C++HDL and Verilator RTL Tests
Use this skill when editing C++HDL tests or harnesses that instantiate a Verilated module and connect it to C++ models such as Axi4Ram, Axi4RegionMux, UART, CLINT, or accelerator devices.
MAIN RULES:
- Never try to explicitly evaluate ports or combs values! They calculated implicitly by calling comb-functions chain. Combs and ports are lambdas in cpphdl and can refresh all chain on demand. Value is cached until next clock start
- Never call _assign() in work cycle - this function only binds lambdas to ports, it does not calculate anything!
- Define variables only in the beginning of methods (this is not our requirement, this is requirements of verilator and yosys_slang)
- Try to not use
returnin_work()method, better putif (reset)in the end of_work()method or useif (reset) { ... } else { ... }. Slang does not supportdisablein always blocks
Hard Rules
- Put
_ASSIGN(...),_ASSIGN_REG(...), ,_ASSIGN_COMB(...),_ASSIGN_I(...),_ASSIGN_REG/COMB_I(...),_ASSIGN_INDEXED(...), and_ASSIGN_REG/COMB_INDEXED(...)assignments only in_assign(). - Assign a C++HDL interface once with
assignIf(...); do not assign its individual ports separately. Make the connection in the immediate common parent's_assign():
void _assign()
{
assignIf(driver, responder, driver.source_out, responder.sink_in);
}
Do not replace this with one assignment per valid, ready, data, or other interface member. assignIf resolves both interface directions and expands the flattened SystemVerilog connections consistently.
- C++HDL forbids assignments across hierarchy levels, matching synthesizable SystemVerilog module encapsulation. A module may connect its own ports and the exposed ports/interfaces of its immediate child modules, but it must not reach through a child into a grandchild or assign a child module's internal state. Expose an intermediate port or interface and connect the hierarchy one level at a time.
- Put
.strobe()and.apply()calls only in the parent_strobe()method. Do not call them from_work(), helper methods, comb functions, or_assign(). _assign()is structural elaboration. It should run once before the simulation cycle loop, not once per cycle. Never run any_assign()after_work()cycles begin- In Verilator mode, write all Verilated input ports before
eval(). - For registered logic, make the C++ harness ordering match the RTL edge being sampled. Do not rely on C++ C++HDL update order accidentally matching Verilator.
- In rare case if you use Verilator for 0-clock latency module testing, to see comb output from Verilator use eval() with clk = 0. This will update combs without updating regs
- sys_clock is mandatory global variable to be incremented once per main loop cycle, it is used to cache combinational variables functions results
- it's better to put
if (reset)section in the end of_work()method to do not forget to call nested_work()methods in case of reset - better use operators >> and << and | when possible instead of bits(a,b) and [] for logic<>, if it does not make logic much longer (bits() is slower)
Verilator Cycle Pattern
For a Verilated DUT connected to C++ responder models, use this shape unless the local test has a proven reason to differ:
void eval_dut(bool reset)
{
dut.reset = reset;
dut.input_a = input_a;
dut.input_b = input_b;
AXI4_RESPONDER_FROM_VERILATOR(dut, ram.axi_in, 0);
dut.eval();
}
void cycle(bool reset = false)
{
dut.clk = 0;
eval_dut(reset);
ram._work(reset);
AXI4_RESPONDER_FROM_VERILATOR(dut, ram.axi_in, 0);
dut.clk = 1;
eval_dut(reset);
ram._strobe();
dut.clk = 0;
eval_dut(reset);
++sys_clock;
}
The important point is not the exact helper names. The important point is that responder outputs are presented to the Verilated DUT before the clock edge that samples them, and C++ model state is strobed after that edge.
Reset
- Drive
resetthrough the sameeval_dut(reset)path as normal cycles so all inputs and AXI responder signals are valid during reset. - Hold reset for multiple cycles when the DUT contains RAM init FSMs or caches.
- Keep C++ model
_work(reset)and_strobe()in the same parent cycle order used after reset. - Do not special-case reset by calling only
dut.eval()and skipping connected C++ models unless the test explicitly needs disconnected reset behavior.
AXI4 Responder Wiring
Use AXI4_RESPONDER_FROM_VERILATOR(dut, responder.axi_in, index) whenever a Verilated DUT master port reads a C++ AXI responder.
Typical use:
AXI4_DRIVER_FROM_VERILATOR(ram.axi_in, dut, 0, u<32>, copy_to_logic<PORT_BITS>);
ram._assign();
// Each eval path, before dut.eval():
AXI4_RESPONDER_FROM_VERILATOR(dut, ram.axi_in, 0);
Pitfalls:
- If
AXI4_RESPONDER_FROM_VERILATORis called only afterdut.eval()at the rising edge, registered RTL can miss a one-cyclervalid/bvalid. - If responder signals are forced too early without first evaluating the DUT's current master outputs, instruction or data refill can hang from reset.
- Keep
rready/breadybehavior legal and stable. A testbench may hold ready high, but the DUT still must observe valid/ready on a clock edge. - For wide AXI data, use the established conversion helper, for example
verilator_logic_to_wide(...)orcopy_to_logic<PORT_BITS>(...); do not hand-copy only one word of a beat.
Register Inputs
Set Verilated register-like inputs directly before eval():
dut.reset_pc_in = reset_pc;
dut.memory_base_in = memory_base;
dut.mem_region_size_in[0] = region0_size;
Do not assign these through _ASSIGN()/_ASSIGN_REG() in Verilator-only code. _ASSIGN() is for C++HDL structural assignment inside _assign(). Verilator requires strait data for ports, not functions
C++ Model Order
For C++ C++HDL models connected around a Verilated DUT:
_assign()wires ports once._work(reset)computes next values for C++ models.- Verilated
eval()computes and samples RTL depending onclk. _strobe()commits C++ model registers and memory buffers.
Keep memory buffer .apply() inside the memory module _strobe() path only. If a parent calls .apply() manually, C++ and Verilator tests can diverge.
Debugging Handshake Hangs
When a Verilator-only hang appears:
- Trace valid/ready on both sides of the boundary: DUT master output, C++ responder input, C++ responder output, DUT responder input.
- Check whether the valid pulse exists only between two
eval()calls. - Compare the harness cycle order with a known passing test such as an L2 cache Verilator test.
- Remove temporary debug prints before finishing.
Common signatures:
- C++ passes, Verilator hangs on first read: responder
rvalidis generated by the C++ model, but is not presented before the Verilated clock edge. - Verilator hangs during instruction refill immediately after reset: responder feedback was presented before the Verilated master outputs were evaluated for the current low-clock phase.
- Direct C++ device test passes, CPU
.elfVerilator test hangs: the device logic may be fine; inspect the mixed C++/Verilator boundary first.
Using Axi4Driver, Axi4Responder and Axi4If interface
Use this skill when editing C++HDL models or tests or harnesses to verilator connected to/from Axi4
Hard Rules
- Use structures with Axi4Driver and Axi4Responder instead of declaring separated registers for Axi4 signals
- Use Axi4If interface instead of defining _PORT() for each Axi4 signal
- Use operator= of Axi4If if you need to assign driver or responder to AxiIf
- Use Module method assignIn if you need to bind 2 Axi4If interfaces of 2 different modules
- If nothing of this works use macroses like AXI4_DRIVER_FROM AXI4_RESPONDER_FROM or similar with _VERILATOR suffix
- Try to use right types like u<ID_WIDTH>, not uint32_t for variables when you need to use them
Developing RTL modules, devices, examples
Use this skill when developing and debugging new C++HDL module, device, example or functionality
Hard Rules
- If you see bug in cpphdl functionality and fixing it - develop a new test in root tests/ folder or add section to correspondent old test
- If you see bug in Tribe cpu code or modules or devices and fixing it - develop a new test in tribe_cpu/tests folder or add section to correspondent old test
C++HDL Template Specialization Conversion
Use these rules when changing template handling in the cpphdl converter.
- Template structs/unions must be emitted only as concrete specializations. Numeric, type, and textual/string-like template arguments are all part of the generated package/type name. Do not generate an unspecialized primary-template package for a template struct.
- Static
constexprvalues in a specialized struct package must be concrete values for that specialization. Generated struct packages must not contain unresolved template parameter names such asWIDTH,CONV_TYPE,FMT, orTAG. - Template
Moduleclasses keep numeric template parameters as SystemVerilog module parameters. Numeric parameters should not be added to the generated module name, and method/member expressions should preserve the symbolic parameter where possible. - Template
Moduleclasses use type and textual/string-like template parameters as specialization identity. These parameters are added to the generated module name and select a separate SV module for each type/text combination. - When a fixed type specialization contributes static
constexprvalues used by a module, resolve those fixed type constexprs to concrete values. Numeric module parameters remain symbolic.
C++HDL conversion development rules
- There shouldnt be code worsening in SV generation just because cpphdl is unsure in something. SV code shouldnt make cast where it didnt in C++ and it is not 100% necessary (due to differences between C++ and SystemVerilog casting/assignments)
리뷰
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
댓글
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.