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\)倍。
//-- 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)
用于初始化,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)
用于位移N位的数据,有N个并行输入;load_shift signal
配置模式,当其为0,在时钟上升沿时载入新数据,当其为1,在时钟上升沿时右移一位(a right shift)。
移位时,最高有效位(the most significant bit)被丢掉,从serin input
读取一位新的值。比如,初始为1001
,load_shift
始终为1,serin
输入0,右移一位,于是变为0010
。
更进一步,我们可以设计一个不断循环的4位移位寄存器(a 4-bit shift register to rotate a sequence of bits)。比如初始为0001
,变为0010
,0100
,1000
,然后回到0001
。
原理:把最高位连接到serin
即可。
//-- 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 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
always @*
case (sel)
0 : out <= source0;
1 : out <= source1;
2 : out <= source2;
3 : out <= source3;
default : out <= 0;
endcase
考虑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.
module init(input wire clk, output ini);
reg ini = 0;
always @(posedge(clk))
ini <= 1;
endmodule
不过我们可以利用上面的Multiplexer, Initializer实现自定义初值的功能。
原理:一个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
仿真结果
更进一步,我们考虑带有同步信号的N位寄存器 (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。
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 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
TX
负责发送,RX
负责接收,我们在这里数据发送时按照8N1(八位数据+一位结束位,没有校验位)的法则发送数据帧,即初始化时为高电平1,开始发送数据后,发送一位低电平0提示开始发送数据,然后发送八位数据,最后发送高电平1,直到下一次再次发送数据。
比如发送字母K,其八位数据:
其发送时的电平:
而波特率就是发送数据时方波的频率
下面是一个只发送字符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)
在实际的电路中,信号并不理想,会存在延迟。
例如,一个非门的理想与实际情况:
线网(wire)A和B上信号并不是始终相反的。
再例如,一个异或门
线网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 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 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 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 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
回顾我们之前做的那个串口程序
我们运用这五个法则,发现上面的设计有以下问题
- 并不是全部都直接连接到唯一一个时钟上,违反法则1
load
没有用寄存器,违反法则3TX
连接到一个异步总线(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
然后考虑总体设计
这里load
和TX
都只在每个时钟的上升沿时,才用寄存器存一下信号,避免了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
仿真结果:
同样的,还可以考虑每隔一段时间发送字符K的程序。
原教程的例程都是西班牙语注释的,与上一个例程类似,这里就不翻译了。
//-- 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
电平拉高)、开始(将待发送字符加入到队列)、传输(传输完队列)。这样可以保证每次传输过程完整。
//-- 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)
把之前讲的整合一起,封装一个异步的串口发送模块。
端口定义:
clk
时钟rstn
重置发送模块start
开始接受数据并开始发送data
待发送数据tx
串口输出ready
高电平时提示空闲
发送数据过程:先向data
发送想要待发送的数据,然后拉高start
电平,开始发送数据,此时ready
被拉低电平,提示正在发送数据中,发送完毕后,重新拉高,提示空闲。
其余大致相同的,不再翻译。
//-- 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)
端口定义:
clk
时钟rstn
重置接收模块rx
接受数据rcv
提示数据到达data
接收到的数据
TODO