• 【前端验证】fork-join_none线程立即执行的一次代码优化记录


    我们的目标是┏ (゜ω゜)=☞芯片前端全栈工程师~喵!

    前言

    【system verilog】fork-join_none与循环语句共同使用的行为探究

    很早之前写过关于fork-join_none的探究文章,最近被人指出了一些错误:

    我仔细理解了下他的意思,觉得确实使用#0来立刻进行进行阻塞,进而达到立即执行fork-join_none内语句的方式是比较合理的(当然了,其他阻塞行为一样会让fork-join_none内的语句执行,但不能达到立刻执行的效果)。经过这个勘误和指点后,我突然觉得又通彻了一些。

    恰巧最近自己也在写一些验证的代码, 遇到了类似的问题,同时还帮别人debug了非常相似的问题(以下为被夸实录):

    场景复盘

    所以具体来说是个什么场景呢?最终完成部分代码片段如下:

    1. while(1)begin
    2. bit[255:0]rdata;
    3. p_sequencer.a_port.get(a_trc);
    4. ...
    5. for(int i=0; i<a_trc.size; i=i+1)begin
    6. rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
    7. if(i == arsize-1)begin
    8. r_transaction r_trc = new();
    9. fork begin
    10. automatic bit[255:0]send_rdata = rdata;
    11. `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == send_rdata; r_trc.last == 1'b1;})
    12. end join_none
    13. #0;
    14. end
    15. else begin
    16. ...
    17. end
    18. end
    19. end

     简单而言,就是一个简化的axi_slave(不过不需要总线地址对齐),当收到ar请求时要按照size返回对应笔rdata到总线上。因此代码组织最开始也比较简单,就是看arsize然后在ram中寻址返回数据并且给数据加一个随机延迟,因此最开始完成的代码是这样的:

    1. while(1)begin
    2. bit[255:0]rdata;
    3. p_sequencer.a_port.get(a_trc);
    4. ...
    5. for(int i=0; i<a_trc.size; i=i+1)begin
    6. rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
    7. if(i == arsize-1)begin
    8. r_transaction r_trc = new();
    9. `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == rdata; r_trc.last == 1'b1;})
    10. end
    11. else begin
    12. ...
    13. end
    14. end
    15. end

     然后就发现了第一个问题,`uvm_do的返回时间是在driver中的seq_item_port.item_done()之后的,那么也就是说回到a_port.get(a_trc)时候是前一个ar的rdata已经发完的时间,而不是真正的从总线上获取到ar请求的时间,在这段时间里ram中的数据可能已经被改写了,因此`uvm_do必须要提并行线程来执行,所以代码就改成了这样:

    1. while(1)begin
    2. bit[255:0]rdata;
    3. p_sequencer.a_port.get(a_trc);
    4. ...
    5. for(int i=0; i<a_trc.size; i=i+1)begin
    6. rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
    7. if(i == arsize-1)begin
    8. r_transaction r_trc = new();
    9. fork
    10. `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == rdata; r_trc.last == 1'b1;})
    11. join_none
    12. end
    13. else begin
    14. ...
    15. end
    16. end
    17. end

    然后继续仿真,就发现了第二个问题,r_trc并没有在`uvm_do_on_with的时刻就开始在driver内执行,而是呈现了比较奇怪的行为(当然这个问题并不一定是不加#0引起的),因此结合前言中的内容,我机智的加入了#0来及时执行提起的线程:

    1. while(1)begin
    2. bit[255:0]rdata;
    3. p_sequencer.a_port.get(a_trc);
    4. ...
    5. for(int i=0; i<a_trc.size; i=i+1)begin
    6. rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
    7. if(i == arsize-1)begin
    8. r_transaction r_trc = new();
    9. fork
    10. `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == rdata; r_trc.last == 1'b1;})
    11. join_none
    12. #0;
    13. end
    14. else begin
    15. ...
    16. end
    17. end
    18. end

    而后继续跑仿真,自然而然的遇到了第三个问题,图示如下:

    我的rdata只有一份,但是线程有多个,那么会导致每个线程中使用的rdata并不是预期的值 ,因此还要进一步的优化代码:

    1. while(1)begin
    2. bit[255:0]rdata;
    3. p_sequencer.a_port.get(a_trc);
    4. ...
    5. for(int i=0; i<a_trc.size; i=i+1)begin
    6. rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
    7. if(i == arsize-1)begin
    8. r_transaction r_trc = new();
    9. fork begin
    10. automatic bit[255:0]send_rdata = rdata;
    11. `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == send_rdata; r_trc.last == 1'b1;})
    12. end join_none
    13. #0;
    14. end
    15. else begin
    16. ...
    17. end
    18. end
    19. end

    经过三次的优化之后,仿真行为终于符合了我的预期!

    最后一下,经过和别人的讨论,其实不加#0也是可以的,这个时候需要把automatic赋值放在fork-join_none外面:

    1. while(1)begin
    2. bit[255:0]rdata;
    3. p_sequencer.a_port.get(a_trc);
    4. ...
    5. for(int i=0; i<a_trc.size; i=i+1)begin
    6. rdata[(i%32)*8 +:8] = p_sequencer.ram[addr][7:0];
    7. if(i == arsize-1)begin
    8. r_transaction r_trc = new();
    9. automatic bit[255:0]send_rdata = rdata;
    10. fork
    11. `uvm_do_on_with(r_trc, p_sequencer.r_sqr, {r_trc.data == send_rdata; r_trc.last == 1'b1;})
    12. join_none
    13. end
    14. else begin
    15. ...
    16. end
    17. end
    18. end

  • 相关阅读:
    请求与响应
    USACO 2021 December Contest, Silver
    93.(cesium篇)cesium动态单体化-倾斜摄影(楼栋)
    Topsis与熵权法
    后渗透之日志分析实验
    C#基础入门教程-判断
    图片批量修改尺寸如何实现?
    Java类的主动加载和被动加载
    Android 扩大View可点击区域范围
    【C语言】指针由浅入深全方位详解
  • 原文地址:https://blog.csdn.net/moon9999/article/details/128014173