Open Fpga Verilog Tutorial 备忘

Open Fpga Verilog Tutorial 备忘

本文是基于Obijuan/open-fpga-verilog-tutorial这一系列教程写的笔记,本文权当个人的提炼以供学习,如有疏忽之处。还请批评指正。

本文会跳过对于FPGA的基础介绍,直接来到第五章的预分频器(Prescaler)。

环境配置可以参考前文ICE40 FPGA 开发全流程环境配置

另外,配图来自原教程,配图里面很多文字都是西班牙语。

Prescaler 预分频器 (Chapter 5, 6, 7, 8)

Prescalers用于减慢时钟信号,原理:使用一个N位的寄存器,每次时钟周期+1,每次输出这个寄存器的第N位,这样时钟周期就被增大为\(T_{out}=T_{in}\times 2^N\),频率\(f_{out}=f_{in} \div 2^N\)

比如一个2位的预分频器,寄存器的第1位\(d_0\)输出周期\(T_0\)为原来的\(2^1\)倍,第2位\(d_1\)输出周期\(T_1\)为原来的\(2^2\)倍。

A 2-bit prescaler

//-- prescaler.v
//-- clk_in: input clock signal
//-- clk_out: output clock signal, lower frequency
module prescaler(input clk_in, output clk_out);
wire clk_in;
wire clk_out;
    
//-- Number of bits of the prescaler (default)
parameter N = 22;
    
//-- Register for implementing the N bit counter
reg [N-1:0] count = 0;
    
//-- The most significant bit goes through the output
assign clk_out = count[N-1];
    
//-- Counter: increases upon a rising edge
always @(posedge(clk_in)) begin
  count <= count + 1;
end
    
endmodule

多个Prescalers的使用

//-- mpres.v
module mpres(input clk_in, output D1, output D2, output D3, output D4);
wire clk_in;
wire D1;
wire D2;
wire D3;
wire D4;
    
//-- Component parameters
//-- Bits for the different prescalers
//-- Change these values according to the desired LED sequence
parameter N0 = 21;  //-- Prescaler base
parameter N1 = 1;
parameter N2 = 2;
parameter N3 = 1;
parameter N4 = 2;
    
//-- Wire with base clock signal: the output of prescaler 0
wire clk_base;
    
//-- Base prescaler. Connected to the input clock signal
//-- Its output is for clk_base
//-- It's N0 bits wide
prescaler #(.N(N0))  
  Pres0(
   .clk_in(clk_in),
   .clk_out(clk_base)
  );
    
//-- Channel 1: Prescaler of N1 bits, connexted to LED 1
prescaler #(.N(N1))
  Pres1(
    .clk_in(clk_base),
    .clk_out(D1)
  );
    
//-- Channel 2: Prescaler of N2 bits, connexted to LED 2
prescaler #(.N(N2))
  Pres2(
    .clk_in(clk_base),
    .clk_out(D2)
  );
    
//-- Channel 3: Prescaler of N3 bits, connexted to LED 3
prescaler #(.N(N3))
  Pres3(
    .clk_in(clk_base),
    .clk_out(D3)
  );
    
//-- Channel 4: Prescaler of N4 bits, connexted to LED 4
prescaler #(.N(N4))
  Pres4(
    .clk_in(clk_base),
    .clk_out(D4)
  );
    
endmodule

Initializer (Chapter 9)

Initializer

用于初始化,Intializer保持为0,当时钟边沿到达时变为1,并且运行过程中保持为1

注意:在Lattice,初值只在仿真中有效,在综合时初值始终为0

//-- init.v  (optimized version)
module init(input wire clk, output ini);

//-- 1 bit register initialized to 0 for sim
//-- for synthesize, this initialization value is ignored. 
reg ini = 0;
    
//-- on the rising edge of the clock, we assign 1 to the output
always @(posedge(clk))
  ini <= 1;
    
endmodule

Shift register 移位寄存器 (Chapter 10)

Shift register

