17.4 实验原理
17.4.1 RAM的介绍
从之前的实验我们可以得知,ADC模块使用的是24MHz的时钟,而驱动液晶屏需要使用到50MHz在时钟。为了使ADC采样数据顺利地传输到另一个时钟域,我们使用了双口RAM进行跨时钟域的数据缓冲。
RAM(随机存取存储器)是计算机系统中的一种主要存储器件,用于存储和读取数据。在RAM中,单端口RAM(Single-port RAM)和双端口RAM(Dual-port RAM)是两种常见的类型,双端口RAM又分为真双端口(True dual-port RAM)和伪双端口RAM(Simple dual-port RAM)。
- 单端口RAM(Single-port RAM): 输入只有一组数据线和一组地址线,读写共用地址线,输出只有一个端口。这意味着,如果CPU需要读取RAM中的数据并将其写入另一个位置,必须先执行读取操作,然后执行写入操作。
- 伪双端口RAM(Simple dual-port RAM): 输入有一组数据线,两组地址线,输出只有一个端口。伪双端口RAM可以提供并行读写操作,避免了传统单端口RAM的等待时间,因此有更快的访问速度和响应时间。
- 真双端口RAM(True dual-port RAM): 输入有两组地址线和两组数据线,输出有两个端口。所以双口RAM两个端口都分别带有读写端口,可以在没有干扰的情况下进行读写,彼此互不干扰。这种RAM通常用于高端计算机系统中,因为它可以提高系统性能。
在本实验中,根据实际需求,我们选择了伪双端口RAM。当然除了RAM以外,我们也可以使用FIFO进行ADC的数据缓冲。

代码实现如下:
module double_ram (
rst_n,
cs_n,//select signal
//A port write signals
clk_a,
wrdata_a,
wraddr_a,
wrena_n,
//B port read signals
clk_b,
rdaddr_b,
rdenb_n,
rddata_b
);
parameter ADDR_WIDTH=10;
parameter DATA_WIDTH=8;
parameter DATA_DEPTH=1024;
input clk_a;
input clk_b;
input rst_n;
input cs_n;
input [DATA_WIDTH-1:0] wrdata_a;
input [ADDR_WIDTH-1:0] wraddr_a;
input wrena_n;
input [ADDR_WIDTH-1:0] rdaddr_b;
input rdenb_n;
output [DATA_WIDTH-1:0] rddata_b;
reg [DATA_WIDTH-1:0] rddata;
integer i;
reg [DATA_WIDTH-1:0] mem [DATA_DEPTH-1:0];
always@(posedge clk_a or negedge rst_n) begin
if(~rst_n) begin
for(i=0;i<DATA_DEPTH;i=i+1)
mem[i]<=0;
end
else if(cs_n==1'b0 & wrena_n==1'b0 )
mem[wraddr_a]<=wrdata_a; //write
end
//read
always@(posedge clk_b or negedge rst_n)begin
if(~rst_n)begin
rddata<=0;
end
else if(cs_n==1'b0 & rdenb_n==1'b0)begin
rddata<=mem[rdaddr_b];
end
else
rddata<=rddata;
end
assign rddata_b=rddata;
endmodule
17.4.2 单比特数据的跨时钟域传输
想象一下,如果频率较高的时钟域A中的信号D1 要传到频率较低的时钟域B,但是D1只有一个时钟脉冲宽度(1T,CLKA),clkb 就有几率采不到D1了,如图:

