{"id":14477,"date":"2020-08-13T10:18:56","date_gmt":"2020-08-13T17:18:56","guid":{"rendered":"https:\/\/blogs.mentor.com\/verificationhorizons\/?p=14477"},"modified":"2026-03-27T08:51:33","modified_gmt":"2026-03-27T12:51:33","slug":"systemverilog-race-condition-challenge-responses","status":"publish","type":"post","link":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2020\/08\/13\/systemverilog-race-condition-challenge-responses\/","title":{"rendered":"SystemVerilog Race Condition Challenge Responses"},"content":{"rendered":"<p>As promised, here is my response to Siemens EDA\u2019s <a href=\"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/2020\/07\/27\/systemverilog-race-condition-challenge\/\">SystemVerilog Race Condition Challenge<\/a>.<\/p>\n<hr \/>\n<h3>Race #1 Blocking and non-blocking assignments<\/h3>\n<pre>\u00a0 byte slam;\n\u00a0\u00a0bit dunk;\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0dunk = ~dunk;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0slam += dunk;\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0end\n\u00a0\u00a0always @(posedge clk) basket &lt;= slam + dunk;\n<\/pre>\n<p><em>Race #1<\/em> must be the number one most common race condition in Verilog\/SystemVerilog. Hardware designers may be more familiar with this race, but verification engineers must deal with this as well. When you have multiple threads or processes running in parallel and they are all synchronized to the same event (a clock edge), there is a race between reading the old value or the updated value from a blocking assignment. That\u2019s why you must always use a non-blocking assignment when one process writes, and another process reads the same variable synchronized to the same clock. Then you\u2019re guaranteed to consistently read the previous value.\u00a0 Most of the code examples here have this problem in common<\/p>\n<hr \/>\n<h3>Race #2 Unknowns at initialization<\/h3>\n<pre>\u00a0logic pong;\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0fork\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if (pong) ping = 0;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else\u00a0 \u00a0 \u00a0 ping = 1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if (ping) pong = 0;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0else\u00a0 \u00a0 \u00a0 pong = 1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0\u00a0\u00a0join_none\n\u00a0\u00a0end<\/pre>\n<p>This is basically the same as <em>Race #1<\/em>. Also, the way this is coded, the unknown values get treated the same as if they were 0. That\u2019s not always the case if you tried to implement this using <strong>ping = !pong;<\/strong> Then everything would remain unknown.<\/p>\n<hr \/>\n<h3>Race #3 Procedural and continuous assignments<\/h3>\n<pre>byte colours, stripes, bouncy;\nalways @(posedge clk) stripes += 1;\nalways begin\n  @(posedge clk);\n\u00a0\u00a0colours += 1;\n\u00a0\u00a0beach_ball = bouncy;\nend\nassign bouncy = colours &amp;&amp; stripes;<\/pre>\n<p>Continuous assignments behave as independent processes, and there\u2019s no deterministic order of execution between any process. Whenever right-hand side operands of a continuous assignment change, there\u2019s an assignment to the left-hand side. But if another process is making changes to those operands there\u2019s no guarantee when the left-hand side updates either before or after the procedural assignment to beach_ball. Note that all the accumulate assignment operators like <strong>+=<\/strong> or <strong>++<\/strong> are just shortcuts for blocking assignments. There are no non-blocking equivalent shortcuts, so you must expand it out:<\/p>\n<pre>always @(posedge clk) stripes &lt;= stripes + 1;<\/pre>\n<hr \/>\n<h3>Race #4 Incomplete sensitivity list<\/h3>\n<pre>\u00a0 bit score;\n\u00a0\u00a0byte fieldgoal, touchdown;\n\u00a0\u00a0byte down;\n\u00a0\u00a0always @(posedge clk) begin\n\u00a0\u00a0\u00a0\u00a0if (down &lt; 4) begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0score &lt;= 1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0fieldgoal &lt;= fieldgoal + 3;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0touchdown &lt;= touchdown + 7;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0down &lt;= down + 1;\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0\u00a0\u00a0else begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0score &lt;= 0;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0down &lt;= 0;\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0end\n\u00a0\u00a0always @(score) football = fieldgoal + touchdown;<\/pre>\n<p>I wouldn\u2019t call this a race, just bad coding. In the earliest versions of Verilog, it was up to you to figure out the sensitivity list for an expression to mimic the behavior of a continuous assignment in an always. Even though this code makes an assignment to score every clock cycle, its value only changes for one half the clock cycles. So, it misses changes to fieldgoal and touchdown. That changed in Verilog-2001 with <strong>always @*<\/strong> and further improved with <strong>always_comb<\/strong> in SystemVerilog<\/p>\n<hr \/>\n<h3>Race #5 fork\/join* that don&#8217;t consume time<\/h3>\n<pre>\u00a0\u00a0bit shot_put;\n\u00a0\u00a0bit javelin;\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0fork\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0shot_put = $random();\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0javelin = $random();\n\u00a0\u00a0\u00a0\u00a0join_none\n\u00a0\u00a0\u00a0\u00a0@(shot_put or javelin)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0throw = (shot_put || javelin);\n\u00a0\u00a0end<\/pre>\n<p>This is not a race.\u00a0The processes inside a fork\/join_none are not supposed to start until its parent process suspends or terminates. This code will calculate throw based on the initial values for shot_put and javelin, which are 0, not the values from $random(). Finally, stop using the deprecated $random() with its poor distribution\/stability, and switch to <strong>$urandom_range(1)<\/strong>.<\/p>\n<hr \/>\n<h3>Race #6 assignments in more than 1 thread<\/h3>\n<pre>\u00a0 bit single, double, triple, homerun, cycle;\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0single = $urandom_range(0,10) &gt; 1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0double = $urandom_range(0,10) &gt; 2;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0triple = $urandom_range(0,10) &gt; 4;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0homerun = $urandom_range(0,10) &gt; 8;\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0end\n\u00a0\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0forever begin\u00a0\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0#1 cycle = &amp; { single, double, triple, homerun };\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0end\n\u00a0\u00a0always @(posedge clk) begin\n\u00a0\u00a0\u00a0\u00a0#1 baseball = baseball + homerun;\n\u00a0\u00a0\u00a0\u00a0if (cycle) begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0#0 cycle = 0;\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0end<\/pre>\n<p>I cringe whenever I see <strong>#0<\/strong> or <strong>#1<\/strong> sprinkled in code. It usually means the coder did not understand SystemVerilog scheduling semantics well enough and throws these in. In this case the race has been moved one timeunit (<strong>#1<\/strong>) away from the clock edge. The assignment to cycle occurs simultaneously to its reading in the <strong>always<\/strong> block.<\/p>\n<hr \/>\n<h3>Race #7 Edge sensitive events<\/h3>\n<pre>\u00a0\u00a0bit [3:0] goal;\n\u00a0\u00a0always @(posedge clk) goal += 2;\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0@(goal == 2) hockey = 1;\n\u00a0\u00a0end<\/pre>\n<p>This is another form of <em>Race #1<\/em>. One process is writing, and another process is reading the same variable. But here the read is an event control waiting the expression <strong>(goal==2)<\/strong> to change. So, depending on the ordering between the <strong>initial<\/strong> and <strong>always<\/strong> blocks, it either catches the rise from false(1\u2019b0) to true(1\u2019b1) on the first clock cycle, or the fall from true to false in the next cycle. Always use non-blocking assignments between synchronous processes as I explained in <em>Race #1<\/em>. And it is rare to use an expression in an edge sensitive event. You should use the following:<\/p>\n<pre>@(posedge clk iff (goal==2));<\/pre>\n<hr \/>\n<h3>Race #8 Named events<\/h3>\n<pre> \u00a0event wicket, batsman;\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0fork\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@batsman;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0repeat (cricket+1) @(posedge clk);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0-&gt;wicket;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0-&gt;batsman;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@wicket;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0cricket += 1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0\u00a0\u00a0join_none\n\u00a0\u00a0end<\/pre>\n<p>Named events are synchronization objects that can suffer from the same kinds of problems as in the previous <em>Race #7<\/em>. You must be waiting for an event before triggering it. If the second forever block triggers batsman before the first forever block starts execution, you get into a deadlock where the first forever block is deadlocked waiting for an event that was already triggered. You can solve this particular race by using non-blocking triggers <strong>-&gt;&gt;<\/strong> to batsman and wicket. I would make sure you have a <em>very good<\/em> understanding of SystemVerilog\u2019s event scheduling algorithm before using named events.<\/p>\n<hr \/>\n<h3>Race #9 NBA functions<\/h3>\n<pre>\u00a0\u00a0bit [3:0] bump, spike, side_out;\n\u00a0\u00a0function bit [3:0] set(bit [3:0] _bump);\n\u00a0\u00a0\u00a0\u00a0set &lt;= _bump ^ (_bump &lt;&lt; 1);\n\u00a0\u00a0endfunction\n\u00a0\u00a0initial begin\n\u00a0\u00a0\u00a0\u00a0forever begin\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0bump += 1;\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0spike = set(bump);\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0side_out = bump &lt; spike ? spike : 0;\n\u00a0\u00a0\u00a0\u00a0end\n\u00a0\u00a0end<\/pre>\n<p>This is a race where you always lose. If you use non-blocking assignments to the return of a function, or to the output arguments of any function or task, the current value gets copied out before the NBA has a chance to update the value. You must use blocking assignments here.<\/p>\n<hr \/>\n<h3>Race #10\u00a0Procedural force\/release<\/h3>\n<pre>\u00a0\u00a0byte pushups, situps, reps;\n  assign situps = 3 + pushups;\n\u00a0\u00a0always @(posedge clk) reps &lt;= reps + 1;\n\u00a0\u00a0always begin\n\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0pushups += reps;\n\u00a0\u00a0\u00a0\u00a0force situps = 0;\n\u00a0\u00a0\u00a0\u00a0@(posedge clk);\n\u00a0\u00a0\u00a0\u00a0release situps;\n\u00a0\u00a0\u00a0\u00a0muscles = pushups + situps;\n\u00a0\u00a0end\n\u00a0\u00a0initial #300 $finish();\nendmodule<\/pre>\n<p>This is the same as <em>Race #3<\/em>. All continuous assignments are independent concurrent processes. You cannot depend on their order of execution within the same time region.<\/p>\n<hr \/>\n<p>And that&#8217;s the conclusion of our little race condition challenge. I hope you were able to get something out of it. By the way, these code fragments all come from real customer designs that we&#8217;ve seen in the last year or so.<\/p>\n<p><i>-dave_rich \ud83e\uddd4\ud83c\udffb<\/i><\/p>\n","protected":false},"excerpt":{"rendered":"<p>As promised, here is my response to Siemens EDA\u2019s SystemVerilog Race Condition Challenge. Race #1 Blocking and non-blocking assignments \u00a0&#8230;<\/p>\n","protected":false},"author":71589,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spanish_translation":"","french_translation":"","german_translation":"","italian_translation":"","polish_translation":"","japanese_translation":"","chinese_translation":"","footnotes":""},"categories":[1],"tags":[506,533,751,827,831],"industry":[],"product":[],"coauthors":[979],"class_list":["post-14477","post","type-post","status-publish","format-standard","hentry","category-news","tag-functional-verification","tag-ieee-1800","tag-systemverilog","tag-verification-methodology","tag-verilog"],"_links":{"self":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/14477","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/users\/71589"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/comments?post=14477"}],"version-history":[{"count":3,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/14477\/revisions"}],"predecessor-version":[{"id":18352,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/posts\/14477\/revisions\/18352"}],"wp:attachment":[{"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/media?parent=14477"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/categories?post=14477"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/tags?post=14477"},{"taxonomy":"industry","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/industry?post=14477"},{"taxonomy":"product","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/product?post=14477"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blogs.sw.siemens.com\/verificationhorizons\/wp-json\/wp\/v2\/coauthors?post=14477"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}