用于位移N位的数据,有N个并行输入;load_shift signal配置模式,当其为0,在时钟上升沿时载入新数据,当其为1,在时钟上升沿时右移一位(a right shift)。

移位时,最高有效位(the most significant bit)被丢掉,从serin input读取一位新的值。比如,初始为1001load_shift始终为1,serin输入0,右移一位,于是变为0010

更进一步,我们可以设计一个不断循环的4位移位寄存器(a 4-bit shift register to rotate a sequence of bits)。比如初始为0001,变为001001001000,然后回到0001

原理:把最高位连接到serin即可。

shift4

//-- shift4.v
module shift4(input wire clk, output reg [3:0] data);
    
//-- parameters of the sequencer
parameter NP = 21;  //-- Bits of the prescaler
parameter INI = 1;  //-- initial value to load into the shift register
    
//-- clock from the prescaler
wire clk_pres;
    
//-- Shift / load. Signal indicating whether the register is loaded or shifted
//-- shift = 0: load
//-- shift = 1: shifted
reg load_shift = 0;
    
//-- serial input of the register
wire serin;
    
//-- Instantiate the N bit prescaler
prescaler #(.N(NP))
  pres1 (
    .clk_in(clk),
    .clk_out(clk_pres)
  );
    
//-- Initializer
always @(posedge(clk_pres)) begin
    load_shift <= 1;
end
    
//-- shift register
always @(posedge(clk_pres)) begin
  if (load_shift == 0)  //-- Load mode
    data <= INI;
  else
    data <= {data[2:0], serin};
end
    
//-- assign the MSB to the serial input to create a ring
assign serin = data[3];
    
endmodule

Multiplexor 数据选择器 (Chapter 11, 12)

用于使用指定的数据来源。

2 to 1 multipleser

首先考虑2 to 1 multiplexer,当sel为0时,选择source 0为输出,否则为source 1

// you can also use "always @*", this list automatically includes all input signals.
always @(source0 or source1 or sel)
  if (sel == 0)
    dout <= source0;
  else
    dout <= source1;

更进一步,考虑4 to 1 multiplexer

4 to 1 multiplexer

always @*
  case (sel)
     0 : out <= source0;
     1 : out <= source1;
     2 : out <= source2;
     3 : out <= source3;
     default : out <= 0;
  endcase

考虑M to 1 multiplexer

M to 1 multiplexer

初始化寄存器值 (Chapter 13, 14)

下面是Initializer的代码,其中reg ini = 0;赋初值的操作,在综合时其实无效,初值都是0。

Some FPGAs (and CPLDs) support initial block for synthesis (Intel comes to mind). Others have FFs that only initialize to zero (Lattice) - and ignore initial blocks during synthesis.

https://electronics.stackexchange.com/a/573755

module init(input wire clk, output ini);
reg ini = 0;
always @(posedge(clk))
  ini <= 1;
endmodule

不过我们可以利用上面的Multiplexer, Initializer实现自定义初值的功能。

Initializing registers

原理:一个2 to 1 Multiplexer的source 0连接到自定义初值,source 1连接到正常输入值,然后一个Initializer连接到这个Multiplexer上,当初始化前,Initializer输出为0,于是Multiplexer输出source 0的自定义初值,初始化后,Multiplexer输出source 1的正常输入值。

一个可以自定义初始值并且会不断翻转的4bit寄存器。

// init_reg_and_reverse.v
module init_reg_and_reverse(
    input wire clk,
    output wire [3:0] data
);

parameter ini = 4'b1010;
parameter n_pres = 23;

wire [3:0] din;
reg [3:0] dout;
assign data = dout;

wire clk_pres;
reg sel=0;

always @(posedge(clk_pres))
    dout <= din;

assign din = (sel == 0) ? ini : ~dout;

always @(posedge(clk_pres))
    sel <= 1;

prescaler #(.N(n_pres))
    PRES(
        .clk_in(clk),
        .clk_out(clk_pres)
    );