对于从50MHz的时钟域到24MHz时钟域的单比特数据,我们可以使用“展宽”在方法,使该信号能够顺利地被24MHz的时钟信号采集到。我们可以使用多级寄存器缓存需要传输的信号,根据缓存寄存器的数据得到对信号的展宽并对其边沿检测,这样就可以现实从频率较高的时钟域向频率较低的时钟域的信号传输。最后通过双级触发器避免亚稳态的干扰。
/*从快时钟域到慢时钟域,展宽脉冲,使24M时钟可以采集*/
reg wrram_or;
always@(posedge clk_50MHz or negedge rst_n)
begin
if(!rst_n) begin
wrram_flag_r0 <= 1'b0;
wrram_flag_r1 <= 1'b0;
end
else begin
wrram_flag_r0 <= wrram_flag;
wrram_flag_r1 <= wrram_flag_r0;
end
end
always@(posedge clk_50MHz or negedge rst_n)
begin
if(!rst_n) begin
wrram_or <= 1'b0;
end
else begin
wrram_or <= (wrram_flag | wrram_flag_r0 | wrram_flag_r1);
end
end
/*双级触发器消除亚稳态*/
reg wrram_sync,wrram_sync_r0,wrram_sync_r1;
reg wrram_pulse;
always@(posedge clk24Mhz or negedge rst_n)
begin
if(!rst_n) begin
wrram_sync <= 1'b0;
wrram_sync_r0 <= 1'b0;
wrram_sync_r1 <= 1'b0;
end
else begin
wrram_sync <= wrram_or;
wrram_sync_r0 <= wrram_sync;
wrram_sync_r1 <= wrram_sync_r0;
end
end
若要让单bit信号从24MHz到50MHz的跨时钟域传输,也可以使用双级触发器消除亚稳态的出现。但如果是对于多比特数据,在进行传输时候会因为时序问题导致所有寄存器不会同时翻转(不是不翻转,是不同时翻转!),所以容易在跨时钟传输的时候出现中间态。使用格雷码可以避免这种现象,但是当格雷码不是按计数顺序变化(非顺序变化相当于每次变化不止一位),这同样是不允许的。所以可以使用一位的使能信号代替多位信号进行跨时钟域的传输,此处获取RAM读取信号达到读取完成的值。
assign wr_ram_fin = (wraddr == RAM_DEPTH-1)?1'b1:1'b0;
always@(posedge clk_50MHz or negedge rst_n)
begin
if(!rst_n) begin
wr_ram_fin_r0 <= 1'b0;
wr_ram_fin_r1 <= 1'b0;
end
else begin
wr_ram_fin_r0 <= wr_ram_fin;
wr_ram_fin_r1 <= wr_ram_fin_r0;
end
end
17.4.3 频率测算模块的设计
在本实验中,我们使用的是周期测量法,即在一个被测信号的周期内,测量基准时钟的个数,得到被测信号的周期,再将其转化为频率。以下图为例 其中:
- sys_clk:系统的基准时钟
- clk_fx:被测信号
在被测信号clk_fx为高电平的时间内,使用基准时钟进行计数,计数个数为cnt(图中为8),则cnt✖基准时钟周期=半个被测信号的周期。所以cnt/ FERQ_SYS(基准时钟频率)=(1/2) * (1/clk_fx),化简后clk_fx = FERQ_SYS/(cnt*2)
上例是对方波进行频率的测量,然而我们的信号发生模块产生的信号除了方波以外,还有正弦波、三角波和锯齿波。所以我们根据adc采样数据的中间值,将这些信号转换为方波信号,这样就可以很简单的测量出信号的频率。获取采样中间值代码如下:
parameter CNT1MS = 240_000;
reg [17:0] cnt_1ms;
reg flag_1ms;
reg [7:0] max_data;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
cnt_1ms <= 'd0;
end
else if(flag_1ms) begin
cnt_1ms <= 'd0;
end
else
cnt_1ms <= cnt_1ms + 1'b1;
end
always@(posedge clk)
flag_1ms <= (cnt_1ms == CNT1MS)?1'b1:1'b0;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
max_data <= 'd0;
end
else if(flag_1ms) begin
max_data <= 'd0;
end
else if (data_in > max_data) begin
max_data <= data_in;
end
else
max_data <= max_data;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
med_data <= 'd0;
end
else begin
med_data <= (flag_1ms)?(max_data>>1):med_data;
end
end
得到可以测量频率的方波(此处adc_med_data为 med_data):
assign adc_digtal = (adc_data > adc_med_data)?1'b1:1'b0;
我们令128为零点,当ADC采样数据大于adc_med_data时,则方波信号为高电平,小于adc_med_data时,方波信号为低电平。当adc_digtal从低电平上升到高电平时开始计数,从高电平下降到低电平时停止计数,测得半个被测时钟周期。利用这半个周期的计数数,我们就可以计算得出信号的频率。
使用状态机控制计数器
always@(*)
begin
if(!rst_n) begin
state_next <= IDLE;
end
else begin
case (state_curr)
IDLE : state_next <= (en)?STATE1:IDLE;
STATE1 : state_next <= (!adc_digtal)?STATE2:STATE1;
STATE2 : state_next <= (adc_digtal)?STATE3:STATE2;
STATE3 : state_next <= (!adc_digtal)?STATE4:STATE3;
STATE4 : state_next <= (bcd_done)?DONE:STATE4;
DONE : state_next <= IDLE;
default: state_next <= IDLE;
endcase
end
End
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
cnt_adc_digtal <= 32'd0;
end
else if(state_curr == STATE3 && adc_digtal) begin
cnt_adc_digtal <= cnt_adc_digtal + 1'b1;
end
else if (state_curr == STATE1)begin
cnt_adc_digtal <= 32'd0;
end
else begin
cnt_adc_digtal <= cnt_adc_digtal;
end
end
计算得到频率。液晶屏显示测算频率时只保留小数点后两位,为了方便计算,节省资源占用,将F24MHz赋值为2400000,免去了额外除以一次10的步骤。此处的除法器可以直接例化前面章节所使用的。
parameter F24MHz = 32'd2_400_000;
assign div_b = (cnt_adc_digtal << 1);
assign div_en = (state_curr == STATE4)?1'b1:1'b0;
always@(posedge clk)begin
temp <= (div_done_w)?shang:temp;
end
div32 u_div32(
.clk (clk ),
.rst_n (rst_n ),
.start (div_en ),
.a (F24MHz ),
.b (div_b ),
.done (div_done_w ),
.yshang (shang ),
.yyushu ()
);
频率测算模块完成后将其例化:
frequence_test u_frequence_test(
.clk (clk24Mhz ),
.rst_n (rst_n ),
.en (en_fre ),
.adc_data (adc_data_w ),
.adc_done (adc_done_w ),
.fx (fx_bcd ),
.done (fre_done ),
.test (test));
17.4.4 图像模块的设计
完成ADC采样以及频率测算后,就可以把图像显示在液晶屏幕上,然而RAM中的保存的采样数据和测得的频率是无法直接在液晶屏上显示。在此之前,我们还需要绘制出波形图像以及频率字符图像。
要绘制出字符图像在前面有关液晶屏的实验已有阐述,本实验将会把注意力集中于波形图像的产生。已知液晶屏分辨率为320X240,我们可以将屏幕分成320列的单元,每一个单元根据RAM中前后两个采样数据确定信号波形在该单元的显示范围。这320个单元就可以组成一帧波形图像。
wire [15:0] x_set;
reg [15:0] x_set_last,x0_set,x1_set;
reg wr_color_arb;
assign x_set = rdram_data;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
x_set_last <= 16'd0;
end
else if(rdram_flag) begin
x_set_last <= x_set;
end
end
/*Compares the size of the current data with the previous data*/
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
x0_set <= 16'd0;
x1_set <= 16'd0;
end
else if(x_set >= x_set_last) begin
x0_set <= x_set_last + OFFSET - wave_amplitude;
x1_set <= x_set + OFFSET - wave_amplitude;
end
else begin
x0_set <= x_set + OFFSET - wave_amplitude;
x1_set <= x_set_last + OFFSET - wave_amplitude;
end
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
wr_color_arb <= 1'b0;
end
else if((cnt_wr_color_data >> 1) == x0_set)
wr_color_arb <= 1'b1;
else if((cnt_wr_color_data >> 1) > x1_set)
wr_color_arb <= 1'b0;
else
wr_color_arb <= wr_color_arb;
end
添上字符显示模块我们就可以实现在 液晶屏上显示波形和波形频率的效果。
