本文举例阐明了怎么用软件完成脉宽调制(PWM),怎么将该规划转换成一个能够在FPGA中运转的逻辑块,并能运用存储器映射I/O接口经过软件完成对该逻辑块的操控。经过了解本文评论的概念和内容,没有太多硬件常识的软件开发人员也能把握在FPGA上开发硬件的技能。
在不远的将来,嵌入式体系规划师将能够根据哪个更有利于处理规划问题来自由挑选硬件和软件计划。但直到现在,关于那些想学习硬件规划的软件工程师来说不少妨碍依然很难跨越。因为硬件描绘言语和编程言语十分类似,因而终究这些妨碍会消失。别的,市场上已有好几种低成本的演示板,上面包括现场可编程门阵列(FPGA)、微处理器以及相应东西,软件开发人员能够借此来学习硬件规划。
本文举例阐明了一个运用FPGA的新规划流程,咱们从中能够知道怎么用软件完成PWM,然后怎么将该规划转换成一个能够在FPGA中运转的逻辑块,并能运用存储器映射I/O接口经过软件完成对该逻辑块的操控。
软硬件区分
现在的状况与曾经有所不同,软件工程师能够便利地参加到硬件规划中。不论是硬件模块仍是软件模块现在都能够用编程言语进行规划。众所周知,C言语是嵌入式软件规划的通用言语。在硬件规划方面,Verilog则是盛行的挑选(用VHDL的人也许多)。Verilog的语法和结构与C编程言语十分类似,从本文的比如中也能够看到这一点。
一起,硬件的晋级和修正也变得越来越便利。曾经能够经过下载新的可履行映像文件晋级软件,但对硬件却行不通。现在状况不同了。就像软件开发人员能够快速修正、从头编译、然后将新代码下载到存储器那样,运用可编程逻辑器件的硬件规划者也能做类似的作业。可编程逻辑改动了嵌入式体系的规划办法,规划者能够像修正软件那样便利地修正硬件。换句话说,在规划和调试阶段,规划者能够灵敏挑选软件方法或硬件方法来作为完成使命的最佳方法。
规划者无需太多的硬件常识就能够运用FPGA供货商供给的东西轻松地开宣布可编程逻辑嵌入式体系。例如,Altera公司的SOPC Builder能协助体系规划师从已有的库中挑选和装备外围电路,并添加用来创立和衔接外围电路的用户逻辑。加上一些可编程逻辑和硬件常识,软件工程师就能够充沛运用硬件的优势改善他们的体系。
PWM软件
PWM操控器会发生一连串脉冲。一般需求规则脉冲的周期和宽度。占空比被界说为脉冲宽度与周期的比值。PWM有着广泛的运用,大多数状况下用于操控模仿电路。因为数字信号接连改动的速率相对较快(当然取决于信号周期),因而终究会构成一个用来操控模仿设备的均匀电压值。当PWM脉冲流运用于马达时,马达的转速就能正比于占空比(从0%到100%)。假如占空比添加,马达转速就会进步,反之,假如占空比减小,马达的转速随之也会下降。
用软件编写这样一个PWM操控器是相对比较简略的使命,但它有助于咱们短小精悍地描绘怎么用Verilog规划硬件。清单1给出了PWM的C代码。
清单1:彻底用软件完成的位脉冲PWM操控器。
void
pwmTask(uint32_t pulse_width, uint32_t period)
{
uint32_t time_on=pulse_width;
uint32_t time_off=period-pulse_width;
while (1)
{
pwm_output=1;
sleep(time_on);
pwm_output=0;
sleep(time_off);
}
}
根据脉宽(pulse_width)和周期(period)参数值,核算出输出为高电平和低电平的时间。接下来将输出引脚置为高电平,并等候time_on设定的时间值之后,将输出变为低电平,并等候time_off参数设定的时间值。下个周期再重复这样的进程,并无限循环下去。
Verilog模块
清单2给出了一个简略的Verilog模块,完成带异步复位功用的8位宽寄存器。寄存器的输入“in”在时钟的上升沿被赋值到输出“out”,直到clr_n复位信号的下降沿到来(此刻输出将被赋值为0)。
清单2:完成带异步复位功用8位宽寄存器的Verilog编写模块。
module simple_register(in, out, clr_n, clk, a);
//端口声明
input
input
input [7:0]
input
output [7:0]
clr_n;
clk;
in;
a;
out;
//信号声明
reg [7:0]
wire
out;
a;
//完成带异步铲除的寄存器
always @(posedge clk or negedge clr_n)
begin
if (clr_n==0) // could also be written if (!clr_n)
out=0;
else
out=in;
end
//接连赋值
assign a=!out[0];
endmodule
粗略地看Verilog与C言语有许多类似之处。分号用于结束每个句子,注释符也是相同的(/* … */和// 都是了解的),运算符“==”也用来测验持平性。Verilog的if..then..else语法与C言语的也十分类似,仅仅Verilog用要害字begin和end替代了C的大括号。事实上,要害字begin和end关于单句子块来说是可有可无的,就与C中的大括号用法相同。Verilog和C都对大小写灵敏。
当然,硬件和软件的一个重要区别是它们的“运转”方法。硬件规划顶用到的许多单元都是并行作业的。一旦设备电源敞开,硬件的每个单元就会一向处于运转状况。尽管根据详细的操控逻辑和数据输入,设备的一些单元或许不会改动它们的输出信号,但它们仍是一向在“运转”中。
相反,在同一时间整个软件规划中只需一小部分(即使是多软件使命也只需一个使命)在履行。假如只需一个处理器,同一时间点只能有一条指令在履行。软件的其它部分能够被以为处于休眠状况,这与硬件有很大的不同。变量或许以一个有用值而存在,但大多数时间里它们都不在运用状况。
软硬件的不同行为会直接导致硬件和软件代码编程方法的不同。软件是串行履行的,每一行代码的履行都要比及前一行代码履行结束后才干进行(中止的非线性或操作体系的指令在外)。
//————————————————————————————————————-
一个Verilog模块的开端是要害字module,紧跟这以后的是模块称号和端口列表,端口列表列出了该模块用到的一切输入输出称号。接下来是端口声明部分。留意:一切的输入输出既出现在模块榜首行的端口列表中,也会出现在端口声明(declaration)部分中。
在Verilog中有二种类型的内部信号用得比较多,它们是reg和wire。它们具有不同的功用。一切端口都有一个称号相同且声明为wire的信号。因而连线line被声明为wire不是必要的。reg会坚持前次的赋值,因而不需求每次都进行驱动。wire型信号用于异步逻辑,有时也用来衔接信号。因为reg能够坚持前次的值,因而输入不能被声明为reg类型。在Verilog模块中能够在任何时候异步地将输入改动为任何事情。reg和wire的首要区别是,reg类型的信号只能在进程块(后边会谈到)中赋值,而wire类型的信号只能在进程块外赋值。这两种信号类型都能够出现在进程块内部和外部的赋值运算符右边。
运用要害字reg并不一定意味着编译器会创立一个寄存器,了解这一点是十分重要的。清单2的代码中有一个reg类型8位宽的内部信号out。该模块运用寄存器源于always模块(进程块的一种)的编程方法。值得留意的是,信号a是一个wire类型,因而只能在接连赋值(continuous assignment)句子中赋值,而reg类型的out信号只能在always块中赋值。
always块是进程块的一种,仅在某种改动发生时用于更新信号。always句子圆括号里的表达式组被称为灵敏列表,格局是:(表达式or表达式…)
只需灵敏列表中的任何一个表达式值为真,always块中的代码就会被履行。Verilog顶用于上升沿和下降沿的要害字别离是posedge和negedge。这二个要害字经常被用于灵敏列表。在本例中,假如clk信号的上升沿或clr_n的下降沿信号发生时,always块内部的句子就会被履行。
为了用好寄存器,输出有必要在时钟的上升沿得到更新(下降沿也能够,但上升沿更常见些)。添加negedge clr_n会使寄存器在clr_n信号的下降沿复位。但并不是一切的灵敏列表都会包括要害字posedge或negedge,因而在实践硬件中并不总是存在实在的寄存器。
always块内的榜首条句子判别clr_n信号的上升沿有没有发生。假如有,下一行代码把out置为0。这些代码行完成了寄存器的异步复位功用。假如条件句子是:if(negedge clr_n and clk==1),那么该句子完成的便是根据时钟的异步复位。
读者或许现已留意到,always块中的赋值运算符与以要害字assign开端的接连赋值句子顶用到的运算符不相同。=运算符用于非堵塞性(nonblocking)赋值,而=运算符用于堵塞性(blocking)赋值。
在一组堵塞性赋值句子中,鄙人一个堵塞性赋值句子履行前需求核算并赋值榜首个赋值句子。这一进程就象C言语中句子的次序履行。而非堵塞句子在履行时,一切赋值句子的右边被一起核算和赋值。接连赋值句子有必要运用堵塞赋值句子(不然编译器会报错)。
为了削减代码犯错的概率,主张在次序逻辑(例如期望以寄存器方法完成的逻辑)always块中的一切赋值句子运用非堵塞性赋值句子。大多数always块应该运用非堵塞性赋值句子。假如always块都是组合逻辑,那么就需求运用堵塞性赋值句子。
//————————————————————————————————————-
PWM硬件
编写存储器映射硬件模块的首要使命是以软件方法决议寄存器映射图。在PWM事例中,一般规划师期望能用软件设置周期和脉宽。在硬件规划顶用计数器计算体系时钟周期数是十分简略的。因而要用到两个寄存器,别离命名为pulse_width和period,而且都在时钟周期内衡量。表1给出了PWM的寄存器映射图。
为了确认输出信号,硬件可简略地经过将period和pulse_width寄存器内容作为运转中的计数器坚持的输出。
接下来要为PWM挑选端口,大多数端口能够根据总线架构而定。表2供给了通用存储器映射PWM的信号描绘概要。一般为低电平有用的信号命名做法是在信号名上加“_n”,关于操控信号更是如此。表2中的write_n和clr_n信号便是低电平有用的信号(下降沿触发)。
至此咱们现已界说好了硬件模块的接口,接下来就能够开端编写Verilog代码了。清单3给出了一个完成比如。
清单3:用Verilog完成的PWM硬件。
module pwm (clk, write_data, cs, write_n, addr, clr_n, read_data, pwm_out);
input
input [31:0]
input
input
input
input
output [31:0]
output
clk;
write_data;
cs;
write_n;
addr;
clr_n;
read_data;
pwm_out;
reg [31:0]
reg [31:0]
reg [31:0]
reg
reg [31:0]
wire
period;
pulse_width;
counter;
off;
read_data;
period_en, pulse_width_en; //写使能
// 界说period和pulse_width寄存器的内容
always @(posedge clk or negedge clr_n)
begin
if (clr_n==0)
begin
period=32’h 00000000;
pulse_width=32’h 00000000;
end
else
begin
if (period_en)
period=write_data[31:0];
else
period=period;
if (pulse_width_en)
pulse_width=write_data[31:0];
else
pulse_width=pulse_width;
end
end
// period和pulse_width寄存器的读拜访
always @(addr or period or pulse_width)
if (addr == 0)
read_data=period;
else
read_data=pulse_width;
always @(posedge clk or negedge clr_n)
begin
if (clr_n==0)
counter=0;
else
if (counter>=period-1)
counter=0;
else
counter=counter+1;
end
always @(posedge clk or negedge clr_n)
begin
if (clr_n==0)
off=0;
else
if (counter>=pulse_width)
off = 1;
else
if (counter==0)
off=0;
else
off=off;
end
assign period_en = cs !write_n !addr;
assign pulse_width_en = cs !write_n addr;
//PWM输出
assign pwm_out=!off;
endmodule
首先是端口阐明,接着是内部信号阐明。构成PWM软件操控接口的存储器映射型寄存器被声明为reg。该代码行只允许以32位的方法拜访这些存储器映射型寄存器。假如需求8位或16位拜访,就有必要将寄存器分割成4个8位寄存器,并添加字节使能信号逻辑。用Verilog代码完成这一功用是十分简略的。always块中已赋过值的一切信号都被声明为reg类型。声明为wire类型的信号是period和pulse_width寄存器写入使能信号。这些信号运用接连赋值句子进行赋值。
清单的余下部分便是实践的代码,共有4个always块,终究还有几个赋值句子。每个always块描绘一个信号或一组有相同根本行为(换句话说,运用相同的操控逻辑)的信号的行为。这是使代码具有可读性并能削减过错的Verilog代码编写风格。一切的always块都有复位逻辑,当clr_n信号被证明(设为0)时,复位逻辑将信号置为0。尽管这样做并不是严厉必需的,但这是一种杰出的规划习气,能使每个信号在复位时都有确认的值。
榜首个always块描绘了寄存器映射中的寄存器行为。当正确的使能信号被证明时,write_data寄存器值就被写入period或pulse_width寄存器中。这是改动任一寄存器值的仅有途径。该文件底部的接连赋值句子界说了写入使能信号。当主写入使能信号和芯片挑选信号一起被证明时,period和pulse_width寄存器的写入使能信号就被证明,此刻period和pulse_width的地址位应别离被置为0和1。
第二个always块界说了寄存器映射图中读寄存器。Period寄存器坐落外围电路的根本地址处,pulse_width寄存器在后边32位字地址处。
第三和第四个always块一起来决议PWM的输出。第三个always块完成计数器功用,它接连计数到period寄存器设置的值时复位到0,然后从头开端计数。第四个always块对该计数器值与pulse_width寄存器值进行比较,当计数器值小于pulse_width值时,PWM输出坚持高电平,不然设为低电平。
需求紧记的是不论在何种条件下每个信号都有必要有明晰的值。回忆一下硬件的根本行为特征——“一直在运转”。例如在终究一个always块(描绘off信号的那个块)中,代码的终究即将off赋于它本身。开端看来好象比较古怪,但假如没有这一行的话,off值将是不确认的。对这一状况坚持盯梢的最便利途径是保证每次信号会在if句子中赋值,在相应的else句子中也赋值。
软件拜访
现在硬件完成了,能够运用寄存器映射图中的寄存器经过软件对PWM施行操控。读者能够用一个简略的带指针的数据结构衔接PWM中的寄存器。
typedef volatile struct
{
uint32_t period;
uint32_t pulse_width;
} PWM;
例如,能够将PWM衔接到LED。先初始化一个名为pLED、类型为PWM*的变量,将其指向PWM基地址。这样做实践上是将硬件笼统进了一个数据结构。向pLED->period写入数据会设置或改动period值,向pLED->pulse_width写入数据将改动占空比,并导致LED的亮度添加或削减。假如运用的是闪耀型LED,只需把周期变长,让肉眼明晰辨别开和关的周期即可。
清单3所示的Verilog PWM完成在本例中是作为Altera的Nios处理器体系的外围电路进行测验的,能够运用前文所述的C结构经过软件对它拜访。Altera的SOPC Builder创立了宏,能够使ModelSim(明导资讯公司的一个硬件仿真器)中的协同仿真。在体系履行C代码时能够运用ModelSim仿真器观察到PWM信号以及其它体系信号的行为。
清单4给出了用于发生图1所示PWM波形的C代码。C代码向PWM寄存器写入数据,创立出周期为5个时钟周期、脉宽为4个时钟周期的PWM输出信号。请留意在波形的开端处,因为period和pulse_width寄存器都被写入了数据,cs和wr_n信号被证明了二次(在写period寄存器时地址信号为低电平,在写pulse_width寄存器时地址信号变成了高电平)。
清单4:用于发生图1所示PWM波形的测验软件。
void
main(void)
{
PWM * const pLED=…
pLED->period=5;
pLED->pulse_width=4;
asm(nop);
asm(nop);
asm(nop);
pLED->pulse_width=2;
}
在寄存器中写入新值后,pwm_output信号开端反映出改动。然后,只参加一些时延咱们再看输出,一些NOP指令被C代码履行了。终究,脉宽变为2个周期,PWM波形相应也有了改动,但周期仍坚持为5个时钟周期。
规划嵌入式体系架构时最好将体系分红硬件和软件二大模块,以便充沛运用各自的优势。跟着开发东西的不断发展,软件和硬件模块的相互交换也变得越来越通明。
一旦充沛了解了本文评论的概念和内容,也就把握了在FPGA上开发硬件的技能。FPGA能被用作微处理器体系中的一个存储器映射式外围电路,能够经过简略的编程完成接口。因为用硬件完成算法的速度快得多,将算法从软件转换成硬件能够极大地进步体系功能。这便是人们常说的硬件加速,把握这一技能是娴熟运用可编程逻辑器件中被有用完成的可装备处理器的要害。从长远来看,即使是软件工程师也能经过硬件加速进步体系功能和功率。