endmodule
// init_reg_and_reverse_tb.v
`timescale 1ns/100ps
module init_reg_and_reverse_tb();

wire [3:0] data;
reg clk=0;

init_reg_and_reverse #(.n_pres(1))
    REG(
        .clk(clk),
        .data(data)
    );

always #1 clk = ~clk;

initial begin
    $dumpfile("reg.vcd");
    $dumpvars(0, init_reg_and_reverse_tb);

    #30 $finish;
end


endmodule

仿真结果

res

更进一步,我们考虑带有同步信号的N位寄存器 (N-bit Register with Synchronous Reset)

N-bit Register with Synchronous Reset

rst信号为0时,寄存器为自定义的初始值INI;当rts为1时,寄存器为输入值din

//-- register.v
module register (rst, clk, din, dout);
    
//-- Parametros:
parameter N = 4;     //-- Register bits
parameter INI = 0;   //-- Initial value
    
//-- Port declaration
input wire rst;
input wire clk;
input wire [N-1:0] din;
output reg [N-1:0] dout;
    
//-- Registration
always @(posedge(clk))
  if (rst == 0)
    dout <= INI; //-- initialization
  else
    dout <= din; //-- normal operation
    
endmodule

Frequency Divider 分频器 (Chapter 15)

相比于Prescaler只能使时钟频率减慢\(2^n\)倍,Frequency Divider更加灵活,可以使时钟频率减少任意倍数。

比如一个Divisor between 3,如下图,虽然占空比改变了,但确实使时钟频率变为了原来的1/3。

A divisor between 3

Frequency Divider 基于 module M counter,module M counter 就是一个从0到M-1循环的计数器。比如module 3 counter就是在3个脉冲后初始化,从0开始,开始计数1,2,然后再回到0,以此循环。

reg [1:0] data = 0;

always @(posedge clk)
  if (data == 2) 
    data <= 0;
  else 
    data <= data + 1;

Frequency Divider 实现原理就是取module M counter的最高位,其实和Prescaler差不多,只不过不再是简单的2的幂次。

Divisor between 3的Verilog实现代码:

//-- div3.v
module div3(input wire clk_in, output wire clk_out);
    
reg [1:0] divcounter = 0;
    
//-- Counter module 3
always @(posedge clk_in)
  if (divcounter == 2) 
    divcounter <= 0;
  else 
    divcounter <= divcounter + 1;
    
//-- Take out the most significant bit per CLK out
assign clk_out = divcounter[1];
    
endmodule

仿真结果

Divisor between 3

更进一步,我们考虑Divisor between M

//- divM.v
module divM(input wire clk_in, output wire clk_out);
    
//-- Divisor default
//-- Because the clock on the popsicle is 12Mhz, we set its value to 12m.
//-- Obtain an output frequency of 1Hz
parameter M = 12_000_000;
    
//-- Number of digits to store divisor
//-- They are calculated with the Verilog $clog2 function, which returns
//-- The number of digits required to represent the number of M
//-- is a local parameter and cannot be modified during instantiation.
localparam N = $clog2(M);
    
//-- Registers that implement counter module M
reg [N-1:0] divcounter = 0;
    
//-- Counter module M
always @(posedge clk_in)
  if (divcounter == M - 1) 
    divcounter <= 0;
  else 
    divcounter <= divcounter + 1;
    
//-- Take out the most significant bit per CLK out
assign clk_out = divcounter[N-1];
    
endmodule

Asynchronous Serial Communications 异步串行通信 (Chapter 20, 21)

即UART

UART

TX负责发送,RX负责接收,我们在这里数据发送时按照8N1(八位数据+一位结束位,没有校验位)的法则发送数据帧,即初始化时为高电平1,开始发送数据后,发送一位低电平0提示开始发送数据,然后发送八位数据,最后发送高电平1,直到下一次再次发送数据。

img

比如发送字母K,其八位数据:

ASCII of K

其发送时的电平:

img

而波特率就是发送数据时方波的频率

img

下面是一个只发送字符K的串口程序,当load signal为0时,发送字符K,为1时拉高TX电平,停止发送。这里的实现用了一个移位寄存器,每次取最低位(右端),然后发送字符K时,先发送1提示TX空闲,然后再发一个0提示开始新的数据帧;空闲时,不断发送1到数据末尾(左端)提示TX空闲即可。

//- file: baudtx. V
`default_nettype none

