Donald E. Thomas - The Verilog Hardware Description Language, Fifth Edition (798541), страница 27
Текст из файла (страница 27)
The slave process then waits for the end of the nextclock cycle.Let’s assume that two back-to-back writes are going to be done to memory. It isinstructive to examine how the two “@(negedge clock)” statements at end of the writecycle work; the one clock event is near the end of the wiggleBusLines task and theother is the clock event at the start of the slave process. Both processes are waiting forthis edge to occur. When it does, one or the other will execute first; we do not knowwhich. The value at issue is dataLines. If wiggleBusLines executes first and starts thesecond write, it will assign dataLines with a new value of data in the first then part.
Ifthe slave starts first, it will write the value of dataLines into memory. So, which valueof dataLines will be written into memory? Given that both transfers are non-blocking, the transfers are synchronized and order independent. Indeed, care must be takento insure the order independence of data transfers. In cycle-accurate descriptions,non-blocking assignments insure this.The read cycle requires an extra clock period in the master and slave models. TaskwiggleBusLines loads addressLines with the address to read from and waits for theend of the second clock cycle before continuing.
At the end of the second cycle, thevalue in dataLines is loaded into data and that value is returned from the task to thebus master.The bus slave waits for the end of the first clock cycle and then puts the value readfrom address addressLines of m into dataLines. Thus the value read appears at thebeginning of the second clock cycle.
The slave then waits for the next negative clockedge event (i.e. the end of the read cycle) before looping around for the next bus cycle.The results of simulating Example 4.8 are shown in Figure 4.3. The simulation isdriven by the bus master process and its calls to the wiggleBusLines task. Essentially,the process reads from addresses 2 and 3, writes the values 5 and 7 respectively toConcurrent Processes127them, and then rereads them to see that the values are written correctly.
Thesystem task is called to end the simulation.The printing is controlled by thestatement in the initial statement. Sincevalues only change on the clock edges, each line of the simulation trace shows the values in the system at the end of a clock cycle. The first line shows values in the systemwhen thefirst executes.
The second line shows the values when the wiggleBusLines task first executes (it shows the system reading from address 2). dataLineshas not been written yet and thus it appears as x. The next line represents the values atthe ends of the two clock cycles in the read cycle. The value read is 29. (This corresponds to the value that was in the memory.data file.) Following through the simulation trace, we can see that the 29 in address 2 is overwritten with the value 5 by thefirst write operation.
Evidence that the value was actually stored in the memory isseen in the second to last read operation where address 2 is reread.There are several features of the description that should be emphasized:The bus master and slave processes are synchronized to the clock signal. At theend of the clock period when the negative edge occurs, these processes execute. Itis important to note that none of these processes immediately changes any of theregisters used to pass information between the processes (i.e. rwLine, addrLines,dataLines). If one of the processes had changed any of these registers, the result ofthe simulation would have relied on the order in which the simulator executedthese events — not good.
Non-blocking assignments insure correct operation.The memory array m is initialized from an external text file using thesystem task. This technique is quite useful for loading machine instructions into asimulation model of a processor, initializing data in a memory as shown here, andloading test vectors that will be applied to other parts of the system. In this case,The Verilog Hardware Description Language128thetask reads whitespace-separated hexadecimal numbers from thememory.data file and loads them consecutively into memory locations starting ataddress 0.
See Appendix F.8 for more details and options.Note that READ and WRITE were defined to be constants but tClock wasdefined to be a parameter. Parameters provide a means of specifying a default constant to use for a module. However, when the module is instantiated, the values ofthe parameters may be overridden. Section 5.2 discusses parameters in moredetail.In one statement we use the operator “!” to specify the complement of clock, andin another statement we use the operator “~” to specify the complement ofrwLine. In this context, either is correct because the values being complementedare one-bit. The “~” specifies a bitwise complement of its operand (i.e.
~4’b0101 is4’b1010). The “!” specifies the complement of the operand’s value. Assume theoperand is multibit. Then if the value is 0 (FALSE), the “!” complement is TRUE. Ifthe multibit operand is nonzero (TRUE), the “!” complement is FALSE. Thus!4’b0101 is false.References: parameters 5.2;memory specification E.2F.8; Verilog operators C; tasks 3.5.1; register specification E.1;4.5 A Simple Pipelined ProcessorThis section presents another example of a concurrent system.
Here we will design avery simple pipelined processor based on the Mark-1 description started in chapter 3.Although the example is not indicative of the complexity of current-day processors,the basic approach suggests methods of modeling such processors.4.5.1 The Basic ProcessorThe model for the processor, using the cycle-accurate style of specification, is shownin Example 4.9. An abstract level of modeling a processor allows the designer tounderstand what functionality will occur during each clock cycle, how that functionality is impacted by concurrent activity in other stages of the processor’s pipeline, andwhat the performance of the machine will be, at least in terms of clock cycles.This example is composed of two always blocks, one for each pipestage of this simple processor. The first always block models the first pipestage of the processor whichfetches instructions.
The second always block models the second pipestage which executes the instructions. Since each is described by an always block, we have modeledthe concurrency found between pipestages of a processor.Concurrent Processes129Non-blocking assignment is used across the design to synchronize the updating ofstate to the clock edge ck. With non-blocking assignment, it is important to remember that all of the right-hand sides of the assignments across the whole design (thetwo always blocks here) are evaluated before any of the left-hand sides are updated.
Inthis example, note that the instruction register (ir) is loaded in the first always blockand it is accessed in the second. Since all accesses are implemented with non-blockingassignments, we know that the instruction fetch which loads the instruction registerwill not interfere with the instruction execution in the second always block — allright-hand sides in the second always block will be evaluated before ir is updated bythe first always block.module mark1Pipe;reg[15:0]signedreg [12:0]signedreg [12:0]signedreg [15:0]regm [0:8191];pc;acc;ir;ck;//signed 8192 x 16 bit memory// signed 13 bit program counter// signed 13 bit accumulator// 16 bit instruction register// a clock signalalways @(posedge ck) beginir <= m [pc];pc <= pc + 1;endalways @(posedge ck)case (ir [15:13])3'b000 :pc <= m [ir [12:0]];3'b001 :pc <= pc + m [ir [12:0]];3'b010 : acc <= -m [ir [12:0]];3'b011: m [ir [12:0]] <= acc;3'b100,3'b101 :acc <= acc - m [ir [12:0]];3'b110 :if(acc<0)pc<=pc+l;endcaseendmoduleExample 4.9 A Pipelined ProcessorHowever, the non-blocking assignments do not guard against all problems in synchronization.
Note that when executing certain instructions, the pc is loaded in bothalways blocks — instruction 0 is such a case. The issue to consider is which update ofthe pc will occur first: the one in the first always block, or in the second? Of course,the order of updates is undefined so we need to alter this description to obtain correctoperation.130The Verilog Hardware Description Language4.5.2 Synchronization Between PipestagesIn the general case, having the same register written by two separate always blocksleads to indeterminate values being stored in the register. If a model can guaranteethat the two always blocks never write the register at the same time (i.e., during thesame state or clock time), then writing a register from two always blocks is perfectlyvalid.
However, in our case though, pc is written during every state by the fetch process and during some states by the execution process.Example 4.10 corrects this problem by adding register pctemp. This register iswritten only by the execute stage while pc is written only by the fetch stage. In thecase where a branch instruction is executed, pctemp is written with the branch target.At the same time, the next sequential instruction is fetched and the pc is incrementedby the fetch stage.