Lecture 06 - 2/06/2019 - Finite State Machine with Datapath 5:00 Hardware Design Recap -------------------------- An important reminder when designing hardware: HARDWARE != C In C you can write code such as: c = c + 1; a = c + 2; which does a sequential assignment of two variables, a and c. Hardware does not work like that. In hardware, variables are stored in REGISTERS. All registers are updated at the same time, namely at the next clock edge. The way we would write the previous statement in hardware would be: (not exact Verilog syntax, only showing the idea) always @(posedge clk) begin c <= cnext; a <= anext; end always @(*) begin cnext = c + 1; anext = cnext + 2; // we use cnext to get exactly the same behavior as in C above end Therefore, never write count = count + 2; in an always @(*) block if you intend to use count as a state variable. - Equals, Arrow Sign - Zooming in Modelsim 5:05 Pattern Recognizer FSM --------------------------- As a brief warmup, we are going to design a 'pattern recognizer' FSM. This is an engine that decodes a stream of data bits in search of a specific pattern. For example, a pattern recognizer of '001' would work as follows: data 0 1 1 0 1 0 1 1 1 0 0 1 1 1 0 0 0 0 1 1 1 1 found 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 ******* ******* We are going to design this as a Moore FSM. Meaning of the states: S0 ^ 0 0 1 (did not find anything) S1 0 ^ 0 1 (did find '0') S2 0 0 ^ 1 (did find '00') S3 0 0 1 ^ (did find '001') State Transition Table: State 0 1 found -------------------------------------- S0 S1 S0 0 S1 S2 S0 0 S2 S2 S3 0 S3 S1 S0 1 Design in Verilog: module dataonly(input wire clk, input wire rst, input wire data, output wire found); reg [2:0] state, nextstate; parameter S0 = 0, S1 = 1, S2 = 2, S3 = 3; always @(posedge clk) state <= (rst) ? 3'b0 : nextstate; always @(*) begin nextstate = state; case (state) S0: if (data == 1'b0) nextstate = S1; S1: if (data == 1'b0) nextstate = S2; else nextstate = S0; S2: if (data == 1'b1) nextstate = S3; S3: if (data == 1'b0) nextstate = S1; else nextstate = S0; default: nextstate = S0; endcase end assign found = (state == S3) ? 1'b1 : 1'b0; endmodule This is as you would expect. Keep in mind: 1/ Two processes, always @(posedge clk), always @(*) 2/ Every register in two variables: state, nextstate 3/ Default assignment on every assigned variable in an always @(*) 4/ default entry in a case statement Testbench commands: ======================================================== git clone https://github.com/vt-ece4514-s19/pattern cd pattern cd dataonly vlib work vlog dataonly.v vlog dataonlytb.v vsim -c dataonlytb -do "run 1200ns" ======================================================== Output: # t 410 data 0 found 0 # t 430 data 1 found 0 # t 450 data 0 found 0 # t 470 data 0 found 0 * # t 490 data 0 found 0 * # t 510 data 1 found 0 * # t 530 data 0 found 1 <---- 5:20 Data clocks ---------------- Let's extend the previous example to the case where the data is much slower, then the system clock, and comes with its data clock. So we are interested in a module like this: +------------+ | | dataclk ->+ | data ---->+ +---> found | | clk ->+ | +------------+ Each column in the following is a clk clock cycle. data 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 dataclk 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 data clock 'edges' | | | | | data bit seen 1 0 0 1 1 found 0 0 0 1 0 This is still the same FSM, but it computes only at the dataclk 0->1 transitions. How do we do this? We could take the previous FSM and make it compute slower. That is, we only allow transitions at the data clock 'edges' reg olddataclk; wire nextolddataclk; always @(posedge clk) begin olddataclk <= (rst) ? 1'b0 : nextolddataclk; end assign dataclkedge = dataclk & ~olddataclk; assign nextolddataclk = dataclk; Now we have a dataclkedge signal, that can be used to control the update of the FSM. Where do we use this signal? In the always @(*) block: always @(*) begin nextstate = state; if (dataclkedge) begin case (state) S0: if (data == 1'b0) nextstate = S1; S1: if (data == 1'b0) nextstate = S2; else nextstate = S0; S2: if (data == 1'b1) nextstate = S3; S3: if (data == 1'b0) nextstate = S1; else nextstate = S0; default: nextstate = S0; endcase end end Note that the overall FSM still works at the fast 50MHz clk. However, by using clk to sample the dataclk input, and by detecting 0->1 transitions on the dataclk input, we can 'slow down' the computation. Testbench commands: ======================================================== git clone https://github.com/vt-ece4514-s19/pattern cd pattern cd dataclk vlib work vlog dataclk.v vlog dataclk.v vsim -c dataclktb -do "run 1200ns" ======================================================== # t 830 data 0 dataclk 1 found 0 # t 850 data 1 dataclk 0 found 0 # t 870 data 1 dataclk 1 found 0 # t 890 data 0 dataclk 0 found 0 # t 910 data 0 dataclk 1 found 0 # t 930 data 0 dataclk 0 found 0 * # t 950 data 0 dataclk 1 found 0 * # t 970 data 0 dataclk 0 found 0 * # t 990 data 0 dataclk 1 found 0 * # t 1010 data 1 dataclk 0 found 0 * # t 1030 data 1 dataclk 1 found 0 * # t 1050 data 0 dataclk 0 found 1 <- # t 1070 data 0 dataclk 1 found 1 <- # t 1090 data 1 dataclk 0 found 0 This trick can be used for I2C, as well as for the audio interface, which we will discuss in Homework 3. 5:30 FSMD Recap --------------- We are designing circuits as a combination of a controller and a datapath. FSMD = FSM + D FSM: at clock_edge: state = state_next; evaluate: state_next = next_state_logic(state, registers, system_inputs); D: at clock_edge: registers = registers_next; evaluate: registers_next = expressions(registers, state, system_inputs) system_outputs = output_encoding(state, registers, system_inputs); The FSM: - takes care of computing the schedule of the algorithm - sends commands to the datapath - observes status from the datapath - reads system inputs writes system outputs The Datapath: - Computes expressions - sends status to the FSM controller - receives commands from the FSM controller - reads system inputs, writes system outputs Design Method: 1. Capture a high-level state machine (sometimes called an algorithmic state machine). The 'high-level' means that the state transition conditions and the state actions (i.e., outputs) can be much more complicated than single bits. 2. Create a datapath to compute the data operations (expressions) required to implement the high-level state machine 3. Formalize the interface between a controller (finite state machine) and the datapath. This means identifying the command signals (from FSM to D) and the status signals (from D to FSM) 4. Design the controller FSM by converting the high-level state machine into the FSM. 5:35 Example 2 - Laser Distance Meter ------------------------------------- The Laser Distance meter counts the amount of seconds it takes for a laser beam to travel distance D, bounce back, and be observed as a flash. By accurate time measurement, we can estimate the distance as: D = 1/2 * T * 300,000,000 m/s with T = delay in s The laser distance meter system looks like this: module laserdistancedp(input wire clk, input wire rst, input wire B, // button output wire L, // laser control input wire S, // sensor input output wire [15:0] D // distance ); Design Method: 1. Create High Level FSM 2. Create the Datapath 3. Formalize the connections Datapath/Controller 4. Design of the detailed FSM We will measure the distance using a fast running counter. The counting frequency determines the resolution. In 1 sec light travels 3.10^8 meter In 1/3.10^-8 sec light goes 1 meter So if we use a 333 MHz clock, we have a resolution of one meter. Step 1: Design the HL FSM Local Registers: 16-bit DCtr // counter register 16-bit Q // result register -------------------------------------------------------- State S0: L = 0 // laser off Q = 0 // clear result State S1: DCtr = 0 // clear counter reg if (B == 0) goto S1 else goto S2 State S2: L = 1 // laser on State S3: L = 0 // laser off DCtr = Dctr + 1 if (S == 0) goto S3 // wait for return flash else goto S4 State S4: Q = Dctr / 2 // compute and display result goto S1 -------------------------------------------------------- Step 2: Create the datapath We have the following register operations of interest: Q = 0 DCtr = 0 DCtr = DCtr + 1 Q = DCtr / 2 We have no data inputs, but Q is also used as data output D We have some control signals to control these operations: Qclr // clear Q Dclr // clear D Dinc // increment D Qupd // update Q module laserdistancedp( input wire clk, input wire Qclr, Dclr, Dinc, Qupd, output wire [15:0] D ); reg [15:0] Dctr wire [15:0] Dctr_next; reg [15:0] Q wire [15:0] Q_next; always @(posedge clk) begin Dctr <= Dctr_next; Q <= Q_next; end assign Dctr_next = Dclr ? 16'b0 : Dinc ? Dctr + 16'b1 : Dctr; assign Q_next = Qupd ? (Dctr >> 1) : Q; endmodule Step 3: Formalize the controller/datapath connections. The controller has to generate the following command signals for the datapath: Dclr, Qclr, Qup, Dinc The controller has to read the following status signals from the datapath: none The controller has the following system inputs: B, S The controller has the following system outputs: L The datapath has the following system outputs (16 bit): Q Step 4: Design the detailed FSM Inputs Outputs CurrentState B S NextState L QClr Dclr Qup Dinc S0 X X S1 0 1 0 0 0 S1 0 X S1 0 0 1 0 0 S1 1 X S2 1 0 0 0 0 S2 X X S3 0 0 0 0 0 S3 X 0 S3 0 0 0 0 1 S3 X 1 S4 0 0 0 1 0 S4 X X S1 0 0 0 0 0 Verilog Design: See https://github.com/vt-ece4514-s19/fsmd/tree/master/laserdistancemeter module laserdistancedp(input wire clk, input wire Qclr, Dclr, Dinc, Qupd, output wire [15:0] D ); reg [15:0] Dctr; wire [15:0] Dctr_next; reg [15:0] Q; wire [15:0] Q_next; always @(posedge clk) begin Dctr <= Dctr_next; Q <= Q_next; end assign Dctr_next = Dclr ? 16'b0 : Dinc ? Dctr + 16'b1 : Dctr; assign Q_next = Qupd ? (Dctr >> 1) : Qclr ? 16'd0 : Q; assign D = Q; endmodule P 5:50 (Coming Up) Homework 3 Demo, Slides