`include "baudgen.vh"

//---The module that sends cunado character loading is set to 1
module baudtx(input wire clk,       //- system clock (12Mhz on popsicle)
              input wire load,      //- load / displacement signal
              output wire tx        //- serial data output (pointing to PC)
             );

//- parameter: transmission speed
parameter BAUD =  `B115200;

//- 10 bit register for storing the grating to be transmitted:
//--1-bit start + 8-bit data + 1-bit stop
reg [9:0] shifter;

//-- transmission clock
wire clk_baud;

//- shift register with parallel load
//- when DTR is 0, load the drawing
//- when DTR is 1, it moves to the right, and
//- enter "1" on the left
always @(posedge clk_baud)
  if (load == 0)
    shifter <= {"K",2'b01};
  else
    shifter <= {1'b1, shifter[9:1]};

//-- take the least significant bit in the shift register from TX
//- when we are in loading mode (DTR = = 0), it is always
//-- the line is always idle. In this way
//--The start TX is in an idle state, although the value of the offset register
//-- unknown
assign tx = (load) ? shifter[0] : 1;

//-- the frequency divider obtains the transmission clock
divider #(BAUD)
  BAUD0 (
    .clk_in(clk),
    .clk_out(clk_baud)
  );

endmodule

其中值得注意的是,代码第一行的default_nettype none指定了默认的数据类型为none(默认类型为wire),这样如果忘记声明某个变量的类型,编译器会立刻报错,避免了潜在的错误声明。

Synchronous Design Rules 同步设计规则 (Chapter 22)

在实际的电路中,信号并不理想,会存在延迟。

例如,一个非门的理想与实际情况:

A NOT door

线网(wire)A和B上信号并不是始终相反的。

再例如,一个异或门

A XOR door

线网C上存在Spurious Pulse,即虚假的信号。

为了避免这些问题,我们需要遵循synchronous design rules

Rule 1: A single clock to rule them all

所有的时钟输入必须直接连接到同一个唯一的时钟上。

ALL clock inputs must be connected directly to a single clock

Rule 1

Rule 2: Sensitivity to the same flank: All to one, fuenteovejuna!

所有对时钟的触发,要么都上升沿触发,要么都下降沿触发。

All elements that wear a watch will be sensitive to the same flank. Either the uphill or the downhill, it is indifferent, but all sensitive to the same

Rule 2

Rule 3: Before entering a combinational circuit, please register

所有组合电路的输入必须来自与时钟同步的时序电路(sequential circuit)的输出,或者来自其他遵循此法则的组合电路的输出上。

All inputs of combinational circuits must be connected to sequential circuit outputs, synchronized with the system clock, or to other combinational circuits that comply with this rule. That is, any input from a combinational circuit has to be captured first by a record. entries that comply with this rule are called synchronized entries

Rule 3

Rule 4: Before entering a sequential circuit, please register

所有时序电路(sequential circuit)的输入必须来自另外一个时序电路的输出,或者来自遵循法则3的组合电路的输出。

All inputs to sequential circuits must come from the outputs of other sequential circuits or from combinational circuits that comply with rule 3. That is, even to enter the sequential circuits, it is necessary that the signals are synchronized.

Rule 4

Rule 5: Outputs of a combinational: Only to inputs of another combinational, synchronous inputs or outputs of the synchronous circuit

组合电路可以输出到另外一个组合电路、另外一个时序电路的同步输入、或者电路的直接输出。不能直接输出到自己本身,或者输出到时钟信号。

This rule tells us where we can connect the output of a combinational circuit. Either at the input of another combinational, at the synchronous input of a sequential or as a direct output of our circuit. It is strictly forbidden to connect them to the inputs of the combinational itself (no direct feedback) or to the input of the clock signal

Rule 5

回顾我们之前做的那个串口程序

The original circuit diagram

我们运用这五个法则,发现上面的设计有以下问题

  • 并不是全部都直接连接到唯一一个时钟上,违反法则1
  • load没有用寄存器,违反法则3
  • TX连接到一个异步总线(asynchronous bus),为了安全考虑,也需要用寄存器。

考虑改进

首先,重新设计一个分频器,因为法则1,所以不能直接输出分频信号作为移位寄存器的时钟信号。

//--Baudgen. V file
`include "baudgen.vh"

//- ITEM:
//-- clk: System clock signal (12 MHz popsicle)
//---clk_ena: enabled.
//--	1. Normal operation. Transmit pulse
//--	0: initialize and stop. No pulse is emitted
//
//- OUTLET:
//---Clock output. Output signal of mark bit time
//- width of one clock cycle. Unregistered output
module baudgen(input wire clk,
               input wire clk_ena, 
               output wire clk_out);

//- baud rate (default)
parameter M = `B115200;

//-- Numero de bits para almacenar el divisor de baudios
localparam N = $clog2(M);

//- number of bits for storing baud rate divider
reg [N-1:0] divcounter = 0;

//--Counter module M
always @(posedge clk)

  if (clk_ena)
	//- normal operation
    divcounter <= (divcounter == M - 1) ? 0 : divcounter + 1;
  else
    //- counter "frozen" to maximum value
    divcounter <= M - 1;

//- if the generator
//- enable (CLK ENA = = 1)
//- otherwise, take 0
assign clk_out = (divcounter == 0) ? clk_ena : 0;

endmodule

然后考虑总体设计

img

这里loadTX都只在每个时钟的上升沿时,才用寄存器存一下信号,避免了Spurious Pulse。

//- file: txtest. V
`default_nettype none

`include "baudgen.vh"

//---Module that sends characters when loaded as 1
//---TX output registered
module txtest(input wire clk,       //- system clock (12Mhz on popsicle)
              input wire load,      //- load / displacement signal
              output reg tx         //- serial data output (pointing to PC)
             );

//- parameter: transmission speed
//- worst case test: 300 baud
parameter BAUD =  `B300;

//- 10 bit register for storing the grating to be transmitted:
//--1-bit start + 8-bit data + 1-bit stop
reg [9:0] shifter;

//- record the load signal
reg load_r; 

//-- transmission clock
wire clk_baud;

//- record the input load
//-(conform to synchronous design rules)
always @(posedge clk)
  load_r <= load;

//- shift register with parallel load
//- when load R is 0, the drawing will be loaded
//-- when load R is 1 and baud rate clock is 1, it will
//- right, send next bit
//- input "1" on the left
always @(posedge clk)
  //- loading mode
  if (load_r == 0)
    shifter <= {"K",2'b01};

  //- scroll mode
  else if (load_r == 1 && clk_baud == 1)
    shifter <= {1'b1, shifter[9:1]};

//-- take the least significant bit in the shift register from TX
//- when we are in load mode (load r = = 0), it is always
//-- the line is always idle. In this way
//--The start TX is in an idle state, although the value of the offset register
//-- unknown
//- this is a registered output because TX is connected to the synchronization bus
//- stray pulse (flicker) must be prevented
always @(posedge clk)
  tx <= (load_r) ? shifter[0] : 1;

//-- the frequency divider obtains the transmission clock
baudgen #(BAUD)
  BAUD0 (
    .clk(clk),
    .clk_ena(load_r),
    .clk_out(clk_baud)
  );

endmodule

仿真结果:

img

同样的,还可以考虑每隔一段时间发送字符K的程序。

img

原教程的例程都是西班牙语注释的,与上一个例程类似,这里就不翻译了。

//-- Fichero txtest3.v
`default_nettype none

`include "baudgen.vh"
`include "divider.vh"

//--- Modulo que envia un caracter periodicamente
//--- La salida tx ESTA REGISTRADA
module txtest3(input wire clk,       //-- Reloj del sistema (12MHz en ICEstick)
               output reg tx         //-- Salida de datos serie (hacia el PC)
              );

//-- Parametro: velocidad de transmision
//-- Pruebas del caso peor: a 300 baudios
parameter BAUD =  `B300;

//-- Parametro: Periodo del caracter
parameter DELAY = `T_250ms;

//-- Registro de 10 bits para almacenar la trama a enviar:
//-- 1 bit start + 8 bits datos + 1 bit stop
reg [9:0] shifter;

//-- Señal de load registrada
reg load_r; 

//-- Reloj para la transmision
wire clk_baud;

//-- Señal periodica
wire load;

//-- Registro de desplazamiento, con carga paralela
//-- Cuando load_r es 0, se carga la trama
//-- Cuando load_r es 1 y el reloj de baudios esta a 1 se desplaza hacia
//-- la derecha, enviando el siguiente bit 
//-- Se introducen '1's por la izquierda
always @(posedge clk)
  //-- Modo carga
  if (load_r == 0)
    shifter <= {"K",2'b01};

  //-- Modo desplazamiento
  else if (load_r == 1 && clk_baud == 1)
    shifter <= {1'b1, shifter[9:1]};

//-- Sacar por tx el bit menos significativo del registros de desplazamiento
//-- Cuando estamos en modo carga (load_r == 0), se saca siempre un 1 para 
//-- que la linea este siempre a un estado de reposo. De esta forma en el 
//-- inicio tx esta en reposo, aunque el valor del registro de desplazamiento
//-- sea desconocido
//-- ES UNA SALIDA REGISTRADA, puesto que tx se conecta a un bus sincrono
//-- y hay que evitar que salgan pulsos espureos (glitches)
always @(posedge clk)
  tx <= (load_r) ? shifter[0] : 1;

//-- Divisor para obtener el reloj de transmision
baudgen #(BAUD)
  BAUD0 (
    .clk(clk),
    .clk_ena(load_r),
    .clk_out(clk_baud)
  );

//-- Registrar la entrada load
//-- (para cumplir con las reglas de diseño sincrono)
always @(posedge clk)
  load_r <= load;

//-- Divisor para generar señal periodica
divider #(DELAY)
  DIV0 (
    .clk_in(clk),
    .clk_out(load)
  );

endmodule

Finite Controllers and Automata 有限状态机 (Chapter 23)

就是有限状态机,这里为串口程序设计三个状态:空闲(保持TX电平拉高)、开始(将待发送字符加入到队列)、传输(传输完队列)。这样可以保证每次传输过程完整。

finite controllers and automata

//-- File fsmtx.v
`default_nettype none

`include "baudgen.vh"

//---Module that sends characters when loaded as 1
//---TX output registered
module fsmtx (input wire clk,       //- system clock (12Mhz on popsicle)
              input wire start,     //- activate 1 transmission
              output reg tx         //- serial data output (pointing to PC)
             );

//- parameter: transmission speed
//- worst case test: 300 baud
parameter BAUD =  `B300;

//--Characters to send
parameter CAR = "A";

//- 10 bit register for storing the grating to be transmitted:
//--1-bit start + 8-bit data + 1-bit stop
reg [9:0] shifter;

//-- start signal has been recorded
reg start_r; 

//-- transmission clock
wire clk_baud;

//-- Reset
reg rstn = 0;

//-- Bitcounter
reg [3:0] bitc;

//--------- Microordenes
wire load;    //- load the shift register. Set to 0 delete
              //- bit counter
wire baud_en; //-- enable transmission baud rate generator

//-------------------------------------
//-- DATA PATH
//-------------------------------------

//-- registration start entry
//-(conform to synchronous design rules)
always @(posedge clk)
  start_r <= start;

//- shift register with parallel load
//- when load R is 0, the drawing will be loaded
//-- when load R is 1 and baud rate clock is 1, it will
//- right, send next bit
//- input "1" on the left
always @(posedge clk)
  //-- Reset
  if (rstn == 0)
    shifter <= 10'b11_1111_1111;

  //- loading mode
  else if (load == 1)
    shifter <= {CAR,2'b01};

  //- scroll mode
  else if (load == 0 && clk_baud == 1)
    shifter <= {1'b1, shifter[9:1]};

always @(posedge clk)
  if (load == 1)
    bitc <= 0;
  else if (load == 0 && clk_baud == 1)
    bitc <= bitc + 1;

//-- take the least significant bit in the shift register from TX
//- when we are in load mode (load r = = 0), it is always
//-- the line is always idle. In this way
//--The start TX is in an idle state, although the value of the offset register
//-- unknown
//- this is a registered output because TX is connected to the synchronization bus
//- stray pulse (flicker) must be prevented
always @(posedge clk)
  tx <= shifter[0];

//-- the frequency divider obtains the transmission clock
baudgen #(BAUD)
  BAUD0 (
    .clk(clk),
    .clk_ena(baud_en),
    .clk_out(clk_baud)
  );

//------------------------------
//-- CONTROLADOR
//------------------------------

//- state of controller finite automaton
localparam IDLE = 0;
localparam START = 1;
localparam TRANS = 2;

//- state of controller automaton
reg [1:0] state;

//-- transition between states
always @(posedge clk)

  //-- automatic reset. Initial state
  if (rstn == 0)
    state <= IDLE;

  else
    //-- switch to the following states:
    case (state)

        //-- rest state. When the signal comes out
        //--Set from to 1
      IDLE: 
        if (start_r == 1) 
          state <= START;
        else 
          state <= IDLE;

        //-- startup status. Ready to start
        //-- transmission. Duration: 1 clock cycle
      START:
        state <= TRANS;

//-- broadcasting. It was in this state until
//- all bits not transmitted have been transmitted
      TRANS:
        if (bitc == 11)
          state <= IDLE;
        else
          state <= TRANS;

//-- default setting. Not used. position
//- cover all cases where no latches are generated
      default:
        state <= IDLE;

    endcase

//- generate micro command
assign load = (state == START) ? 1 : 0;
assign baud_en = (state == IDLE) ? 0 : 1;


//- Initializer
always @(posedge clk)
  rstn <= 1;

endmodule

Asynchronous Serial Transmission Unit 异步串口发送模块 (Chapter 24)

把之前讲的整合一起,封装一个异步的串口发送模块。

UART-TX Module

端口定义:

  • clk 时钟
  • rstn 重置发送模块
  • start 开始接受数据并开始发送
  • data 待发送数据
  • tx 串口输出
  • ready 高电平时提示空闲

发送数据过程:先向data发送想要待发送的数据,然后拉高start电平,开始发送数据,此时ready被拉低电平,提示正在发送数据中,发送完毕后,重新拉高,提示空闲。

process

其余大致相同的,不再翻译。

//-- Fichero: uart_tx.v
`default_nettype none

`include "baudgen.vh"

//----Serial transmission module
//---TX output registered
module uart_tx (
         input wire clk,        //- system clock (12Mhz on popsicle)
         input wire rstn,       //--Global reset (active low level)
         input wire start,      //- activate 1 transmission
         input wire [7:0] data, //--Bytes to transfer
         output reg tx,         //- serial data output (pointing to PC)
         output wire ready      //- transmitter ready / busy
       );

//-- Parametro: velocidad de transmision
parameter BAUD =  `B115200;

//-- Señal de start registrada
reg start_r; 

//-- Reloj para la transmision
wire clk_baud;

//-- Bitcounter
reg [3:0] bitc;

//-- Datos registrados
reg [7:0] data_r;

//--------- Microordenes
wire load;    //-- Carga del registro de desplazamiento. Puesta a 0 del
              //-- contador de bits
wire baud_en; //-- Habilitar el generador de baudios para la transmision

//-------------------------------------
//-- RUTA DE DATOS
//-------------------------------------

//-- Registrar la entrada start
//-- (para cumplir con las reglas de diseño sincrono)
always @(posedge clk)
  start_r <= start;

always @(posedge clk)
  if (start == 1 && state == IDLE)
    data_r <= data;

//-- Registro de 10 bits para almacenar la trama a enviar:
//-- 1 bit start + 8 bits datos + 1 bit stop
reg [9:0] shifter;

//-- Cuando la microorden load es 1 se carga la trama
//-- con load 0 se desplaza a la derecha y se envia un bit, al
//-- activarse la señal de clk_baud que marca el tiempo de bit
//-- Se introducen 1s por la izquierda
always @(posedge clk)
  //-- Reset
  if (rstn == 0)
    shifter <= 10'b11_1111_1111;

  //-- Modo carga
  else if (load == 1)
    shifter <= {data_r,2'b01};

  //-- Modo desplazamiento
  else if (load == 0 && clk_baud == 1)
    shifter <= {1'b1, shifter[9:1]};

//-- Contador de bits enviados
//-- Con la microorden load (=1) se hace un reset del contador
//-- con load = 0 se realiza la cuenta de los bits, al activarse
//-- clk_baud, que indica el tiempo de bit
always @(posedge clk)
  if (load == 1)
    bitc <= 0;
  else if (load == 0 && clk_baud == 1)
    bitc <= bitc + 1;

//-- Sacar por tx el bit menos significativo del registros de desplazamiento
//-- ES UNA SALIDA REGISTRADA, puesto que tx se conecta a un bus sincrono
//-- y hay que evitar que salgan pulsos espureos (glitches)
always @(posedge clk)
  tx <= shifter[0];

//-- Divisor para obtener el reloj de transmision
baudgen #(BAUD)
  BAUD0 (
    .clk(clk),
    .clk_ena(baud_en),
    .clk_out(clk_baud)
  );

//------------------------------
//-- CONTROLADOR
//------------------------------

//-- Estados del automata finito del controlador
localparam IDLE  = 0;  //-- Estado de reposo
localparam START = 1;  //-- Comienzo de transmision
localparam TRANS = 2;  //-- Estado: transmitiendo dato

//-- Estados del autómata del controlador
reg [1:0] state;

//-- Transiciones entre los estados
always @(posedge clk)

  //-- Reset del automata. Al estado inicial
  if (rstn == 0)
    state <= IDLE;

  else
    //-- Transiciones a los siguientes estados
    case (state)

      //-- Estado de reposo. Se sale cuando la señal
      //-- de start se pone a 1
      IDLE: 
        if (start_r == 1) 
          state <= START;
        else 
          state <= IDLE;

      //-- Estado de comienzo. Prepararse para empezar
      //-- a transmitir. Duracion: 1 ciclo de reloj
      START:
        state <= TRANS;

      //-- Transmitiendo. Se esta en este estado hasta
      //-- que se hayan transmitido todos los bits pendientes
      TRANS:
        if (bitc == 11)
          state <= IDLE;
        else
          state <= TRANS;

      //-- Por defecto. NO USADO. Puesto para
      //-- cubrir todos los casos y que no se generen latches
      default:
        state <= IDLE;

    endcase

//-- Generacion de las microordenes
assign load = (state == START) ? 1 : 0;
assign baud_en = (state == IDLE) ? 0 : 1;

//-- Señal de salida. Esta a 1 cuando estamos en reposo (listos
//-- para transmitir). En caso contrario esta a 0
assign ready = (state == IDLE) ? 1 : 0;

endmodule

Asynchronous Series Receiving Unit 异步串口接收模块 (Chapter 25)

rx

端口定义:

  • clk 时钟
  • rstn 重置接收模块
  • rx 接受数据
  • rcv 提示数据到达
  • data 接收到的数据

TODO

posted @ 2022-08-05 22:11  Santiego  阅读(203)  评论(0编辑  收藏  举报